HeadsetStateMachine.java revision bcbeaf69468424800a939b3e8678eaef21efa3d6
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                case HeadsetHalConstants.CONNECTION_STATE_SLC_CONNECTED:
633                    processSlcConnected();
634                    break;
635              default:
636                  Log.e(TAG, "Connection State Device: " + device + " bad state: " + state);
637                  break;
638            }
639        }
640
641        // in Connected state
642        private void processAudioEvent(int state, BluetoothDevice device) {
643            if (!mCurrentDevice.equals(device)) {
644                Log.e(TAG, "Audio changed on disconnected device: " + device);
645                return;
646            }
647
648            switch (state) {
649                case HeadsetHalConstants.AUDIO_STATE_CONNECTED:
650                    // TODO(BT) should I save the state for next broadcast as the prevState?
651                    mAudioState = BluetoothHeadset.STATE_AUDIO_CONNECTED;
652                    mAudioManager.setBluetoothScoOn(true);
653                    broadcastAudioState(device, BluetoothHeadset.STATE_AUDIO_CONNECTED,
654                                        BluetoothHeadset.STATE_AUDIO_CONNECTING);
655                    transitionTo(mAudioOn);
656                    break;
657                case HeadsetHalConstants.AUDIO_STATE_CONNECTING:
658                    mAudioState = BluetoothHeadset.STATE_AUDIO_CONNECTING;
659                    broadcastAudioState(device, BluetoothHeadset.STATE_AUDIO_CONNECTING,
660                                        BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
661                    break;
662                    // TODO(BT) process other states
663                default:
664                    Log.e(TAG, "Audio State Device: " + device + " bad state: " + state);
665                    break;
666            }
667        }
668
669        private void processSlcConnected() {
670            if (mPhoneProxy != null) {
671                try {
672                    mPhoneProxy.queryPhoneState();
673                } catch (RemoteException e) {
674                    Log.e(TAG, Log.getStackTraceString(new Throwable()));
675                }
676            } else {
677                Log.e(TAG, "Handsfree phone proxy null for query phone state");
678            }
679
680        }
681    }
682
683    private class AudioOn extends State {
684        // Audio parameters
685        private static final String HEADSET_NAME = "bt_headset_name";
686        private static final String HEADSET_NREC = "bt_headset_nrec";
687
688        @Override
689        public void enter() {
690            log("Enter Audio: " + getCurrentMessage().what);
691            mAudioManager.setParameters(HEADSET_NAME + "=" + getCurrentDeviceName() + ";" +
692                                        HEADSET_NREC + "=on");
693        }
694
695        @Override
696        public boolean processMessage(Message message) {
697            log("AudioOn process message: " + message.what);
698            if (DBG) {
699                if (mCurrentDevice == null) {
700                    log("ERROR: mCurrentDevice is null in AudioOn");
701                    return NOT_HANDLED;
702                }
703            }
704
705            boolean retValue = HANDLED;
706            switch(message.what) {
707                case DISCONNECT_AUDIO:
708                    // TODO(BT) when failure broadcast a audio disconnecting to connected intent
709                    //          check if device matches mCurrentDevice
710                    disconnectAudioNative(getByteAddress(mCurrentDevice));
711                    break;
712                case VOICE_RECOGNITION_START:
713                    // TODO(BT) should we check if device matches mCurrentDevice?
714                    startVoiceRecognitionNative();
715                    break;
716                case VOICE_RECOGNITION_STOP:
717                    stopVoiceRecognitionNative();
718                    break;
719                case INTENT_SCO_VOLUME_CHANGED:
720                    processIntentScoVolume((Intent) message.obj);
721                    break;
722                case CALL_STATE_CHANGED:
723                    processCallState((HeadsetCallState) message.obj);
724                    break;
725                case INTENT_BATTERY_CHANGED:
726                    processIntentBatteryChanged((Intent) message.obj);
727                    break;
728                case ROAM_CHANGED:
729                    processRoamChanged((Boolean) message.obj);
730                    break;
731                case DEVICE_STATE_CHANGED:
732                    processDeviceStateChanged((HeadsetDeviceState) message.obj);
733                    break;
734                case SEND_CCLC_RESPONSE:
735                    processSendClccResponse((HeadsetClccResponse) message.obj);
736                    break;
737                case DIALING_OUT_TIMEOUT:
738                    if (mDialingOut) {
739                        mDialingOut= false;
740                        atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR);
741                    }
742                    break;
743                case STACK_EVENT:
744                    StackEvent event = (StackEvent) message.obj;
745                    if (DBG) {
746                        log("event type: " + event.type);
747                    }
748                    switch (event.type) {
749                        case EVENT_TYPE_AUDIO_STATE_CHANGED:
750                            processAudioEvent(event.valueInt, event.device);
751                            break;
752                        case EVENT_TYPE_VR_STATE_CHANGED:
753                            processVrEvent(event.valueInt);
754                            break;
755                        case EVENT_TYPE_ANSWER_CALL:
756                            processAnswerCall();
757                            break;
758                        case EVENT_TYPE_HANGUP_CALL:
759                            processHangupCall();
760                            break;
761                        case EVENT_TYPE_VOLUME_CHANGED:
762                            processVolumeEvent(event.valueInt, event.valueInt2);
763                            break;
764                        case EVENT_TYPE_DIAL_CALL:
765                            processDialCall(event.valueString);
766                            break;
767                        case EVENT_TYPE_SEND_DTMF:
768                            processSendDtmf(event.valueInt);
769                            break;
770                        case EVENT_TYPE_NOICE_REDUCTION:
771                            processNoiceReductionEvent(event.valueInt);
772                            break;
773                        case EVENT_TYPE_AT_CHLD:
774                            processAtChld(event.valueInt);
775                            break;
776                        case EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST:
777                            processSubscriberNumberRequest();
778                            break;
779                        case EVENT_TYPE_AT_CIND:
780                            processAtCind();
781                            break;
782                        case EVENT_TYPE_AT_COPS:
783                            processAtCops();
784                            break;
785                        case EVENT_TYPE_AT_CLCC:
786                            processAtClcc();
787                            break;
788                        case EVENT_TYPE_UNKNOWN_AT:
789                            processUnknownAt(event.valueString);
790                            break;
791                        case EVENT_TYPE_KEY_PRESSED:
792                            processKeyPressed();
793                            break;
794                        default:
795                            Log.e(TAG, "Unknown stack event: " + event.type);
796                            break;
797                    }
798                    break;
799                default:
800                    return NOT_HANDLED;
801            }
802            return retValue;
803        }
804
805        // in AudioOn state
806        private void processAudioEvent(int state, BluetoothDevice device) {
807            if (!mCurrentDevice.equals(device)) {
808                Log.e(TAG, "Audio changed on disconnected device: " + device);
809                return;
810            }
811
812            switch (state) {
813                case HeadsetHalConstants.AUDIO_STATE_DISCONNECTED:
814                    mAudioState = BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
815                    mAudioManager.setBluetoothScoOn(false);
816                    broadcastAudioState(device, BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
817                                        BluetoothHeadset.STATE_AUDIO_CONNECTED);
818                    transitionTo(mConnected);
819                    break;
820                case HeadsetHalConstants.AUDIO_STATE_DISCONNECTING:
821                    // TODO(BT) adding STATE_AUDIO_DISCONNECTING in BluetoothHeadset?
822                    //broadcastAudioState(device, BluetoothHeadset.STATE_AUDIO_DISCONNECTING,
823                    //                    BluetoothHeadset.STATE_AUDIO_CONNECTED);
824                    break;
825                default:
826                    Log.e(TAG, "Audio State Device: " + device + " bad state: " + state);
827                    break;
828            }
829        }
830
831        private void processVrEvent(int state) {
832            if (state == HeadsetHalConstants.VR_STATE_STARTED) {
833                mVoiceRecognitionStarted = true;
834                // TODO(BT) should we send out Intent.ACTION_VOICE_COMMAND intent
835                //     and do expectVoiceRecognition, acquire wake lock etc
836            } else if (state == HeadsetHalConstants.VR_STATE_STOPPED) {
837                mVoiceRecognitionStarted = false;
838            } else {
839                Log.e(TAG, "Bad Voice Recognition state: " + state);
840            }
841        }
842
843        // enable 1 enable noice reduction
844        //        0 disable noice reduction
845        private void processNoiceReductionEvent(int enable) {
846            if (enable == 1) {
847                mAudioManager.setParameters(HEADSET_NREC + "=on");
848            } else {
849                mAudioManager.setParameters(HEADSET_NREC + "off");
850            }
851        }
852
853        private void processIntentScoVolume(Intent intent) {
854            int volumeValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
855            if (mPhoneState.getSpeakerVolume() != volumeValue) {
856                mPhoneState.setSpeakerVolume(volumeValue);
857                setVolumeNative(HeadsetHalConstants.VOLUME_TYPE_SPK, volumeValue);
858            }
859        }
860    }
861
862    private ServiceConnection mConnection = new ServiceConnection() {
863        public void onServiceConnected(ComponentName className, IBinder service) {
864            if (DBG) Log.d(TAG, "Proxy object connected");
865            mPhoneProxy = IBluetoothHeadsetPhone.Stub.asInterface(service);
866        }
867
868        public void onServiceDisconnected(ComponentName className) {
869            if (DBG) Log.d(TAG, "Proxy object disconnected");
870            mPhoneProxy = null;
871        }
872    };
873
874    // HFP Connection state of the device could be changed by the state machine
875    // in separate thread while this method is executing.
876    int getConnectionState(BluetoothDevice device) {
877        if (getCurrentState() == mDisconnected) {
878            return BluetoothProfile.STATE_DISCONNECTED;
879        }
880
881        synchronized (this) {
882            IState currentState = getCurrentState();
883            if (currentState == mPending) {
884                if ((mTargetDevice != null) && mTargetDevice.equals(device)) {
885                    return BluetoothProfile.STATE_CONNECTING;
886                }
887                if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
888                    return BluetoothProfile.STATE_DISCONNECTING;
889                }
890                if ((mIncomingDevice != null) && mIncomingDevice.equals(device)) {
891                    return BluetoothProfile.STATE_CONNECTING; // incoming connection
892                }
893                return BluetoothProfile.STATE_DISCONNECTED;
894            }
895
896            if (currentState == mConnected || currentState == mAudioOn) {
897                if (mCurrentDevice.equals(device)) {
898                    return BluetoothProfile.STATE_CONNECTED;
899                }
900                return BluetoothProfile.STATE_DISCONNECTED;
901            } else {
902                Log.e(TAG, "Bad currentState: " + currentState);
903                return BluetoothProfile.STATE_DISCONNECTED;
904            }
905        }
906    }
907
908    List<BluetoothDevice> getConnectedDevices() {
909        List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
910        synchronized(this) {
911            if (isConnected()) {
912                devices.add(mCurrentDevice);
913            }
914        }
915        return devices;
916    }
917
918    boolean isAudioOn() {
919        return (getCurrentState() == mAudioOn);
920    }
921
922    boolean isAudioConnected(BluetoothDevice device) {
923        synchronized(this) {
924            if (getCurrentState() == mAudioOn && mCurrentDevice.equals(device)) {
925                return true;
926            }
927        }
928        return false;
929    }
930
931    int getAudioState(BluetoothDevice device) {
932        synchronized(this) {
933            if (mCurrentDevice == null || !mCurrentDevice.equals(device)) {
934                return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
935            }
936        }
937        return mAudioState;
938    }
939
940    List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
941        List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();
942        Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
943        int connectionState;
944        synchronized (this) {
945            for (BluetoothDevice device : bondedDevices) {
946                ParcelUuid[] featureUuids = device.getUuids();
947                if (!BluetoothUuid.containsAnyUuid(featureUuids, HEADSET_UUIDS)) {
948                    continue;
949                }
950                connectionState = getConnectionState(device);
951                for(int i = 0; i < states.length; i++) {
952                    if (connectionState == states[i]) {
953                        deviceList.add(device);
954                    }
955                }
956            }
957        }
958        return deviceList;
959    }
960
961    // This method does not check for error conditon (newState == prevState)
962    private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) {
963        Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
964        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
965        intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
966        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
967        mContext.sendBroadcast(intent, HeadsetService.BLUETOOTH_PERM);
968        if (DBG) log("Connection state " + device + ": " + prevState + "->" + newState);
969        try {
970            mAdapterService.sendConnectionStateChange(device, BluetoothProfile.HEADSET, newState,
971                                                      prevState);
972        } catch (RemoteException e) {
973            Log.e(TAG, Log.getStackTraceString(new Throwable()));
974        }
975    }
976
977    private void broadcastAudioState(BluetoothDevice device, int newState, int prevState) {
978        Intent intent = new Intent(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
979        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
980        intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
981        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
982        mContext.sendBroadcast(intent, HeadsetService.BLUETOOTH_PERM);
983        if (DBG) log("Audio state " + device + ": " + prevState + "->" + newState);
984    }
985
986    private void processAnswerCall() {
987        if (mPhoneProxy != null) {
988            try {
989                mPhoneProxy.answerCall();
990            } catch (RemoteException e) {
991                Log.e(TAG, Log.getStackTraceString(new Throwable()));
992            }
993        } else {
994            Log.e(TAG, "Handsfree phone proxy null for answering call");
995        }
996    }
997
998    private void processHangupCall() {
999        if (mPhoneProxy != null) {
1000            try {
1001                mPhoneProxy.hangupCall();
1002            } catch (RemoteException e) {
1003                Log.e(TAG, Log.getStackTraceString(new Throwable()));
1004            }
1005        } else {
1006            Log.e(TAG, "Handsfree phone proxy null for hanging up call");
1007        }
1008    }
1009
1010    private void processDialCall(String number) {
1011        String dialNumber;
1012        if (number == null) {
1013            dialNumber = mPhonebook.getLastDialledNumber();
1014            if (dialNumber == null) {
1015                if (DBG) log("processDialCall, last dial number null");
1016                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR);
1017                return;
1018            }
1019        } else if (number.charAt(0) == '>') {
1020            // Yuck - memory dialling requested.
1021            // Just dial last number for now
1022            if (number.startsWith(">9999")) {   // for PTS test
1023                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR);
1024                return;
1025            }
1026            if (DBG) log("processDialCall, memory dial do last dial for now");
1027            dialNumber = mPhonebook.getLastDialledNumber();
1028            if (dialNumber == null) {
1029                if (DBG) log("processDialCall, last dial number null");
1030                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR);
1031                return;
1032            }
1033        } else {
1034            dialNumber = PhoneNumberUtils.convertPreDial(number);
1035        }
1036        // TODO(BT) do we need to terminate virtual call first
1037        //          like call terminateScoUsingVirtualVoiceCall()?
1038        Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
1039                                   Uri.fromParts(SCHEME_TEL, dialNumber, null));
1040        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1041        mContext.startActivity(intent);
1042        // TODO(BT) continue send OK reults code after call starts
1043        //          hold wait lock, start a timer, set wait call flag
1044        //          Get call started indication from bluetooth phone
1045        mDialingOut = true;
1046        sendMessageDelayed(DIALING_OUT_TIMEOUT, DIALING_OUT_TIMEOUT_VALUE);
1047    }
1048
1049    private void processVolumeEvent(int volumeType, int volume) {
1050        if (volumeType == HeadsetHalConstants.VOLUME_TYPE_SPK) {
1051            mPhoneState.setSpeakerVolume(volume);
1052            int flag = (getCurrentState() == mAudioOn) ? AudioManager.FLAG_SHOW_UI : 0;
1053            mAudioManager.setStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO, volume, flag);
1054        } else if (volumeType == HeadsetHalConstants.VOLUME_TYPE_MIC) {
1055            mPhoneState.setMicVolume(volume);
1056        } else {
1057            Log.e(TAG, "Bad voluem type: " + volumeType);
1058        }
1059    }
1060
1061    private void processSendDtmf(int dtmf) {
1062        if (mPhoneProxy != null) {
1063            try {
1064                mPhoneProxy.sendDtmf(dtmf);
1065            } catch (RemoteException e) {
1066                Log.e(TAG, Log.getStackTraceString(new Throwable()));
1067            }
1068        } else {
1069            Log.e(TAG, "Handsfree phone proxy null for sending DTMF");
1070        }
1071    }
1072
1073    private void processCallState(HeadsetCallState callState) {
1074        mPhoneState.setNumActiveCall(callState.mNumActive);
1075        mPhoneState.setNumHeldCall(callState.mNumHeld);
1076        mPhoneState.setCallState(callState.mCallState);
1077        if (mDialingOut && callState.mCallState == HeadsetHalConstants.CALL_STATE_DIALING) {
1078                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK);
1079                removeMessages(DIALING_OUT_TIMEOUT);
1080                mDialingOut = false;
1081        }
1082        log("mNumActive: " + callState.mNumActive + " mNumHeld: " + callState.mNumHeld +
1083            " mCallState: " + callState.mCallState);
1084        log("mNumber: " + callState.mNumber + " mType: " + callState.mType);
1085        if (getCurrentState() != mDisconnected) {
1086            phoneStateChangeNative(callState.mNumActive, callState.mNumHeld, callState.mCallState,
1087                                   callState.mNumber, callState.mType);
1088        }
1089    }
1090
1091    private void processAtChld(int chld) {
1092        if (mPhoneProxy != null) {
1093            try {
1094                if (mPhoneProxy.processChld(chld)) {
1095                    atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK);
1096                } else {
1097                    atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR);
1098                }
1099            } catch (RemoteException e) {
1100                Log.e(TAG, Log.getStackTraceString(new Throwable()));
1101                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR);
1102            }
1103        } else {
1104            Log.e(TAG, "Handsfree phone proxy null for At+Chld");
1105            atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR);
1106        }
1107    }
1108
1109    private void processSubscriberNumberRequest() {
1110        if (mPhoneProxy != null) {
1111            try {
1112                String number = mPhoneProxy.getSubscriberNumber();
1113                if (number != null) {
1114                    atResponseStringNative("+CNUM: ,\"" + number + "\"," +
1115                                           PhoneNumberUtils.toaFromString(number) + ",,4");
1116                }
1117            } catch (RemoteException e) {
1118                Log.e(TAG, Log.getStackTraceString(new Throwable()));
1119            }
1120        } else {
1121            Log.e(TAG, "Handsfree phone proxy null for At+CNUM");
1122        }
1123    }
1124
1125    private void processAtCind() {
1126        cindResponseNative(mPhoneState.getService(), mPhoneState.getNumActiveCall(),
1127                           mPhoneState.getNumHeldCall(), mPhoneState.getCallState(),
1128                           mPhoneState.getSignal(), mPhoneState.getRoam(),
1129                           mPhoneState.getBatteryCharge());
1130    }
1131
1132    private void processAtCops() {
1133        if (mPhoneProxy != null) {
1134            try {
1135                String operatorName = mPhoneProxy.getNetworkOperator();
1136                if (operatorName == null) {
1137                    operatorName = "";
1138                }
1139                copsResponseNative(operatorName);
1140            } catch (RemoteException e) {
1141                Log.e(TAG, Log.getStackTraceString(new Throwable()));
1142                copsResponseNative("");
1143            }
1144        } else {
1145            Log.e(TAG, "Handsfree phone proxy null for At+COPS");
1146            copsResponseNative("");
1147        }
1148    }
1149
1150    private void processAtClcc() {
1151        if (mPhoneProxy != null) {
1152            try {
1153                if (!mPhoneProxy.listCurrentCalls()) {
1154                    clccResponseNative(0, 0, 0, 0, false, "", 0);
1155                }
1156            } catch (RemoteException e) {
1157                Log.e(TAG, Log.getStackTraceString(new Throwable()));
1158                clccResponseNative(0, 0, 0, 0, false, "", 0);
1159            }
1160        } else {
1161            Log.e(TAG, "Handsfree phone proxy null for At+CLCC");
1162            clccResponseNative(0, 0, 0, 0, false, "", 0);
1163        }
1164    }
1165
1166    private void processUnknownAt(String atString) {
1167        // TODO (BT)
1168        atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR);
1169    }
1170
1171    private void processKeyPressed() {
1172        if (mPhoneState.getCallState() == HeadsetHalConstants.CALL_STATE_INCOMING) {
1173            if (mPhoneProxy != null) {
1174                try {
1175                    mPhoneProxy.answerCall();
1176                } catch (RemoteException e) {
1177                    Log.e(TAG, Log.getStackTraceString(new Throwable()));
1178                }
1179            } else {
1180                Log.e(TAG, "Handsfree phone proxy null for answering call");
1181            }
1182        } else if (mPhoneState.getNumActiveCall() > 0) {
1183            if (mPhoneProxy != null) {
1184                try {
1185                    mPhoneProxy.answerCall();
1186                } catch (RemoteException e) {
1187                    Log.e(TAG, Log.getStackTraceString(new Throwable()));
1188                }
1189            } else {
1190                Log.e(TAG, "Handsfree phone proxy null for hangup call");
1191            }
1192        } else {
1193            String dialNumber = mPhonebook.getLastDialledNumber();
1194            if (dialNumber == null) {
1195                if (DBG) log("processKeyPressed, last dial number null");
1196                return;
1197            }
1198            Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
1199                                       Uri.fromParts(SCHEME_TEL, dialNumber, null));
1200            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1201            mContext.startActivity(intent);
1202        }
1203    }
1204
1205    private void onConnectionStateChanged(int state, byte[] address) {
1206        StackEvent event = new StackEvent(EVENT_TYPE_CONNECTION_STATE_CHANGED);
1207        event.valueInt = state;
1208        event.device = getDevice(address);
1209        sendMessage(STACK_EVENT, event);
1210    }
1211
1212    private void onAudioStateChanged(int state, byte[] address) {
1213        StackEvent event = new StackEvent(EVENT_TYPE_AUDIO_STATE_CHANGED);
1214        event.valueInt = state;
1215        event.device = getDevice(address);
1216        sendMessage(STACK_EVENT, event);
1217    }
1218
1219    private void onVrStateChanged(int state) {
1220        StackEvent event = new StackEvent(EVENT_TYPE_VR_STATE_CHANGED);
1221        event.valueInt = state;
1222        sendMessage(STACK_EVENT, event);
1223    }
1224
1225    private void onAnswerCall() {
1226        StackEvent event = new StackEvent(EVENT_TYPE_ANSWER_CALL);
1227        sendMessage(STACK_EVENT, event);
1228    }
1229
1230    private void onHangupCall() {
1231        StackEvent event = new StackEvent(EVENT_TYPE_HANGUP_CALL);
1232        sendMessage(STACK_EVENT, event);
1233    }
1234
1235    private void onVolumeChanged(int type, int volume) {
1236        StackEvent event = new StackEvent(EVENT_TYPE_VOLUME_CHANGED);
1237        event.valueInt = type;
1238        event.valueInt2 = volume;
1239        sendMessage(STACK_EVENT, event);
1240    }
1241
1242    private void onDialCall(String number) {
1243        StackEvent event = new StackEvent(EVENT_TYPE_DIAL_CALL);
1244        event.valueString = number;
1245        sendMessage(STACK_EVENT, event);
1246    }
1247
1248    private void onSendDtmf(int dtmf) {
1249        StackEvent event = new StackEvent(EVENT_TYPE_SEND_DTMF);
1250        event.valueInt = dtmf;
1251        sendMessage(STACK_EVENT, event);
1252    }
1253
1254    private void onNoiceReductionEnable(boolean enable) {
1255        StackEvent event = new StackEvent(EVENT_TYPE_NOICE_REDUCTION);
1256        event.valueInt = enable ? 1 : 0;
1257        sendMessage(STACK_EVENT, event);
1258    }
1259
1260    private void onAtChld(int chld) {
1261        StackEvent event = new StackEvent(EVENT_TYPE_AT_CHLD);
1262        event.valueInt = chld;
1263        sendMessage(STACK_EVENT, event);
1264    }
1265
1266    private void onAtCnum() {
1267        StackEvent event = new StackEvent(EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST);
1268        sendMessage(STACK_EVENT, event);
1269    }
1270
1271    private void onAtCind() {
1272        StackEvent event = new StackEvent(EVENT_TYPE_AT_CIND);
1273        sendMessage(STACK_EVENT, event);
1274    }
1275
1276    private void onAtCops() {
1277        StackEvent event = new StackEvent(EVENT_TYPE_AT_COPS);
1278        sendMessage(STACK_EVENT, event);
1279    }
1280
1281    private void onAtClcc() {
1282        StackEvent event = new StackEvent(EVENT_TYPE_AT_CLCC);
1283        sendMessage(STACK_EVENT, event);
1284    }
1285
1286    private void onUnknownAt(String atString) {
1287        StackEvent event = new StackEvent(EVENT_TYPE_UNKNOWN_AT);
1288        event.valueString = atString;
1289        sendMessage(STACK_EVENT, event);
1290    }
1291
1292    private void onKeyPressed() {
1293        StackEvent event = new StackEvent(EVENT_TYPE_KEY_PRESSED);
1294        sendMessage(STACK_EVENT, event);
1295    }
1296
1297    private void processIntentBatteryChanged(Intent intent) {
1298        int batteryLevel = intent.getIntExtra("level", -1);
1299        int scale = intent.getIntExtra("scale", -1);
1300        if (batteryLevel == -1 || scale == -1 || scale == 0) {
1301            Log.e(TAG, "Bad Battery Changed intent: " + batteryLevel + "," + scale);
1302            return;
1303        }
1304        batteryLevel = batteryLevel * 5 / scale;
1305        mPhoneState.setBatteryCharge(batteryLevel);
1306    }
1307
1308    private void processRoamChanged(boolean roam) {
1309        mPhoneState.setRoam(roam ? HeadsetHalConstants.SERVICE_TYPE_ROAMING :
1310                            HeadsetHalConstants.SERVICE_TYPE_HOME);
1311    }
1312
1313    private void processDeviceStateChanged(HeadsetDeviceState deviceState) {
1314        notifyDeviceStatusNative(deviceState.mService, deviceState.mRoam, deviceState.mSignal,
1315                                 deviceState.mBatteryCharge);
1316    }
1317
1318    private void processSendClccResponse(HeadsetClccResponse clcc) {
1319        clccResponseNative(clcc.mIndex, clcc.mDirection, clcc.mStatus, clcc.mMode, clcc.mMpty,
1320                           clcc.mNumber, clcc.mType);
1321    }
1322
1323    private String getCurrentDeviceName() {
1324        String defaultName = "<unknown>";
1325        if (mCurrentDevice == null) {
1326            return defaultName;
1327        }
1328        String deviceName = mCurrentDevice.getName();
1329        if (deviceName == null) {
1330            return defaultName;
1331        }
1332        return deviceName;
1333    }
1334
1335    private byte[] getByteAddress(BluetoothDevice device) {
1336        return Utils.getBytesFromAddress(device.getAddress());
1337    }
1338
1339    private BluetoothDevice getDevice(byte[] address) {
1340        return mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
1341    }
1342
1343    private boolean isInCall() {
1344        return mPhoneState.isInCall();
1345    }
1346
1347    boolean isConnected() {
1348        IState currentState = getCurrentState();
1349        return (currentState == mConnected || currentState == mAudioOn);
1350    }
1351
1352    private void log(String msg) {
1353        if (DBG) {
1354            Log.d(TAG, msg);
1355        }
1356    }
1357
1358    private static final String SCHEME_TEL = "tel";
1359
1360    // Event types for STACK_EVENT message
1361    final private static int EVENT_TYPE_NONE = 0;
1362    final private static int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
1363    final private static int EVENT_TYPE_AUDIO_STATE_CHANGED = 2;
1364    final private static int EVENT_TYPE_VR_STATE_CHANGED = 3;
1365    final private static int EVENT_TYPE_ANSWER_CALL = 4;
1366    final private static int EVENT_TYPE_HANGUP_CALL = 5;
1367    final private static int EVENT_TYPE_VOLUME_CHANGED = 6;
1368    final private static int EVENT_TYPE_DIAL_CALL = 7;
1369    final private static int EVENT_TYPE_SEND_DTMF = 8;
1370    final private static int EVENT_TYPE_NOICE_REDUCTION = 9;
1371    final private static int EVENT_TYPE_AT_CHLD = 10;
1372    final private static int EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST = 11;
1373    final private static int EVENT_TYPE_AT_CIND = 12;
1374    final private static int EVENT_TYPE_AT_COPS = 13;
1375    final private static int EVENT_TYPE_AT_CLCC = 14;
1376    final private static int EVENT_TYPE_UNKNOWN_AT = 15;
1377    final private static int EVENT_TYPE_KEY_PRESSED = 16;
1378
1379    private class StackEvent {
1380        int type = EVENT_TYPE_NONE;
1381        int valueInt = 0;
1382        int valueInt2 = 0;
1383        String valueString = null;
1384        BluetoothDevice device = null;
1385
1386        private StackEvent(int type) {
1387            this.type = type;
1388        }
1389    }
1390
1391    private native static void classInitNative();
1392    private native void initializeNativeDataNative();
1393    private native boolean connectHfpNative(byte[] address);
1394    private native boolean disconnectHfpNative(byte[] address);
1395    private native boolean connectAudioNative(byte[] address);
1396    private native boolean disconnectAudioNative(byte[] address);
1397    private native boolean startVoiceRecognitionNative();
1398    private native boolean stopVoiceRecognitionNative();
1399    private native boolean setVolumeNative(int volumeType, int volume);
1400    private native boolean cindResponseNative(int service, int numActive, int numHeld,
1401                                              int callState, int signal, int roam,
1402                                              int batteryCharge);
1403    private native boolean notifyDeviceStatusNative(int networkState, int serviceType, int signal,
1404                                                    int batteryCharge);
1405    private native boolean atResponseCodeNative(int responseCode);
1406    private native boolean clccResponseNative(int index, int dir, int status, int mode,
1407                                              boolean mpty, String number, int type);
1408    private native boolean copsResponseNative(String operatorName);
1409    private native boolean atResponseStringNative(String responseString);
1410    private native boolean phoneStateChangeNative(int numActive, int numHeld, int callState,
1411                                                  String number, int type);
1412}
1413