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