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