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