HeadsetStateMachine.java revision 9a0efb0ee8c63797280b3b9309782541134e9205
1/*
2 * Copyright (C) 2012 Google Inc.
3 */
4
5/**
6 * Bluetooth Handset StateMachine
7 *                      (Disconnected)
8 *                           |    ^
9 *                   CONNECT |    | DISCONNECTED
10 *                           V    |
11 *                         (Pending)
12 *                           |    ^
13 *                 CONNECTED |    | CONNECT
14 *                           V    |
15 *                        (Connected)
16 *                           |    ^
17 *             CONNECT_AUDIO |    | DISCONNECT_AUDIO
18 *                           V    |
19 *                         (AudioOn)
20 */
21package com.android.bluetooth.hfp;
22
23import android.bluetooth.BluetoothAdapter;
24import android.bluetooth.BluetoothDevice;
25import android.bluetooth.BluetoothHeadset;
26import android.bluetooth.BluetoothProfile;
27import android.bluetooth.BluetoothUuid;
28import android.bluetooth.IBluetooth;
29import android.bluetooth.IBluetoothHeadsetPhone;
30import android.content.ComponentName;
31import android.content.Context;
32import android.content.Intent;
33import android.content.ServiceConnection;
34import android.content.ActivityNotFoundException;
35import android.media.AudioManager;
36import android.net.Uri;
37import android.os.IBinder;
38import android.os.Message;
39import android.os.ParcelUuid;
40import android.os.RemoteException;
41import android.os.ServiceManager;
42import android.os.PowerManager;
43import android.os.PowerManager.WakeLock;
44import android.telephony.PhoneNumberUtils;
45import android.util.Log;
46import com.android.bluetooth.Utils;
47import com.android.bluetooth.btservice.AdapterService;
48import com.android.internal.util.IState;
49import com.android.internal.util.State;
50import com.android.internal.util.StateMachine;
51import java.util.ArrayList;
52import java.util.List;
53import java.util.Set;
54
55final class HeadsetStateMachine extends StateMachine {
56    private static final String TAG = "HeadsetStateMachine";
57    private static final boolean DBG = true;
58
59    static final int CONNECT = 1;
60    static final int DISCONNECT = 2;
61    static final int CONNECT_AUDIO = 3;
62    static final int DISCONNECT_AUDIO = 4;
63    static final int VOICE_RECOGNITION_START = 5;
64    static final int VOICE_RECOGNITION_STOP = 6;
65
66    // message.obj is an intent AudioManager.VOLUME_CHANGED_ACTION
67    // EXTRA_VOLUME_STREAM_TYPE is STREAM_BLUETOOTH_SCO
68    static final int INTENT_SCO_VOLUME_CHANGED = 7;
69    static final int SET_MIC_VOLUME = 8;
70    static final int CALL_STATE_CHANGED = 9;
71    static final int INTENT_BATTERY_CHANGED = 10;
72    static final int DEVICE_STATE_CHANGED = 11;
73    static final int ROAM_CHANGED = 12;
74    static final int SEND_CCLC_RESPONSE = 13;
75
76    private static final int STACK_EVENT = 101;
77    private static final int DIALING_OUT_TIMEOUT = 102;
78    private static final int START_VR_TIMEOUT = 103;
79
80    private static final int CONNECT_TIMEOUT = 201;
81
82    private static final int DIALING_OUT_TIMEOUT_VALUE = 10000;
83    private static final int START_VR_TIMEOUT_VALUE = 5000;
84
85    private static final ParcelUuid[] HEADSET_UUIDS = {
86        BluetoothUuid.HSP,
87        BluetoothUuid.Handsfree,
88    };
89
90    private Disconnected mDisconnected;
91    private Pending mPending;
92    private Connected mConnected;
93    private AudioOn mAudioOn;
94
95    private Context mContext;
96
97    private PowerManager mPowerManager;
98
99    private boolean mVoiceRecognitionStarted = false;
100    private boolean mWaitingForVoiceRecognition = false;
101    private WakeLock mStartVoiceRecognitionWakeLock;  // held while waiting for voice recognition
102
103    private boolean mDialingOut = false;
104    private AudioManager mAudioManager;
105    private AtPhonebook mPhonebook;
106
107    private static Intent sVoiceCommandIntent;
108
109    private HeadsetPhoneState mPhoneState;
110    private int mAudioState;
111    private BluetoothAdapter mAdapter;
112    private IBluetoothHeadsetPhone mPhoneProxy;
113
114    // mCurrentDevice is the device connected before the state changes
115    // mTargetDevice is the device to be connected
116    // mIncomingDevice is the device connecting to us, valid only in Pending state
117    //                when mIncomingDevice is not null, both mCurrentDevice
118    //                  and mTargetDevice are null
119    //                when either mCurrentDevice or mTargetDevice is not null,
120    //                  mIncomingDevice is null
121    // Stable states
122    //   No connection, Disconnected state
123    //                  both mCurrentDevice and mTargetDevice are null
124    //   Connected, Connected state
125    //              mCurrentDevice is not null, mTargetDevice is null
126    // Interim states
127    //   Connecting to a device, Pending
128    //                           mCurrentDevice is null, mTargetDevice is not null
129    //   Disconnecting device, Connecting to new device
130    //     Pending
131    //     Both mCurrentDevice and mTargetDevice are not null
132    //   Disconnecting device Pending
133    //                        mCurrentDevice is not null, mTargetDevice is null
134    //   Incoming connections Pending
135    //                        Both mCurrentDevice and mTargetDevice are null
136    private BluetoothDevice mCurrentDevice = null;
137    private BluetoothDevice mTargetDevice = null;
138    private BluetoothDevice mIncomingDevice = null;
139
140    static {
141        classInitNative();
142    }
143
144    HeadsetStateMachine(Context context) {
145        super(TAG);
146
147        mContext = context;
148        mVoiceRecognitionStarted = false;
149        mWaitingForVoiceRecognition = false;
150
151        mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
152        mStartVoiceRecognitionWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
153                                                       TAG + ":VoiceRecognition");
154        mStartVoiceRecognitionWakeLock.setReferenceCounted(false);
155
156        mDialingOut = false;
157        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
158        mPhonebook = new AtPhonebook(mContext);
159        mPhoneState = new HeadsetPhoneState(context, this);
160        mAudioState = BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
161        mAdapter = BluetoothAdapter.getDefaultAdapter();
162        if (!context.bindService(new Intent(IBluetoothHeadsetPhone.class.getName()),
163                                 mConnection, 0)) {
164            Log.e(TAG, "Could not bind to Bluetooth Headset Phone Service");
165        }
166
167        initializeNative();
168
169        mDisconnected = new Disconnected();
170        mPending = new Pending();
171        mConnected = new Connected();
172        mAudioOn = new AudioOn();
173
174        if (sVoiceCommandIntent == null) {
175            sVoiceCommandIntent = new Intent(Intent.ACTION_VOICE_COMMAND);
176            sVoiceCommandIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
177        }
178
179        addState(mDisconnected);
180        addState(mPending);
181        addState(mConnected);
182        addState(mAudioOn);
183
184        setInitialState(mDisconnected);
185    }
186
187    public void cleanup() {
188        cleanupNative();
189        if (mPhoneProxy != null) {
190            if (DBG) Log.d(TAG,"Unbinding service...");
191            synchronized (mConnection) {
192                try {
193                    mPhoneProxy = null;
194                    mContext.unbindService(mConnection);
195                } catch (Exception re) {
196                    Log.e(TAG,"",re);
197                }
198        }
199        }
200    }
201
202    private class Disconnected extends State {
203        @Override
204        public void enter() {
205            log("Enter Disconnected: " + getCurrentMessage().what);
206            mPhoneState.listenForPhoneState(false);
207        }
208
209        @Override
210        public boolean processMessage(Message message) {
211            log("Disconnected process message: " + message.what);
212            if (DBG) {
213                if (mCurrentDevice != null || mTargetDevice != null || mIncomingDevice != null) {
214                    log("ERROR: current, target, or mIncomingDevice not null in Disconnected");
215                    return NOT_HANDLED;
216                }
217            }
218
219            boolean retValue = HANDLED;
220            switch(message.what) {
221                case CONNECT:
222                    BluetoothDevice device = (BluetoothDevice) message.obj;
223                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
224                                   BluetoothProfile.STATE_DISCONNECTED);
225
226                    if (!connectHfpNative(getByteAddress(device)) ) {
227                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
228                                       BluetoothProfile.STATE_CONNECTING);
229                        break;
230                    }
231
232                    synchronized (HeadsetStateMachine.this) {
233                        mTargetDevice = device;
234                        transitionTo(mPending);
235                    }
236                    // TODO(BT) remove CONNECT_TIMEOUT when the stack
237                    //          sends back events consistently
238                    sendMessageDelayed(CONNECT_TIMEOUT, 30000);
239                    break;
240                case DISCONNECT:
241                    // ignore
242                    break;
243                case INTENT_BATTERY_CHANGED:
244                    processIntentBatteryChanged((Intent) message.obj);
245                    break;
246                case ROAM_CHANGED:
247                    processRoamChanged((Boolean) message.obj);
248                    break;
249                case CALL_STATE_CHANGED:
250                    processCallState((HeadsetCallState) message.obj);
251                    break;
252                case STACK_EVENT:
253                    StackEvent event = (StackEvent) message.obj;
254                    if (DBG) {
255                        log("event type: " + event.type);
256                    }
257                    switch (event.type) {
258                        case EVENT_TYPE_CONNECTION_STATE_CHANGED:
259                            processConnectionEvent(event.valueInt, event.device);
260                            break;
261                        default:
262                            Log.e(TAG, "Unexpected stack event: " + event.type);
263                            break;
264                    }
265                    break;
266                default:
267                    return NOT_HANDLED;
268            }
269            return retValue;
270        }
271
272        @Override
273        public void exit() {
274            log("Exit Disconnected: " + getCurrentMessage().what);
275            mPhoneState.listenForPhoneState(true);
276        }
277
278        // in Disconnected state
279        private void processConnectionEvent(int state, BluetoothDevice device) {
280            switch (state) {
281            case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED:
282                Log.w(TAG, "Ignore HF DISCONNECTED event, device: " + device);
283                break;
284            case HeadsetHalConstants.CONNECTION_STATE_CONNECTING:
285                // TODO(BT) Assume it's incoming connection
286                //     Do we need to check priority and accept/reject accordingly?
287                broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
288                                         BluetoothProfile.STATE_DISCONNECTED);
289                synchronized (HeadsetStateMachine.this) {
290                    mIncomingDevice = device;
291                    transitionTo(mPending);
292                }
293                break;
294            case HeadsetHalConstants.CONNECTION_STATE_CONNECTED:
295                Log.w(TAG, "HFP Connected from Disconnected state");
296                broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
297                                         BluetoothProfile.STATE_DISCONNECTED);
298                synchronized (HeadsetStateMachine.this) {
299                    mCurrentDevice = device;
300                    transitionTo(mConnected);
301                }
302                break;
303            case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTING:
304                Log.w(TAG, "Ignore HF DISCONNECTING event, device: " + device);
305                break;
306            default:
307                Log.e(TAG, "Incorrect state: " + state);
308                break;
309            }
310        }
311    }
312
313    private class Pending extends State {
314        @Override
315        public void enter() {
316            log("Enter Pending: " + getCurrentMessage().what);
317        }
318
319        @Override
320        public boolean processMessage(Message message) {
321            log("Pending process message: " + message.what);
322
323            boolean retValue = HANDLED;
324            switch(message.what) {
325                case CONNECT:
326                case CONNECT_AUDIO:
327                    deferMessage(message);
328                    break;
329                case CONNECT_TIMEOUT:
330                    onConnectionStateChanged(HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED,
331                                             getByteAddress(mTargetDevice));
332                    break;
333                case DISCONNECT:
334                    BluetoothDevice device = (BluetoothDevice) message.obj;
335                    if (mCurrentDevice != null && mTargetDevice != null &&
336                        mTargetDevice.equals(device) ) {
337                        // cancel connection to the mTargetDevice
338                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
339                                       BluetoothProfile.STATE_CONNECTING);
340                        synchronized (HeadsetStateMachine.this) {
341                            mTargetDevice = null;
342                        }
343                    } else {
344                        deferMessage(message);
345                    }
346                    break;
347                case INTENT_BATTERY_CHANGED:
348                    processIntentBatteryChanged((Intent) message.obj);
349                    break;
350                case ROAM_CHANGED:
351                    processRoamChanged((Boolean) message.obj);
352                    break;
353                case CALL_STATE_CHANGED:
354                    processCallState((HeadsetCallState) message.obj);
355                    break;
356                case STACK_EVENT:
357                    StackEvent event = (StackEvent) message.obj;
358                    if (DBG) {
359                        log("event type: " + event.type);
360                    }
361                    switch (event.type) {
362                        case EVENT_TYPE_CONNECTION_STATE_CHANGED:
363                            removeMessages(CONNECT_TIMEOUT);
364                            processConnectionEvent(event.valueInt, event.device);
365                            break;
366                        default:
367                            Log.e(TAG, "Unexpected event: " + event.type);
368                            break;
369                    }
370                    break;
371                default:
372                    return NOT_HANDLED;
373            }
374            return retValue;
375        }
376
377        // in Pending state
378        private void processConnectionEvent(int state, BluetoothDevice device) {
379            switch (state) {
380                case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED:
381                    if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
382                        broadcastConnectionState(mCurrentDevice,
383                                                 BluetoothProfile.STATE_DISCONNECTED,
384                                                 BluetoothProfile.STATE_DISCONNECTING);
385                        synchronized (HeadsetStateMachine.this) {
386                            mCurrentDevice = null;
387                        }
388
389                        if (mTargetDevice != null) {
390                            if (!connectHfpNative(getByteAddress(mTargetDevice))) {
391                                broadcastConnectionState(mTargetDevice,
392                                                         BluetoothProfile.STATE_DISCONNECTED,
393                                                         BluetoothProfile.STATE_CONNECTING);
394                                synchronized (HeadsetStateMachine.this) {
395                                    mTargetDevice = null;
396                                    transitionTo(mDisconnected);
397                                }
398                            }
399                        } else {
400                            synchronized (HeadsetStateMachine.this) {
401                                mIncomingDevice = null;
402                                transitionTo(mDisconnected);
403                            }
404                        }
405                    } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
406                        // outgoing connection failed
407                        broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
408                                                 BluetoothProfile.STATE_CONNECTING);
409                        synchronized (HeadsetStateMachine.this) {
410                            mTargetDevice = null;
411                            transitionTo(mDisconnected);
412                        }
413                    } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
414                        broadcastConnectionState(mIncomingDevice,
415                                                 BluetoothProfile.STATE_DISCONNECTED,
416                                                 BluetoothProfile.STATE_CONNECTING);
417                        synchronized (HeadsetStateMachine.this) {
418                            mIncomingDevice = null;
419                            transitionTo(mDisconnected);
420                        }
421                    } else {
422                        Log.e(TAG, "Unknown device Disconnected: " + device);
423                    }
424                    break;
425            case HeadsetHalConstants.CONNECTION_STATE_CONNECTED:
426                if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
427                    // disconnection failed
428                    broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED,
429                                             BluetoothProfile.STATE_DISCONNECTING);
430                    if (mTargetDevice != null) {
431                        broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
432                                                 BluetoothProfile.STATE_CONNECTING);
433                    }
434                    synchronized (HeadsetStateMachine.this) {
435                        mTargetDevice = null;
436                        transitionTo(mConnected);
437                    }
438                } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
439                    broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_CONNECTED,
440                                             BluetoothProfile.STATE_CONNECTING);
441                    synchronized (HeadsetStateMachine.this) {
442                        mCurrentDevice = mTargetDevice;
443                        mTargetDevice = null;
444                        transitionTo(mConnected);
445                    }
446                } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
447                    broadcastConnectionState(mIncomingDevice, BluetoothProfile.STATE_CONNECTED,
448                                             BluetoothProfile.STATE_CONNECTING);
449                    synchronized (HeadsetStateMachine.this) {
450                        mCurrentDevice = mIncomingDevice;
451                        mIncomingDevice = null;
452                        transitionTo(mConnected);
453                    }
454                } else {
455                    Log.e(TAG, "Unknown device Connected: " + device);
456                    // something is wrong here, but sync our state with stack
457                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
458                                             BluetoothProfile.STATE_DISCONNECTED);
459                    synchronized (HeadsetStateMachine.this) {
460                        mCurrentDevice = device;
461                        mTargetDevice = null;
462                        mIncomingDevice = null;
463                        transitionTo(mConnected);
464                    }
465                }
466                break;
467            case HeadsetHalConstants.CONNECTION_STATE_CONNECTING:
468                if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
469                    log("current device tries to connect back");
470                    // TODO(BT) ignore or reject
471                } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
472                    // The stack is connecting to target device or
473                    // there is an incoming connection from the target device at the same time
474                    // we already broadcasted the intent, doing nothing here
475                    if (DBG) {
476                        log("Stack and target device are connecting");
477                    }
478                }
479                else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
480                    Log.e(TAG, "Another connecting event on the incoming device");
481                } else {
482                    // We get an incoming connecting request while Pending
483                    // TODO(BT) is stack handing this case? let's ignore it for now
484                    log("Incoming connection while pending, ignore");
485                }
486                break;
487            case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTING:
488                if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
489                    // we already broadcasted the intent, doing nothing here
490                    if (DBG) {
491                        log("stack is disconnecting mCurrentDevice");
492                    }
493                } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
494                    Log.e(TAG, "TargetDevice is getting disconnected");
495                } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
496                    Log.e(TAG, "IncomingDevice is getting disconnected");
497                } else {
498                    Log.e(TAG, "Disconnecting unknow device: " + device);
499                }
500                break;
501            default:
502                Log.e(TAG, "Incorrect state: " + state);
503                break;
504            }
505        }
506
507    }
508
509    private class Connected extends State {
510        @Override
511        public void enter() {
512            log("Enter Connected: " + getCurrentMessage().what);
513        }
514
515        @Override
516        public boolean processMessage(Message message) {
517            log("Connected process message: " + message.what);
518            if (DBG) {
519                if (mCurrentDevice == null) {
520                    log("ERROR: mCurrentDevice is null in Connected");
521                    return NOT_HANDLED;
522                }
523            }
524
525            boolean retValue = HANDLED;
526            switch(message.what) {
527                case CONNECT:
528                {
529                    BluetoothDevice device = (BluetoothDevice) message.obj;
530                    if (mCurrentDevice.equals(device)) {
531                        break;
532                    }
533
534                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
535                                   BluetoothProfile.STATE_DISCONNECTED);
536                    if (!disconnectHfpNative(getByteAddress(mCurrentDevice))) {
537                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
538                                       BluetoothProfile.STATE_CONNECTING);
539                        break;
540                    }
541
542                    synchronized (HeadsetStateMachine.this) {
543                        mTargetDevice = device;
544                        transitionTo(mPending);
545                    }
546                }
547                    break;
548                case DISCONNECT:
549                {
550                    BluetoothDevice device = (BluetoothDevice) message.obj;
551                    if (!mCurrentDevice.equals(device)) {
552                        break;
553                    }
554                    broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING,
555                                   BluetoothProfile.STATE_CONNECTED);
556                    if (!disconnectHfpNative(getByteAddress(device))) {
557                        broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
558                                       BluetoothProfile.STATE_DISCONNECTED);
559                        break;
560                    }
561                    transitionTo(mPending);
562                }
563                    break;
564                case CONNECT_AUDIO:
565                    // TODO(BT) when failure, broadcast audio connecting to disconnected intent
566                    //          check if device matches mCurrentDevice
567                    connectAudioNative(getByteAddress(mCurrentDevice));
568                    break;
569                case VOICE_RECOGNITION_START:
570                    processLocalVrEvent(HeadsetHalConstants.VR_STATE_STARTED);
571                    break;
572                case VOICE_RECOGNITION_STOP:
573                    processLocalVrEvent(HeadsetHalConstants.VR_STATE_STOPPED);
574                    break;
575                case CALL_STATE_CHANGED:
576                    processCallState((HeadsetCallState) message.obj);
577                    break;
578                case INTENT_BATTERY_CHANGED:
579                    processIntentBatteryChanged((Intent) message.obj);
580                    break;
581                case ROAM_CHANGED:
582                    processRoamChanged((Boolean) message.obj);
583                    break;
584                case DEVICE_STATE_CHANGED:
585                    processDeviceStateChanged((HeadsetDeviceState) message.obj);
586                    break;
587                case SEND_CCLC_RESPONSE:
588                    processSendClccResponse((HeadsetClccResponse) message.obj);
589                    break;
590                case DIALING_OUT_TIMEOUT:
591                    if (mDialingOut) {
592                        mDialingOut= false;
593                        atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR);
594                    }
595                    break;
596                case START_VR_TIMEOUT:
597                    if (mWaitingForVoiceRecognition) {
598                        mWaitingForVoiceRecognition = false;
599                        Log.e(TAG, "Timeout waiting for voice recognition to start");
600                        atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR);
601                    }
602                    break;
603                case STACK_EVENT:
604                    StackEvent event = (StackEvent) message.obj;
605                    if (DBG) {
606                        log("event type: " + event.type);
607                    }
608                    switch (event.type) {
609                        case EVENT_TYPE_CONNECTION_STATE_CHANGED:
610                            processConnectionEvent(event.valueInt, event.device);
611                            break;
612                        case EVENT_TYPE_AUDIO_STATE_CHANGED:
613                            processAudioEvent(event.valueInt, event.device);
614                            break;
615                        case EVENT_TYPE_VR_STATE_CHANGED:
616                            processVrEvent(event.valueInt);
617                            break;
618                        case EVENT_TYPE_ANSWER_CALL:
619                            // TODO(BT) could answer call happen on Connected state?
620                            processAnswerCall();
621                            break;
622                        case EVENT_TYPE_HANGUP_CALL:
623                            // TODO(BT) could hangup call happen on Connected state?
624                            processHangupCall();
625                            break;
626                        case EVENT_TYPE_VOLUME_CHANGED:
627                            processVolumeEvent(event.valueInt, event.valueInt2);
628                            break;
629                        case EVENT_TYPE_DIAL_CALL:
630                            processDialCall(event.valueString);
631                            break;
632                        case EVENT_TYPE_SEND_DTMF:
633                            processSendDtmf(event.valueInt);
634                            break;
635                        case EVENT_TYPE_AT_CHLD:
636                            processAtChld(event.valueInt);
637                            break;
638                        case EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST:
639                            processSubscriberNumberRequest();
640                            break;
641                        case EVENT_TYPE_AT_CIND:
642                            processAtCind();
643                            break;
644                        case EVENT_TYPE_AT_COPS:
645                            processAtCops();
646                            break;
647                        case EVENT_TYPE_AT_CLCC:
648                            processAtClcc();
649                            break;
650                        case EVENT_TYPE_UNKNOWN_AT:
651                            processUnknownAt(event.valueString);
652                            break;
653                        case EVENT_TYPE_KEY_PRESSED:
654                            processKeyPressed();
655                            break;
656                        default:
657                            Log.e(TAG, "Unknown stack event: " + event.type);
658                            break;
659                    }
660                    break;
661                default:
662                    return NOT_HANDLED;
663            }
664            return retValue;
665        }
666
667        // in Connected state
668        private void processConnectionEvent(int state, BluetoothDevice device) {
669            switch (state) {
670                case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED:
671                    if (mCurrentDevice.equals(device)) {
672                        broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED,
673                                                 BluetoothProfile.STATE_CONNECTED);
674                        synchronized (HeadsetStateMachine.this) {
675                            mCurrentDevice = null;
676                            transitionTo(mDisconnected);
677                        }
678                    } else {
679                        Log.e(TAG, "Disconnected from unknown device: " + device);
680                    }
681                    break;
682                case HeadsetHalConstants.CONNECTION_STATE_SLC_CONNECTED:
683                    processSlcConnected();
684                    break;
685              default:
686                  Log.e(TAG, "Connection State Device: " + device + " bad state: " + state);
687                  break;
688            }
689        }
690
691        // in Connected state
692        private void processAudioEvent(int state, BluetoothDevice device) {
693            if (!mCurrentDevice.equals(device)) {
694                Log.e(TAG, "Audio changed on disconnected device: " + device);
695                return;
696            }
697
698            switch (state) {
699                case HeadsetHalConstants.AUDIO_STATE_CONNECTED:
700                    // TODO(BT) should I save the state for next broadcast as the prevState?
701                    mAudioState = BluetoothHeadset.STATE_AUDIO_CONNECTED;
702                    mAudioManager.setBluetoothScoOn(true);
703                    broadcastAudioState(device, BluetoothHeadset.STATE_AUDIO_CONNECTED,
704                                        BluetoothHeadset.STATE_AUDIO_CONNECTING);
705                    transitionTo(mAudioOn);
706                    break;
707                case HeadsetHalConstants.AUDIO_STATE_CONNECTING:
708                    mAudioState = BluetoothHeadset.STATE_AUDIO_CONNECTING;
709                    broadcastAudioState(device, BluetoothHeadset.STATE_AUDIO_CONNECTING,
710                                        BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
711                    break;
712                    // TODO(BT) process other states
713                default:
714                    Log.e(TAG, "Audio State Device: " + device + " bad state: " + state);
715                    break;
716            }
717        }
718
719        private void processSlcConnected() {
720            if (mPhoneProxy != null) {
721                try {
722                    mPhoneProxy.queryPhoneState();
723                } catch (RemoteException e) {
724                    Log.e(TAG, Log.getStackTraceString(new Throwable()));
725                }
726            } else {
727                Log.e(TAG, "Handsfree phone proxy null for query phone state");
728            }
729
730        }
731    }
732
733    private class AudioOn extends State {
734        // Audio parameters
735        private static final String HEADSET_NAME = "bt_headset_name";
736        private static final String HEADSET_NREC = "bt_headset_nrec";
737
738        @Override
739        public void enter() {
740            log("Enter AudioOn: " + getCurrentMessage().what);
741            mAudioManager.setParameters(HEADSET_NAME + "=" + getCurrentDeviceName() + ";" +
742                                        HEADSET_NREC + "=on");
743        }
744
745        @Override
746        public boolean processMessage(Message message) {
747            log("AudioOn process message: " + message.what);
748            if (DBG) {
749                if (mCurrentDevice == null) {
750                    log("ERROR: mCurrentDevice is null in AudioOn");
751                    return NOT_HANDLED;
752                }
753            }
754
755            boolean retValue = HANDLED;
756            switch(message.what) {
757                case DISCONNECT:
758                {
759                    BluetoothDevice device = (BluetoothDevice) message.obj;
760                    if (!mCurrentDevice.equals(device)) {
761                        break;
762                    }
763                    deferMessage(obtainMessage(DISCONNECT, message.obj));
764                }
765                // fall through
766                case DISCONNECT_AUDIO:
767                    disconnectAudioNative(getByteAddress(mCurrentDevice));
768                    break;
769                case VOICE_RECOGNITION_START:
770                    processLocalVrEvent(HeadsetHalConstants.VR_STATE_STARTED);
771                    break;
772                case VOICE_RECOGNITION_STOP:
773                    processLocalVrEvent(HeadsetHalConstants.VR_STATE_STOPPED);
774                    break;
775                case INTENT_SCO_VOLUME_CHANGED:
776                    processIntentScoVolume((Intent) message.obj);
777                    break;
778                case CALL_STATE_CHANGED:
779                    processCallState((HeadsetCallState) message.obj);
780                    break;
781                case INTENT_BATTERY_CHANGED:
782                    processIntentBatteryChanged((Intent) message.obj);
783                    break;
784                case ROAM_CHANGED:
785                    processRoamChanged((Boolean) message.obj);
786                    break;
787                case DEVICE_STATE_CHANGED:
788                    processDeviceStateChanged((HeadsetDeviceState) message.obj);
789                    break;
790                case SEND_CCLC_RESPONSE:
791                    processSendClccResponse((HeadsetClccResponse) message.obj);
792                    break;
793                case DIALING_OUT_TIMEOUT:
794                    if (mDialingOut) {
795                        mDialingOut= false;
796                        atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR);
797                    }
798                    break;
799                case START_VR_TIMEOUT:
800                    if (mWaitingForVoiceRecognition) {
801                        mWaitingForVoiceRecognition = false;
802                        Log.e(TAG, "Timeout waiting for voice recognition to start");
803                        atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR);
804                    }
805                    break;
806                case STACK_EVENT:
807                    StackEvent event = (StackEvent) message.obj;
808                    if (DBG) {
809                        log("event type: " + event.type);
810                    }
811                    switch (event.type) {
812                        case EVENT_TYPE_AUDIO_STATE_CHANGED:
813                            processAudioEvent(event.valueInt, event.device);
814                            break;
815                        case EVENT_TYPE_VR_STATE_CHANGED:
816                            processVrEvent(event.valueInt);
817                            break;
818                        case EVENT_TYPE_ANSWER_CALL:
819                            processAnswerCall();
820                            break;
821                        case EVENT_TYPE_HANGUP_CALL:
822                            processHangupCall();
823                            break;
824                        case EVENT_TYPE_VOLUME_CHANGED:
825                            processVolumeEvent(event.valueInt, event.valueInt2);
826                            break;
827                        case EVENT_TYPE_DIAL_CALL:
828                            processDialCall(event.valueString);
829                            break;
830                        case EVENT_TYPE_SEND_DTMF:
831                            processSendDtmf(event.valueInt);
832                            break;
833                        case EVENT_TYPE_NOICE_REDUCTION:
834                            processNoiceReductionEvent(event.valueInt);
835                            break;
836                        case EVENT_TYPE_AT_CHLD:
837                            processAtChld(event.valueInt);
838                            break;
839                        case EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST:
840                            processSubscriberNumberRequest();
841                            break;
842                        case EVENT_TYPE_AT_CIND:
843                            processAtCind();
844                            break;
845                        case EVENT_TYPE_AT_COPS:
846                            processAtCops();
847                            break;
848                        case EVENT_TYPE_AT_CLCC:
849                            processAtClcc();
850                            break;
851                        case EVENT_TYPE_UNKNOWN_AT:
852                            processUnknownAt(event.valueString);
853                            break;
854                        case EVENT_TYPE_KEY_PRESSED:
855                            processKeyPressed();
856                            break;
857                        default:
858                            Log.e(TAG, "Unknown stack event: " + event.type);
859                            break;
860                    }
861                    break;
862                default:
863                    return NOT_HANDLED;
864            }
865            return retValue;
866        }
867
868        // in AudioOn state
869        private void processAudioEvent(int state, BluetoothDevice device) {
870            if (!mCurrentDevice.equals(device)) {
871                Log.e(TAG, "Audio changed on disconnected device: " + device);
872                return;
873            }
874
875            switch (state) {
876                case HeadsetHalConstants.AUDIO_STATE_DISCONNECTED:
877                    mAudioState = BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
878                    mAudioManager.setBluetoothScoOn(false);
879                    broadcastAudioState(device, BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
880                                        BluetoothHeadset.STATE_AUDIO_CONNECTED);
881                    transitionTo(mConnected);
882                    break;
883                case HeadsetHalConstants.AUDIO_STATE_DISCONNECTING:
884                    // TODO(BT) adding STATE_AUDIO_DISCONNECTING in BluetoothHeadset?
885                    //broadcastAudioState(device, BluetoothHeadset.STATE_AUDIO_DISCONNECTING,
886                    //                    BluetoothHeadset.STATE_AUDIO_CONNECTED);
887                    break;
888                default:
889                    Log.e(TAG, "Audio State Device: " + device + " bad state: " + state);
890                    break;
891            }
892        }
893
894        // enable 1 enable noice reduction
895        //        0 disable noice reduction
896        private void processNoiceReductionEvent(int enable) {
897            if (enable == 1) {
898                mAudioManager.setParameters(HEADSET_NREC + "=on");
899            } else {
900                mAudioManager.setParameters(HEADSET_NREC + "off");
901            }
902        }
903
904        private void processIntentScoVolume(Intent intent) {
905            int volumeValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
906            if (mPhoneState.getSpeakerVolume() != volumeValue) {
907                mPhoneState.setSpeakerVolume(volumeValue);
908                setVolumeNative(HeadsetHalConstants.VOLUME_TYPE_SPK, volumeValue);
909            }
910        }
911    }
912
913    private ServiceConnection mConnection = new ServiceConnection() {
914        public void onServiceConnected(ComponentName className, IBinder service) {
915            if (DBG) Log.d(TAG, "Proxy object connected");
916            mPhoneProxy = IBluetoothHeadsetPhone.Stub.asInterface(service);
917        }
918
919        public void onServiceDisconnected(ComponentName className) {
920            if (DBG) Log.d(TAG, "Proxy object disconnected");
921            mPhoneProxy = null;
922        }
923    };
924
925    // HFP Connection state of the device could be changed by the state machine
926    // in separate thread while this method is executing.
927    int getConnectionState(BluetoothDevice device) {
928        if (getCurrentState() == mDisconnected) {
929            return BluetoothProfile.STATE_DISCONNECTED;
930        }
931
932        synchronized (this) {
933            IState currentState = getCurrentState();
934            if (currentState == mPending) {
935                if ((mTargetDevice != null) && mTargetDevice.equals(device)) {
936                    return BluetoothProfile.STATE_CONNECTING;
937                }
938                if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
939                    return BluetoothProfile.STATE_DISCONNECTING;
940                }
941                if ((mIncomingDevice != null) && mIncomingDevice.equals(device)) {
942                    return BluetoothProfile.STATE_CONNECTING; // incoming connection
943                }
944                return BluetoothProfile.STATE_DISCONNECTED;
945            }
946
947            if (currentState == mConnected || currentState == mAudioOn) {
948                if (mCurrentDevice.equals(device)) {
949                    return BluetoothProfile.STATE_CONNECTED;
950                }
951                return BluetoothProfile.STATE_DISCONNECTED;
952            } else {
953                Log.e(TAG, "Bad currentState: " + currentState);
954                return BluetoothProfile.STATE_DISCONNECTED;
955            }
956        }
957    }
958
959    List<BluetoothDevice> getConnectedDevices() {
960        List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
961        synchronized(this) {
962            if (isConnected()) {
963                devices.add(mCurrentDevice);
964            }
965        }
966        return devices;
967    }
968
969    boolean isAudioOn() {
970        return (getCurrentState() == mAudioOn);
971    }
972
973    boolean isAudioConnected(BluetoothDevice device) {
974        synchronized(this) {
975            if (getCurrentState() == mAudioOn && mCurrentDevice.equals(device)) {
976                return true;
977            }
978        }
979        return false;
980    }
981
982    int getAudioState(BluetoothDevice device) {
983        synchronized(this) {
984            if (mCurrentDevice == null || !mCurrentDevice.equals(device)) {
985                return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
986            }
987        }
988        return mAudioState;
989    }
990
991    private void processVrEvent(int state) {
992        Log.d(TAG, "processVrEvent: state=" + state + " mVoiceRecognitionStarted: " +
993            mVoiceRecognitionStarted + " mWaitingforVoiceRecognition: " + mWaitingForVoiceRecognition +
994            " isInCall: " + isInCall());
995        if (state == HeadsetHalConstants.VR_STATE_STARTED) {
996            // TODO(BT) handle virtualcall
997            if (!mVoiceRecognitionStarted &&
998                !isInCall())
999            {
1000                try {
1001                    mContext.startActivity(sVoiceCommandIntent);
1002                } catch (ActivityNotFoundException e) {
1003                    atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR);
1004                    return;
1005                }
1006                expectVoiceRecognition();
1007            }
1008        } else if (state == HeadsetHalConstants.VR_STATE_STOPPED) {
1009            if (mVoiceRecognitionStarted || mWaitingForVoiceRecognition)
1010            {
1011                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK);
1012                mVoiceRecognitionStarted = false;
1013                mWaitingForVoiceRecognition = false;
1014                if (!isInCall())
1015                    disconnectAudioNative(getByteAddress(mCurrentDevice));
1016            }
1017            else
1018            {
1019                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR);
1020            }
1021        } else {
1022            Log.e(TAG, "Bad Voice Recognition state: " + state);
1023        }
1024    }
1025
1026    private void processLocalVrEvent(int state)
1027    {
1028        if (state == HeadsetHalConstants.VR_STATE_STARTED)
1029        {
1030            boolean needAudio = true;
1031            if (mVoiceRecognitionStarted || isInCall())
1032            {
1033                Log.e(TAG, "Voice recognition started when call is active. isInCall:" + isInCall() +
1034                    " mVoiceRecognitionStarted: " + mVoiceRecognitionStarted);
1035                return;
1036            }
1037            mVoiceRecognitionStarted = true;
1038
1039            if (mWaitingForVoiceRecognition)
1040            {
1041                Log.d(TAG, "Voice recognition started successfully");
1042                mWaitingForVoiceRecognition = false;
1043                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK);
1044                removeMessages(START_VR_TIMEOUT);
1045            }
1046            else
1047            {
1048                Log.d(TAG, "Voice recognition started locally");
1049                needAudio = startVoiceRecognitionNative();
1050            }
1051
1052            if (needAudio && !isAudioOn())
1053            {
1054                Log.d(TAG, "Initiating audio connection for Voice Recognition");
1055                connectAudioNative(getByteAddress(mCurrentDevice));
1056            }
1057
1058            if (mStartVoiceRecognitionWakeLock.isHeld()) {
1059                mStartVoiceRecognitionWakeLock.release();
1060            }
1061        }
1062        else
1063        {
1064            Log.d(TAG, "Voice Recognition stopped. mVoiceRecognitionStarted: " + mVoiceRecognitionStarted +
1065                " mWaitingForVoiceRecognition: " + mWaitingForVoiceRecognition);
1066            if (mVoiceRecognitionStarted || mWaitingForVoiceRecognition)
1067            {
1068                mVoiceRecognitionStarted = false;
1069                mWaitingForVoiceRecognition = false;
1070
1071                if (stopVoiceRecognitionNative() && !isInCall())
1072                    disconnectAudioNative(getByteAddress(mCurrentDevice));
1073            }
1074        }
1075    }
1076
1077    private synchronized void expectVoiceRecognition() {
1078        mWaitingForVoiceRecognition = true;
1079        sendMessageDelayed(START_VR_TIMEOUT, START_VR_TIMEOUT_VALUE);
1080        if (!mStartVoiceRecognitionWakeLock.isHeld()) {
1081            mStartVoiceRecognitionWakeLock.acquire(START_VR_TIMEOUT_VALUE);
1082        }
1083    }
1084
1085    List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
1086        List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();
1087        Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
1088        int connectionState;
1089        synchronized (this) {
1090            for (BluetoothDevice device : bondedDevices) {
1091                ParcelUuid[] featureUuids = device.getUuids();
1092                if (!BluetoothUuid.containsAnyUuid(featureUuids, HEADSET_UUIDS)) {
1093                    continue;
1094                }
1095                connectionState = getConnectionState(device);
1096                for(int i = 0; i < states.length; i++) {
1097                    if (connectionState == states[i]) {
1098                        deviceList.add(device);
1099                    }
1100                }
1101            }
1102        }
1103        return deviceList;
1104    }
1105
1106    // This method does not check for error conditon (newState == prevState)
1107    private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) {
1108        Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
1109        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
1110        intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
1111        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
1112        mContext.sendBroadcast(intent, HeadsetService.BLUETOOTH_PERM);
1113        if (DBG) log("Connection state " + device + ": " + prevState + "->" + newState);
1114        AdapterService svc = AdapterService.getAdapterService();
1115        if (svc != null) {
1116            svc.onProfileConnectionStateChanged(device, BluetoothProfile.HEADSET, newState, prevState);
1117        }
1118    }
1119
1120    private void broadcastAudioState(BluetoothDevice device, int newState, int prevState) {
1121        Intent intent = new Intent(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
1122        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
1123        intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
1124        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
1125        mContext.sendBroadcast(intent, HeadsetService.BLUETOOTH_PERM);
1126        if (DBG) log("Audio state " + device + ": " + prevState + "->" + newState);
1127    }
1128
1129    private void processAnswerCall() {
1130        if (mPhoneProxy != null) {
1131            try {
1132                mPhoneProxy.answerCall();
1133            } catch (RemoteException e) {
1134                Log.e(TAG, Log.getStackTraceString(new Throwable()));
1135            }
1136        } else {
1137            Log.e(TAG, "Handsfree phone proxy null for answering call");
1138        }
1139    }
1140
1141    private void processHangupCall() {
1142        if (mPhoneProxy != null) {
1143            try {
1144                mPhoneProxy.hangupCall();
1145            } catch (RemoteException e) {
1146                Log.e(TAG, Log.getStackTraceString(new Throwable()));
1147            }
1148        } else {
1149            Log.e(TAG, "Handsfree phone proxy null for hanging up call");
1150        }
1151    }
1152
1153    private void processDialCall(String number) {
1154        String dialNumber;
1155        if ((number == null) || (number.length() == 0)) {
1156            dialNumber = mPhonebook.getLastDialledNumber();
1157            if (dialNumber == null) {
1158                if (DBG) log("processDialCall, last dial number null");
1159                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR);
1160                return;
1161            }
1162        } else if (number.charAt(0) == '>') {
1163            // Yuck - memory dialling requested.
1164            // Just dial last number for now
1165            if (number.startsWith(">9999")) {   // for PTS test
1166                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR);
1167                return;
1168            }
1169            if (DBG) log("processDialCall, memory dial do last dial for now");
1170            dialNumber = mPhonebook.getLastDialledNumber();
1171            if (dialNumber == null) {
1172                if (DBG) log("processDialCall, last dial number null");
1173                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR);
1174                return;
1175            }
1176        } else {
1177            // Remove trailing ';'
1178            if (number.charAt(number.length() - 1) == ';') {
1179                number = number.substring(0, number.length() - 1);
1180            }
1181
1182            dialNumber = PhoneNumberUtils.convertPreDial(number);
1183        }
1184        // TODO(BT) do we need to terminate virtual call first
1185        //          like call terminateScoUsingVirtualVoiceCall()?
1186        Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
1187                                   Uri.fromParts(SCHEME_TEL, dialNumber, null));
1188        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1189        mContext.startActivity(intent);
1190        // TODO(BT) continue send OK reults code after call starts
1191        //          hold wait lock, start a timer, set wait call flag
1192        //          Get call started indication from bluetooth phone
1193        mDialingOut = true;
1194        sendMessageDelayed(DIALING_OUT_TIMEOUT, DIALING_OUT_TIMEOUT_VALUE);
1195    }
1196
1197    private void processVolumeEvent(int volumeType, int volume) {
1198        if (volumeType == HeadsetHalConstants.VOLUME_TYPE_SPK) {
1199            mPhoneState.setSpeakerVolume(volume);
1200            int flag = (getCurrentState() == mAudioOn) ? AudioManager.FLAG_SHOW_UI : 0;
1201            mAudioManager.setStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO, volume, flag);
1202        } else if (volumeType == HeadsetHalConstants.VOLUME_TYPE_MIC) {
1203            mPhoneState.setMicVolume(volume);
1204        } else {
1205            Log.e(TAG, "Bad voluem type: " + volumeType);
1206        }
1207    }
1208
1209    private void processSendDtmf(int dtmf) {
1210        if (mPhoneProxy != null) {
1211            try {
1212                mPhoneProxy.sendDtmf(dtmf);
1213            } catch (RemoteException e) {
1214                Log.e(TAG, Log.getStackTraceString(new Throwable()));
1215            }
1216        } else {
1217            Log.e(TAG, "Handsfree phone proxy null for sending DTMF");
1218        }
1219    }
1220
1221    private void processCallState(HeadsetCallState callState) {
1222        mPhoneState.setNumActiveCall(callState.mNumActive);
1223        mPhoneState.setNumHeldCall(callState.mNumHeld);
1224        mPhoneState.setCallState(callState.mCallState);
1225        if (mDialingOut && callState.mCallState == HeadsetHalConstants.CALL_STATE_DIALING) {
1226                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK);
1227                removeMessages(DIALING_OUT_TIMEOUT);
1228                mDialingOut = false;
1229        }
1230        log("mNumActive: " + callState.mNumActive + " mNumHeld: " + callState.mNumHeld +
1231            " mCallState: " + callState.mCallState);
1232        log("mNumber: " + callState.mNumber + " mType: " + callState.mType);
1233        if (getCurrentState() != mDisconnected) {
1234            phoneStateChangeNative(callState.mNumActive, callState.mNumHeld, callState.mCallState,
1235                                   callState.mNumber, callState.mType);
1236        }
1237    }
1238
1239    private void processAtChld(int chld) {
1240        if (mPhoneProxy != null) {
1241            try {
1242                if (mPhoneProxy.processChld(chld)) {
1243                    atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK);
1244                } else {
1245                    atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR);
1246                }
1247            } catch (RemoteException e) {
1248                Log.e(TAG, Log.getStackTraceString(new Throwable()));
1249                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR);
1250            }
1251        } else {
1252            Log.e(TAG, "Handsfree phone proxy null for At+Chld");
1253            atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR);
1254        }
1255    }
1256
1257    private void processSubscriberNumberRequest() {
1258        if (mPhoneProxy != null) {
1259            try {
1260                String number = mPhoneProxy.getSubscriberNumber();
1261                if (number != null) {
1262                    atResponseStringNative("+CNUM: ,\"" + number + "\"," +
1263                                           PhoneNumberUtils.toaFromString(number) + ",,4");
1264                    atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK);
1265                }
1266            } catch (RemoteException e) {
1267                Log.e(TAG, Log.getStackTraceString(new Throwable()));
1268                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR);
1269            }
1270        } else {
1271            Log.e(TAG, "Handsfree phone proxy null for At+CNUM");
1272        }
1273    }
1274
1275    private void processAtCind() {
1276        cindResponseNative(mPhoneState.getService(), mPhoneState.getNumActiveCall(),
1277                           mPhoneState.getNumHeldCall(), mPhoneState.getCallState(),
1278                           mPhoneState.getSignal(), mPhoneState.getRoam(),
1279                           mPhoneState.getBatteryCharge());
1280    }
1281
1282    private void processAtCops() {
1283        if (mPhoneProxy != null) {
1284            try {
1285                String operatorName = mPhoneProxy.getNetworkOperator();
1286                if (operatorName == null) {
1287                    operatorName = "";
1288                }
1289                copsResponseNative(operatorName);
1290            } catch (RemoteException e) {
1291                Log.e(TAG, Log.getStackTraceString(new Throwable()));
1292                copsResponseNative("");
1293            }
1294        } else {
1295            Log.e(TAG, "Handsfree phone proxy null for At+COPS");
1296            copsResponseNative("");
1297        }
1298    }
1299
1300    private void processAtClcc() {
1301        if (mPhoneProxy != null) {
1302            try {
1303                if (!mPhoneProxy.listCurrentCalls()) {
1304                    clccResponseNative(0, 0, 0, 0, false, "", 0);
1305                }
1306            } catch (RemoteException e) {
1307                Log.e(TAG, Log.getStackTraceString(new Throwable()));
1308                clccResponseNative(0, 0, 0, 0, false, "", 0);
1309            }
1310        } else {
1311            Log.e(TAG, "Handsfree phone proxy null for At+CLCC");
1312            clccResponseNative(0, 0, 0, 0, false, "", 0);
1313        }
1314    }
1315
1316    private void processUnknownAt(String atString) {
1317        // TODO (BT)
1318        atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR);
1319    }
1320
1321    private void processKeyPressed() {
1322        if (mPhoneState.getCallState() == HeadsetHalConstants.CALL_STATE_INCOMING) {
1323            if (mPhoneProxy != null) {
1324                try {
1325                    mPhoneProxy.answerCall();
1326                } catch (RemoteException e) {
1327                    Log.e(TAG, Log.getStackTraceString(new Throwable()));
1328                }
1329            } else {
1330                Log.e(TAG, "Handsfree phone proxy null for answering call");
1331            }
1332        } else if (mPhoneState.getNumActiveCall() > 0) {
1333            if (!isAudioOn())
1334            {
1335                connectAudioNative(getByteAddress(mCurrentDevice));
1336            }
1337            else
1338            {
1339                if (mPhoneProxy != null) {
1340                    try {
1341                        mPhoneProxy.hangupCall();
1342                    } catch (RemoteException e) {
1343                        Log.e(TAG, Log.getStackTraceString(new Throwable()));
1344                    }
1345                } else {
1346                    Log.e(TAG, "Handsfree phone proxy null for hangup call");
1347                }
1348            }
1349        } else {
1350            String dialNumber = mPhonebook.getLastDialledNumber();
1351            if (dialNumber == null) {
1352                if (DBG) log("processKeyPressed, last dial number null");
1353                return;
1354            }
1355            Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
1356                                       Uri.fromParts(SCHEME_TEL, dialNumber, null));
1357            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1358            mContext.startActivity(intent);
1359        }
1360    }
1361
1362    private void onConnectionStateChanged(int state, byte[] address) {
1363        StackEvent event = new StackEvent(EVENT_TYPE_CONNECTION_STATE_CHANGED);
1364        event.valueInt = state;
1365        event.device = getDevice(address);
1366        sendMessage(STACK_EVENT, event);
1367    }
1368
1369    private void onAudioStateChanged(int state, byte[] address) {
1370        StackEvent event = new StackEvent(EVENT_TYPE_AUDIO_STATE_CHANGED);
1371        event.valueInt = state;
1372        event.device = getDevice(address);
1373        sendMessage(STACK_EVENT, event);
1374    }
1375
1376    private void onVrStateChanged(int state) {
1377        StackEvent event = new StackEvent(EVENT_TYPE_VR_STATE_CHANGED);
1378        event.valueInt = state;
1379        sendMessage(STACK_EVENT, event);
1380    }
1381
1382    private void onAnswerCall() {
1383        StackEvent event = new StackEvent(EVENT_TYPE_ANSWER_CALL);
1384        sendMessage(STACK_EVENT, event);
1385    }
1386
1387    private void onHangupCall() {
1388        StackEvent event = new StackEvent(EVENT_TYPE_HANGUP_CALL);
1389        sendMessage(STACK_EVENT, event);
1390    }
1391
1392    private void onVolumeChanged(int type, int volume) {
1393        StackEvent event = new StackEvent(EVENT_TYPE_VOLUME_CHANGED);
1394        event.valueInt = type;
1395        event.valueInt2 = volume;
1396        sendMessage(STACK_EVENT, event);
1397    }
1398
1399    private void onDialCall(String number) {
1400        StackEvent event = new StackEvent(EVENT_TYPE_DIAL_CALL);
1401        event.valueString = number;
1402        sendMessage(STACK_EVENT, event);
1403    }
1404
1405    private void onSendDtmf(int dtmf) {
1406        StackEvent event = new StackEvent(EVENT_TYPE_SEND_DTMF);
1407        event.valueInt = dtmf;
1408        sendMessage(STACK_EVENT, event);
1409    }
1410
1411    private void onNoiceReductionEnable(boolean enable) {
1412        StackEvent event = new StackEvent(EVENT_TYPE_NOICE_REDUCTION);
1413        event.valueInt = enable ? 1 : 0;
1414        sendMessage(STACK_EVENT, event);
1415    }
1416
1417    private void onAtChld(int chld) {
1418        StackEvent event = new StackEvent(EVENT_TYPE_AT_CHLD);
1419        event.valueInt = chld;
1420        sendMessage(STACK_EVENT, event);
1421    }
1422
1423    private void onAtCnum() {
1424        StackEvent event = new StackEvent(EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST);
1425        sendMessage(STACK_EVENT, event);
1426    }
1427
1428    private void onAtCind() {
1429        StackEvent event = new StackEvent(EVENT_TYPE_AT_CIND);
1430        sendMessage(STACK_EVENT, event);
1431    }
1432
1433    private void onAtCops() {
1434        StackEvent event = new StackEvent(EVENT_TYPE_AT_COPS);
1435        sendMessage(STACK_EVENT, event);
1436    }
1437
1438    private void onAtClcc() {
1439        StackEvent event = new StackEvent(EVENT_TYPE_AT_CLCC);
1440        sendMessage(STACK_EVENT, event);
1441    }
1442
1443    private void onUnknownAt(String atString) {
1444        StackEvent event = new StackEvent(EVENT_TYPE_UNKNOWN_AT);
1445        event.valueString = atString;
1446        sendMessage(STACK_EVENT, event);
1447    }
1448
1449    private void onKeyPressed() {
1450        StackEvent event = new StackEvent(EVENT_TYPE_KEY_PRESSED);
1451        sendMessage(STACK_EVENT, event);
1452    }
1453
1454    private void processIntentBatteryChanged(Intent intent) {
1455        int batteryLevel = intent.getIntExtra("level", -1);
1456        int scale = intent.getIntExtra("scale", -1);
1457        if (batteryLevel == -1 || scale == -1 || scale == 0) {
1458            Log.e(TAG, "Bad Battery Changed intent: " + batteryLevel + "," + scale);
1459            return;
1460        }
1461        batteryLevel = batteryLevel * 5 / scale;
1462        mPhoneState.setBatteryCharge(batteryLevel);
1463    }
1464
1465    private void processRoamChanged(boolean roam) {
1466        mPhoneState.setRoam(roam ? HeadsetHalConstants.SERVICE_TYPE_ROAMING :
1467                            HeadsetHalConstants.SERVICE_TYPE_HOME);
1468    }
1469
1470    private void processDeviceStateChanged(HeadsetDeviceState deviceState) {
1471        notifyDeviceStatusNative(deviceState.mService, deviceState.mRoam, deviceState.mSignal,
1472                                 deviceState.mBatteryCharge);
1473    }
1474
1475    private void processSendClccResponse(HeadsetClccResponse clcc) {
1476        clccResponseNative(clcc.mIndex, clcc.mDirection, clcc.mStatus, clcc.mMode, clcc.mMpty,
1477                           clcc.mNumber, clcc.mType);
1478    }
1479
1480    private String getCurrentDeviceName() {
1481        String defaultName = "<unknown>";
1482        if (mCurrentDevice == null) {
1483            return defaultName;
1484        }
1485        String deviceName = mCurrentDevice.getName();
1486        if (deviceName == null) {
1487            return defaultName;
1488        }
1489        return deviceName;
1490    }
1491
1492    private byte[] getByteAddress(BluetoothDevice device) {
1493        return Utils.getBytesFromAddress(device.getAddress());
1494    }
1495
1496    private BluetoothDevice getDevice(byte[] address) {
1497        return mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
1498    }
1499
1500    private boolean isInCall() {
1501        return ((mPhoneState.getNumActiveCall() > 0) || (mPhoneState.getNumHeldCall() > 0) ||
1502                (mPhoneState.getCallState() != HeadsetHalConstants.CALL_STATE_IDLE));
1503    }
1504
1505    boolean isConnected() {
1506        IState currentState = getCurrentState();
1507        return (currentState == mConnected || currentState == mAudioOn);
1508    }
1509
1510    private void log(String msg) {
1511        if (DBG) {
1512            Log.d(TAG, msg);
1513        }
1514    }
1515
1516    private static final String SCHEME_TEL = "tel";
1517
1518    // Event types for STACK_EVENT message
1519    final private static int EVENT_TYPE_NONE = 0;
1520    final private static int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
1521    final private static int EVENT_TYPE_AUDIO_STATE_CHANGED = 2;
1522    final private static int EVENT_TYPE_VR_STATE_CHANGED = 3;
1523    final private static int EVENT_TYPE_ANSWER_CALL = 4;
1524    final private static int EVENT_TYPE_HANGUP_CALL = 5;
1525    final private static int EVENT_TYPE_VOLUME_CHANGED = 6;
1526    final private static int EVENT_TYPE_DIAL_CALL = 7;
1527    final private static int EVENT_TYPE_SEND_DTMF = 8;
1528    final private static int EVENT_TYPE_NOICE_REDUCTION = 9;
1529    final private static int EVENT_TYPE_AT_CHLD = 10;
1530    final private static int EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST = 11;
1531    final private static int EVENT_TYPE_AT_CIND = 12;
1532    final private static int EVENT_TYPE_AT_COPS = 13;
1533    final private static int EVENT_TYPE_AT_CLCC = 14;
1534    final private static int EVENT_TYPE_UNKNOWN_AT = 15;
1535    final private static int EVENT_TYPE_KEY_PRESSED = 16;
1536
1537    private class StackEvent {
1538        int type = EVENT_TYPE_NONE;
1539        int valueInt = 0;
1540        int valueInt2 = 0;
1541        String valueString = null;
1542        BluetoothDevice device = null;
1543
1544        private StackEvent(int type) {
1545            this.type = type;
1546        }
1547    }
1548
1549    private native static void classInitNative();
1550    private native void initializeNative();
1551    private native void cleanupNative();
1552    private native boolean connectHfpNative(byte[] address);
1553    private native boolean disconnectHfpNative(byte[] address);
1554    private native boolean connectAudioNative(byte[] address);
1555    private native boolean disconnectAudioNative(byte[] address);
1556    private native boolean startVoiceRecognitionNative();
1557    private native boolean stopVoiceRecognitionNative();
1558    private native boolean setVolumeNative(int volumeType, int volume);
1559    private native boolean cindResponseNative(int service, int numActive, int numHeld,
1560                                              int callState, int signal, int roam,
1561                                              int batteryCharge);
1562    private native boolean notifyDeviceStatusNative(int networkState, int serviceType, int signal,
1563                                                    int batteryCharge);
1564    private native boolean atResponseCodeNative(int responseCode);
1565    private native boolean clccResponseNative(int index, int dir, int status, int mode,
1566                                              boolean mpty, String number, int type);
1567    private native boolean copsResponseNative(String operatorName);
1568    private native boolean atResponseStringNative(String responseString);
1569    private native boolean phoneStateChangeNative(int numActive, int numHeld, int callState,
1570                                                  String number, int type);
1571}
1572