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