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