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