HeadsetStateMachine.java revision fd0cb64aeb1f806a041a5881dd2ba846b0aa6a93
1/*
2 * Copyright (C) 2012 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 Handset StateMachine
19 *                      (Disconnected)
20 *                           |    ^
21 *                   CONNECT |    | DISCONNECTED
22 *                           V    |
23 *                         (Pending)
24 *                           |    ^
25 *                 CONNECTED |    | CONNECT
26 *                           V    |
27 *                        (Connected)
28 *                           |    ^
29 *             CONNECT_AUDIO |    | DISCONNECT_AUDIO
30 *                           V    |
31 *                         (AudioOn)
32 */
33package com.android.bluetooth.hfp;
34
35import android.bluetooth.BluetoothAdapter;
36import android.bluetooth.BluetoothAssignedNumbers;
37import android.bluetooth.BluetoothDevice;
38import android.bluetooth.BluetoothHeadset;
39import android.bluetooth.BluetoothProfile;
40import android.bluetooth.BluetoothUuid;
41import android.bluetooth.IBluetooth;
42import android.bluetooth.IBluetoothHeadsetPhone;
43import android.content.ComponentName;
44import android.content.Context;
45import android.content.Intent;
46import android.content.ServiceConnection;
47import android.content.ActivityNotFoundException;
48import android.media.AudioManager;
49import android.net.Uri;
50import android.os.IBinder;
51import android.os.Message;
52import android.os.ParcelUuid;
53import android.os.RemoteException;
54import android.os.ServiceManager;
55import android.os.PowerManager;
56import android.os.PowerManager.WakeLock;
57import android.telephony.PhoneNumberUtils;
58import android.util.Log;
59import com.android.bluetooth.Utils;
60import com.android.bluetooth.btservice.AdapterService;
61import com.android.internal.util.IState;
62import com.android.internal.util.State;
63import com.android.internal.util.StateMachine;
64import java.util.ArrayList;
65import java.util.List;
66import java.util.Set;
67
68final class HeadsetStateMachine extends StateMachine {
69    private static final String TAG = "HeadsetStateMachine";
70    private static final boolean DBG = false;
71    //For Debugging only
72    private static int sRefCount=0;
73
74    private static final String HEADSET_NAME = "bt_headset_name";
75    private static final String HEADSET_NREC = "bt_headset_nrec";
76
77    static final int CONNECT = 1;
78    static final int DISCONNECT = 2;
79    static final int CONNECT_AUDIO = 3;
80    static final int DISCONNECT_AUDIO = 4;
81    static final int VOICE_RECOGNITION_START = 5;
82    static final int VOICE_RECOGNITION_STOP = 6;
83
84    // message.obj is an intent AudioManager.VOLUME_CHANGED_ACTION
85    // EXTRA_VOLUME_STREAM_TYPE is STREAM_BLUETOOTH_SCO
86    static final int INTENT_SCO_VOLUME_CHANGED = 7;
87    static final int SET_MIC_VOLUME = 8;
88    static final int CALL_STATE_CHANGED = 9;
89    static final int INTENT_BATTERY_CHANGED = 10;
90    static final int DEVICE_STATE_CHANGED = 11;
91    static final int SEND_CCLC_RESPONSE = 12;
92
93    static final int VIRTUAL_CALL_START = 13;
94    static final int VIRTUAL_CALL_STOP = 14;
95
96    private static final int STACK_EVENT = 101;
97    private static final int DIALING_OUT_TIMEOUT = 102;
98    private static final int START_VR_TIMEOUT = 103;
99
100    private static final int CONNECT_TIMEOUT = 201;
101
102    private static final int DIALING_OUT_TIMEOUT_VALUE = 10000;
103    private static final int START_VR_TIMEOUT_VALUE = 5000;
104
105    private static final ParcelUuid[] HEADSET_UUIDS = {
106        BluetoothUuid.HSP,
107        BluetoothUuid.Handsfree,
108    };
109
110    private Disconnected mDisconnected;
111    private Pending mPending;
112    private Connected mConnected;
113    private AudioOn mAudioOn;
114
115    private HeadsetService mService;
116    private PowerManager mPowerManager;
117    private boolean mVirtualCallStarted = false;
118    private boolean mVoiceRecognitionStarted = false;
119    private boolean mWaitingForVoiceRecognition = false;
120    private WakeLock mStartVoiceRecognitionWakeLock;  // held while waiting for voice recognition
121
122    private boolean mDialingOut = false;
123    private AudioManager mAudioManager;
124    private AtPhonebook mPhonebook;
125
126    private static Intent sVoiceCommandIntent;
127
128    private HeadsetPhoneState mPhoneState;
129    private int mAudioState;
130    private BluetoothAdapter mAdapter;
131    private IBluetoothHeadsetPhone mPhoneProxy;
132    private boolean mNativeAvailable;
133
134    // mCurrentDevice is the device connected before the state changes
135    // mTargetDevice is the device to be connected
136    // mIncomingDevice is the device connecting to us, valid only in Pending state
137    //                when mIncomingDevice is not null, both mCurrentDevice
138    //                  and mTargetDevice are null
139    //                when either mCurrentDevice or mTargetDevice is not null,
140    //                  mIncomingDevice is null
141    // Stable states
142    //   No connection, Disconnected state
143    //                  both mCurrentDevice and mTargetDevice are null
144    //   Connected, Connected state
145    //              mCurrentDevice is not null, mTargetDevice is null
146    // Interim states
147    //   Connecting to a device, Pending
148    //                           mCurrentDevice is null, mTargetDevice is not null
149    //   Disconnecting device, Connecting to new device
150    //     Pending
151    //     Both mCurrentDevice and mTargetDevice are not null
152    //   Disconnecting device Pending
153    //                        mCurrentDevice is not null, mTargetDevice is null
154    //   Incoming connections Pending
155    //                        Both mCurrentDevice and mTargetDevice are null
156    private BluetoothDevice mCurrentDevice = null;
157    private BluetoothDevice mTargetDevice = null;
158    private BluetoothDevice mIncomingDevice = null;
159
160    static {
161        classInitNative();
162    }
163
164    private HeadsetStateMachine(HeadsetService context) {
165        super(TAG);
166        mService = context;
167        mVoiceRecognitionStarted = false;
168        mWaitingForVoiceRecognition = false;
169
170        mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
171        mStartVoiceRecognitionWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
172                                                       TAG + ":VoiceRecognition");
173        mStartVoiceRecognitionWakeLock.setReferenceCounted(false);
174
175        mDialingOut = false;
176        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
177        mPhonebook = new AtPhonebook(mService, this);
178        mPhoneState = new HeadsetPhoneState(context, this);
179        mAudioState = BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
180        mAdapter = BluetoothAdapter.getDefaultAdapter();
181        if (!context.bindService(new Intent(IBluetoothHeadsetPhone.class.getName()),
182                                 mConnection, 0)) {
183            Log.e(TAG, "Could not bind to Bluetooth Headset Phone Service");
184        }
185
186        initializeNative();
187        mNativeAvailable=true;
188
189        mDisconnected = new Disconnected();
190        mPending = new Pending();
191        mConnected = new Connected();
192        mAudioOn = new AudioOn();
193
194        if (sVoiceCommandIntent == null) {
195            sVoiceCommandIntent = new Intent(Intent.ACTION_VOICE_COMMAND);
196            sVoiceCommandIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
197        }
198
199        addState(mDisconnected);
200        addState(mPending);
201        addState(mConnected);
202        addState(mAudioOn);
203
204        setInitialState(mDisconnected);
205    }
206
207    static HeadsetStateMachine make(HeadsetService context) {
208        Log.d(TAG, "make");
209        HeadsetStateMachine hssm = new HeadsetStateMachine(context);
210        hssm.start();
211        return hssm;
212    }
213
214
215    public void doQuit() {
216        quitNow();
217    }
218
219    public void cleanup() {
220        if (mPhoneProxy != null) {
221            if (DBG) Log.d(TAG,"Unbinding service...");
222            synchronized (mConnection) {
223                try {
224                    mPhoneProxy = null;
225                    mService.unbindService(mConnection);
226                } catch (Exception re) {
227                    Log.e(TAG,"Error unbinding from IBluetoothHeadsetPhone",re);
228                }
229            }
230        }
231        if (mPhoneState != null) {
232            mPhoneState.listenForPhoneState(false);
233            mPhoneState.cleanup();
234        }
235        if (mPhonebook != null) {
236            mPhonebook.cleanup();
237        }
238        if (mNativeAvailable) {
239            cleanupNative();
240            mNativeAvailable = false;
241        }
242    }
243
244    private class Disconnected extends State {
245        @Override
246        public void enter() {
247            log("Enter Disconnected: " + getCurrentMessage().what);
248            mPhonebook.resetAtState();
249            mPhoneState.listenForPhoneState(false);
250        }
251
252        @Override
253        public boolean processMessage(Message message) {
254            log("Disconnected process message: " + message.what);
255            if (mCurrentDevice != null || mTargetDevice != null || mIncomingDevice != null) {
256                Log.e(TAG, "ERROR: current, target, or mIncomingDevice not null in Disconnected");
257                return NOT_HANDLED;
258            }
259
260            boolean retValue = HANDLED;
261            switch(message.what) {
262                case CONNECT:
263                    BluetoothDevice device = (BluetoothDevice) message.obj;
264                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
265                                   BluetoothProfile.STATE_DISCONNECTED);
266
267                    if (!connectHfpNative(getByteAddress(device)) ) {
268                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
269                                       BluetoothProfile.STATE_CONNECTING);
270                        break;
271                    }
272
273                    synchronized (HeadsetStateMachine.this) {
274                        mTargetDevice = device;
275                        transitionTo(mPending);
276                    }
277                    // TODO(BT) remove CONNECT_TIMEOUT when the stack
278                    //          sends back events consistently
279                    sendMessageDelayed(CONNECT_TIMEOUT, 30000);
280                    break;
281                case DISCONNECT:
282                    // ignore
283                    break;
284                case INTENT_BATTERY_CHANGED:
285                    processIntentBatteryChanged((Intent) message.obj);
286                    break;
287                case CALL_STATE_CHANGED:
288                    processCallState((HeadsetCallState) message.obj,
289                        ((message.arg1 == 1)?true:false));
290                    break;
291                case STACK_EVENT:
292                    StackEvent event = (StackEvent) message.obj;
293                    if (DBG) {
294                        log("event type: " + event.type);
295                    }
296                    switch (event.type) {
297                        case EVENT_TYPE_CONNECTION_STATE_CHANGED:
298                            processConnectionEvent(event.valueInt, event.device);
299                            break;
300                        default:
301                            Log.e(TAG, "Unexpected stack event: " + event.type);
302                            break;
303                    }
304                    break;
305                default:
306                    return NOT_HANDLED;
307            }
308            return retValue;
309        }
310
311        @Override
312        public void exit() {
313            log("Exit Disconnected: " + getCurrentMessage().what);
314        }
315
316        // in Disconnected state
317        private void processConnectionEvent(int state, BluetoothDevice device) {
318            switch (state) {
319            case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED:
320                Log.w(TAG, "Ignore HF DISCONNECTED event, device: " + device);
321                break;
322            case HeadsetHalConstants.CONNECTION_STATE_CONNECTING:
323                if (okToConnect(device)){
324                    Log.i(TAG,"Incoming Hf accepted");
325                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
326                                             BluetoothProfile.STATE_DISCONNECTED);
327                    synchronized (HeadsetStateMachine.this) {
328                        mIncomingDevice = device;
329                        transitionTo(mPending);
330                    }
331                } else {
332                    Log.i(TAG,"Incoming Hf rejected. priority=" + mService.getPriority(device)+
333                              " bondState=" + device.getBondState());
334                    //reject the connection and stay in Disconnected state itself
335                    disconnectHfpNative(getByteAddress(device));
336                    // the other profile connection should be initiated
337                    AdapterService adapterService = AdapterService.getAdapterService();
338                    if ( adapterService != null) {
339                        adapterService.connectOtherProfile(device,
340                                                           AdapterService.PROFILE_CONN_REJECTED);
341                    }
342                }
343                break;
344            case HeadsetHalConstants.CONNECTION_STATE_CONNECTED:
345                Log.w(TAG, "HFP Connected from Disconnected state");
346                if (okToConnect(device)){
347                    Log.i(TAG,"Incoming Hf accepted");
348                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
349                                             BluetoothProfile.STATE_DISCONNECTED);
350                    synchronized (HeadsetStateMachine.this) {
351                        mCurrentDevice = device;
352                        transitionTo(mConnected);
353                    }
354                    configAudioParameters();
355                } else {
356                    //reject the connection and stay in Disconnected state itself
357                    Log.i(TAG,"Incoming Hf rejected. priority=" + mService.getPriority(device) +
358                              " bondState=" + device.getBondState());
359                    disconnectHfpNative(getByteAddress(device));
360                    // the other profile connection should be initiated
361                    AdapterService adapterService = AdapterService.getAdapterService();
362                    if ( adapterService != null) {
363                        adapterService.connectOtherProfile(device,
364                                                           AdapterService.PROFILE_CONN_REJECTED);
365                    }
366                }
367                break;
368            case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTING:
369                Log.w(TAG, "Ignore HF DISCONNECTING event, device: " + device);
370                break;
371            default:
372                Log.e(TAG, "Incorrect state: " + state);
373                break;
374            }
375        }
376    }
377
378    private class Pending extends State {
379        @Override
380        public void enter() {
381            log("Enter Pending: " + getCurrentMessage().what);
382        }
383
384        @Override
385        public boolean processMessage(Message message) {
386            log("Pending process message: " + message.what);
387
388            boolean retValue = HANDLED;
389            switch(message.what) {
390                case CONNECT:
391                case CONNECT_AUDIO:
392                    deferMessage(message);
393                    break;
394                case CONNECT_TIMEOUT:
395                    onConnectionStateChanged(HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED,
396                                             getByteAddress(mTargetDevice));
397                    break;
398                case DISCONNECT:
399                    BluetoothDevice device = (BluetoothDevice) message.obj;
400                    if (mCurrentDevice != null && mTargetDevice != null &&
401                        mTargetDevice.equals(device) ) {
402                        // cancel connection to the mTargetDevice
403                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
404                                       BluetoothProfile.STATE_CONNECTING);
405                        synchronized (HeadsetStateMachine.this) {
406                            mTargetDevice = null;
407                        }
408                    } else {
409                        deferMessage(message);
410                    }
411                    break;
412                case INTENT_BATTERY_CHANGED:
413                    processIntentBatteryChanged((Intent) message.obj);
414                    break;
415                case CALL_STATE_CHANGED:
416                    processCallState((HeadsetCallState) message.obj,
417                        ((message.arg1 == 1)?true:false));
418                    break;
419                case STACK_EVENT:
420                    StackEvent event = (StackEvent) message.obj;
421                    if (DBG) {
422                        log("event type: " + event.type);
423                    }
424                    switch (event.type) {
425                        case EVENT_TYPE_CONNECTION_STATE_CHANGED:
426                            removeMessages(CONNECT_TIMEOUT);
427                            processConnectionEvent(event.valueInt, event.device);
428                            break;
429                        default:
430                            Log.e(TAG, "Unexpected event: " + event.type);
431                            break;
432                    }
433                    break;
434                default:
435                    return NOT_HANDLED;
436            }
437            return retValue;
438        }
439
440        // in Pending state
441        private void processConnectionEvent(int state, BluetoothDevice device) {
442            switch (state) {
443                case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED:
444                    if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
445                        broadcastConnectionState(mCurrentDevice,
446                                                 BluetoothProfile.STATE_DISCONNECTED,
447                                                 BluetoothProfile.STATE_DISCONNECTING);
448                        synchronized (HeadsetStateMachine.this) {
449                            mCurrentDevice = null;
450                        }
451
452                        if (mTargetDevice != null) {
453                            if (!connectHfpNative(getByteAddress(mTargetDevice))) {
454                                broadcastConnectionState(mTargetDevice,
455                                                         BluetoothProfile.STATE_DISCONNECTED,
456                                                         BluetoothProfile.STATE_CONNECTING);
457                                synchronized (HeadsetStateMachine.this) {
458                                    mTargetDevice = null;
459                                    transitionTo(mDisconnected);
460                                }
461                            }
462                        } else {
463                            synchronized (HeadsetStateMachine.this) {
464                                mIncomingDevice = null;
465                                transitionTo(mDisconnected);
466                            }
467                        }
468                    } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
469                        // outgoing connection failed
470                        broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
471                                                 BluetoothProfile.STATE_CONNECTING);
472                        synchronized (HeadsetStateMachine.this) {
473                            mTargetDevice = null;
474                            transitionTo(mDisconnected);
475                        }
476                    } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
477                        broadcastConnectionState(mIncomingDevice,
478                                                 BluetoothProfile.STATE_DISCONNECTED,
479                                                 BluetoothProfile.STATE_CONNECTING);
480                        synchronized (HeadsetStateMachine.this) {
481                            mIncomingDevice = null;
482                            transitionTo(mDisconnected);
483                        }
484                    } else {
485                        Log.e(TAG, "Unknown device Disconnected: " + device);
486                    }
487                    break;
488            case HeadsetHalConstants.CONNECTION_STATE_CONNECTED:
489                if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
490                    // disconnection failed
491                    broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED,
492                                             BluetoothProfile.STATE_DISCONNECTING);
493                    if (mTargetDevice != null) {
494                        broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
495                                                 BluetoothProfile.STATE_CONNECTING);
496                    }
497                    synchronized (HeadsetStateMachine.this) {
498                        mTargetDevice = null;
499                        transitionTo(mConnected);
500                    }
501                } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
502                    broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_CONNECTED,
503                                             BluetoothProfile.STATE_CONNECTING);
504                    synchronized (HeadsetStateMachine.this) {
505                        mCurrentDevice = mTargetDevice;
506                        mTargetDevice = null;
507                        transitionTo(mConnected);
508                    }
509                } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
510                    broadcastConnectionState(mIncomingDevice, BluetoothProfile.STATE_CONNECTED,
511                                             BluetoothProfile.STATE_CONNECTING);
512                    synchronized (HeadsetStateMachine.this) {
513                        mCurrentDevice = mIncomingDevice;
514                        mIncomingDevice = null;
515                        transitionTo(mConnected);
516                    }
517                } else {
518                    Log.e(TAG, "Unknown device Connected: " + device);
519                    // something is wrong here, but sync our state with stack
520                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
521                                             BluetoothProfile.STATE_DISCONNECTED);
522                    synchronized (HeadsetStateMachine.this) {
523                        mCurrentDevice = device;
524                        mTargetDevice = null;
525                        mIncomingDevice = null;
526                        transitionTo(mConnected);
527                    }
528                }
529                configAudioParameters();
530                break;
531            case HeadsetHalConstants.CONNECTION_STATE_CONNECTING:
532                if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
533                    log("current device tries to connect back");
534                    // TODO(BT) ignore or reject
535                } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
536                    // The stack is connecting to target device or
537                    // there is an incoming connection from the target device at the same time
538                    // we already broadcasted the intent, doing nothing here
539                    if (DBG) {
540                        log("Stack and target device are connecting");
541                    }
542                }
543                else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
544                    Log.e(TAG, "Another connecting event on the incoming device");
545                } else {
546                    // We get an incoming connecting request while Pending
547                    // TODO(BT) is stack handing this case? let's ignore it for now
548                    log("Incoming connection while pending, ignore");
549                }
550                break;
551            case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTING:
552                if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
553                    // we already broadcasted the intent, doing nothing here
554                    if (DBG) {
555                        log("stack is disconnecting mCurrentDevice");
556                    }
557                } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
558                    Log.e(TAG, "TargetDevice is getting disconnected");
559                } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
560                    Log.e(TAG, "IncomingDevice is getting disconnected");
561                } else {
562                    Log.e(TAG, "Disconnecting unknow device: " + device);
563                }
564                break;
565            default:
566                Log.e(TAG, "Incorrect state: " + state);
567                break;
568            }
569        }
570
571    }
572
573    private class Connected extends State {
574        @Override
575        public void enter() {
576            log("Enter Connected: " + getCurrentMessage().what);
577        }
578
579        @Override
580        public boolean processMessage(Message message) {
581            log("Connected process message: " + message.what);
582            if (DBG) {
583                if (mCurrentDevice == null) {
584                    log("ERROR: mCurrentDevice is null in Connected");
585                    return NOT_HANDLED;
586                }
587            }
588
589            boolean retValue = HANDLED;
590            switch(message.what) {
591                case CONNECT:
592                {
593                    BluetoothDevice device = (BluetoothDevice) message.obj;
594                    if (mCurrentDevice.equals(device)) {
595                        break;
596                    }
597
598                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
599                                   BluetoothProfile.STATE_DISCONNECTED);
600                    if (!disconnectHfpNative(getByteAddress(mCurrentDevice))) {
601                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
602                                       BluetoothProfile.STATE_CONNECTING);
603                        break;
604                    }
605
606                    synchronized (HeadsetStateMachine.this) {
607                        mTargetDevice = device;
608                        transitionTo(mPending);
609                    }
610                }
611                    break;
612                case DISCONNECT:
613                {
614                    BluetoothDevice device = (BluetoothDevice) message.obj;
615                    if (!mCurrentDevice.equals(device)) {
616                        break;
617                    }
618                    broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING,
619                                   BluetoothProfile.STATE_CONNECTED);
620                    if (!disconnectHfpNative(getByteAddress(device))) {
621                        broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
622                                       BluetoothProfile.STATE_DISCONNECTED);
623                        break;
624                    }
625                    transitionTo(mPending);
626                }
627                    break;
628                case CONNECT_AUDIO:
629                    // TODO(BT) when failure, broadcast audio connecting to disconnected intent
630                    //          check if device matches mCurrentDevice
631                    connectAudioNative(getByteAddress(mCurrentDevice));
632                    break;
633                case VOICE_RECOGNITION_START:
634                    processLocalVrEvent(HeadsetHalConstants.VR_STATE_STARTED);
635                    break;
636                case VOICE_RECOGNITION_STOP:
637                    processLocalVrEvent(HeadsetHalConstants.VR_STATE_STOPPED);
638                    break;
639                case CALL_STATE_CHANGED:
640                    processCallState((HeadsetCallState) message.obj, ((message.arg1==1)?true:false));
641                    break;
642                case INTENT_BATTERY_CHANGED:
643                    processIntentBatteryChanged((Intent) message.obj);
644                    break;
645                case DEVICE_STATE_CHANGED:
646                    processDeviceStateChanged((HeadsetDeviceState) message.obj);
647                    break;
648                case SEND_CCLC_RESPONSE:
649                    processSendClccResponse((HeadsetClccResponse) message.obj);
650                    break;
651                case DIALING_OUT_TIMEOUT:
652                    if (mDialingOut) {
653                        mDialingOut= false;
654                        atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
655                    }
656                    break;
657                case VIRTUAL_CALL_START:
658                    initiateScoUsingVirtualVoiceCall();
659                    break;
660                case VIRTUAL_CALL_STOP:
661                    terminateScoUsingVirtualVoiceCall();
662                    break;
663                case START_VR_TIMEOUT:
664                    if (mWaitingForVoiceRecognition) {
665                        mWaitingForVoiceRecognition = false;
666                        Log.e(TAG, "Timeout waiting for voice recognition to start");
667                        atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
668                    }
669                    break;
670                case STACK_EVENT:
671                    StackEvent event = (StackEvent) message.obj;
672                    if (DBG) {
673                        log("event type: " + event.type);
674                    }
675                    switch (event.type) {
676                        case EVENT_TYPE_CONNECTION_STATE_CHANGED:
677                            processConnectionEvent(event.valueInt, event.device);
678                            break;
679                        case EVENT_TYPE_AUDIO_STATE_CHANGED:
680                            processAudioEvent(event.valueInt, event.device);
681                            break;
682                        case EVENT_TYPE_VR_STATE_CHANGED:
683                            processVrEvent(event.valueInt);
684                            break;
685                        case EVENT_TYPE_ANSWER_CALL:
686                            // TODO(BT) could answer call happen on Connected state?
687                            processAnswerCall();
688                            break;
689                        case EVENT_TYPE_HANGUP_CALL:
690                            // TODO(BT) could hangup call happen on Connected state?
691                            processHangupCall();
692                            break;
693                        case EVENT_TYPE_VOLUME_CHANGED:
694                            processVolumeEvent(event.valueInt, event.valueInt2);
695                            break;
696                        case EVENT_TYPE_DIAL_CALL:
697                            processDialCall(event.valueString);
698                            break;
699                        case EVENT_TYPE_SEND_DTMF:
700                            processSendDtmf(event.valueInt);
701                            break;
702                        case EVENT_TYPE_NOICE_REDUCTION:
703                            processNoiceReductionEvent(event.valueInt);
704                            break;
705                        case EVENT_TYPE_AT_CHLD:
706                            processAtChld(event.valueInt);
707                            break;
708                        case EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST:
709                            processSubscriberNumberRequest();
710                            break;
711                        case EVENT_TYPE_AT_CIND:
712                            processAtCind();
713                            break;
714                        case EVENT_TYPE_AT_COPS:
715                            processAtCops();
716                            break;
717                        case EVENT_TYPE_AT_CLCC:
718                            processAtClcc();
719                            break;
720                        case EVENT_TYPE_UNKNOWN_AT:
721                            processUnknownAt(event.valueString);
722                            break;
723                        case EVENT_TYPE_KEY_PRESSED:
724                            processKeyPressed();
725                            break;
726                        default:
727                            Log.e(TAG, "Unknown stack event: " + event.type);
728                            break;
729                    }
730                    break;
731                default:
732                    return NOT_HANDLED;
733            }
734            return retValue;
735        }
736
737        // in Connected state
738        private void processConnectionEvent(int state, BluetoothDevice device) {
739            switch (state) {
740                case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED:
741                    if (mCurrentDevice.equals(device)) {
742                        broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED,
743                                                 BluetoothProfile.STATE_CONNECTED);
744                        synchronized (HeadsetStateMachine.this) {
745                            mCurrentDevice = null;
746                            transitionTo(mDisconnected);
747                        }
748                    } else {
749                        Log.e(TAG, "Disconnected from unknown device: " + device);
750                    }
751                    break;
752                case HeadsetHalConstants.CONNECTION_STATE_SLC_CONNECTED:
753                    processSlcConnected();
754                    break;
755              default:
756                  Log.e(TAG, "Connection State Device: " + device + " bad state: " + state);
757                  break;
758            }
759        }
760
761        // in Connected state
762        private void processAudioEvent(int state, BluetoothDevice device) {
763            if (!mCurrentDevice.equals(device)) {
764                Log.e(TAG, "Audio changed on disconnected device: " + device);
765                return;
766            }
767
768            switch (state) {
769                case HeadsetHalConstants.AUDIO_STATE_CONNECTED:
770                    // TODO(BT) should I save the state for next broadcast as the prevState?
771                    mAudioState = BluetoothHeadset.STATE_AUDIO_CONNECTED;
772                    mAudioManager.setBluetoothScoOn(true);
773                    broadcastAudioState(device, BluetoothHeadset.STATE_AUDIO_CONNECTED,
774                                        BluetoothHeadset.STATE_AUDIO_CONNECTING);
775                    transitionTo(mAudioOn);
776                    break;
777                case HeadsetHalConstants.AUDIO_STATE_CONNECTING:
778                    mAudioState = BluetoothHeadset.STATE_AUDIO_CONNECTING;
779                    broadcastAudioState(device, BluetoothHeadset.STATE_AUDIO_CONNECTING,
780                                        BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
781                    break;
782                    // TODO(BT) process other states
783                default:
784                    Log.e(TAG, "Audio State Device: " + device + " bad state: " + state);
785                    break;
786            }
787        }
788
789        private void processSlcConnected() {
790            if (mPhoneProxy != null) {
791                try {
792                    // start phone state listener here, instead of on disconnected exit()
793                    // On BT off, exitting SM sends a SM exit() call which incorrectly forces
794                    // a listenForPhoneState(true).
795                    // Additionally, no indicator updates should be sent prior to SLC setup
796                    mPhoneState.listenForPhoneState(true);
797                    mPhoneProxy.queryPhoneState();
798                } catch (RemoteException e) {
799                    Log.e(TAG, Log.getStackTraceString(new Throwable()));
800                }
801            } else {
802                Log.e(TAG, "Handsfree phone proxy null for query phone state");
803            }
804
805        }
806    }
807
808    private class AudioOn extends State {
809
810        @Override
811        public void enter() {
812            log("Enter AudioOn: " + getCurrentMessage().what);
813        }
814
815        @Override
816        public boolean processMessage(Message message) {
817            log("AudioOn process message: " + message.what);
818            if (DBG) {
819                if (mCurrentDevice == null) {
820                    log("ERROR: mCurrentDevice is null in AudioOn");
821                    return NOT_HANDLED;
822                }
823            }
824
825            boolean retValue = HANDLED;
826            switch(message.what) {
827                case DISCONNECT:
828                {
829                    BluetoothDevice device = (BluetoothDevice) message.obj;
830                    if (!mCurrentDevice.equals(device)) {
831                        break;
832                    }
833                    deferMessage(obtainMessage(DISCONNECT, message.obj));
834                }
835                // fall through
836                case DISCONNECT_AUDIO:
837                    if (disconnectAudioNative(getByteAddress(mCurrentDevice))) {
838                        mAudioState = BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
839                        mAudioManager.setBluetoothScoOn(false);
840                        broadcastAudioState(mCurrentDevice, BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
841                                            BluetoothHeadset.STATE_AUDIO_CONNECTED);
842                    }
843                    break;
844                case VOICE_RECOGNITION_START:
845                    processLocalVrEvent(HeadsetHalConstants.VR_STATE_STARTED);
846                    break;
847                case VOICE_RECOGNITION_STOP:
848                    processLocalVrEvent(HeadsetHalConstants.VR_STATE_STOPPED);
849                    break;
850                case INTENT_SCO_VOLUME_CHANGED:
851                    processIntentScoVolume((Intent) message.obj);
852                    break;
853                case CALL_STATE_CHANGED:
854                    processCallState((HeadsetCallState) message.obj, ((message.arg1 == 1)?true:false));
855                    break;
856                case INTENT_BATTERY_CHANGED:
857                    processIntentBatteryChanged((Intent) message.obj);
858                    break;
859                case DEVICE_STATE_CHANGED:
860                    processDeviceStateChanged((HeadsetDeviceState) message.obj);
861                    break;
862                case SEND_CCLC_RESPONSE:
863                    processSendClccResponse((HeadsetClccResponse) message.obj);
864                    break;
865
866                case VIRTUAL_CALL_START:
867                    initiateScoUsingVirtualVoiceCall();
868                    break;
869                case VIRTUAL_CALL_STOP:
870                    terminateScoUsingVirtualVoiceCall();
871                    break;
872
873                case DIALING_OUT_TIMEOUT:
874                    if (mDialingOut) {
875                        mDialingOut= false;
876                        atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
877                    }
878                    break;
879                case START_VR_TIMEOUT:
880                    if (mWaitingForVoiceRecognition) {
881                        mWaitingForVoiceRecognition = false;
882                        Log.e(TAG, "Timeout waiting for voice recognition to start");
883                        atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
884                    }
885                    break;
886                case STACK_EVENT:
887                    StackEvent event = (StackEvent) message.obj;
888                    if (DBG) {
889                        log("event type: " + event.type);
890                    }
891                    switch (event.type) {
892                        case EVENT_TYPE_CONNECTION_STATE_CHANGED:
893                            processConnectionEvent(event.valueInt, event.device);
894                            break;
895                        case EVENT_TYPE_AUDIO_STATE_CHANGED:
896                            processAudioEvent(event.valueInt, event.device);
897                            break;
898                        case EVENT_TYPE_VR_STATE_CHANGED:
899                            processVrEvent(event.valueInt);
900                            break;
901                        case EVENT_TYPE_ANSWER_CALL:
902                            processAnswerCall();
903                            break;
904                        case EVENT_TYPE_HANGUP_CALL:
905                            processHangupCall();
906                            break;
907                        case EVENT_TYPE_VOLUME_CHANGED:
908                            processVolumeEvent(event.valueInt, event.valueInt2);
909                            break;
910                        case EVENT_TYPE_DIAL_CALL:
911                            processDialCall(event.valueString);
912                            break;
913                        case EVENT_TYPE_SEND_DTMF:
914                            processSendDtmf(event.valueInt);
915                            break;
916                        case EVENT_TYPE_NOICE_REDUCTION:
917                            processNoiceReductionEvent(event.valueInt);
918                            break;
919                        case EVENT_TYPE_AT_CHLD:
920                            processAtChld(event.valueInt);
921                            break;
922                        case EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST:
923                            processSubscriberNumberRequest();
924                            break;
925                        case EVENT_TYPE_AT_CIND:
926                            processAtCind();
927                            break;
928                        case EVENT_TYPE_AT_COPS:
929                            processAtCops();
930                            break;
931                        case EVENT_TYPE_AT_CLCC:
932                            processAtClcc();
933                            break;
934                        case EVENT_TYPE_UNKNOWN_AT:
935                            processUnknownAt(event.valueString);
936                            break;
937                        case EVENT_TYPE_KEY_PRESSED:
938                            processKeyPressed();
939                            break;
940                        default:
941                            Log.e(TAG, "Unknown stack event: " + event.type);
942                            break;
943                    }
944                    break;
945                default:
946                    return NOT_HANDLED;
947            }
948            return retValue;
949        }
950
951        // in AudioOn state. Some headsets disconnect RFCOMM prior to SCO down. Handle this
952        private void processConnectionEvent(int state, BluetoothDevice device) {
953            switch (state) {
954                case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED:
955                    if (mCurrentDevice.equals(device)) {
956                        processAudioEvent (HeadsetHalConstants.AUDIO_STATE_DISCONNECTED, device);
957                        broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED,
958                                                 BluetoothProfile.STATE_CONNECTED);
959                        synchronized (HeadsetStateMachine.this) {
960                            mCurrentDevice = null;
961                            transitionTo(mDisconnected);
962                        }
963                    } else {
964                        Log.e(TAG, "Disconnected from unknown device: " + device);
965                    }
966                    break;
967              default:
968                  Log.e(TAG, "Connection State Device: " + device + " bad state: " + state);
969                  break;
970            }
971        }
972
973        // in AudioOn state
974        private void processAudioEvent(int state, BluetoothDevice device) {
975            if (!mCurrentDevice.equals(device)) {
976                Log.e(TAG, "Audio changed on disconnected device: " + device);
977                return;
978            }
979
980            switch (state) {
981                case HeadsetHalConstants.AUDIO_STATE_DISCONNECTED:
982                    if (mAudioState != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
983                        mAudioState = BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
984                        mAudioManager.setBluetoothScoOn(false);
985                        broadcastAudioState(device, BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
986                                            BluetoothHeadset.STATE_AUDIO_CONNECTED);
987                    }
988                    transitionTo(mConnected);
989                    break;
990                case HeadsetHalConstants.AUDIO_STATE_DISCONNECTING:
991                    // TODO(BT) adding STATE_AUDIO_DISCONNECTING in BluetoothHeadset?
992                    //broadcastAudioState(device, BluetoothHeadset.STATE_AUDIO_DISCONNECTING,
993                    //                    BluetoothHeadset.STATE_AUDIO_CONNECTED);
994                    break;
995                default:
996                    Log.e(TAG, "Audio State Device: " + device + " bad state: " + state);
997                    break;
998            }
999        }
1000
1001        private void processIntentScoVolume(Intent intent) {
1002            int volumeValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
1003            if (mPhoneState.getSpeakerVolume() != volumeValue) {
1004                mPhoneState.setSpeakerVolume(volumeValue);
1005                setVolumeNative(HeadsetHalConstants.VOLUME_TYPE_SPK, volumeValue);
1006            }
1007        }
1008    }
1009
1010    private ServiceConnection mConnection = new ServiceConnection() {
1011        public void onServiceConnected(ComponentName className, IBinder service) {
1012            if (DBG) Log.d(TAG, "Proxy object connected");
1013            mPhoneProxy = IBluetoothHeadsetPhone.Stub.asInterface(service);
1014        }
1015
1016        public void onServiceDisconnected(ComponentName className) {
1017            if (DBG) Log.d(TAG, "Proxy object disconnected");
1018            mPhoneProxy = null;
1019        }
1020    };
1021
1022    // HFP Connection state of the device could be changed by the state machine
1023    // in separate thread while this method is executing.
1024    int getConnectionState(BluetoothDevice device) {
1025        if (getCurrentState() == mDisconnected) {
1026            return BluetoothProfile.STATE_DISCONNECTED;
1027        }
1028
1029        synchronized (this) {
1030            IState currentState = getCurrentState();
1031            if (currentState == mPending) {
1032                if ((mTargetDevice != null) && mTargetDevice.equals(device)) {
1033                    return BluetoothProfile.STATE_CONNECTING;
1034                }
1035                if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
1036                    return BluetoothProfile.STATE_DISCONNECTING;
1037                }
1038                if ((mIncomingDevice != null) && mIncomingDevice.equals(device)) {
1039                    return BluetoothProfile.STATE_CONNECTING; // incoming connection
1040                }
1041                return BluetoothProfile.STATE_DISCONNECTED;
1042            }
1043
1044            if (currentState == mConnected || currentState == mAudioOn) {
1045                if (mCurrentDevice.equals(device)) {
1046                    return BluetoothProfile.STATE_CONNECTED;
1047                }
1048                return BluetoothProfile.STATE_DISCONNECTED;
1049            } else {
1050                Log.e(TAG, "Bad currentState: " + currentState);
1051                return BluetoothProfile.STATE_DISCONNECTED;
1052            }
1053        }
1054    }
1055
1056    List<BluetoothDevice> getConnectedDevices() {
1057        List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
1058        synchronized(this) {
1059            if (isConnected()) {
1060                devices.add(mCurrentDevice);
1061            }
1062        }
1063        return devices;
1064    }
1065
1066    boolean isAudioOn() {
1067        return (getCurrentState() == mAudioOn);
1068    }
1069
1070    boolean isAudioConnected(BluetoothDevice device) {
1071        synchronized(this) {
1072
1073            /*  Additional check for audio state included for the case when PhoneApp queries
1074            Bluetooth Audio state, before we receive the close event from the stack for the
1075            sco disconnect issued in AudioOn state. This was causing a mismatch in the
1076            Incall screen UI. */
1077
1078            if (getCurrentState() == mAudioOn && mCurrentDevice.equals(device)
1079                && mAudioState != BluetoothHeadset.STATE_AUDIO_DISCONNECTED)
1080            {
1081                return true;
1082            }
1083        }
1084        return false;
1085    }
1086
1087    int getAudioState(BluetoothDevice device) {
1088        synchronized(this) {
1089            if (mCurrentDevice == null || !mCurrentDevice.equals(device)) {
1090                return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
1091            }
1092        }
1093        return mAudioState;
1094    }
1095
1096    private void processVrEvent(int state) {
1097        Log.d(TAG, "processVrEvent: state=" + state + " mVoiceRecognitionStarted: " +
1098            mVoiceRecognitionStarted + " mWaitingforVoiceRecognition: " + mWaitingForVoiceRecognition +
1099            " isInCall: " + isInCall());
1100        if (state == HeadsetHalConstants.VR_STATE_STARTED) {
1101            if (!mVoiceRecognitionStarted &&
1102                !isVirtualCallInProgress() &&
1103                !isInCall())
1104            {
1105                try {
1106                    mService.startActivity(sVoiceCommandIntent);
1107                } catch (ActivityNotFoundException e) {
1108                    atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
1109                    return;
1110                }
1111                expectVoiceRecognition();
1112            }
1113        } else if (state == HeadsetHalConstants.VR_STATE_STOPPED) {
1114            if (mVoiceRecognitionStarted || mWaitingForVoiceRecognition)
1115            {
1116                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0);
1117                mVoiceRecognitionStarted = false;
1118                mWaitingForVoiceRecognition = false;
1119                if (!isInCall()) {
1120                    disconnectAudioNative(getByteAddress(mCurrentDevice));
1121                    mAudioManager.setParameters("A2dpSuspended=false");
1122                }
1123            }
1124            else
1125            {
1126                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
1127            }
1128        } else {
1129            Log.e(TAG, "Bad Voice Recognition state: " + state);
1130        }
1131    }
1132
1133    private void processLocalVrEvent(int state)
1134    {
1135        if (state == HeadsetHalConstants.VR_STATE_STARTED)
1136        {
1137            boolean needAudio = true;
1138            if (mVoiceRecognitionStarted || isInCall())
1139            {
1140                Log.e(TAG, "Voice recognition started when call is active. isInCall:" + isInCall() +
1141                    " mVoiceRecognitionStarted: " + mVoiceRecognitionStarted);
1142                return;
1143            }
1144            mVoiceRecognitionStarted = true;
1145
1146            if (mWaitingForVoiceRecognition)
1147            {
1148                Log.d(TAG, "Voice recognition started successfully");
1149                mWaitingForVoiceRecognition = false;
1150                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0);
1151                removeMessages(START_VR_TIMEOUT);
1152            }
1153            else
1154            {
1155                Log.d(TAG, "Voice recognition started locally");
1156                needAudio = startVoiceRecognitionNative();
1157            }
1158
1159            if (needAudio && !isAudioOn())
1160            {
1161                Log.d(TAG, "Initiating audio connection for Voice Recognition");
1162                // At this stage, we need to be sure that AVDTP is not streaming. This is needed
1163                // to be compliant with the AV+HFP Whitepaper as we cannot have A2DP in
1164                // streaming state while a SCO connection is established.
1165                // This is needed for VoiceDial scenario alone and not for
1166                // incoming call/outgoing call scenarios as the phone enters MODE_RINGTONE
1167                // or MODE_IN_CALL which shall automatically suspend the AVDTP stream if needed.
1168                // Whereas for VoiceDial we want to activate the SCO connection but we are still
1169                // in MODE_NORMAL and hence the need to explicitly suspend the A2DP stream
1170                mAudioManager.setParameters("A2dpSuspended=true");
1171                connectAudioNative(getByteAddress(mCurrentDevice));
1172            }
1173
1174            if (mStartVoiceRecognitionWakeLock.isHeld()) {
1175                mStartVoiceRecognitionWakeLock.release();
1176            }
1177        }
1178        else
1179        {
1180            Log.d(TAG, "Voice Recognition stopped. mVoiceRecognitionStarted: " + mVoiceRecognitionStarted +
1181                " mWaitingForVoiceRecognition: " + mWaitingForVoiceRecognition);
1182            if (mVoiceRecognitionStarted || mWaitingForVoiceRecognition)
1183            {
1184                mVoiceRecognitionStarted = false;
1185                mWaitingForVoiceRecognition = false;
1186
1187                if (stopVoiceRecognitionNative() && !isInCall()) {
1188                    disconnectAudioNative(getByteAddress(mCurrentDevice));
1189                    mAudioManager.setParameters("A2dpSuspended=false");
1190                }
1191            }
1192        }
1193    }
1194
1195    private synchronized void expectVoiceRecognition() {
1196        mWaitingForVoiceRecognition = true;
1197        sendMessageDelayed(START_VR_TIMEOUT, START_VR_TIMEOUT_VALUE);
1198        if (!mStartVoiceRecognitionWakeLock.isHeld()) {
1199            mStartVoiceRecognitionWakeLock.acquire(START_VR_TIMEOUT_VALUE);
1200        }
1201    }
1202
1203    List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
1204        List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();
1205        Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
1206        int connectionState;
1207        synchronized (this) {
1208            for (BluetoothDevice device : bondedDevices) {
1209                ParcelUuid[] featureUuids = device.getUuids();
1210                if (!BluetoothUuid.containsAnyUuid(featureUuids, HEADSET_UUIDS)) {
1211                    continue;
1212                }
1213                connectionState = getConnectionState(device);
1214                for(int i = 0; i < states.length; i++) {
1215                    if (connectionState == states[i]) {
1216                        deviceList.add(device);
1217                    }
1218                }
1219            }
1220        }
1221        return deviceList;
1222    }
1223
1224    // This method does not check for error conditon (newState == prevState)
1225    private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) {
1226        log("Connection state " + device + ": " + prevState + "->" + newState);
1227        if(prevState == BluetoothProfile.STATE_CONNECTED) {
1228            // Headset is disconnecting, stop Virtual call if active.
1229            terminateScoUsingVirtualVoiceCall();
1230        }
1231
1232        /* Notifying the connection state change of the profile before sending the intent for
1233           connection state change, as it was causing a race condition, with the UI not being
1234           updated with the correct connection state. */
1235        mService.notifyProfileConnectionStateChanged(device, BluetoothProfile.HEADSET,
1236                                                     newState, prevState);
1237        Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
1238        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
1239        intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
1240        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
1241        mService.sendBroadcast(intent, HeadsetService.BLUETOOTH_PERM);
1242    }
1243
1244    private void broadcastAudioState(BluetoothDevice device, int newState, int prevState) {
1245        if(prevState == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
1246            // When SCO gets disconnected during call transfer, Virtual call
1247            //needs to be cleaned up.So call terminateScoUsingVirtualVoiceCall.
1248            terminateScoUsingVirtualVoiceCall();
1249        }
1250        Intent intent = new Intent(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
1251        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
1252        intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
1253        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
1254        mService.sendBroadcast(intent, HeadsetService.BLUETOOTH_PERM);
1255        log("Audio state " + device + ": " + prevState + "->" + newState);
1256    }
1257
1258    /*
1259     * Put the AT command, company ID, arguments, and device in an Intent and broadcast it.
1260     */
1261    private void broadcastVendorSpecificEventIntent(String command,
1262                                                    int companyId,
1263                                                    int commandType,
1264                                                    Object[] arguments,
1265                                                    BluetoothDevice device) {
1266        log("broadcastVendorSpecificEventIntent(" + command + ")");
1267        Intent intent =
1268                new Intent(BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT);
1269        intent.putExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD, command);
1270        intent.putExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE,
1271                        commandType);
1272        // assert: all elements of args are Serializable
1273        intent.putExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS, arguments);
1274        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
1275
1276        intent.addCategory(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY
1277            + "." + Integer.toString(companyId));
1278
1279        mService.sendBroadcast(intent, HeadsetService.BLUETOOTH_PERM);
1280    }
1281
1282    private void configAudioParameters()
1283    {
1284        // Reset NREC on connect event. Headset will override later
1285        mAudioManager.setParameters(HEADSET_NAME + "=" + getCurrentDeviceName() + ";" +
1286                                    HEADSET_NREC + "=on");
1287    }
1288
1289    private String parseUnknownAt(String atString)
1290    {
1291        StringBuilder atCommand = new StringBuilder(atString.length());
1292        String result = null;
1293
1294        for (int i = 0; i < atString.length(); i++) {
1295            char c = atString.charAt(i);
1296            if (c == '"') {
1297                int j = atString.indexOf('"', i + 1 );  // search for closing "
1298                if (j == -1) {  // unmatched ", insert one.
1299                    atCommand.append(atString.substring(i, atString.length()));
1300                    atCommand.append('"');
1301                    break;
1302                }
1303                atCommand.append(atString.substring(i, j + 1));
1304                i = j;
1305            } else if (c != ' ') {
1306                atCommand.append(Character.toUpperCase(c));
1307            }
1308        }
1309        result = atCommand.toString();
1310        return result;
1311    }
1312
1313    private int getAtCommandType(String atCommand)
1314    {
1315        int commandType = mPhonebook.TYPE_UNKNOWN;
1316        String atString = null;
1317        atCommand = atCommand.trim();
1318        if (atCommand.length() > 5)
1319        {
1320            atString = atCommand.substring(5);
1321            if (atString.startsWith("?"))     // Read
1322                commandType = mPhonebook.TYPE_READ;
1323            else if (atString.startsWith("=?"))   // Test
1324                commandType = mPhonebook.TYPE_TEST;
1325            else if (atString.startsWith("="))   // Set
1326                commandType = mPhonebook.TYPE_SET;
1327            else
1328                commandType = mPhonebook.TYPE_UNKNOWN;
1329        }
1330        return commandType;
1331    }
1332
1333    /* Method to check if Virtual Call in Progress */
1334    private boolean isVirtualCallInProgress() {
1335        return mVirtualCallStarted;
1336    }
1337
1338    void setVirtualCallInProgress(boolean state) {
1339        mVirtualCallStarted = state;
1340    }
1341
1342    /* NOTE: Currently the VirtualCall API does not support handling of
1343    call transfers. If it is initiated from the handsfree device,
1344    HeadsetStateMachine will end the virtual call by calling
1345    terminateScoUsingVirtualVoiceCall() in broadcastAudioState() */
1346    synchronized boolean initiateScoUsingVirtualVoiceCall() {
1347        if (DBG) log("initiateScoUsingVirtualVoiceCall: Received");
1348        // 1. Check if the SCO state is idle
1349        if (isInCall() || mVoiceRecognitionStarted) {
1350            Log.e(TAG, "initiateScoUsingVirtualVoiceCall: Call in progress.");
1351            return false;
1352        }
1353
1354        // 2. Send virtual phone state changed to initialize SCO
1355        processCallState(new HeadsetCallState(0, 0,
1356            HeadsetHalConstants.CALL_STATE_DIALING, "", 0), true);
1357        processCallState(new HeadsetCallState(0, 0,
1358            HeadsetHalConstants.CALL_STATE_ALERTING, "", 0), true);
1359        processCallState(new HeadsetCallState(1, 0,
1360            HeadsetHalConstants.CALL_STATE_IDLE, "", 0), true);
1361        setVirtualCallInProgress(true);
1362        // Done
1363        if (DBG) log("initiateScoUsingVirtualVoiceCall: Done");
1364        return true;
1365    }
1366
1367    synchronized boolean terminateScoUsingVirtualVoiceCall() {
1368        if (DBG) log("terminateScoUsingVirtualVoiceCall: Received");
1369
1370        if (!isVirtualCallInProgress()) {
1371            Log.e(TAG, "terminateScoUsingVirtualVoiceCall:"+
1372                "No present call to terminate");
1373            return false;
1374        }
1375
1376        // 2. Send virtual phone state changed to close SCO
1377        processCallState(new HeadsetCallState(0, 0,
1378            HeadsetHalConstants.CALL_STATE_IDLE, "", 0), true);
1379        setVirtualCallInProgress(false);
1380        // Done
1381        if (DBG) log("terminateScoUsingVirtualVoiceCall: Done");
1382        return true;
1383    }
1384
1385    private void processAnswerCall() {
1386        if (mPhoneProxy != null) {
1387            try {
1388                mPhoneProxy.answerCall();
1389            } catch (RemoteException e) {
1390                Log.e(TAG, Log.getStackTraceString(new Throwable()));
1391            }
1392        } else {
1393            Log.e(TAG, "Handsfree phone proxy null for answering call");
1394        }
1395    }
1396
1397    private void processHangupCall() {
1398        // Close the virtual call if active. Virtual call should be
1399        // terminated for CHUP callback event
1400        if (isVirtualCallInProgress()) {
1401            terminateScoUsingVirtualVoiceCall();
1402        } else {
1403            if (mPhoneProxy != null) {
1404                try {
1405                    mPhoneProxy.hangupCall();
1406                } catch (RemoteException e) {
1407                    Log.e(TAG, Log.getStackTraceString(new Throwable()));
1408                }
1409            } else {
1410                Log.e(TAG, "Handsfree phone proxy null for hanging up call");
1411            }
1412        }
1413    }
1414
1415    private void processDialCall(String number) {
1416        String dialNumber;
1417        if ((number == null) || (number.length() == 0)) {
1418            dialNumber = mPhonebook.getLastDialledNumber();
1419            if (dialNumber == null) {
1420                if (DBG) log("processDialCall, last dial number null");
1421                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
1422                return;
1423            }
1424        } else if (number.charAt(0) == '>') {
1425            // Yuck - memory dialling requested.
1426            // Just dial last number for now
1427            if (number.startsWith(">9999")) {   // for PTS test
1428                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
1429                return;
1430            }
1431            if (DBG) log("processDialCall, memory dial do last dial for now");
1432            dialNumber = mPhonebook.getLastDialledNumber();
1433            if (dialNumber == null) {
1434                if (DBG) log("processDialCall, last dial number null");
1435                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
1436                return;
1437            }
1438        } else {
1439            // Remove trailing ';'
1440            if (number.charAt(number.length() - 1) == ';') {
1441                number = number.substring(0, number.length() - 1);
1442            }
1443
1444            dialNumber = PhoneNumberUtils.convertPreDial(number);
1445        }
1446        // Check for virtual call to terminate before sending Call Intent
1447        terminateScoUsingVirtualVoiceCall();
1448
1449        Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
1450                                   Uri.fromParts(SCHEME_TEL, dialNumber, null));
1451        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1452        mService.startActivity(intent);
1453        // TODO(BT) continue send OK reults code after call starts
1454        //          hold wait lock, start a timer, set wait call flag
1455        //          Get call started indication from bluetooth phone
1456        mDialingOut = true;
1457        sendMessageDelayed(DIALING_OUT_TIMEOUT, DIALING_OUT_TIMEOUT_VALUE);
1458    }
1459
1460    private void processVolumeEvent(int volumeType, int volume) {
1461        if (volumeType == HeadsetHalConstants.VOLUME_TYPE_SPK) {
1462            mPhoneState.setSpeakerVolume(volume);
1463            int flag = (getCurrentState() == mAudioOn) ? AudioManager.FLAG_SHOW_UI : 0;
1464            mAudioManager.setStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO, volume, flag);
1465        } else if (volumeType == HeadsetHalConstants.VOLUME_TYPE_MIC) {
1466            mPhoneState.setMicVolume(volume);
1467        } else {
1468            Log.e(TAG, "Bad voluem type: " + volumeType);
1469        }
1470    }
1471
1472    private void processSendDtmf(int dtmf) {
1473        if (mPhoneProxy != null) {
1474            try {
1475                mPhoneProxy.sendDtmf(dtmf);
1476            } catch (RemoteException e) {
1477                Log.e(TAG, Log.getStackTraceString(new Throwable()));
1478            }
1479        } else {
1480            Log.e(TAG, "Handsfree phone proxy null for sending DTMF");
1481        }
1482    }
1483
1484    private void processCallState(HeadsetCallState callState) {
1485        processCallState(callState, false);
1486    }
1487
1488    private void processCallState(HeadsetCallState callState,
1489        boolean isVirtualCall) {
1490        mPhoneState.setNumActiveCall(callState.mNumActive);
1491        mPhoneState.setNumHeldCall(callState.mNumHeld);
1492        mPhoneState.setCallState(callState.mCallState);
1493        if (mDialingOut && callState.mCallState ==
1494            HeadsetHalConstants.CALL_STATE_DIALING) {
1495                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0);
1496                removeMessages(DIALING_OUT_TIMEOUT);
1497                mDialingOut = false;
1498        }
1499        log("mNumActive: " + callState.mNumActive + " mNumHeld: " +
1500            callState.mNumHeld +" mCallState: " + callState.mCallState);
1501        log("mNumber: " + callState.mNumber + " mType: " + callState.mType);
1502        if(!isVirtualCall) {
1503            /* Not a Virtual call request. End the virtual call, if running,
1504            before sending phoneStateChangeNative to BTIF */
1505            terminateScoUsingVirtualVoiceCall();
1506        }
1507        if (getCurrentState() != mDisconnected) {
1508            phoneStateChangeNative(callState.mNumActive, callState.mNumHeld,
1509                callState.mCallState, callState.mNumber, callState.mType);
1510        }
1511    }
1512
1513    // enable 1 enable noice reduction
1514    //        0 disable noice reduction
1515    private void processNoiceReductionEvent(int enable) {
1516        if (enable == 1) {
1517            mAudioManager.setParameters(HEADSET_NREC + "=on");
1518        } else {
1519            mAudioManager.setParameters(HEADSET_NREC + "=off");
1520        }
1521    }
1522
1523    private void processAtChld(int chld) {
1524        if (mPhoneProxy != null) {
1525            try {
1526                if (mPhoneProxy.processChld(chld)) {
1527                    atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0);
1528                } else {
1529                    atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
1530                }
1531            } catch (RemoteException e) {
1532                Log.e(TAG, Log.getStackTraceString(new Throwable()));
1533                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
1534            }
1535        } else {
1536            Log.e(TAG, "Handsfree phone proxy null for At+Chld");
1537            atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
1538        }
1539    }
1540
1541    private void processSubscriberNumberRequest() {
1542        if (mPhoneProxy != null) {
1543            try {
1544                String number = mPhoneProxy.getSubscriberNumber();
1545                if (number != null) {
1546                    atResponseStringNative("+CNUM: ,\"" + number + "\"," +
1547                                           PhoneNumberUtils.toaFromString(number) + ",,4");
1548                    atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0);
1549                }
1550            } catch (RemoteException e) {
1551                Log.e(TAG, Log.getStackTraceString(new Throwable()));
1552                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
1553            }
1554        } else {
1555            Log.e(TAG, "Handsfree phone proxy null for At+CNUM");
1556        }
1557    }
1558
1559    private void processAtCind() {
1560        int call, call_setup;
1561
1562        /* Handsfree carkits expect that +CIND is properly responded to
1563         Hence we ensure that a proper response is sent
1564         for the virtual call too.*/
1565        if (isVirtualCallInProgress()) {
1566            call = 1;
1567            call_setup = 0;
1568        } else {
1569            // regular phone call
1570            call = mPhoneState.getNumActiveCall();
1571            call_setup = mPhoneState.getNumHeldCall();
1572        }
1573
1574        cindResponseNative(mPhoneState.getService(), call,
1575                           call_setup, mPhoneState.getCallState(),
1576                           mPhoneState.getSignal(), mPhoneState.getRoam(),
1577                           mPhoneState.getBatteryCharge());
1578    }
1579
1580    private void processAtCops() {
1581        if (mPhoneProxy != null) {
1582            try {
1583                String operatorName = mPhoneProxy.getNetworkOperator();
1584                if (operatorName == null) {
1585                    operatorName = "";
1586                }
1587                copsResponseNative(operatorName);
1588            } catch (RemoteException e) {
1589                Log.e(TAG, Log.getStackTraceString(new Throwable()));
1590                copsResponseNative("");
1591            }
1592        } else {
1593            Log.e(TAG, "Handsfree phone proxy null for At+COPS");
1594            copsResponseNative("");
1595        }
1596    }
1597
1598    private void processAtClcc() {
1599        if (mPhoneProxy != null) {
1600            try {
1601                if(isVirtualCallInProgress()) {
1602                    String phoneNumber = "";
1603                    int type = PhoneNumberUtils.TOA_Unknown;
1604                    try {
1605                        phoneNumber = mPhoneProxy.getSubscriberNumber();
1606                        type = PhoneNumberUtils.toaFromString(phoneNumber);
1607                    } catch (RemoteException ee) {
1608                        Log.e(TAG, "Unable to retrieve phone number"+
1609                            "using IBluetoothHeadsetPhone proxy");
1610                        phoneNumber = "";
1611                    }
1612                    clccResponseNative(1, 0, 0, 0, false, phoneNumber, type);
1613                }
1614                else if (!mPhoneProxy.listCurrentCalls()) {
1615                    clccResponseNative(0, 0, 0, 0, false, "", 0);
1616                }
1617            } catch (RemoteException e) {
1618                Log.e(TAG, Log.getStackTraceString(new Throwable()));
1619                clccResponseNative(0, 0, 0, 0, false, "", 0);
1620            }
1621        } else {
1622            Log.e(TAG, "Handsfree phone proxy null for At+CLCC");
1623            clccResponseNative(0, 0, 0, 0, false, "", 0);
1624        }
1625    }
1626
1627    private void processAtCscs(String atString, int type) {
1628        log("processAtCscs - atString = "+ atString);
1629        if(mPhonebook != null) {
1630            mPhonebook.handleCscsCommand(atString, type);
1631        }
1632        else {
1633            Log.e(TAG, "Phonebook handle null for At+CSCS");
1634            atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
1635        }
1636    }
1637
1638    private void processAtCpbs(String atString, int type) {
1639        log("processAtCpbs - atString = "+ atString);
1640        if(mPhonebook != null) {
1641            mPhonebook.handleCpbsCommand(atString, type);
1642        }
1643        else {
1644            Log.e(TAG, "Phonebook handle null for At+CPBS");
1645            atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
1646        }
1647    }
1648
1649    private void processAtCpbr(String atString, int type, BluetoothDevice mCurrentDevice) {
1650        log("processAtCpbr - atString = "+ atString);
1651        if(mPhonebook != null) {
1652            mPhonebook.handleCpbrCommand(atString, type, mCurrentDevice);
1653        }
1654        else {
1655            Log.e(TAG, "Phonebook handle null for At+CPBR");
1656            atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
1657        }
1658    }
1659
1660    /**
1661     * Find a character ch, ignoring quoted sections.
1662     * Return input.length() if not found.
1663     */
1664    static private int findChar(char ch, String input, int fromIndex) {
1665        for (int i = fromIndex; i < input.length(); i++) {
1666            char c = input.charAt(i);
1667            if (c == '"') {
1668                i = input.indexOf('"', i + 1);
1669                if (i == -1) {
1670                    return input.length();
1671                }
1672            } else if (c == ch) {
1673                return i;
1674            }
1675        }
1676        return input.length();
1677    }
1678
1679    /**
1680     * Break an argument string into individual arguments (comma delimited).
1681     * Integer arguments are turned into Integer objects. Otherwise a String
1682     * object is used.
1683     */
1684    static private Object[] generateArgs(String input) {
1685        int i = 0;
1686        int j;
1687        ArrayList<Object> out = new ArrayList<Object>();
1688        while (i <= input.length()) {
1689            j = findChar(',', input, i);
1690
1691            String arg = input.substring(i, j);
1692            try {
1693                out.add(new Integer(arg));
1694            } catch (NumberFormatException e) {
1695                out.add(arg);
1696            }
1697
1698            i = j + 1; // move past comma
1699        }
1700        return out.toArray();
1701    }
1702
1703    private void processAtXevent(String atString) {
1704        log("processAtXevent - atString = "+ atString);
1705        if (atString.startsWith("=") && !atString.startsWith("=?")) {
1706            Object[] args = generateArgs(atString.substring(1));
1707            broadcastVendorSpecificEventIntent("+XEVENT",
1708                                               BluetoothAssignedNumbers.PLANTRONICS,
1709                                               BluetoothHeadset.AT_CMD_TYPE_SET,
1710                                               args,
1711                                               mCurrentDevice);
1712            atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0);
1713        }
1714        else {
1715            Log.e(TAG, "processAtXevent: command type error");
1716            atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
1717        }
1718    }
1719
1720    private void processUnknownAt(String atString) {
1721        // TODO (BT)
1722        log("processUnknownAt - atString = "+ atString);
1723        String atCommand = parseUnknownAt(atString);
1724        int commandType = getAtCommandType(atCommand);
1725        if (atCommand.startsWith("+CSCS"))
1726            processAtCscs(atCommand.substring(5), commandType);
1727        else if (atCommand.startsWith("+CPBS"))
1728            processAtCpbs(atCommand.substring(5), commandType);
1729        else if (atCommand.startsWith("+CPBR"))
1730            processAtCpbr(atCommand.substring(5), commandType, mCurrentDevice);
1731        else if (atCommand.startsWith("+XEVENT"))
1732            processAtXevent(atCommand.substring(7));
1733        else
1734            atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
1735    }
1736
1737    private void processKeyPressed() {
1738        if (mPhoneState.getCallState() == HeadsetHalConstants.CALL_STATE_INCOMING) {
1739            if (mPhoneProxy != null) {
1740                try {
1741                    mPhoneProxy.answerCall();
1742                } catch (RemoteException e) {
1743                    Log.e(TAG, Log.getStackTraceString(new Throwable()));
1744                }
1745            } else {
1746                Log.e(TAG, "Handsfree phone proxy null for answering call");
1747            }
1748        } else if (mPhoneState.getNumActiveCall() > 0) {
1749            if (!isAudioOn())
1750            {
1751                connectAudioNative(getByteAddress(mCurrentDevice));
1752            }
1753            else
1754            {
1755                if (mPhoneProxy != null) {
1756                    try {
1757                        mPhoneProxy.hangupCall();
1758                    } catch (RemoteException e) {
1759                        Log.e(TAG, Log.getStackTraceString(new Throwable()));
1760                    }
1761                } else {
1762                    Log.e(TAG, "Handsfree phone proxy null for hangup call");
1763                }
1764            }
1765        } else {
1766            String dialNumber = mPhonebook.getLastDialledNumber();
1767            if (dialNumber == null) {
1768                if (DBG) log("processKeyPressed, last dial number null");
1769                return;
1770            }
1771            Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
1772                                       Uri.fromParts(SCHEME_TEL, dialNumber, null));
1773            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1774            mService.startActivity(intent);
1775        }
1776    }
1777
1778    private void onConnectionStateChanged(int state, byte[] address) {
1779        StackEvent event = new StackEvent(EVENT_TYPE_CONNECTION_STATE_CHANGED);
1780        event.valueInt = state;
1781        event.device = getDevice(address);
1782        sendMessage(STACK_EVENT, event);
1783    }
1784
1785    private void onAudioStateChanged(int state, byte[] address) {
1786        StackEvent event = new StackEvent(EVENT_TYPE_AUDIO_STATE_CHANGED);
1787        event.valueInt = state;
1788        event.device = getDevice(address);
1789        sendMessage(STACK_EVENT, event);
1790    }
1791
1792    private void onVrStateChanged(int state) {
1793        StackEvent event = new StackEvent(EVENT_TYPE_VR_STATE_CHANGED);
1794        event.valueInt = state;
1795        sendMessage(STACK_EVENT, event);
1796    }
1797
1798    private void onAnswerCall() {
1799        StackEvent event = new StackEvent(EVENT_TYPE_ANSWER_CALL);
1800        sendMessage(STACK_EVENT, event);
1801    }
1802
1803    private void onHangupCall() {
1804        StackEvent event = new StackEvent(EVENT_TYPE_HANGUP_CALL);
1805        sendMessage(STACK_EVENT, event);
1806    }
1807
1808    private void onVolumeChanged(int type, int volume) {
1809        StackEvent event = new StackEvent(EVENT_TYPE_VOLUME_CHANGED);
1810        event.valueInt = type;
1811        event.valueInt2 = volume;
1812        sendMessage(STACK_EVENT, event);
1813    }
1814
1815    private void onDialCall(String number) {
1816        StackEvent event = new StackEvent(EVENT_TYPE_DIAL_CALL);
1817        event.valueString = number;
1818        sendMessage(STACK_EVENT, event);
1819    }
1820
1821    private void onSendDtmf(int dtmf) {
1822        StackEvent event = new StackEvent(EVENT_TYPE_SEND_DTMF);
1823        event.valueInt = dtmf;
1824        sendMessage(STACK_EVENT, event);
1825    }
1826
1827    private void onNoiceReductionEnable(boolean enable) {
1828        StackEvent event = new StackEvent(EVENT_TYPE_NOICE_REDUCTION);
1829        event.valueInt = enable ? 1 : 0;
1830        sendMessage(STACK_EVENT, event);
1831    }
1832
1833    private void onAtChld(int chld) {
1834        StackEvent event = new StackEvent(EVENT_TYPE_AT_CHLD);
1835        event.valueInt = chld;
1836        sendMessage(STACK_EVENT, event);
1837    }
1838
1839    private void onAtCnum() {
1840        StackEvent event = new StackEvent(EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST);
1841        sendMessage(STACK_EVENT, event);
1842    }
1843
1844    private void onAtCind() {
1845        StackEvent event = new StackEvent(EVENT_TYPE_AT_CIND);
1846        sendMessage(STACK_EVENT, event);
1847    }
1848
1849    private void onAtCops() {
1850        StackEvent event = new StackEvent(EVENT_TYPE_AT_COPS);
1851        sendMessage(STACK_EVENT, event);
1852    }
1853
1854    private void onAtClcc() {
1855        StackEvent event = new StackEvent(EVENT_TYPE_AT_CLCC);
1856        sendMessage(STACK_EVENT, event);
1857    }
1858
1859    private void onUnknownAt(String atString) {
1860        StackEvent event = new StackEvent(EVENT_TYPE_UNKNOWN_AT);
1861        event.valueString = atString;
1862        sendMessage(STACK_EVENT, event);
1863    }
1864
1865    private void onKeyPressed() {
1866        StackEvent event = new StackEvent(EVENT_TYPE_KEY_PRESSED);
1867        sendMessage(STACK_EVENT, event);
1868    }
1869
1870    private void processIntentBatteryChanged(Intent intent) {
1871        int batteryLevel = intent.getIntExtra("level", -1);
1872        int scale = intent.getIntExtra("scale", -1);
1873        if (batteryLevel == -1 || scale == -1 || scale == 0) {
1874            Log.e(TAG, "Bad Battery Changed intent: " + batteryLevel + "," + scale);
1875            return;
1876        }
1877        batteryLevel = batteryLevel * 5 / scale;
1878        mPhoneState.setBatteryCharge(batteryLevel);
1879    }
1880
1881    private void processDeviceStateChanged(HeadsetDeviceState deviceState) {
1882        notifyDeviceStatusNative(deviceState.mService, deviceState.mRoam, deviceState.mSignal,
1883                                 deviceState.mBatteryCharge);
1884    }
1885
1886    private void processSendClccResponse(HeadsetClccResponse clcc) {
1887        clccResponseNative(clcc.mIndex, clcc.mDirection, clcc.mStatus, clcc.mMode, clcc.mMpty,
1888                           clcc.mNumber, clcc.mType);
1889    }
1890
1891    private String getCurrentDeviceName() {
1892        String defaultName = "<unknown>";
1893        if (mCurrentDevice == null) {
1894            return defaultName;
1895        }
1896        String deviceName = mCurrentDevice.getName();
1897        if (deviceName == null) {
1898            return defaultName;
1899        }
1900        return deviceName;
1901    }
1902
1903    private byte[] getByteAddress(BluetoothDevice device) {
1904        return Utils.getBytesFromAddress(device.getAddress());
1905    }
1906
1907    private BluetoothDevice getDevice(byte[] address) {
1908        return mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
1909    }
1910
1911    private boolean isInCall() {
1912        return ((mPhoneState.getNumActiveCall() > 0) || (mPhoneState.getNumHeldCall() > 0) ||
1913                (mPhoneState.getCallState() != HeadsetHalConstants.CALL_STATE_IDLE));
1914    }
1915
1916    boolean isConnected() {
1917        IState currentState = getCurrentState();
1918        return (currentState == mConnected || currentState == mAudioOn);
1919    }
1920
1921    boolean okToConnect(BluetoothDevice device) {
1922        AdapterService adapterService = AdapterService.getAdapterService();
1923        int priority = mService.getPriority(device);
1924        boolean ret = false;
1925        //check if this is an incoming connection in Quiet mode.
1926        if((adapterService == null) ||
1927           ((adapterService.isQuietModeEnabled() == true) &&
1928           (mTargetDevice == null))){
1929            ret = false;
1930        }
1931        // check priority and accept or reject the connection. if priority is undefined
1932        // it is likely that our SDP has not completed and peer is initiating the
1933        // connection. Allow this connection, provided the device is bonded
1934        else if((BluetoothProfile.PRIORITY_OFF < priority) ||
1935                ((BluetoothProfile.PRIORITY_UNDEFINED == priority) &&
1936                (device.getBondState() != BluetoothDevice.BOND_NONE))){
1937            ret= true;
1938        }
1939        return ret;
1940    }
1941
1942    @Override
1943    protected void log(String msg) {
1944        if (DBG) {
1945            super.log(msg);
1946        }
1947    }
1948
1949    public void handleAccessPermissionResult(Intent intent) {
1950        log("handleAccessPermissionResult");
1951        if(mPhonebook != null) {
1952            if (!mPhonebook.getCheckingAccessPermission()) {
1953                return;
1954            }
1955            int atCommandResult = 0;
1956            int atCommandErrorCode = 0;
1957            //HeadsetBase headset = mHandsfree.getHeadset();
1958            // ASSERT: (headset != null) && headSet.isConnected()
1959            // REASON: mCheckingAccessPermission is true, otherwise resetAtState
1960            // has set mCheckingAccessPermission to false
1961            if (intent.getAction().equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
1962                if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
1963                    BluetoothDevice.CONNECTION_ACCESS_NO) ==
1964                    BluetoothDevice.CONNECTION_ACCESS_YES) {
1965                    if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
1966                        mCurrentDevice.setTrust(true);
1967                    }
1968                    atCommandResult = mPhonebook.processCpbrCommand();
1969                }
1970            }
1971            mPhonebook.setCpbrIndex(-1);
1972            mPhonebook.setCheckingAccessPermission(false);
1973
1974            if (atCommandResult >= 0) {
1975                atResponseCodeNative(atCommandResult, atCommandErrorCode);
1976            }
1977            else
1978                log("handleAccessPermissionResult - RESULT_NONE");
1979        }
1980        else {
1981            Log.e(TAG, "Phonebook handle null");
1982            atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
1983        }
1984    }
1985
1986    private static final String SCHEME_TEL = "tel";
1987
1988    // Event types for STACK_EVENT message
1989    final private static int EVENT_TYPE_NONE = 0;
1990    final private static int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
1991    final private static int EVENT_TYPE_AUDIO_STATE_CHANGED = 2;
1992    final private static int EVENT_TYPE_VR_STATE_CHANGED = 3;
1993    final private static int EVENT_TYPE_ANSWER_CALL = 4;
1994    final private static int EVENT_TYPE_HANGUP_CALL = 5;
1995    final private static int EVENT_TYPE_VOLUME_CHANGED = 6;
1996    final private static int EVENT_TYPE_DIAL_CALL = 7;
1997    final private static int EVENT_TYPE_SEND_DTMF = 8;
1998    final private static int EVENT_TYPE_NOICE_REDUCTION = 9;
1999    final private static int EVENT_TYPE_AT_CHLD = 10;
2000    final private static int EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST = 11;
2001    final private static int EVENT_TYPE_AT_CIND = 12;
2002    final private static int EVENT_TYPE_AT_COPS = 13;
2003    final private static int EVENT_TYPE_AT_CLCC = 14;
2004    final private static int EVENT_TYPE_UNKNOWN_AT = 15;
2005    final private static int EVENT_TYPE_KEY_PRESSED = 16;
2006
2007    private class StackEvent {
2008        int type = EVENT_TYPE_NONE;
2009        int valueInt = 0;
2010        int valueInt2 = 0;
2011        String valueString = null;
2012        BluetoothDevice device = null;
2013
2014        private StackEvent(int type) {
2015            this.type = type;
2016        }
2017    }
2018
2019    /*package*/native boolean atResponseCodeNative(int responseCode, int errorCode);
2020    /*package*/ native boolean atResponseStringNative(String responseString);
2021
2022    private native static void classInitNative();
2023    private native void initializeNative();
2024    private native void cleanupNative();
2025    private native boolean connectHfpNative(byte[] address);
2026    private native boolean disconnectHfpNative(byte[] address);
2027    private native boolean connectAudioNative(byte[] address);
2028    private native boolean disconnectAudioNative(byte[] address);
2029    private native boolean startVoiceRecognitionNative();
2030    private native boolean stopVoiceRecognitionNative();
2031    private native boolean setVolumeNative(int volumeType, int volume);
2032    private native boolean cindResponseNative(int service, int numActive, int numHeld,
2033                                              int callState, int signal, int roam,
2034                                              int batteryCharge);
2035    private native boolean notifyDeviceStatusNative(int networkState, int serviceType, int signal,
2036                                                    int batteryCharge);
2037
2038    private native boolean clccResponseNative(int index, int dir, int status, int mode,
2039                                              boolean mpty, String number, int type);
2040    private native boolean copsResponseNative(String operatorName);
2041
2042    private native boolean phoneStateChangeNative(int numActive, int numHeld, int callState,
2043                                                  String number, int type);
2044}
2045