HeadsetClientStateMachine.java revision 58cdc21bcdc90adb8f8dd629881a192ad26c977b
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            mCalls.clear();
790            mCallsUpdate.clear();
791
792            mPeerFeatures = 0;
793            mChldFeatures = 0;
794
795            removeMessages(QUERY_CURRENT_CALLS);
796
797            if (mPrevState == mConnecting) {
798                broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED,
799                        BluetoothProfile.STATE_CONNECTING);
800            } else if (mPrevState == mConnected || mPrevState == mAudioOn) {
801                broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED,
802                        BluetoothProfile.STATE_CONNECTED);
803            } else if (mPrevState != null) { // null is the default state before Disconnected
804                Log.e(TAG, "Connected: Illegal state transition from " + mPrevState.getName()
805                        + " to Connecting, mCurrentDevice=" + mCurrentDevice);
806            }
807            mCurrentDevice = null;
808        }
809
810        @Override
811        public synchronized boolean processMessage(Message message) {
812            Log.d(TAG, "Disconnected process message: " + message.what);
813
814            if (mCurrentDevice != null) {
815                Log.e(TAG, "ERROR: current device not null in Disconnected");
816                return NOT_HANDLED;
817            }
818
819            switch (message.what) {
820                case CONNECT:
821                    BluetoothDevice device = (BluetoothDevice) message.obj;
822                    if (!NativeInterface.connectNative(getByteAddress(device))) {
823                        // No state transition is involved, fire broadcast immediately
824                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
825                                BluetoothProfile.STATE_DISCONNECTED);
826                        break;
827                    }
828                    mCurrentDevice = device;
829                    transitionTo(mConnecting);
830                    break;
831                case DISCONNECT:
832                    // ignore
833                    break;
834                case StackEvent.STACK_EVENT:
835                    StackEvent event = (StackEvent) message.obj;
836                    if (DBG) {
837                        Log.d(TAG, "Stack event type: " + event.type);
838                    }
839                    switch (event.type) {
840                        case StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
841                            if (DBG) {
842                                Log.d(TAG, "Disconnected: Connection " + event.device
843                                        + " state changed:" + event.valueInt);
844                            }
845                            processConnectionEvent(event.valueInt, event.device);
846                            break;
847                        default:
848                            Log.e(TAG, "Disconnected: Unexpected stack event: " + event.type);
849                            break;
850                    }
851                    break;
852                default:
853                    return NOT_HANDLED;
854            }
855            return HANDLED;
856        }
857
858        // in Disconnected state
859        private void processConnectionEvent(int state, BluetoothDevice device) {
860            switch (state) {
861                case HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED:
862                    Log.w(TAG, "HFPClient Connecting from Disconnected state");
863                    if (okToConnect(device)) {
864                        Log.i(TAG, "Incoming AG accepted");
865                        mCurrentDevice = device;
866                        transitionTo(mConnecting);
867                    } else {
868                        Log.i(TAG, "Incoming AG rejected. priority=" + mService.getPriority(device)
869                                + " bondState=" + device.getBondState());
870                        // reject the connection and stay in Disconnected state
871                        // itself
872                        NativeInterface.disconnectNative(getByteAddress(device));
873                        // the other profile connection should be initiated
874                        AdapterService adapterService = AdapterService.getAdapterService();
875                        // No state transition is involved, fire broadcast immediately
876                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
877                                BluetoothProfile.STATE_DISCONNECTED);
878                    }
879                    break;
880                case HeadsetClientHalConstants.CONNECTION_STATE_CONNECTING:
881                case HeadsetClientHalConstants.CONNECTION_STATE_DISCONNECTED:
882                case HeadsetClientHalConstants.CONNECTION_STATE_DISCONNECTING:
883                default:
884                    Log.i(TAG, "ignoring state: " + state);
885                    break;
886            }
887        }
888
889        @Override
890        public void exit() {
891            if (DBG) {
892                Log.d(TAG, "Exit Disconnected: " + getCurrentMessage().what);
893            }
894            mPrevState = this;
895        }
896    }
897
898    class Connecting extends State {
899        @Override
900        public void enter() {
901            if (DBG) {
902                Log.d(TAG, "Enter Connecting: " + getCurrentMessage().what);
903            }
904            // This message is either consumed in processMessage or
905            // removed in exit. It is safe to send a CONNECTING_TIMEOUT here since
906            // the only transition is when connection attempt is initiated.
907            sendMessageDelayed(CONNECTING_TIMEOUT, CONNECTING_TIMEOUT_MS);
908            if (mPrevState == mDisconnected) {
909                broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTING,
910                        BluetoothProfile.STATE_DISCONNECTED);
911            } else {
912                String prevStateName = mPrevState == null ? "null" : mPrevState.getName();
913                Log.e(TAG, "Connected: Illegal state transition from " + prevStateName
914                        + " to Connecting, mCurrentDevice=" + mCurrentDevice);
915            }
916        }
917
918        @Override
919        public synchronized boolean processMessage(Message message) {
920            if (DBG) {
921                Log.d(TAG, "Connecting process message: " + message.what);
922            }
923
924            switch (message.what) {
925                case CONNECT:
926                case CONNECT_AUDIO:
927                case DISCONNECT:
928                    deferMessage(message);
929                    break;
930                case StackEvent.STACK_EVENT:
931                    StackEvent event = (StackEvent) message.obj;
932                    if (DBG) {
933                        Log.d(TAG, "Connecting: event type: " + event.type);
934                    }
935                    switch (event.type) {
936                        case StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
937                            if (DBG) {
938                                Log.d(TAG,
939                                        "Connecting: Connection " + event.device + " state changed:"
940                                                + event.valueInt);
941                            }
942                            processConnectionEvent(event.valueInt, event.valueInt2, event.valueInt3,
943                                    event.device);
944                            break;
945                        case StackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED:
946                        case StackEvent.EVENT_TYPE_NETWORK_STATE:
947                        case StackEvent.EVENT_TYPE_ROAMING_STATE:
948                        case StackEvent.EVENT_TYPE_NETWORK_SIGNAL:
949                        case StackEvent.EVENT_TYPE_BATTERY_LEVEL:
950                        case StackEvent.EVENT_TYPE_CALL:
951                        case StackEvent.EVENT_TYPE_CALLSETUP:
952                        case StackEvent.EVENT_TYPE_CALLHELD:
953                        case StackEvent.EVENT_TYPE_RESP_AND_HOLD:
954                        case StackEvent.EVENT_TYPE_CLIP:
955                        case StackEvent.EVENT_TYPE_CALL_WAITING:
956                        case StackEvent.EVENT_TYPE_VOLUME_CHANGED:
957                            deferMessage(message);
958                            break;
959                        case StackEvent.EVENT_TYPE_CMD_RESULT:
960                        case StackEvent.EVENT_TYPE_SUBSCRIBER_INFO:
961                        case StackEvent.EVENT_TYPE_CURRENT_CALLS:
962                        case StackEvent.EVENT_TYPE_OPERATOR_NAME:
963                        default:
964                            Log.e(TAG, "Connecting: ignoring stack event: " + event.type);
965                            break;
966                    }
967                    break;
968                case CONNECTING_TIMEOUT:
969                    // We timed out trying to connect, transition to disconnected.
970                    Log.w(TAG, "Connection timeout for " + mCurrentDevice);
971                    transitionTo(mDisconnected);
972                    break;
973
974                default:
975                    Log.w(TAG, "Message not handled " + message);
976                    return NOT_HANDLED;
977            }
978            return HANDLED;
979        }
980
981        // in Connecting state
982        private void processConnectionEvent(int state, int peerFeat, int chldFeat,
983                BluetoothDevice device) {
984            switch (state) {
985                case HeadsetClientHalConstants.CONNECTION_STATE_DISCONNECTED:
986                    transitionTo(mDisconnected);
987                    break;
988
989                case HeadsetClientHalConstants.CONNECTION_STATE_SLC_CONNECTED:
990                    Log.d(TAG, "HFPClient Connected from Connecting state");
991
992                    mPeerFeatures = peerFeat;
993                    mChldFeatures = chldFeat;
994
995                    // We do not support devices which do not support enhanced call status (ECS).
996                    if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_ECS) == 0) {
997                        NativeInterface.disconnectNative(getByteAddress(device));
998                        return;
999                    }
1000
1001                    // Send AT+NREC to remote if supported by audio
1002                    if (HeadsetClientHalConstants.HANDSFREECLIENT_NREC_SUPPORTED && (
1003                            (mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_ECNR)
1004                                    == HeadsetClientHalConstants.PEER_FEAT_ECNR)) {
1005                        if (NativeInterface.sendATCmdNative(getByteAddress(mCurrentDevice),
1006                                HeadsetClientHalConstants.HANDSFREECLIENT_AT_CMD_NREC, 1, 0,
1007                                null)) {
1008                            addQueuedAction(DISABLE_NREC);
1009                        } else {
1010                            Log.e(TAG, "Failed to send NREC");
1011                        }
1012                    }
1013
1014                    int amVol = sAudioManager.getStreamVolume(AudioManager.STREAM_VOICE_CALL);
1015                    deferMessage(
1016                            obtainMessage(HeadsetClientStateMachine.SET_SPEAKER_VOLUME, amVol, 0));
1017                    // Mic is either in ON state (full volume) or OFF state. There is no way in
1018                    // Android to change the MIC volume.
1019                    deferMessage(obtainMessage(HeadsetClientStateMachine.SET_MIC_VOLUME,
1020                            sAudioManager.isMicrophoneMute() ? 0 : 15, 0));
1021                    // query subscriber info
1022                    deferMessage(obtainMessage(HeadsetClientStateMachine.SUBSCRIBER_INFO));
1023                    transitionTo(mConnected);
1024                    break;
1025
1026                case HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED:
1027                    if (!mCurrentDevice.equals(device)) {
1028                        Log.w(TAG, "incoming connection event, device: " + device);
1029                        // No state transition is involved, fire broadcast immediately
1030                        broadcastConnectionState(mCurrentDevice,
1031                                BluetoothProfile.STATE_DISCONNECTED,
1032                                BluetoothProfile.STATE_CONNECTING);
1033                        broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
1034                                BluetoothProfile.STATE_DISCONNECTED);
1035
1036                        mCurrentDevice = device;
1037                    }
1038                    break;
1039                case HeadsetClientHalConstants.CONNECTION_STATE_CONNECTING:
1040                    /* outgoing connecting started */
1041                    if (DBG) {
1042                        Log.d(TAG, "outgoing connection started, ignore");
1043                    }
1044                    break;
1045                case HeadsetClientHalConstants.CONNECTION_STATE_DISCONNECTING:
1046                default:
1047                    Log.e(TAG, "Incorrect state: " + state);
1048                    break;
1049            }
1050        }
1051
1052        @Override
1053        public void exit() {
1054            if (DBG) {
1055                Log.d(TAG, "Exit Connecting: " + getCurrentMessage().what);
1056            }
1057            removeMessages(CONNECTING_TIMEOUT);
1058            mPrevState = this;
1059        }
1060    }
1061
1062    class Connected extends State {
1063        int mCommandedSpeakerVolume = -1;
1064
1065        @Override
1066        public void enter() {
1067            if (DBG) {
1068                Log.d(TAG, "Enter Connected: " + getCurrentMessage().what);
1069            }
1070            mAudioWbs = false;
1071            mCommandedSpeakerVolume = -1;
1072            if (mPrevState == mConnecting) {
1073                broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED,
1074                        BluetoothProfile.STATE_CONNECTING);
1075            } else if (mPrevState != mAudioOn) {
1076                String prevStateName = mPrevState == null ? "null" : mPrevState.getName();
1077                Log.e(TAG, "Connected: Illegal state transition from " + prevStateName
1078                        + " to Connecting, mCurrentDevice=" + mCurrentDevice);
1079            }
1080        }
1081
1082        @Override
1083        public synchronized boolean processMessage(Message message) {
1084            if (DBG) {
1085                Log.d(TAG, "Connected process message: " + message.what);
1086            }
1087            if (DBG) {
1088                if (mCurrentDevice == null) {
1089                    Log.e(TAG, "ERROR: mCurrentDevice is null in Connected");
1090                    return NOT_HANDLED;
1091                }
1092            }
1093
1094            switch (message.what) {
1095                case CONNECT:
1096                    BluetoothDevice device = (BluetoothDevice) message.obj;
1097                    if (mCurrentDevice.equals(device)) {
1098                        // already connected to this device, do nothing
1099                        break;
1100                    }
1101                    NativeInterface.connectNative(getByteAddress(device));
1102                    break;
1103                case DISCONNECT:
1104                    BluetoothDevice dev = (BluetoothDevice) message.obj;
1105                    if (!mCurrentDevice.equals(dev)) {
1106                        break;
1107                    }
1108                    if (NativeInterface.disconnectNative(getByteAddress(dev))) {
1109                        // No state transition is involved, fire broadcast immediately
1110                        broadcastConnectionState(dev, BluetoothProfile.STATE_DISCONNECTING,
1111                                BluetoothProfile.STATE_CONNECTED);
1112                    } else {
1113                        Log.e(TAG, "disconnectNative failed for " + dev);
1114                    }
1115                    break;
1116
1117                case CONNECT_AUDIO:
1118                    if (!NativeInterface.connectAudioNative(getByteAddress(mCurrentDevice))) {
1119                        Log.e(TAG, "ERROR: Couldn't connect Audio for device " + mCurrentDevice);
1120                        // No state transition is involved, fire broadcast immediately
1121                        broadcastAudioState(mCurrentDevice,
1122                                BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED,
1123                                BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED);
1124                    } else { // We have successfully sent a connect request!
1125                        mAudioState = BluetoothHeadsetClient.STATE_AUDIO_CONNECTING;
1126                    }
1127                    break;
1128
1129                case DISCONNECT_AUDIO:
1130                    if (!NativeInterface.disconnectAudioNative(getByteAddress(mCurrentDevice))) {
1131                        Log.e(TAG, "ERROR: Couldn't disconnect Audio for device " + mCurrentDevice);
1132                    }
1133                    break;
1134
1135                case VOICE_RECOGNITION_START:
1136                    if (mVoiceRecognitionActive == HeadsetClientHalConstants.VR_STATE_STOPPED) {
1137                        if (NativeInterface.startVoiceRecognitionNative(
1138                                    getByteAddress(mCurrentDevice))) {
1139                            addQueuedAction(VOICE_RECOGNITION_START);
1140                        } else {
1141                            Log.e(TAG, "ERROR: Couldn't start voice recognition");
1142                        }
1143                    }
1144                    break;
1145
1146                case VOICE_RECOGNITION_STOP:
1147                    if (mVoiceRecognitionActive == HeadsetClientHalConstants.VR_STATE_STARTED) {
1148                        if (NativeInterface.stopVoiceRecognitionNative(
1149                                    getByteAddress(mCurrentDevice))) {
1150                            addQueuedAction(VOICE_RECOGNITION_STOP);
1151                        } else {
1152                            Log.e(TAG, "ERROR: Couldn't stop voice recognition");
1153                        }
1154                    }
1155                    break;
1156
1157                // Called only for Mute/Un-mute - Mic volume change is not allowed.
1158                case SET_MIC_VOLUME:
1159                    break;
1160                case SET_SPEAKER_VOLUME:
1161                    // This message should always contain the volume in AudioManager max normalized.
1162                    int amVol = message.arg1;
1163                    int hfVol = amToHfVol(amVol);
1164                    if (amVol != mCommandedSpeakerVolume) {
1165                        Log.d(TAG, "Volume" + amVol + ":" + mCommandedSpeakerVolume);
1166                        // Volume was changed by a 3rd party
1167                        mCommandedSpeakerVolume = -1;
1168                        if (NativeInterface.setVolumeNative(getByteAddress(mCurrentDevice),
1169                                HeadsetClientHalConstants.VOLUME_TYPE_SPK, hfVol)) {
1170                            addQueuedAction(SET_SPEAKER_VOLUME);
1171                        }
1172                    }
1173                    break;
1174                case DIAL_NUMBER:
1175                    // Add the call as an outgoing call.
1176                    BluetoothHeadsetClientCall c = (BluetoothHeadsetClientCall) message.obj;
1177                    mCalls.put(HF_ORIGINATED_CALL_ID, c);
1178
1179                    if (NativeInterface.dialNative(getByteAddress(mCurrentDevice), c.getNumber())) {
1180                        addQueuedAction(DIAL_NUMBER, c.getNumber());
1181                        // Start looping on calling current calls.
1182                        sendMessage(QUERY_CURRENT_CALLS);
1183                    } else {
1184                        Log.e(TAG,
1185                                "ERROR: Cannot dial with a given number:" + (String) message.obj);
1186                        // Set the call to terminated remove.
1187                        c.setState(BluetoothHeadsetClientCall.CALL_STATE_TERMINATED);
1188                        sendCallChangedIntent(c);
1189                        mCalls.remove(HF_ORIGINATED_CALL_ID);
1190                    }
1191                    break;
1192                case ACCEPT_CALL:
1193                    acceptCall(message.arg1);
1194                    break;
1195                case REJECT_CALL:
1196                    rejectCall();
1197                    break;
1198                case HOLD_CALL:
1199                    holdCall();
1200                    break;
1201                case TERMINATE_CALL:
1202                    terminateCall();
1203                    break;
1204                case ENTER_PRIVATE_MODE:
1205                    enterPrivateMode(message.arg1);
1206                    break;
1207                case EXPLICIT_CALL_TRANSFER:
1208                    explicitCallTransfer();
1209                    break;
1210                case SEND_DTMF:
1211                    if (NativeInterface.sendDtmfNative(getByteAddress(mCurrentDevice),
1212                            (byte) message.arg1)) {
1213                        addQueuedAction(SEND_DTMF);
1214                    } else {
1215                        Log.e(TAG, "ERROR: Couldn't send DTMF");
1216                    }
1217                    break;
1218                case SUBSCRIBER_INFO:
1219                    if (NativeInterface.retrieveSubscriberInfoNative(
1220                            getByteAddress(mCurrentDevice))) {
1221                        addQueuedAction(SUBSCRIBER_INFO);
1222                    } else {
1223                        Log.e(TAG, "ERROR: Couldn't retrieve subscriber info");
1224                    }
1225                    break;
1226                case QUERY_CURRENT_CALLS:
1227                    // Whenever the timer expires we query calls if there are outstanding requests
1228                    // for query calls.
1229                    long currentElapsed = SystemClock.elapsedRealtime();
1230                    if (mClccTimer < currentElapsed) {
1231                        queryCallsStart();
1232                        mClccTimer = currentElapsed + QUERY_CURRENT_CALLS_WAIT_MILLIS;
1233                        // Request satisfied, ignore all other call query messages.
1234                        removeMessages(QUERY_CURRENT_CALLS);
1235                    } else {
1236                        // Replace all messages with one concrete message.
1237                        removeMessages(QUERY_CURRENT_CALLS);
1238                        sendMessageDelayed(QUERY_CURRENT_CALLS, QUERY_CURRENT_CALLS_WAIT_MILLIS);
1239                    }
1240                    break;
1241                case StackEvent.STACK_EVENT:
1242                    Intent intent = null;
1243                    StackEvent event = (StackEvent) message.obj;
1244                    if (DBG) {
1245                        Log.d(TAG, "Connected: event type: " + event.type);
1246                    }
1247
1248                    switch (event.type) {
1249                        case StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
1250                            if (DBG) {
1251                                Log.d(TAG, "Connected: Connection state changed: " + event.device
1252                                        + ": " + event.valueInt);
1253                            }
1254                            processConnectionEvent(event.valueInt, event.device);
1255                            break;
1256                        case StackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED:
1257                            if (DBG) {
1258                                Log.d(TAG, "Connected: Audio state changed: " + event.device + ": "
1259                                        + event.valueInt);
1260                            }
1261                            processAudioEvent(event.valueInt, event.device);
1262                            break;
1263                        case StackEvent.EVENT_TYPE_NETWORK_STATE:
1264                            if (DBG) {
1265                                Log.d(TAG, "Connected: Network state: " + event.valueInt);
1266                            }
1267                            mIndicatorNetworkState = event.valueInt;
1268
1269                            intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
1270                            intent.putExtra(BluetoothHeadsetClient.EXTRA_NETWORK_STATUS,
1271                                    event.valueInt);
1272
1273                            if (mIndicatorNetworkState
1274                                    == HeadsetClientHalConstants.NETWORK_STATE_NOT_AVAILABLE) {
1275                                mOperatorName = null;
1276                                intent.putExtra(BluetoothHeadsetClient.EXTRA_OPERATOR_NAME,
1277                                        mOperatorName);
1278                            }
1279
1280                            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
1281                            mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
1282
1283                            if (mIndicatorNetworkState
1284                                    == HeadsetClientHalConstants.NETWORK_STATE_AVAILABLE) {
1285                                if (NativeInterface.queryCurrentOperatorNameNative(
1286                                        getByteAddress(mCurrentDevice))) {
1287                                    addQueuedAction(QUERY_OPERATOR_NAME);
1288                                } else {
1289                                    Log.e(TAG, "ERROR: Couldn't querry operator name");
1290                                }
1291                            }
1292                            break;
1293                        case StackEvent.EVENT_TYPE_ROAMING_STATE:
1294                            mIndicatorNetworkType = event.valueInt;
1295
1296                            intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
1297                            intent.putExtra(BluetoothHeadsetClient.EXTRA_NETWORK_ROAMING,
1298                                    event.valueInt);
1299                            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
1300                            mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
1301                            break;
1302                        case StackEvent.EVENT_TYPE_NETWORK_SIGNAL:
1303                            mIndicatorNetworkSignal = event.valueInt;
1304
1305                            intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
1306                            intent.putExtra(BluetoothHeadsetClient.EXTRA_NETWORK_SIGNAL_STRENGTH,
1307                                    event.valueInt);
1308                            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
1309                            mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
1310                            break;
1311                        case StackEvent.EVENT_TYPE_BATTERY_LEVEL:
1312                            mIndicatorBatteryLevel = event.valueInt;
1313
1314                            intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
1315                            intent.putExtra(BluetoothHeadsetClient.EXTRA_BATTERY_LEVEL,
1316                                    event.valueInt);
1317                            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
1318                            mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
1319                            break;
1320                        case StackEvent.EVENT_TYPE_OPERATOR_NAME:
1321                            mOperatorName = event.valueString;
1322
1323                            intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
1324                            intent.putExtra(BluetoothHeadsetClient.EXTRA_OPERATOR_NAME,
1325                                    event.valueString);
1326                            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
1327                            mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
1328                            break;
1329                        case StackEvent.EVENT_TYPE_VR_STATE_CHANGED:
1330                            if (mVoiceRecognitionActive != event.valueInt) {
1331                                mVoiceRecognitionActive = event.valueInt;
1332
1333                                intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
1334                                intent.putExtra(BluetoothHeadsetClient.EXTRA_VOICE_RECOGNITION,
1335                                        mVoiceRecognitionActive);
1336                                intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
1337                                mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
1338                            }
1339                            break;
1340                        case StackEvent.EVENT_TYPE_CALL:
1341                        case StackEvent.EVENT_TYPE_CALLSETUP:
1342                        case StackEvent.EVENT_TYPE_CALLHELD:
1343                        case StackEvent.EVENT_TYPE_RESP_AND_HOLD:
1344                        case StackEvent.EVENT_TYPE_CLIP:
1345                        case StackEvent.EVENT_TYPE_CALL_WAITING:
1346                            sendMessage(QUERY_CURRENT_CALLS);
1347                            break;
1348                        case StackEvent.EVENT_TYPE_CURRENT_CALLS:
1349                            queryCallsUpdate(event.valueInt, event.valueInt3, event.valueString,
1350                                    event.valueInt4
1351                                            == HeadsetClientHalConstants.CALL_MPTY_TYPE_MULTI,
1352                                    event.valueInt2
1353                                            == HeadsetClientHalConstants.CALL_DIRECTION_OUTGOING);
1354                            break;
1355                        case StackEvent.EVENT_TYPE_VOLUME_CHANGED:
1356                            if (event.valueInt == HeadsetClientHalConstants.VOLUME_TYPE_SPK) {
1357                                mCommandedSpeakerVolume = hfToAmVol(event.valueInt2);
1358                                Log.d(TAG, "AM volume set to " + mCommandedSpeakerVolume);
1359                                sAudioManager.setStreamVolume(AudioManager.STREAM_VOICE_CALL,
1360                                        +mCommandedSpeakerVolume, AudioManager.FLAG_SHOW_UI);
1361                            } else if (event.valueInt
1362                                    == HeadsetClientHalConstants.VOLUME_TYPE_MIC) {
1363                                sAudioManager.setMicrophoneMute(event.valueInt2 == 0);
1364                            }
1365                            break;
1366                        case StackEvent.EVENT_TYPE_CMD_RESULT:
1367                            Pair<Integer, Object> queuedAction = mQueuedActions.poll();
1368
1369                            // should not happen but...
1370                            if (queuedAction == null || queuedAction.first == NO_ACTION) {
1371                                clearPendingAction();
1372                                break;
1373                            }
1374
1375                            if (DBG) {
1376                                Log.d(TAG, "Connected: command result: " + event.valueInt
1377                                        + " queuedAction: " + queuedAction.first);
1378                            }
1379
1380                            switch (queuedAction.first) {
1381                                case QUERY_CURRENT_CALLS:
1382                                    queryCallsDone();
1383                                    break;
1384                                case VOICE_RECOGNITION_START:
1385                                    if (event.valueInt == AT_OK) {
1386                                        mVoiceRecognitionActive =
1387                                                HeadsetClientHalConstants.VR_STATE_STARTED;
1388                                    }
1389                                    break;
1390                                case VOICE_RECOGNITION_STOP:
1391                                    if (event.valueInt == AT_OK) {
1392                                        mVoiceRecognitionActive =
1393                                                HeadsetClientHalConstants.VR_STATE_STOPPED;
1394                                    }
1395                                    break;
1396                                default:
1397                                    Log.w(TAG, "Unhandled AT OK " + event);
1398                                    break;
1399                            }
1400
1401                            break;
1402                        case StackEvent.EVENT_TYPE_SUBSCRIBER_INFO:
1403                            mSubscriberInfo = event.valueString;
1404                            intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
1405                            intent.putExtra(BluetoothHeadsetClient.EXTRA_SUBSCRIBER_INFO,
1406                                    mSubscriberInfo);
1407                            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
1408                            mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
1409                            break;
1410                        case StackEvent.EVENT_TYPE_RING_INDICATION:
1411                            // Ringing is not handled at this indication and rather should be
1412                            // implemented (by the client of this service). Use the
1413                            // CALL_STATE_INCOMING (and similar) handle ringing.
1414                            break;
1415                        default:
1416                            Log.e(TAG, "Unknown stack event: " + event.type);
1417                            break;
1418                    }
1419
1420                    break;
1421                default:
1422                    return NOT_HANDLED;
1423            }
1424            return HANDLED;
1425        }
1426
1427        // in Connected state
1428        private void processConnectionEvent(int state, BluetoothDevice device) {
1429            switch (state) {
1430                case HeadsetClientHalConstants.CONNECTION_STATE_DISCONNECTED:
1431                    if (DBG) {
1432                        Log.d(TAG, "Connected disconnects.");
1433                    }
1434                    // AG disconnects
1435                    if (mCurrentDevice.equals(device)) {
1436                        transitionTo(mDisconnected);
1437                    } else {
1438                        Log.e(TAG, "Disconnected from unknown device: " + device);
1439                    }
1440                    break;
1441                default:
1442                    Log.e(TAG, "Connection State Device: " + device + " bad state: " + state);
1443                    break;
1444            }
1445        }
1446
1447        // in Connected state
1448        private void processAudioEvent(int state, BluetoothDevice device) {
1449            // message from old device
1450            if (!mCurrentDevice.equals(device)) {
1451                Log.e(TAG, "Audio changed on disconnected device: " + device);
1452                return;
1453            }
1454
1455            switch (state) {
1456                case HeadsetClientHalConstants.AUDIO_STATE_CONNECTED_MSBC:
1457                    mAudioWbs = true;
1458                    // fall through
1459                case HeadsetClientHalConstants.AUDIO_STATE_CONNECTED:
1460                    // Audio state is split in two parts, the audio focus is maintained by the
1461                    // entity exercising this service (typically the Telecom stack) and audio
1462                    // routing is handled by the bluetooth stack itself. The only reason to do so is
1463                    // because Bluetooth SCO connection from the HF role is not entirely supported
1464                    // for routing and volume purposes.
1465                    // NOTE: All calls here are routed via the setParameters which changes the
1466                    // routing at the Audio HAL level.
1467
1468                    if (mService.isScoRouted()) {
1469                        StackEvent event =
1470                                new StackEvent(StackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED);
1471                        event.valueInt = state;
1472                        event.device = device;
1473                        sendMessageDelayed(StackEvent.STACK_EVENT, event, ROUTING_DELAY_MS);
1474                        break;
1475                    }
1476
1477                    mAudioState = BluetoothHeadsetClient.STATE_AUDIO_CONNECTED;
1478
1479                    // We need to set the volume after switching into HFP mode as some Audio HALs
1480                    // reset the volume to a known-default on mode switch.
1481                    final int amVol = sAudioManager.getStreamVolume(AudioManager.STREAM_VOICE_CALL);
1482                    final int hfVol = amToHfVol(amVol);
1483
1484                    if (DBG) {
1485                        Log.d(TAG, "hfp_enable=true mAudioWbs is " + mAudioWbs);
1486                    }
1487                    if (mAudioWbs) {
1488                        if (DBG) {
1489                            Log.d(TAG, "Setting sampling rate as 16000");
1490                        }
1491                        sAudioManager.setParameters("hfp_set_sampling_rate=16000");
1492                    } else {
1493                        if (DBG) {
1494                            Log.d(TAG, "Setting sampling rate as 8000");
1495                        }
1496                        sAudioManager.setParameters("hfp_set_sampling_rate=8000");
1497                    }
1498                    if (DBG) {
1499                        Log.d(TAG, "hf_volume " + hfVol);
1500                    }
1501                    routeHfpAudio(true);
1502                    sAudioManager.setParameters("hfp_volume=" + hfVol);
1503                    transitionTo(mAudioOn);
1504                    break;
1505
1506                case HeadsetClientHalConstants.AUDIO_STATE_CONNECTING:
1507                    // No state transition is involved, fire broadcast immediately
1508                    broadcastAudioState(device, BluetoothHeadsetClient.STATE_AUDIO_CONNECTING,
1509                            mAudioState);
1510                    mAudioState = BluetoothHeadsetClient.STATE_AUDIO_CONNECTING;
1511                    break;
1512
1513                case HeadsetClientHalConstants.AUDIO_STATE_DISCONNECTED:
1514                    // No state transition is involved, fire broadcast immediately
1515                    broadcastAudioState(device, BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED,
1516                            mAudioState);
1517                    mAudioState = BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED;
1518                    break;
1519
1520                default:
1521                    Log.e(TAG, "Audio State Device: " + device + " bad state: " + state);
1522                    break;
1523            }
1524        }
1525
1526        @Override
1527        public void exit() {
1528            if (DBG) {
1529                Log.d(TAG, "Exit Connected: " + getCurrentMessage().what);
1530            }
1531            mPrevState = this;
1532        }
1533    }
1534
1535    class AudioOn extends State {
1536        @Override
1537        public void enter() {
1538            if (DBG) {
1539                Log.d(TAG, "Enter AudioOn: " + getCurrentMessage().what);
1540            }
1541            broadcastAudioState(mCurrentDevice, BluetoothHeadsetClient.STATE_AUDIO_CONNECTED,
1542                    BluetoothHeadsetClient.STATE_AUDIO_CONNECTING);
1543        }
1544
1545        @Override
1546        public synchronized boolean processMessage(Message message) {
1547            if (DBG) {
1548                Log.d(TAG, "AudioOn process message: " + message.what);
1549            }
1550            if (DBG) {
1551                if (mCurrentDevice == null) {
1552                    Log.e(TAG, "ERROR: mCurrentDevice is null in Connected");
1553                    return NOT_HANDLED;
1554                }
1555            }
1556
1557            switch (message.what) {
1558                case DISCONNECT:
1559                    BluetoothDevice device = (BluetoothDevice) message.obj;
1560                    if (!mCurrentDevice.equals(device)) {
1561                        break;
1562                    }
1563                    deferMessage(message);
1564                    /*
1565                     * fall through - disconnect audio first then expect
1566                     * deferred DISCONNECT message in Connected state
1567                     */
1568                case DISCONNECT_AUDIO:
1569                    /*
1570                     * just disconnect audio and wait for
1571                     * StackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED, that triggers State
1572                     * Machines state changing
1573                     */
1574                    if (NativeInterface.disconnectAudioNative(getByteAddress(mCurrentDevice))) {
1575                        routeHfpAudio(false);
1576                    }
1577                    break;
1578
1579                case HOLD_CALL:
1580                    holdCall();
1581                    break;
1582
1583                case StackEvent.STACK_EVENT:
1584                    StackEvent event = (StackEvent) message.obj;
1585                    if (DBG) {
1586                        Log.d(TAG, "AudioOn: event type: " + event.type);
1587                    }
1588                    switch (event.type) {
1589                        case StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
1590                            if (DBG) {
1591                                Log.d(TAG, "AudioOn connection state changed" + event.device + ": "
1592                                        + event.valueInt);
1593                            }
1594                            processConnectionEvent(event.valueInt, event.device);
1595                            break;
1596                        case StackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED:
1597                            if (DBG) {
1598                                Log.d(TAG, "AudioOn audio state changed" + event.device + ": "
1599                                        + event.valueInt);
1600                            }
1601                            processAudioEvent(event.valueInt, event.device);
1602                            break;
1603                        default:
1604                            return NOT_HANDLED;
1605                    }
1606                    break;
1607                default:
1608                    return NOT_HANDLED;
1609            }
1610            return HANDLED;
1611        }
1612
1613        // in AudioOn state. Can AG disconnect RFCOMM prior to SCO? Handle this
1614        private void processConnectionEvent(int state, BluetoothDevice device) {
1615            switch (state) {
1616                case HeadsetClientHalConstants.CONNECTION_STATE_DISCONNECTED:
1617                    if (mCurrentDevice.equals(device)) {
1618                        processAudioEvent(HeadsetClientHalConstants.AUDIO_STATE_DISCONNECTED,
1619                                device);
1620                        transitionTo(mDisconnected);
1621                    } else {
1622                        Log.e(TAG, "Disconnected from unknown device: " + device);
1623                    }
1624                    break;
1625                default:
1626                    Log.e(TAG, "Connection State Device: " + device + " bad state: " + state);
1627                    break;
1628            }
1629        }
1630
1631        // in AudioOn state
1632        private void processAudioEvent(int state, BluetoothDevice device) {
1633            if (!mCurrentDevice.equals(device)) {
1634                Log.e(TAG, "Audio changed on disconnected device: " + device);
1635                return;
1636            }
1637
1638            switch (state) {
1639                case HeadsetClientHalConstants.AUDIO_STATE_DISCONNECTED:
1640                    removeMessages(DISCONNECT_AUDIO);
1641                    mAudioState = BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED;
1642                    // Audio focus may still be held by the entity controlling the actual call
1643                    // (such as Telecom) and hence this will still keep the call around, there
1644                    // is not much we can do here since dropping the call without user consent
1645                    // even if the audio connection snapped may not be a good idea.
1646                    routeHfpAudio(false);
1647                    transitionTo(mConnected);
1648                    break;
1649
1650                default:
1651                    Log.e(TAG, "Audio State Device: " + device + " bad state: " + state);
1652                    break;
1653            }
1654        }
1655
1656        @Override
1657        public void exit() {
1658            if (DBG) {
1659                Log.d(TAG, "Exit AudioOn: " + getCurrentMessage().what);
1660            }
1661            mPrevState = this;
1662            broadcastAudioState(mCurrentDevice, BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED,
1663                    BluetoothHeadsetClient.STATE_AUDIO_CONNECTED);
1664        }
1665    }
1666
1667    /**
1668     * @hide
1669     */
1670    public synchronized int getConnectionState(BluetoothDevice device) {
1671        if (mCurrentDevice == null) {
1672            return BluetoothProfile.STATE_DISCONNECTED;
1673        }
1674
1675        if (!mCurrentDevice.equals(device)) {
1676            return BluetoothProfile.STATE_DISCONNECTED;
1677        }
1678
1679        IState currentState = getCurrentState();
1680        if (currentState == mConnecting) {
1681            return BluetoothProfile.STATE_CONNECTING;
1682        }
1683
1684        if (currentState == mConnected || currentState == mAudioOn) {
1685            return BluetoothProfile.STATE_CONNECTED;
1686        }
1687
1688        Log.e(TAG, "Bad currentState: " + currentState);
1689        return BluetoothProfile.STATE_DISCONNECTED;
1690    }
1691
1692    private void broadcastAudioState(BluetoothDevice device, int newState, int prevState) {
1693        Intent intent = new Intent(BluetoothHeadsetClient.ACTION_AUDIO_STATE_CHANGED);
1694        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
1695        intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
1696        if (newState == BluetoothHeadsetClient.STATE_AUDIO_CONNECTED) {
1697            intent.putExtra(BluetoothHeadsetClient.EXTRA_AUDIO_WBS, mAudioWbs);
1698        }
1699        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
1700        mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
1701        if (DBG) {
1702            Log.d(TAG, "Audio state " + device + ": " + prevState + "->" + newState);
1703        }
1704    }
1705
1706    // This method does not check for error condition (newState == prevState)
1707    private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) {
1708        if (DBG) {
1709            Log.d(TAG, "Connection state " + device + ": " + prevState + "->" + newState);
1710        }
1711        /*
1712         * Notifying the connection state change of the profile before sending
1713         * the intent for connection state change, as it was causing a race
1714         * condition, with the UI not being updated with the correct connection
1715         * state.
1716         */
1717        Intent intent = new Intent(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
1718        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
1719        intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
1720        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
1721
1722        // add feature extras when connected
1723        if (newState == BluetoothProfile.STATE_CONNECTED) {
1724            if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_3WAY)
1725                    == HeadsetClientHalConstants.PEER_FEAT_3WAY) {
1726                intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_3WAY_CALLING, true);
1727            }
1728            if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_REJECT)
1729                    == HeadsetClientHalConstants.PEER_FEAT_REJECT) {
1730                intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_REJECT_CALL, true);
1731            }
1732            if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_ECC)
1733                    == HeadsetClientHalConstants.PEER_FEAT_ECC) {
1734                intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_ECC, true);
1735            }
1736
1737            // add individual CHLD support extras
1738            if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_HOLD_ACC)
1739                    == HeadsetClientHalConstants.CHLD_FEAT_HOLD_ACC) {
1740                intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL,
1741                        true);
1742            }
1743            if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_REL)
1744                    == HeadsetClientHalConstants.CHLD_FEAT_REL) {
1745                intent.putExtra(
1746                        BluetoothHeadsetClient.EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL, true);
1747            }
1748            if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_REL_ACC)
1749                    == HeadsetClientHalConstants.CHLD_FEAT_REL_ACC) {
1750                intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT, true);
1751            }
1752            if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_MERGE)
1753                    == HeadsetClientHalConstants.CHLD_FEAT_MERGE) {
1754                intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_MERGE, true);
1755            }
1756            if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_MERGE_DETACH)
1757                    == HeadsetClientHalConstants.CHLD_FEAT_MERGE_DETACH) {
1758                intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_MERGE_AND_DETACH, true);
1759            }
1760        }
1761        mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
1762    }
1763
1764    boolean isConnected() {
1765        IState currentState = getCurrentState();
1766        return (currentState == mConnected || currentState == mAudioOn);
1767    }
1768
1769    List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
1770        List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();
1771        Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
1772        int connectionState;
1773        synchronized (this) {
1774            for (BluetoothDevice device : bondedDevices) {
1775                ParcelUuid[] featureUuids = device.getUuids();
1776                if (!BluetoothUuid.isUuidPresent(featureUuids, BluetoothUuid.Handsfree_AG)) {
1777                    continue;
1778                }
1779                connectionState = getConnectionState(device);
1780                for (int state : states) {
1781                    if (connectionState == state) {
1782                        deviceList.add(device);
1783                    }
1784                }
1785            }
1786        }
1787        return deviceList;
1788    }
1789
1790    boolean okToConnect(BluetoothDevice device) {
1791        int priority = mService.getPriority(device);
1792        boolean ret = false;
1793        // check priority and accept or reject the connection. if priority is
1794        // undefined
1795        // it is likely that our SDP has not completed and peer is initiating
1796        // the
1797        // connection. Allow this connection, provided the device is bonded
1798        if ((BluetoothProfile.PRIORITY_OFF < priority) || (
1799                (BluetoothProfile.PRIORITY_UNDEFINED == priority) && (device.getBondState()
1800                        != BluetoothDevice.BOND_NONE))) {
1801            ret = true;
1802        }
1803        return ret;
1804    }
1805
1806    boolean isAudioOn() {
1807        return (getCurrentState() == mAudioOn);
1808    }
1809
1810    synchronized int getAudioState(BluetoothDevice device) {
1811        if (mCurrentDevice == null || !mCurrentDevice.equals(device)) {
1812            return BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED;
1813        }
1814        return mAudioState;
1815    }
1816
1817    List<BluetoothDevice> getConnectedDevices() {
1818        List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
1819        synchronized (this) {
1820            if (isConnected()) {
1821                devices.add(mCurrentDevice);
1822            }
1823        }
1824        return devices;
1825    }
1826
1827    private byte[] getByteAddress(BluetoothDevice device) {
1828        return Utils.getBytesFromAddress(device.getAddress());
1829    }
1830
1831    public List<BluetoothHeadsetClientCall> getCurrentCalls() {
1832        return new ArrayList<BluetoothHeadsetClientCall>(mCalls.values());
1833    }
1834
1835    public Bundle getCurrentAgEvents() {
1836        Bundle b = new Bundle();
1837        b.putInt(BluetoothHeadsetClient.EXTRA_NETWORK_STATUS, mIndicatorNetworkState);
1838        b.putInt(BluetoothHeadsetClient.EXTRA_NETWORK_SIGNAL_STRENGTH, mIndicatorNetworkSignal);
1839        b.putInt(BluetoothHeadsetClient.EXTRA_NETWORK_ROAMING, mIndicatorNetworkType);
1840        b.putInt(BluetoothHeadsetClient.EXTRA_BATTERY_LEVEL, mIndicatorBatteryLevel);
1841        b.putString(BluetoothHeadsetClient.EXTRA_OPERATOR_NAME, mOperatorName);
1842        b.putString(BluetoothHeadsetClient.EXTRA_SUBSCRIBER_INFO, mSubscriberInfo);
1843        return b;
1844    }
1845}
1846