BluetoothHandsfree.java revision b1164d370e8d83a8a4f3cbdc73dffc087254cabd
1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.phone;
18
19import android.bluetooth.AtCommandHandler;
20import android.bluetooth.AtCommandResult;
21import android.bluetooth.AtParser;
22import android.bluetooth.BluetoothDevice;
23import android.bluetooth.BluetoothHeadset;
24import android.bluetooth.BluetoothIntent;
25import android.bluetooth.HeadsetBase;
26import android.bluetooth.ScoSocket;
27import android.content.ActivityNotFoundException;
28import android.content.BroadcastReceiver;
29import android.content.Context;
30import android.content.Intent;
31import android.content.IntentFilter;
32import android.media.AudioManager;
33import android.net.Uri;
34import android.os.AsyncResult;
35import android.os.Bundle;
36import android.os.Handler;
37import android.os.Message;
38import android.os.PowerManager;
39import android.os.PowerManager.WakeLock;
40import android.os.SystemProperties;
41import android.telephony.PhoneNumberUtils;
42import android.telephony.ServiceState;
43import android.telephony.SignalStrength;
44import android.util.Log;
45
46import com.android.internal.telephony.Call;
47import com.android.internal.telephony.Connection;
48import com.android.internal.telephony.Phone;
49import com.android.internal.telephony.TelephonyIntents;
50
51import java.util.LinkedList;
52/**
53 * Bluetooth headset manager for the Phone app.
54 * @hide
55 */
56public class BluetoothHandsfree {
57    private static final String TAG = "BT HS/HF";
58    private static final boolean DBG = (PhoneApp.DBG_LEVEL >= 2);
59    private static final boolean VDBG = false;  // even more logging
60
61    public static final int TYPE_UNKNOWN           = 0;
62    public static final int TYPE_HEADSET           = 1;
63    public static final int TYPE_HANDSFREE         = 2;
64
65    private final Context mContext;
66    private final Phone mPhone;
67    private ServiceState mServiceState;
68    private HeadsetBase mHeadset;  // null when not connected
69    private int mHeadsetType;
70    private boolean mAudioPossible;
71    private ScoSocket mIncomingSco;
72    private ScoSocket mOutgoingSco;
73    private ScoSocket mConnectedSco;
74
75    private Call mForegroundCall;
76    private Call mBackgroundCall;
77    private Call mRingingCall;
78
79    private AudioManager mAudioManager;
80    private PowerManager mPowerManager;
81
82    private boolean mUserWantsAudio;
83    private WakeLock mStartCallWakeLock;  // held while waiting for the intent to start call
84    private WakeLock mStartVoiceRecognitionWakeLock;  // held while waiting for voice recognition
85
86    // AT command state
87    private static final int MAX_CONNECTIONS = 6;  // Max connections allowed by GSM
88
89    private long mBgndEarliestConnectionTime = 0;
90    private boolean mClip = false;  // Calling Line Information Presentation
91    private boolean mIndicatorsEnabled = false;
92    private boolean mCmee = false;  // Extended Error reporting
93    private long[] mClccTimestamps; // Timestamps associated with each clcc index
94    private boolean[] mClccUsed;     // Is this clcc index in use
95    private boolean mWaitingForCallStart;
96    private boolean mWaitingForVoiceRecognition;
97    // do not connect audio until service connection is established
98    // for 3-way supported devices, this is after AT+CHLD
99    // for non-3-way supported devices, this is after AT+CMER (see spec)
100    private boolean mServiceConnectionEstablished;
101    private final BluetoothPhoneState mBluetoothPhoneState;  // for CIND and CIEV updates
102    private final BluetoothAtPhonebook mPhonebook;
103    private Phone.State mPhoneState = Phone.State.IDLE;
104
105    private DebugThread mDebugThread;
106    private int mScoGain = Integer.MIN_VALUE;
107
108    private static Intent sVoiceCommandIntent;
109
110    // Audio parameters
111    private static final String HEADSET_NREC = "bt_headset_nrec";
112    private static final String HEADSET_NAME = "bt_headset_name";
113
114    private int mRemoteBrsf = 0;
115    private int mLocalBrsf = 0;
116
117    /* Constants from Bluetooth Specification Hands-Free profile version 1.5 */
118    private static final int BRSF_AG_THREE_WAY_CALLING = 1 << 0;
119    private static final int BRSF_AG_EC_NR = 1 << 1;
120    private static final int BRSF_AG_VOICE_RECOG = 1 << 2;
121    private static final int BRSF_AG_IN_BAND_RING = 1 << 3;
122    private static final int BRSF_AG_VOICE_TAG_NUMBE = 1 << 4;
123    private static final int BRSF_AG_REJECT_CALL = 1 << 5;
124    private static final int BRSF_AG_ENHANCED_CALL_STATUS = 1 <<  6;
125    private static final int BRSF_AG_ENHANCED_CALL_CONTROL = 1 << 7;
126    private static final int BRSF_AG_ENHANCED_ERR_RESULT_CODES = 1 << 8;
127
128    private static final int BRSF_HF_EC_NR = 1 << 0;
129    private static final int BRSF_HF_CW_THREE_WAY_CALLING = 1 << 1;
130    private static final int BRSF_HF_CLIP = 1 << 2;
131    private static final int BRSF_HF_VOICE_REG_ACT = 1 << 3;
132    private static final int BRSF_HF_REMOTE_VOL_CONTROL = 1 << 4;
133    private static final int BRSF_HF_ENHANCED_CALL_STATUS = 1 <<  5;
134    private static final int BRSF_HF_ENHANCED_CALL_CONTROL = 1 << 6;
135
136    public static String typeToString(int type) {
137        switch (type) {
138        case TYPE_UNKNOWN:
139            return "unknown";
140        case TYPE_HEADSET:
141            return "headset";
142        case TYPE_HANDSFREE:
143            return "handsfree";
144        }
145        return null;
146    }
147
148    public BluetoothHandsfree(Context context, Phone phone) {
149        mPhone = phone;
150        mContext = context;
151        BluetoothDevice bluetooth =
152                (BluetoothDevice)context.getSystemService(Context.BLUETOOTH_SERVICE);
153        boolean bluetoothCapable = (bluetooth != null);
154        mHeadset = null;  // nothing connected yet
155
156        mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
157        mStartCallWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
158                                                       TAG + ":StartCall");
159        mStartCallWakeLock.setReferenceCounted(false);
160        mStartVoiceRecognitionWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
161                                                       TAG + ":VoiceRecognition");
162        mStartVoiceRecognitionWakeLock.setReferenceCounted(false);
163
164        mLocalBrsf = BRSF_AG_THREE_WAY_CALLING |
165                     BRSF_AG_EC_NR |
166                     BRSF_AG_REJECT_CALL |
167                     BRSF_AG_ENHANCED_CALL_STATUS;
168
169        if (sVoiceCommandIntent == null) {
170            sVoiceCommandIntent = new Intent(Intent.ACTION_VOICE_COMMAND);
171            sVoiceCommandIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
172        }
173        if (mContext.getPackageManager().resolveActivity(sVoiceCommandIntent, 0) != null &&
174                !BluetoothHeadset.DISABLE_BT_VOICE_DIALING) {
175            mLocalBrsf |= BRSF_AG_VOICE_RECOG;
176        }
177
178        if (bluetoothCapable) {
179            resetAtState();
180        }
181
182        mRingingCall = mPhone.getRingingCall();
183        mForegroundCall = mPhone.getForegroundCall();
184        mBackgroundCall = mPhone.getBackgroundCall();
185        mBluetoothPhoneState = new BluetoothPhoneState();
186        mUserWantsAudio = true;
187        mPhonebook = new BluetoothAtPhonebook(mContext, this);
188        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
189    }
190
191    /* package */ synchronized void onBluetoothEnabled() {
192        /* Bluez has a bug where it will always accept and then orphan
193         * incoming SCO connections, regardless of whether we have a listening
194         * SCO socket. So the best thing to do is always run a listening socket
195         * while bluetooth is on so that at least we can diconnect it
196         * immediately when we don't want it.
197         */
198        if (mIncomingSco == null) {
199            mIncomingSco = createScoSocket();
200            mIncomingSco.accept();
201        }
202    }
203
204    /* package */ synchronized void onBluetoothDisabled() {
205        if (mConnectedSco != null) {
206            mAudioManager.setBluetoothScoOn(false);
207            broadcastAudioStateIntent(BluetoothHeadset.AUDIO_STATE_DISCONNECTED);
208            mConnectedSco.close();
209            mConnectedSco = null;
210        }
211        if (mOutgoingSco != null) {
212            mOutgoingSco.close();
213            mOutgoingSco = null;
214        }
215        if (mIncomingSco != null) {
216            mIncomingSco.close();
217            mIncomingSco = null;
218        }
219    }
220
221    private boolean isHeadsetConnected() {
222        if (mHeadset == null) {
223            return false;
224        }
225        return mHeadset.isConnected();
226    }
227
228    /* package */ void connectHeadset(HeadsetBase headset, int headsetType) {
229        mHeadset = headset;
230        mHeadsetType = headsetType;
231        if (mHeadsetType == TYPE_HEADSET) {
232            initializeHeadsetAtParser();
233        } else {
234            initializeHandsfreeAtParser();
235        }
236        headset.startEventThread();
237        configAudioParameters();
238
239        if (inDebug()) {
240            startDebug();
241        }
242
243        if (isIncallAudio()) {
244            audioOn();
245        }
246    }
247
248    /* returns true if there is some kind of in-call audio we may wish to route
249     * bluetooth to */
250    private boolean isIncallAudio() {
251        Call.State state = mForegroundCall.getState();
252
253        return (state == Call.State.ACTIVE || state == Call.State.ALERTING);
254    }
255
256    /* package */ void disconnectHeadset() {
257        mHeadset = null;
258        stopDebug();
259        resetAtState();
260    }
261
262    private void resetAtState() {
263        mClip = false;
264        mIndicatorsEnabled = false;
265        mServiceConnectionEstablished = false;
266        mCmee = false;
267        mClccTimestamps = new long[MAX_CONNECTIONS];
268        mClccUsed = new boolean[MAX_CONNECTIONS];
269        for (int i = 0; i < MAX_CONNECTIONS; i++) {
270            mClccUsed[i] = false;
271        }
272        mRemoteBrsf = 0;
273    }
274
275    private void configAudioParameters() {
276        String name = mHeadset.getName();
277        if (name == null) {
278            name = "<unknown>";
279        }
280        mAudioManager.setParameter(HEADSET_NAME, name);
281        mAudioManager.setParameter(HEADSET_NREC, "on");
282    }
283
284
285    /** Represents the data that we send in a +CIND or +CIEV command to the HF
286     */
287    private class BluetoothPhoneState {
288        // 0: no service
289        // 1: service
290        private int mService;
291
292        // 0: no active call
293        // 1: active call (where active means audio is routed - not held call)
294        private int mCall;
295
296        // 0: not in call setup
297        // 1: incoming call setup
298        // 2: outgoing call setup
299        // 3: remote party being alerted in an outgoing call setup
300        private int mCallsetup;
301
302        // 0: no calls held
303        // 1: held call and active call
304        // 2: held call only
305        private int mCallheld;
306
307        // cellular signal strength of AG: 0-5
308        private int mSignal;
309
310        // cellular signal strength in CSQ rssi scale
311        private int mRssi;  // for CSQ
312
313        // 0: roaming not active (home)
314        // 1: roaming active
315        private int mRoam;
316
317        // battery charge of AG: 0-5
318        private int mBattchg;
319
320        // 0: not registered
321        // 1: registered, home network
322        // 5: registered, roaming
323        private int mStat;  // for CREG
324
325        private String mRingingNumber;  // Context for in-progress RING's
326        private int    mRingingType;
327        private boolean mIgnoreRing = false;
328
329        private static final int SERVICE_STATE_CHANGED = 1;
330        private static final int PHONE_STATE_CHANGED = 2;
331        private static final int RING = 3;
332        private static final int PHONE_CDMA_CALL_WAITING = 4;
333
334        private Handler mStateChangeHandler = new Handler() {
335            @Override
336            public void handleMessage(Message msg) {
337                switch(msg.what) {
338                case RING:
339                    AtCommandResult result = ring();
340                    if (result != null) {
341                        sendURC(result.toString());
342                    }
343                    break;
344                case SERVICE_STATE_CHANGED:
345                    ServiceState state = (ServiceState) ((AsyncResult) msg.obj).result;
346                    updateServiceState(sendUpdate(), state);
347                    break;
348                case PHONE_STATE_CHANGED:
349                case PHONE_CDMA_CALL_WAITING:
350                    Connection connection = null;
351                    if (((AsyncResult) msg.obj).result instanceof Connection) {
352                        connection = (Connection) ((AsyncResult) msg.obj).result;
353                    }
354                    updatePhoneState(sendUpdate(), connection);
355                    break;
356                }
357            }
358        };
359
360        private BluetoothPhoneState() {
361            // init members
362            updateServiceState(false, mPhone.getServiceState());
363            updatePhoneState(false, null);
364            mBattchg = 5;  // There is currently no API to get battery level
365                           // on demand, so set to 5 and wait for an update
366            mSignal = asuToSignal(mPhone.getSignalStrength());
367
368            // register for updates
369            mPhone.registerForServiceStateChanged(mStateChangeHandler,
370                                                  SERVICE_STATE_CHANGED, null);
371            mPhone.registerForPhoneStateChanged(mStateChangeHandler,
372                                                PHONE_STATE_CHANGED, null);
373            if (mPhone.getPhoneName().equals("CDMA")) {
374                mPhone.registerForCallWaiting(mStateChangeHandler,
375                                              PHONE_CDMA_CALL_WAITING, null);
376            }
377            IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
378            filter.addAction(TelephonyIntents.ACTION_SIGNAL_STRENGTH_CHANGED);
379            mContext.registerReceiver(mStateReceiver, filter);
380        }
381
382        private void updateBtPhoneStateAfterRadioTechnologyChange() {
383            if(DBG) Log.d(TAG, "updateBtPhoneStateAfterRadioTechnologyChange...");
384
385            //Unregister all events from the old obsolete phone
386            mPhone.unregisterForServiceStateChanged(mStateChangeHandler);
387            mPhone.unregisterForPhoneStateChanged(mStateChangeHandler);
388            mPhone.unregisterForCallWaiting(mStateChangeHandler);
389
390            //Register all events new to the new active phone
391            mPhone.registerForServiceStateChanged(mStateChangeHandler, SERVICE_STATE_CHANGED, null);
392            mPhone.registerForPhoneStateChanged(mStateChangeHandler, PHONE_STATE_CHANGED, null);
393            if (mPhone.getPhoneName().equals("CDMA")) {
394                mPhone.registerForCallWaiting(mStateChangeHandler,
395                                              PHONE_CDMA_CALL_WAITING, null);
396            }
397        }
398
399        private boolean sendUpdate() {
400            return isHeadsetConnected() && mHeadsetType == TYPE_HANDSFREE && mIndicatorsEnabled;
401        }
402
403        private boolean sendClipUpdate() {
404            return isHeadsetConnected() && mHeadsetType == TYPE_HANDSFREE && mClip;
405        }
406
407        /* convert [0,31] ASU signal strength to the [0,5] expected by
408         * bluetooth devices. Scale is similar to status bar policy
409         */
410        private int gsmAsuToSignal(SignalStrength signalStrength) {
411            int asu = signalStrength.getGsmSignalStrength();
412            if      (asu >= 16) return 5;
413            else if (asu >= 8)  return 4;
414            else if (asu >= 4)  return 3;
415            else if (asu >= 2)  return 2;
416            else if (asu >= 1)  return 1;
417            else                return 0;
418        }
419
420        /**
421         * Convert the cdma / evdo db levels to appropriate icon level.
422         * The scale is similar to the one used in status bar policy.
423         *
424         * @param signalStrength
425         * @return the icon level
426         */
427        private int cdmaDbmEcioToSignal(SignalStrength signalStrength) {
428            int levelDbm = 0;
429            int levelEcio = 0;
430            int cdmaIconLevel = 0;
431            int evdoIconLevel = 0;
432            int cdmaDbm = signalStrength.getCdmaDbm();
433            int cdmaEcio = signalStrength.getCdmaEcio();
434
435            if (cdmaDbm >= -75) levelDbm = 4;
436            else if (cdmaDbm >= -85) levelDbm = 3;
437            else if (cdmaDbm >= -95) levelDbm = 2;
438            else if (cdmaDbm >= -100) levelDbm = 1;
439            else levelDbm = 0;
440
441            // Ec/Io are in dB*10
442            if (cdmaEcio >= -90) levelEcio = 4;
443            else if (cdmaEcio >= -110) levelEcio = 3;
444            else if (cdmaEcio >= -130) levelEcio = 2;
445            else if (cdmaEcio >= -150) levelEcio = 1;
446            else levelEcio = 0;
447
448            cdmaIconLevel = (levelDbm < levelEcio) ? levelDbm : levelEcio;
449
450            if (mServiceState != null &&
451                  (mServiceState.getRadioTechnology() == ServiceState.RADIO_TECHNOLOGY_EVDO_0 ||
452                   mServiceState.getRadioTechnology() == ServiceState.RADIO_TECHNOLOGY_EVDO_A)) {
453                  int evdoEcio = signalStrength.getEvdoEcio();
454                  int evdoSnr = signalStrength.getEvdoSnr();
455                  int levelEvdoEcio = 0;
456                  int levelEvdoSnr = 0;
457
458                  // Ec/Io are in dB*10
459                  if (evdoEcio >= -650) levelEvdoEcio = 4;
460                  else if (evdoEcio >= -750) levelEvdoEcio = 3;
461                  else if (evdoEcio >= -900) levelEvdoEcio = 2;
462                  else if (evdoEcio >= -1050) levelEvdoEcio = 1;
463                  else levelEvdoEcio = 0;
464
465                  if (evdoSnr > 7) levelEvdoSnr = 4;
466                  else if (evdoSnr > 5) levelEvdoSnr = 3;
467                  else if (evdoSnr > 3) levelEvdoSnr = 2;
468                  else if (evdoSnr > 1) levelEvdoSnr = 1;
469                  else levelEvdoSnr = 0;
470
471                  evdoIconLevel = (levelEvdoEcio < levelEvdoSnr) ? levelEvdoEcio : levelEvdoSnr;
472            }
473            // TODO(): There is a bug open regarding what should be sent.
474            return (cdmaIconLevel > evdoIconLevel) ?  cdmaIconLevel : evdoIconLevel;
475
476        }
477
478
479        private int asuToSignal(SignalStrength signalStrength) {
480            if (signalStrength.isGsm()) {
481                return gsmAsuToSignal(signalStrength);
482            } else {
483                return cdmaDbmEcioToSignal(signalStrength);
484            }
485        }
486
487
488        /* convert [0,5] signal strength to a rssi signal strength for CSQ
489         * which is [0,31]. Despite the same scale, this is not the same value
490         * as ASU.
491         */
492        private int signalToRssi(int signal) {
493            // using C4A suggested values
494            switch (signal) {
495            case 0: return 0;
496            case 1: return 4;
497            case 2: return 8;
498            case 3: return 13;
499            case 4: return 19;
500            case 5: return 31;
501            }
502            return 0;
503        }
504
505
506        private final BroadcastReceiver mStateReceiver = new BroadcastReceiver() {
507            @Override
508            public void onReceive(Context context, Intent intent) {
509                if (intent.getAction().equals(Intent.ACTION_BATTERY_CHANGED)) {
510                    updateBatteryState(intent);
511                } else if (intent.getAction().equals(TelephonyIntents.ACTION_SIGNAL_STRENGTH_CHANGED)) {
512                    updateSignalState(intent);
513                }
514            }
515        };
516
517        private synchronized void updateBatteryState(Intent intent) {
518            int batteryLevel = intent.getIntExtra("level", -1);
519            int scale = intent.getIntExtra("scale", -1);
520            if (batteryLevel == -1 || scale == -1) {
521                return;  // ignore
522            }
523            batteryLevel = batteryLevel * 5 / scale;
524            if (mBattchg != batteryLevel) {
525                mBattchg = batteryLevel;
526                if (sendUpdate()) {
527                    sendURC("+CIEV: 7," + mBattchg);
528                }
529            }
530        }
531
532        private synchronized void updateSignalState(Intent intent) {
533            // NOTE this function is called by the BroadcastReceiver mStateReceiver after intent
534            // ACTION_SIGNAL_STRENGTH_CHANGED and by the DebugThread mDebugThread
535            SignalStrength signalStrength = SignalStrength.newFromBundle(intent.getExtras());
536            int signal;
537
538            if (signalStrength != null) {
539                signal = asuToSignal(signalStrength);
540                mRssi = signalToRssi(signal);  // no unsolicited CSQ
541                if (signal != mSignal) {
542                    mSignal = signal;
543                    if (sendUpdate()) {
544                        sendURC("+CIEV: 5," + mSignal);
545                    }
546                }
547            } else {
548                Log.e(TAG, "Signal Strength null");
549            }
550        }
551
552        private synchronized void updateServiceState(boolean sendUpdate, ServiceState state) {
553            int service = state.getState() == ServiceState.STATE_IN_SERVICE ? 1 : 0;
554            int roam = state.getRoaming() ? 1 : 0;
555            int stat;
556            AtCommandResult result = new AtCommandResult(AtCommandResult.UNSOLICITED);
557            mServiceState = state;
558            if (service == 0) {
559                stat = 0;
560            } else {
561                stat = (roam == 1) ? 5 : 1;
562            }
563
564            if (service != mService) {
565                mService = service;
566                if (sendUpdate) {
567                    result.addResponse("+CIEV: 1," + mService);
568                }
569            }
570            if (roam != mRoam) {
571                mRoam = roam;
572                if (sendUpdate) {
573                    result.addResponse("+CIEV: 6," + mRoam);
574                }
575            }
576            if (stat != mStat) {
577                mStat = stat;
578                if (sendUpdate) {
579                    result.addResponse(toCregString());
580                }
581            }
582
583            sendURC(result.toString());
584        }
585
586        private synchronized void updatePhoneState(boolean sendUpdate, Connection connection) {
587            int call = 0;
588            int callsetup = 0;
589            int callheld = 0;
590            int prevCallsetup = mCallsetup;
591            AtCommandResult result = new AtCommandResult(AtCommandResult.UNSOLICITED);
592
593            if (DBG) log("updatePhoneState()");
594
595            Phone.State newState = mPhone.getState();
596            if (newState != mPhoneState) {
597                mPhoneState = newState;
598                switch (mPhoneState) {
599                case IDLE:
600                    mUserWantsAudio = true;  // out of call - reset state
601                    audioOff();
602                    break;
603                default:
604                    callStarted();
605                }
606            }
607
608            switch(mForegroundCall.getState()) {
609            case ACTIVE:
610                call = 1;
611                mAudioPossible = true;
612                break;
613            case DIALING:
614                callsetup = 2;
615                mAudioPossible = false;
616                break;
617            case ALERTING:
618                callsetup = 3;
619                // Open the SCO channel for the outgoing call.
620                audioOn();
621                mAudioPossible = true;
622                break;
623            default:
624                mAudioPossible = false;
625            }
626
627            switch(mRingingCall.getState()) {
628            case INCOMING:
629            case WAITING:
630                callsetup = 1;
631                break;
632            }
633
634            switch(mBackgroundCall.getState()) {
635            case HOLDING:
636                if (call == 1) {
637                    callheld = 1;
638                } else {
639                    call = 1;
640                    callheld = 2;
641                }
642                break;
643            }
644
645            if (mCall != call) {
646                if (call == 1) {
647                    // This means that a call has transitioned from NOT ACTIVE to ACTIVE.
648                    // Switch on audio.
649                    audioOn();
650                }
651                mCall = call;
652                if (sendUpdate) {
653                    result.addResponse("+CIEV: 2," + mCall);
654                }
655            }
656            if (mCallsetup != callsetup) {
657                mCallsetup = callsetup;
658                if (sendUpdate) {
659                    // If mCall = 0, send CIEV
660                    // mCall = 1, mCallsetup = 0, send CIEV
661                    // mCall = 1, mCallsetup = 1, send CIEV after CCWA,
662                    // if 3 way supported.
663                    // mCall = 1, mCallsetup = 2 / 3 -> send CIEV,
664                    // if 3 way is supported
665                    if (mCall != 1 || mCallsetup == 0 ||
666                        mCallsetup != 1 && (mRemoteBrsf & BRSF_HF_CW_THREE_WAY_CALLING) != 0x0) {
667                        result.addResponse("+CIEV: 3," + mCallsetup);
668                    }
669                }
670            }
671
672            boolean callsSwitched =
673                (callheld == 1 && ! (mBackgroundCall.getEarliestConnectTime() ==
674                    mBgndEarliestConnectionTime));
675
676            mBgndEarliestConnectionTime = mBackgroundCall.getEarliestConnectTime();
677
678            if (mCallheld != callheld || callsSwitched) {
679                mCallheld = callheld;
680                if (sendUpdate) {
681                    result.addResponse("+CIEV: 4," + mCallheld);
682                }
683            }
684
685            if (callsetup == 1 && callsetup != prevCallsetup) {
686                // new incoming call
687                String number = null;
688                int type = 128;
689                // find incoming phone number and type
690                if (connection == null) {
691                    connection = mRingingCall.getEarliestConnection();
692                    if (connection == null) {
693                        Log.e(TAG, "Could not get a handle on Connection object for new " +
694                              "incoming call");
695                    }
696                }
697                if (connection != null) {
698                    number = connection.getAddress();
699                    if (number != null) {
700                        type = PhoneNumberUtils.toaFromString(number);
701                    }
702                }
703                if (number == null) {
704                    number = "";
705                }
706                if ((call != 0 || callheld != 0) && sendUpdate) {
707                    // call waiting
708                    if ((mRemoteBrsf & BRSF_HF_CW_THREE_WAY_CALLING) != 0x0) {
709                        result.addResponse("+CCWA: \"" + number + "\"," + type);
710                        result.addResponse("+CIEV: 3," + callsetup);
711                    }
712                } else {
713                    // regular new incoming call
714                    mRingingNumber = number;
715                    mRingingType = type;
716                    mIgnoreRing = false;
717
718                    if ((mLocalBrsf & BRSF_AG_IN_BAND_RING) == 0x1) {
719                        audioOn();
720                    }
721                    result.addResult(ring());
722                }
723            }
724            sendURC(result.toString());
725        }
726
727        private AtCommandResult ring() {
728            if (!mIgnoreRing && mRingingCall.isRinging()) {
729                AtCommandResult result = new AtCommandResult(AtCommandResult.UNSOLICITED);
730                result.addResponse("RING");
731                if (sendClipUpdate()) {
732                    result.addResponse("+CLIP: \"" + mRingingNumber + "\"," + mRingingType);
733                }
734
735                Message msg = mStateChangeHandler.obtainMessage(RING);
736                mStateChangeHandler.sendMessageDelayed(msg, 3000);
737                return result;
738            }
739            return null;
740        }
741
742        private synchronized String toCregString() {
743            return new String("+CREG: 1," + mStat);
744        }
745
746        private synchronized AtCommandResult toCindResult() {
747            AtCommandResult result = new AtCommandResult(AtCommandResult.OK);
748            String status = "+CIND: " + mService + "," + mCall + "," + mCallsetup + "," +
749                            mCallheld + "," + mSignal + "," + mRoam + "," + mBattchg;
750            result.addResponse(status);
751            return result;
752        }
753
754        private synchronized AtCommandResult toCsqResult() {
755            AtCommandResult result = new AtCommandResult(AtCommandResult.OK);
756            String status = "+CSQ: " + mRssi + ",99";
757            result.addResponse(status);
758            return result;
759        }
760
761
762        private synchronized AtCommandResult getCindTestResult() {
763            return new AtCommandResult("+CIND: (\"service\",(0-1))," + "(\"call\",(0-1))," +
764                        "(\"callsetup\",(0-3)),(\"callheld\",(0-2)),(\"signal\",(0-5))," +
765                        "(\"roam\",(0-1)),(\"battchg\",(0-5))");
766        }
767
768        private synchronized void ignoreRing() {
769            mCallsetup = 0;
770            mIgnoreRing = true;
771            if (sendUpdate()) {
772                sendURC("+CIEV: 3," + mCallsetup);
773            }
774        }
775
776    };
777
778    private static final int SCO_ACCEPTED = 1;
779    private static final int SCO_CONNECTED = 2;
780    private static final int SCO_CLOSED = 3;
781    private static final int CHECK_CALL_STARTED = 4;
782    private static final int CHECK_VOICE_RECOGNITION_STARTED = 5;
783
784    private final Handler mHandler = new Handler() {
785        @Override
786        public synchronized void handleMessage(Message msg) {
787            switch (msg.what) {
788            case SCO_ACCEPTED:
789                if (msg.arg1 == ScoSocket.STATE_CONNECTED) {
790                    if (isHeadsetConnected() && (mAudioPossible || allowAudioAnytime()) &&
791                            mConnectedSco == null) {
792                        Log.i(TAG, "Routing audio for incoming SCO connection");
793                        mConnectedSco = (ScoSocket)msg.obj;
794                        mAudioManager.setBluetoothScoOn(true);
795                        broadcastAudioStateIntent(BluetoothHeadset.AUDIO_STATE_CONNECTED);
796                    } else {
797                        Log.i(TAG, "Rejecting incoming SCO connection");
798                        ((ScoSocket)msg.obj).close();
799                    }
800                } // else error trying to accept, try again
801                mIncomingSco = createScoSocket();
802                mIncomingSco.accept();
803                break;
804            case SCO_CONNECTED:
805                if (msg.arg1 == ScoSocket.STATE_CONNECTED && isHeadsetConnected() &&
806                        mConnectedSco == null) {
807                    if (DBG) log("Routing audio for outgoing SCO conection");
808                    mConnectedSco = (ScoSocket)msg.obj;
809                    mAudioManager.setBluetoothScoOn(true);
810                    broadcastAudioStateIntent(BluetoothHeadset.AUDIO_STATE_CONNECTED);
811                } else if (msg.arg1 == ScoSocket.STATE_CONNECTED) {
812                    if (DBG) log("Rejecting new connected outgoing SCO socket");
813                    ((ScoSocket)msg.obj).close();
814                    mOutgoingSco.close();
815                }
816                mOutgoingSco = null;
817                break;
818            case SCO_CLOSED:
819                if (mConnectedSco == (ScoSocket)msg.obj) {
820                    mConnectedSco = null;
821                    mAudioManager.setBluetoothScoOn(false);
822                    broadcastAudioStateIntent(BluetoothHeadset.AUDIO_STATE_DISCONNECTED);
823                } else if (mOutgoingSco == (ScoSocket)msg.obj) {
824                    mOutgoingSco = null;
825                } else if (mIncomingSco == (ScoSocket)msg.obj) {
826                    mIncomingSco = null;
827                }
828                break;
829            case CHECK_CALL_STARTED:
830                if (mWaitingForCallStart) {
831                    mWaitingForCallStart = false;
832                    Log.e(TAG, "Timeout waiting for call to start");
833                    sendURC("ERROR");
834                    if (mStartCallWakeLock.isHeld()) {
835                        mStartCallWakeLock.release();
836                    }
837                }
838                break;
839            case CHECK_VOICE_RECOGNITION_STARTED:
840                if (mWaitingForVoiceRecognition) {
841                    mWaitingForVoiceRecognition = false;
842                    Log.e(TAG, "Timeout waiting for voice recognition to start");
843                    sendURC("ERROR");
844                }
845                break;
846            }
847        }
848    };
849
850    private ScoSocket createScoSocket() {
851        return new ScoSocket(mPowerManager, mHandler, SCO_ACCEPTED, SCO_CONNECTED, SCO_CLOSED);
852    }
853
854    private void broadcastAudioStateIntent(int state) {
855        if (VDBG) log("broadcastAudioStateIntent(" + state + ")");
856        Intent intent = new Intent(BluetoothIntent.HEADSET_AUDIO_STATE_CHANGED_ACTION);
857        intent.putExtra(BluetoothIntent.HEADSET_AUDIO_STATE, state);
858        mContext.sendBroadcast(intent, android.Manifest.permission.BLUETOOTH);
859    }
860
861
862    void updateBtHandsfreeAfterRadioTechnologyChange() {
863        if(DBG) Log.d(TAG, "updateBtHandsfreeAfterRadioTechnologyChange...");
864
865        //Get the Call references from the new active phone again
866        mRingingCall = mPhone.getRingingCall();
867        mForegroundCall = mPhone.getForegroundCall();
868        mBackgroundCall = mPhone.getBackgroundCall();
869
870        mBluetoothPhoneState.updateBtPhoneStateAfterRadioTechnologyChange();
871    }
872
873    /** Request to establish SCO (audio) connection to bluetooth
874     * headset/handsfree, if one is connected. Does not block.
875     * Returns false if the user has requested audio off, or if there
876     * is some other immediate problem that will prevent BT audio.
877     */
878    /* package */ synchronized boolean audioOn() {
879        if (VDBG) log("audioOn()");
880        if (!isHeadsetConnected()) {
881            if (DBG) log("audioOn(): headset is not connected!");
882            return false;
883        }
884        if (mHeadsetType == TYPE_HANDSFREE && !mServiceConnectionEstablished) {
885            if (DBG) log("audioOn(): service connection not yet established!");
886            return false;
887        }
888
889        if (mConnectedSco != null) {
890            if (DBG) log("audioOn(): audio is already connected");
891            return true;
892        }
893
894        if (!mUserWantsAudio) {
895            if (DBG) log("audioOn(): user requested no audio, ignoring");
896            return false;
897        }
898
899        if (mOutgoingSco != null) {
900            if (DBG) log("audioOn(): outgoing SCO already in progress");
901            return true;
902        }
903        mOutgoingSco = createScoSocket();
904        if (!mOutgoingSco.connect(mHeadset.getAddress())) {
905            mOutgoingSco = null;
906        }
907
908        return true;
909    }
910
911    /** Used to indicate the user requested BT audio on.
912     *  This will establish SCO (BT audio), even if the user requested it off
913     *  previously on this call.
914     */
915    /* package */ synchronized void userWantsAudioOn() {
916        mUserWantsAudio = true;
917        audioOn();
918    }
919    /** Used to indicate the user requested BT audio off.
920     *  This will prevent us from establishing BT audio again during this call
921     *  if audioOn() is called.
922     */
923    /* package */ synchronized void userWantsAudioOff() {
924        mUserWantsAudio = false;
925        audioOff();
926    }
927
928    /** Request to disconnect SCO (audio) connection to bluetooth
929     * headset/handsfree, if one is connected. Does not block.
930     */
931    /* package */ synchronized void audioOff() {
932        if (VDBG) log("audioOff()");
933
934        if (mConnectedSco != null) {
935            mAudioManager.setBluetoothScoOn(false);
936            broadcastAudioStateIntent(BluetoothHeadset.AUDIO_STATE_DISCONNECTED);
937            mConnectedSco.close();
938            mConnectedSco = null;
939        }
940        if (mOutgoingSco != null) {
941            mOutgoingSco.close();
942            mOutgoingSco = null;
943        }
944    }
945
946    /* package */ boolean isAudioOn() {
947        return (mConnectedSco != null);
948    }
949
950    /* package */ void ignoreRing() {
951        mBluetoothPhoneState.ignoreRing();
952    }
953
954    private void sendURC(String urc) {
955        if (isHeadsetConnected()) {
956            mHeadset.sendURC(urc);
957        }
958    }
959
960    /** helper to redial last dialled number */
961    private AtCommandResult redial() {
962        String number = mPhonebook.getLastDialledNumber();
963        if (number == null) {
964            // spec seems to suggest sending ERROR if we dont have a
965            // number to redial
966            if (DBG) log("Bluetooth redial requested (+BLDN), but no previous " +
967                  "outgoing calls found. Ignoring");
968            return new AtCommandResult(AtCommandResult.ERROR);
969        }
970        Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
971                Uri.fromParts("tel", number, null));
972        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
973        mContext.startActivity(intent);
974
975        // We do not immediately respond OK, wait until we get a phone state
976        // update. If we return OK now and the handsfree immeidately requests
977        // our phone state it will say we are not in call yet which confuses
978        // some devices
979        expectCallStart();
980        return new AtCommandResult(AtCommandResult.UNSOLICITED);  // send nothing
981    }
982
983    /** Build the +CLCC result
984     *  The complexity arises from the fact that we need to maintain the same
985     *  CLCC index even as a call moves between states. */
986    private synchronized AtCommandResult getClccResult() {
987        // Collect all known connections
988        Connection[] clccConnections = new Connection[MAX_CONNECTIONS];  // indexed by CLCC index
989        LinkedList<Connection> newConnections = new LinkedList<Connection>();
990        LinkedList<Connection> connections = new LinkedList<Connection>();
991        if (mRingingCall.getState().isAlive()) {
992            connections.addAll(mRingingCall.getConnections());
993        }
994        if (mForegroundCall.getState().isAlive()) {
995            connections.addAll(mForegroundCall.getConnections());
996        }
997        if (mBackgroundCall.getState().isAlive()) {
998            connections.addAll(mBackgroundCall.getConnections());
999        }
1000
1001        // Mark connections that we already known about
1002        boolean clccUsed[] = new boolean[MAX_CONNECTIONS];
1003        for (int i = 0; i < MAX_CONNECTIONS; i++) {
1004            clccUsed[i] = mClccUsed[i];
1005            mClccUsed[i] = false;
1006        }
1007        for (Connection c : connections) {
1008            boolean found = false;
1009            long timestamp = c.getCreateTime();
1010            for (int i = 0; i < MAX_CONNECTIONS; i++) {
1011                if (clccUsed[i] && timestamp == mClccTimestamps[i]) {
1012                    mClccUsed[i] = true;
1013                    found = true;
1014                    clccConnections[i] = c;
1015                    break;
1016                }
1017            }
1018            if (!found) {
1019                newConnections.add(c);
1020            }
1021        }
1022
1023        // Find a CLCC index for new connections
1024        while (!newConnections.isEmpty()) {
1025            // Find lowest empty index
1026            int i = 0;
1027            while (mClccUsed[i]) i++;
1028            // Find earliest connection
1029            long earliestTimestamp = newConnections.get(0).getCreateTime();
1030            Connection earliestConnection = newConnections.get(0);
1031            for (int j = 0; j < newConnections.size(); j++) {
1032                long timestamp = newConnections.get(j).getCreateTime();
1033                if (timestamp < earliestTimestamp) {
1034                    earliestTimestamp = timestamp;
1035                    earliestConnection = newConnections.get(j);
1036                }
1037            }
1038
1039            // update
1040            mClccUsed[i] = true;
1041            mClccTimestamps[i] = earliestTimestamp;
1042            clccConnections[i] = earliestConnection;
1043            newConnections.remove(earliestConnection);
1044        }
1045
1046        // Build CLCC
1047        AtCommandResult result = new AtCommandResult(AtCommandResult.OK);
1048        for (int i = 0; i < clccConnections.length; i++) {
1049            if (mClccUsed[i]) {
1050                String clccEntry = connectionToClccEntry(i, clccConnections[i]);
1051                if (clccEntry != null) {
1052                    result.addResponse(clccEntry);
1053                }
1054            }
1055        }
1056
1057        return result;
1058    }
1059
1060    /** Convert a Connection object into a single +CLCC result */
1061    private String connectionToClccEntry(int index, Connection c) {
1062        int state;
1063        switch (c.getState()) {
1064        case ACTIVE:
1065            state = 0;
1066            break;
1067        case HOLDING:
1068            state = 1;
1069            break;
1070        case DIALING:
1071            state = 2;
1072            break;
1073        case ALERTING:
1074            state = 3;
1075            break;
1076        case INCOMING:
1077            state = 4;
1078            break;
1079        case WAITING:
1080            state = 5;
1081            break;
1082        default:
1083            return null;  // bad state
1084        }
1085
1086        int mpty = 0;
1087        Call call = c.getCall();
1088        if (call != null) {
1089            mpty = call.isMultiparty() ? 1 : 0;
1090        }
1091
1092        int direction = c.isIncoming() ? 1 : 0;
1093
1094        String number = c.getAddress();
1095        int type = -1;
1096        if (number != null) {
1097            type = PhoneNumberUtils.toaFromString(number);
1098        }
1099
1100        String result = "+CLCC: " + (index + 1) + "," + direction + "," + state + ",0," + mpty;
1101        if (number != null) {
1102            result += ",\"" + number + "\"," + type;
1103        }
1104        return result;
1105    }
1106    /**
1107     * Register AT Command handlers to implement the Headset profile
1108     */
1109    private void initializeHeadsetAtParser() {
1110        if (DBG) log("Registering Headset AT commands");
1111        AtParser parser = mHeadset.getAtParser();
1112        // Headset's usually only have one button, which is meant to cause the
1113        // HS to send us AT+CKPD=200 or AT+CKPD.
1114        parser.register("+CKPD", new AtCommandHandler() {
1115            private AtCommandResult headsetButtonPress() {
1116                if (mRingingCall.isRinging()) {
1117                    // Answer the call
1118                    PhoneUtils.answerCall(mPhone);
1119                    // If in-band ring tone is supported, SCO connection will already
1120                    // be up and the following call will just return.
1121                    audioOn();
1122                } else if (mForegroundCall.getState().isAlive()) {
1123                    if (!isAudioOn()) {
1124                        // Transfer audio from AG to HS
1125                        audioOn();
1126                    } else {
1127                        if (mHeadset.getDirection() == HeadsetBase.DIRECTION_INCOMING &&
1128                          (System.currentTimeMillis() - mHeadset.getConnectTimestamp()) < 5000) {
1129                            // Headset made a recent ACL connection to us - and
1130                            // made a mandatory AT+CKPD request to connect
1131                            // audio which races with our automatic audio
1132                            // setup.  ignore
1133                        } else {
1134                            // Hang up the call
1135                            audioOff();
1136                            PhoneUtils.hangup(mPhone);
1137                        }
1138                    }
1139                } else {
1140                    // No current call - redial last number
1141                    return redial();
1142                }
1143                return new AtCommandResult(AtCommandResult.OK);
1144            }
1145            @Override
1146            public AtCommandResult handleActionCommand() {
1147                return headsetButtonPress();
1148            }
1149            @Override
1150            public AtCommandResult handleSetCommand(Object[] args) {
1151                return headsetButtonPress();
1152            }
1153        });
1154    }
1155
1156    /**
1157     * Register AT Command handlers to implement the Handsfree profile
1158     */
1159    private void initializeHandsfreeAtParser() {
1160        if (DBG) log("Registering Handsfree AT commands");
1161        AtParser parser = mHeadset.getAtParser();
1162
1163        // Answer
1164        parser.register('A', new AtCommandHandler() {
1165            @Override
1166            public AtCommandResult handleBasicCommand(String args) {
1167                PhoneUtils.answerCall(mPhone);
1168                return new AtCommandResult(AtCommandResult.OK);
1169            }
1170        });
1171        parser.register('D', new AtCommandHandler() {
1172            @Override
1173            public AtCommandResult handleBasicCommand(String args) {
1174                if (args.length() > 0) {
1175                    if (args.charAt(0) == '>') {
1176                        // Yuck - memory dialling requested.
1177                        // Just dial last number for now
1178                        if (args.startsWith(">9999")) {   // for PTS test
1179                            return new AtCommandResult(AtCommandResult.ERROR);
1180                        }
1181                        return redial();
1182                    } else {
1183                        // Remove trailing ';'
1184                        if (args.charAt(args.length() - 1) == ';') {
1185                            args = args.substring(0, args.length() - 1);
1186                        }
1187                        Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
1188                                Uri.fromParts("tel", args, null));
1189                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1190                        mContext.startActivity(intent);
1191
1192                        expectCallStart();
1193                        return new AtCommandResult(AtCommandResult.UNSOLICITED);  // send nothing
1194                    }
1195                }
1196                return new AtCommandResult(AtCommandResult.ERROR);
1197            }
1198        });
1199
1200        // Hang-up command
1201        parser.register("+CHUP", new AtCommandHandler() {
1202            @Override
1203            public AtCommandResult handleActionCommand() {
1204                if (!mForegroundCall.isIdle()) {
1205                    PhoneUtils.hangup(mForegroundCall);
1206                } else if (!mRingingCall.isIdle()) {
1207                    PhoneUtils.hangup(mRingingCall);
1208                } else if (!mBackgroundCall.isIdle()) {
1209                    PhoneUtils.hangup(mBackgroundCall);
1210                }
1211                return new AtCommandResult(AtCommandResult.OK);
1212            }
1213        });
1214
1215        // Bluetooth Retrieve Supported Features command
1216        parser.register("+BRSF", new AtCommandHandler() {
1217            private AtCommandResult sendBRSF() {
1218                return new AtCommandResult("+BRSF: " + mLocalBrsf);
1219            }
1220            @Override
1221            public AtCommandResult handleSetCommand(Object[] args) {
1222                // AT+BRSF=<handsfree supported features bitmap>
1223                // Handsfree is telling us which features it supports. We
1224                // send the features we support
1225                if (args.length == 1 && (args[0] instanceof Integer)) {
1226                    mRemoteBrsf = (Integer) args[0];
1227                } else {
1228                    Log.w(TAG, "HF didn't sent BRSF assuming 0");
1229                }
1230                return sendBRSF();
1231            }
1232            @Override
1233            public AtCommandResult handleActionCommand() {
1234                // This seems to be out of spec, but lets do the nice thing
1235                return sendBRSF();
1236            }
1237            @Override
1238            public AtCommandResult handleReadCommand() {
1239                // This seems to be out of spec, but lets do the nice thing
1240                return sendBRSF();
1241            }
1242        });
1243
1244        // Call waiting notification on/off
1245        parser.register("+CCWA", new AtCommandHandler() {
1246            @Override
1247            public AtCommandResult handleActionCommand() {
1248                // Seems to be out of spec, but lets return nicely
1249                return new AtCommandResult(AtCommandResult.OK);
1250            }
1251            @Override
1252            public AtCommandResult handleReadCommand() {
1253                // Call waiting is always on
1254                return new AtCommandResult("+CCWA: 1");
1255            }
1256            @Override
1257            public AtCommandResult handleSetCommand(Object[] args) {
1258                // AT+CCWA=<n>
1259                // Handsfree is trying to enable/disable call waiting. We
1260                // cannot disable in the current implementation.
1261                return new AtCommandResult(AtCommandResult.OK);
1262            }
1263            @Override
1264            public AtCommandResult handleTestCommand() {
1265                // Request for range of supported CCWA paramters
1266                return new AtCommandResult("+CCWA: (\"n\",(1))");
1267            }
1268        });
1269
1270        // Mobile Equipment Event Reporting enable/disable command
1271        // Of the full 3GPP syntax paramters (mode, keyp, disp, ind, bfr) we
1272        // only support paramter ind (disable/enable evert reporting using
1273        // +CDEV)
1274        parser.register("+CMER", new AtCommandHandler() {
1275            @Override
1276            public AtCommandResult handleReadCommand() {
1277                return new AtCommandResult(
1278                        "+CMER: 3,0,0," + (mIndicatorsEnabled ? "1" : "0"));
1279            }
1280            @Override
1281            public AtCommandResult handleSetCommand(Object[] args) {
1282                if (args.length < 4) {
1283                    // This is a syntax error
1284                    return new AtCommandResult(AtCommandResult.ERROR);
1285                } else if (args[0].equals(3) && args[1].equals(0) &&
1286                           args[2].equals(0)) {
1287                    boolean valid = false;
1288                    if (args[3].equals(0)) {
1289                        mIndicatorsEnabled = false;
1290                        valid = true;
1291                    } else if (args[3].equals(1)) {
1292                        mIndicatorsEnabled = true;
1293                        valid = true;
1294                    }
1295                    if (valid) {
1296                        if ((mRemoteBrsf & BRSF_HF_CW_THREE_WAY_CALLING) == 0x0) {
1297                            mServiceConnectionEstablished = true;
1298                            sendURC("OK");  // send immediately, then initiate audio
1299                            if (isIncallAudio()) {
1300                                audioOn();
1301                            }
1302                            // only send OK once
1303                            return new AtCommandResult(AtCommandResult.UNSOLICITED);
1304                        } else {
1305                            return new AtCommandResult(AtCommandResult.OK);
1306                        }
1307                    }
1308                }
1309                return reportCmeError(BluetoothCmeError.OPERATION_NOT_SUPPORTED);
1310            }
1311            @Override
1312            public AtCommandResult handleTestCommand() {
1313                return new AtCommandResult("+CMER: (3),(0),(0),(0-1)");
1314            }
1315        });
1316
1317        // Mobile Equipment Error Reporting enable/disable
1318        parser.register("+CMEE", new AtCommandHandler() {
1319            @Override
1320            public AtCommandResult handleActionCommand() {
1321                // out of spec, assume they want to enable
1322                mCmee = true;
1323                return new AtCommandResult(AtCommandResult.OK);
1324            }
1325            @Override
1326            public AtCommandResult handleReadCommand() {
1327                return new AtCommandResult("+CMEE: " + (mCmee ? "1" : "0"));
1328            }
1329            @Override
1330            public AtCommandResult handleSetCommand(Object[] args) {
1331                // AT+CMEE=<n>
1332                if (args.length == 0) {
1333                    // <n> ommitted - default to 0
1334                    mCmee = false;
1335                    return new AtCommandResult(AtCommandResult.OK);
1336                } else if (!(args[0] instanceof Integer)) {
1337                    // Syntax error
1338                    return new AtCommandResult(AtCommandResult.ERROR);
1339                } else {
1340                    mCmee = ((Integer)args[0] == 1);
1341                    return new AtCommandResult(AtCommandResult.OK);
1342                }
1343            }
1344            @Override
1345            public AtCommandResult handleTestCommand() {
1346                // Probably not required but spec, but no harm done
1347                return new AtCommandResult("+CMEE: (0-1)");
1348            }
1349        });
1350
1351        // Bluetooth Last Dialled Number
1352        parser.register("+BLDN", new AtCommandHandler() {
1353            @Override
1354            public AtCommandResult handleActionCommand() {
1355                return redial();
1356            }
1357        });
1358
1359        // Indicator Update command
1360        parser.register("+CIND", new AtCommandHandler() {
1361            @Override
1362            public AtCommandResult handleReadCommand() {
1363                return mBluetoothPhoneState.toCindResult();
1364            }
1365            @Override
1366            public AtCommandResult handleTestCommand() {
1367                return mBluetoothPhoneState.getCindTestResult();
1368            }
1369        });
1370
1371        // Query Signal Quality (legacy)
1372        parser.register("+CSQ", new AtCommandHandler() {
1373            @Override
1374            public AtCommandResult handleActionCommand() {
1375                return mBluetoothPhoneState.toCsqResult();
1376            }
1377        });
1378
1379        // Query network registration state
1380        parser.register("+CREG", new AtCommandHandler() {
1381            @Override
1382            public AtCommandResult handleReadCommand() {
1383                return new AtCommandResult(mBluetoothPhoneState.toCregString());
1384            }
1385        });
1386
1387        // Send DTMF. I don't know if we are also expected to play the DTMF tone
1388        // locally, right now we don't
1389        parser.register("+VTS", new AtCommandHandler() {
1390            @Override
1391            public AtCommandResult handleSetCommand(Object[] args) {
1392                if (args.length >= 1) {
1393                    char c;
1394                    if (args[0] instanceof Integer) {
1395                        c = ((Integer) args[0]).toString().charAt(0);
1396                    } else {
1397                        c = ((String) args[0]).charAt(0);
1398                    }
1399                    if (isValidDtmf(c)) {
1400                        mPhone.sendDtmf(c);
1401                        return new AtCommandResult(AtCommandResult.OK);
1402                    }
1403                }
1404                return new AtCommandResult(AtCommandResult.ERROR);
1405            }
1406            private boolean isValidDtmf(char c) {
1407                switch (c) {
1408                case '#':
1409                case '*':
1410                    return true;
1411                default:
1412                    if (Character.digit(c, 14) != -1) {
1413                        return true;  // 0-9 and A-D
1414                    }
1415                    return false;
1416                }
1417            }
1418        });
1419
1420        // List calls
1421        parser.register("+CLCC", new AtCommandHandler() {
1422            @Override
1423            public AtCommandResult handleActionCommand() {
1424                return getClccResult();
1425            }
1426        });
1427
1428        // Call Hold and Multiparty Handling command
1429        parser.register("+CHLD", new AtCommandHandler() {
1430            @Override
1431            public AtCommandResult handleSetCommand(Object[] args) {
1432                if (args.length >= 1) {
1433                    if (args[0].equals(0)) {
1434                        boolean result;
1435                        if (mRingingCall.isRinging()) {
1436                            result = PhoneUtils.hangupRingingCall(mPhone);
1437                        } else {
1438                            result = PhoneUtils.hangupHoldingCall(mPhone);
1439                        }
1440                        if (result) {
1441                            return new AtCommandResult(AtCommandResult.OK);
1442                        } else {
1443                            return new AtCommandResult(AtCommandResult.ERROR);
1444                        }
1445                    } else if (args[0].equals(1)) {
1446                        if (mPhone.getPhoneName().equals("CDMA")) {
1447                            // For CDMA, there is no answerAndEndActive, so we can behave
1448                            // the same way as CHLD=2 here
1449                            PhoneUtils.answerCall(mPhone);
1450                            PhoneUtils.setMute(mPhone, false);
1451                            return new AtCommandResult(AtCommandResult.OK);
1452                        } else {
1453                            // Hangup active call, answer held call
1454                            if (PhoneUtils.answerAndEndActive(mPhone)) {
1455                                return new AtCommandResult(AtCommandResult.OK);
1456                            } else {
1457                                return new AtCommandResult(AtCommandResult.ERROR);
1458                            }
1459                        }
1460                    } else if (args[0].equals(2)) {
1461                        if (mPhone.getPhoneName().equals("CDMA")) {
1462                            // For CDMA, the way we switch to a new incoming call is by
1463                            // calling PhoneUtils.answerCall(). switchAndHoldActive() won't
1464                            // properly update the call state within telephony.
1465                            PhoneUtils.answerCall(mPhone);
1466                            PhoneUtils.setMute(mPhone, false);
1467                        } else {
1468                            PhoneUtils.switchHoldingAndActive(mPhone);
1469                        }
1470                        return new AtCommandResult(AtCommandResult.OK);
1471                    } else if (args[0].equals(3)) {
1472                        if (mForegroundCall.getState().isAlive() &&
1473                            mBackgroundCall.getState().isAlive()) {
1474                            PhoneUtils.mergeCalls(mPhone);
1475                        }
1476                        return new AtCommandResult(AtCommandResult.OK);
1477                    }
1478                }
1479                return new AtCommandResult(AtCommandResult.ERROR);
1480            }
1481            @Override
1482            public AtCommandResult handleTestCommand() {
1483                mServiceConnectionEstablished = true;
1484                sendURC("+CHLD: (0,1,2,3)");
1485                sendURC("OK");  // send reply first, then connect audio
1486                if (isIncallAudio()) {
1487                    audioOn();
1488                }
1489                // already replied
1490                return new AtCommandResult(AtCommandResult.UNSOLICITED);
1491            }
1492        });
1493
1494        // Get Network operator name
1495        parser.register("+COPS", new AtCommandHandler() {
1496            @Override
1497            public AtCommandResult handleReadCommand() {
1498                String operatorName = mPhone.getServiceState().getOperatorAlphaLong();
1499                if (operatorName != null) {
1500                    if (operatorName.length() > 16) {
1501                        operatorName = operatorName.substring(0, 16);
1502                    }
1503                    return new AtCommandResult(
1504                            "+COPS: 0,0,\"" + operatorName + "\"");
1505                } else {
1506                    return new AtCommandResult(
1507                            "+COPS: 0,0,\"UNKNOWN\",0");
1508                }
1509            }
1510            @Override
1511            public AtCommandResult handleSetCommand(Object[] args) {
1512                // Handsfree only supports AT+COPS=3,0
1513                if (args.length != 2 || !(args[0] instanceof Integer)
1514                    || !(args[1] instanceof Integer)) {
1515                    // syntax error
1516                    return new AtCommandResult(AtCommandResult.ERROR);
1517                } else if ((Integer)args[0] != 3 || (Integer)args[1] != 0) {
1518                    return reportCmeError(BluetoothCmeError.OPERATION_NOT_SUPPORTED);
1519                } else {
1520                    return new AtCommandResult(AtCommandResult.OK);
1521                }
1522            }
1523            @Override
1524            public AtCommandResult handleTestCommand() {
1525                // Out of spec, but lets be friendly
1526                return new AtCommandResult("+COPS: (3),(0)");
1527            }
1528        });
1529
1530        // Mobile PIN
1531        // AT+CPIN is not in the handsfree spec (although it is in 3GPP)
1532        parser.register("+CPIN", new AtCommandHandler() {
1533            @Override
1534            public AtCommandResult handleReadCommand() {
1535                return new AtCommandResult("+CPIN: READY");
1536            }
1537        });
1538
1539        // Bluetooth Response and Hold
1540        // Only supported on PDC (Japan) and CDMA networks.
1541        parser.register("+BTRH", new AtCommandHandler() {
1542            @Override
1543            public AtCommandResult handleReadCommand() {
1544                // Replying with just OK indicates no response and hold
1545                // features in use now
1546                return new AtCommandResult(AtCommandResult.OK);
1547            }
1548            @Override
1549            public AtCommandResult handleSetCommand(Object[] args) {
1550                // Neeed PDC or CDMA
1551                return new AtCommandResult(AtCommandResult.ERROR);
1552            }
1553        });
1554
1555        // Request International Mobile Subscriber Identity (IMSI)
1556        // Not in bluetooth handset spec
1557        parser.register("+CIMI", new AtCommandHandler() {
1558            @Override
1559            public AtCommandResult handleActionCommand() {
1560                // AT+CIMI
1561                String imsi = mPhone.getSubscriberId();
1562                if (imsi == null || imsi.length() == 0) {
1563                    return reportCmeError(BluetoothCmeError.SIM_FAILURE);
1564                } else {
1565                    return new AtCommandResult(imsi);
1566                }
1567            }
1568        });
1569
1570        // Calling Line Identification Presentation
1571        parser.register("+CLIP", new AtCommandHandler() {
1572            @Override
1573            public AtCommandResult handleReadCommand() {
1574                // Currently assumes the network is provisioned for CLIP
1575                return new AtCommandResult("+CLIP: " + (mClip ? "1" : "0") + ",1");
1576            }
1577            @Override
1578            public AtCommandResult handleSetCommand(Object[] args) {
1579                // AT+CLIP=<n>
1580                if (args.length >= 1 && (args[0].equals(0) || args[0].equals(1))) {
1581                    mClip = args[0].equals(1);
1582                    return new AtCommandResult(AtCommandResult.OK);
1583                } else {
1584                    return new AtCommandResult(AtCommandResult.ERROR);
1585                }
1586            }
1587            @Override
1588            public AtCommandResult handleTestCommand() {
1589                return new AtCommandResult("+CLIP: (0-1)");
1590            }
1591        });
1592
1593        // AT+CGSN - Returns the device IMEI number.
1594        parser.register("+CGSN", new AtCommandHandler() {
1595            @Override
1596            public AtCommandResult handleActionCommand() {
1597                // Get the IMEI of the device.
1598                // mPhone will not be NULL at this point.
1599                return new AtCommandResult("+CGSN: " + mPhone.getDeviceId());
1600            }
1601        });
1602
1603        // AT+CGMM - Query Model Information
1604        parser.register("+CGMM", new AtCommandHandler() {
1605            @Override
1606            public AtCommandResult handleActionCommand() {
1607                // Return the Model Information.
1608                String model = SystemProperties.get("ro.product.model");
1609                if (model != null) {
1610                    return new AtCommandResult("+CGMM: " + model);
1611                } else {
1612                    return new AtCommandResult(AtCommandResult.ERROR);
1613                }
1614            }
1615        });
1616
1617        // AT+CGMI - Query Manufacturer Information
1618        parser.register("+CGMI", new AtCommandHandler() {
1619            @Override
1620            public AtCommandResult handleActionCommand() {
1621                // Return the Model Information.
1622                String manuf = SystemProperties.get("ro.product.manufacturer");
1623                if (manuf != null) {
1624                    return new AtCommandResult("+CGMI: " + manuf);
1625                } else {
1626                    return new AtCommandResult(AtCommandResult.ERROR);
1627                }
1628            }
1629        });
1630
1631        // Noise Reduction and Echo Cancellation control
1632        parser.register("+NREC", new AtCommandHandler() {
1633            @Override
1634            public AtCommandResult handleSetCommand(Object[] args) {
1635                if (args[0].equals(0)) {
1636                    mAudioManager.setParameter(HEADSET_NREC, "off");
1637                    return new AtCommandResult(AtCommandResult.OK);
1638                } else if (args[0].equals(1)) {
1639                    mAudioManager.setParameter(HEADSET_NREC, "on");
1640                    return new AtCommandResult(AtCommandResult.OK);
1641                }
1642                return new AtCommandResult(AtCommandResult.ERROR);
1643            }
1644        });
1645
1646        // Voice recognition (dialing)
1647        parser.register("+BVRA", new AtCommandHandler() {
1648            @Override
1649            public AtCommandResult handleSetCommand(Object[] args) {
1650                if (BluetoothHeadset.DISABLE_BT_VOICE_DIALING) {
1651                    return new AtCommandResult(AtCommandResult.ERROR);
1652                }
1653                if (args.length >= 1 && args[0].equals(1)) {
1654                    synchronized (BluetoothHandsfree.this) {
1655                        if (!mWaitingForVoiceRecognition) {
1656                            try {
1657                                mContext.startActivity(sVoiceCommandIntent);
1658                            } catch (ActivityNotFoundException e) {
1659                                return new AtCommandResult(AtCommandResult.ERROR);
1660                            }
1661                            expectVoiceRecognition();
1662                        }
1663                    }
1664                    return new AtCommandResult(AtCommandResult.UNSOLICITED);  // send nothing yet
1665                } else if (args.length >= 1 && args[0].equals(0)) {
1666                    audioOff();
1667                    return new AtCommandResult(AtCommandResult.OK);
1668                }
1669                return new AtCommandResult(AtCommandResult.ERROR);
1670            }
1671            @Override
1672            public AtCommandResult handleTestCommand() {
1673                return new AtCommandResult("+BVRA: (0-1)");
1674            }
1675        });
1676
1677        // Retrieve Subscriber Number
1678        parser.register("+CNUM", new AtCommandHandler() {
1679            @Override
1680            public AtCommandResult handleActionCommand() {
1681                String number = mPhone.getLine1Number();
1682                if (number == null) {
1683                    return new AtCommandResult(AtCommandResult.OK);
1684                }
1685                return new AtCommandResult("+CNUM: ,\"" + number + "\"," +
1686                        PhoneNumberUtils.toaFromString(number) + ",,4");
1687            }
1688        });
1689
1690        // Microphone Gain
1691        parser.register("+VGM", new AtCommandHandler() {
1692            @Override
1693            public AtCommandResult handleSetCommand(Object[] args) {
1694                // AT+VGM=<gain>    in range [0,15]
1695                // Headset/Handsfree is reporting its current gain setting
1696                return new AtCommandResult(AtCommandResult.OK);
1697            }
1698        });
1699
1700        // Speaker Gain
1701        parser.register("+VGS", new AtCommandHandler() {
1702            @Override
1703            public AtCommandResult handleSetCommand(Object[] args) {
1704                // AT+VGS=<gain>    in range [0,15]
1705                if (args.length != 1 || !(args[0] instanceof Integer)) {
1706                    return new AtCommandResult(AtCommandResult.ERROR);
1707                }
1708                mScoGain = (Integer) args[0];
1709                int flag =  mAudioManager.isBluetoothScoOn() ? AudioManager.FLAG_SHOW_UI:0;
1710
1711                mAudioManager.setStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO, mScoGain, flag);
1712                return new AtCommandResult(AtCommandResult.OK);
1713            }
1714        });
1715
1716        // Phone activity status
1717        parser.register("+CPAS", new AtCommandHandler() {
1718            @Override
1719            public AtCommandResult handleActionCommand() {
1720                int status = 0;
1721                switch (mPhone.getState()) {
1722                case IDLE:
1723                    status = 0;
1724                    break;
1725                case RINGING:
1726                    status = 3;
1727                    break;
1728                case OFFHOOK:
1729                    status = 4;
1730                    break;
1731                }
1732                return new AtCommandResult("+CPAS: " + status);
1733            }
1734        });
1735        mPhonebook.register(parser);
1736    }
1737
1738    public void sendScoGainUpdate(int gain) {
1739        if (mScoGain != gain && (mRemoteBrsf & BRSF_HF_REMOTE_VOL_CONTROL) != 0x0) {
1740            sendURC("+VGS:" + gain);
1741            mScoGain = gain;
1742        }
1743    }
1744
1745    public AtCommandResult reportCmeError(int error) {
1746        if (mCmee) {
1747            AtCommandResult result = new AtCommandResult(AtCommandResult.UNSOLICITED);
1748            result.addResponse("+CME ERROR: " + error);
1749            return result;
1750        } else {
1751            return new AtCommandResult(AtCommandResult.ERROR);
1752        }
1753    }
1754
1755    private static final int START_CALL_TIMEOUT = 10000;  // ms
1756
1757    private synchronized void expectCallStart() {
1758        mWaitingForCallStart = true;
1759        Message msg = Message.obtain(mHandler, CHECK_CALL_STARTED);
1760        mHandler.sendMessageDelayed(msg, START_CALL_TIMEOUT);
1761        if (!mStartCallWakeLock.isHeld()) {
1762            mStartCallWakeLock.acquire(START_CALL_TIMEOUT);
1763        }
1764    }
1765
1766    private synchronized void callStarted() {
1767        if (mWaitingForCallStart) {
1768            mWaitingForCallStart = false;
1769            sendURC("OK");
1770            if (mStartCallWakeLock.isHeld()) {
1771                mStartCallWakeLock.release();
1772            }
1773        }
1774    }
1775
1776    private static final int START_VOICE_RECOGNITION_TIMEOUT = 5000;  // ms
1777
1778    private synchronized void expectVoiceRecognition() {
1779        mWaitingForVoiceRecognition = true;
1780        Message msg = Message.obtain(mHandler, CHECK_VOICE_RECOGNITION_STARTED);
1781        mHandler.sendMessageDelayed(msg, START_VOICE_RECOGNITION_TIMEOUT);
1782        if (!mStartVoiceRecognitionWakeLock.isHeld()) {
1783            mStartVoiceRecognitionWakeLock.acquire(START_VOICE_RECOGNITION_TIMEOUT);
1784        }
1785    }
1786
1787    /* package */ synchronized boolean startVoiceRecognition() {
1788        if (mWaitingForVoiceRecognition) {
1789            // HF initiated
1790            mWaitingForVoiceRecognition = false;
1791            sendURC("OK");
1792        } else {
1793            // AG initiated
1794            sendURC("+BVRA: 1");
1795        }
1796        boolean ret = audioOn();
1797        if (mStartVoiceRecognitionWakeLock.isHeld()) {
1798            mStartVoiceRecognitionWakeLock.release();
1799        }
1800        return ret;
1801    }
1802
1803    /* package */ synchronized boolean stopVoiceRecognition() {
1804        sendURC("+BVRA: 0");
1805        audioOff();
1806        return true;
1807    }
1808
1809    private boolean inDebug() {
1810        return DBG && SystemProperties.getBoolean(DebugThread.DEBUG_HANDSFREE, false);
1811    }
1812
1813    private boolean allowAudioAnytime() {
1814        return inDebug() && SystemProperties.getBoolean(DebugThread.DEBUG_HANDSFREE_AUDIO_ANYTIME,
1815                false);
1816    }
1817
1818    private void startDebug() {
1819        if (DBG && mDebugThread == null) {
1820            mDebugThread = new DebugThread();
1821            mDebugThread.start();
1822        }
1823    }
1824
1825    private void stopDebug() {
1826        if (mDebugThread != null) {
1827            mDebugThread.interrupt();
1828            mDebugThread = null;
1829        }
1830    }
1831
1832    /** Debug thread to read debug properties - runs when debug.bt.hfp is true
1833     *  at the time a bluetooth handsfree device is connected. Debug properties
1834     *  are polled and mock updates sent every 1 second */
1835    private class DebugThread extends Thread {
1836        /** Turns on/off handsfree profile debugging mode */
1837        private static final String DEBUG_HANDSFREE = "debug.bt.hfp";
1838
1839        /** Mock battery level change - use 0 to 5 */
1840        private static final String DEBUG_HANDSFREE_BATTERY = "debug.bt.hfp.battery";
1841
1842        /** Mock no cellular service when false */
1843        private static final String DEBUG_HANDSFREE_SERVICE = "debug.bt.hfp.service";
1844
1845        /** Mock cellular roaming when true */
1846        private static final String DEBUG_HANDSFREE_ROAM = "debug.bt.hfp.roam";
1847
1848        /** false to true transition will force an audio (SCO) connection to
1849         *  be established. true to false will force audio to be disconnected
1850         */
1851        private static final String DEBUG_HANDSFREE_AUDIO = "debug.bt.hfp.audio";
1852
1853        /** true allows incoming SCO connection out of call.
1854         */
1855        private static final String DEBUG_HANDSFREE_AUDIO_ANYTIME = "debug.bt.hfp.audio_anytime";
1856
1857        /** Mock signal strength change in ASU - use 0 to 31 */
1858        private static final String DEBUG_HANDSFREE_SIGNAL = "debug.bt.hfp.signal";
1859
1860        /** Debug AT+CLCC: print +CLCC result */
1861        private static final String DEBUG_HANDSFREE_CLCC = "debug.bt.hfp.clcc";
1862
1863        /** Debug AT+BSIR - Send In Band Ringtones Unsolicited AT command.
1864         * debug.bt.unsol.inband = 0 => AT+BSIR = 0 sent by the AG
1865         * debug.bt.unsol.inband = 1 => AT+BSIR = 0 sent by the AG
1866         * Other values are ignored.
1867         */
1868
1869        private static final String DEBUG_UNSOL_INBAND_RINGTONE =
1870            "debug.bt.unsol.inband";
1871
1872        @Override
1873        public void run() {
1874            boolean oldService = true;
1875            boolean oldRoam = false;
1876            boolean oldAudio = false;
1877
1878            while (!isInterrupted() && inDebug()) {
1879                int batteryLevel = SystemProperties.getInt(DEBUG_HANDSFREE_BATTERY, -1);
1880                if (batteryLevel >= 0 && batteryLevel <= 5) {
1881                    Intent intent = new Intent();
1882                    intent.putExtra("level", batteryLevel);
1883                    intent.putExtra("scale", 5);
1884                    mBluetoothPhoneState.updateBatteryState(intent);
1885                }
1886
1887                boolean serviceStateChanged = false;
1888                if (SystemProperties.getBoolean(DEBUG_HANDSFREE_SERVICE, true) != oldService) {
1889                    oldService = !oldService;
1890                    serviceStateChanged = true;
1891                }
1892                if (SystemProperties.getBoolean(DEBUG_HANDSFREE_ROAM, false) != oldRoam) {
1893                    oldRoam = !oldRoam;
1894                    serviceStateChanged = true;
1895                }
1896                if (serviceStateChanged) {
1897                    Bundle b = new Bundle();
1898                    b.putInt("state", oldService ? 0 : 1);
1899                    b.putBoolean("roaming", oldRoam);
1900                    mBluetoothPhoneState.updateServiceState(true, ServiceState.newFromBundle(b));
1901                }
1902
1903                if (SystemProperties.getBoolean(DEBUG_HANDSFREE_AUDIO, false) != oldAudio) {
1904                    oldAudio = !oldAudio;
1905                    if (oldAudio) {
1906                        audioOn();
1907                    } else {
1908                        audioOff();
1909                    }
1910                }
1911
1912                int signalLevel = SystemProperties.getInt(DEBUG_HANDSFREE_SIGNAL, -1);
1913                if (signalLevel >= 0 && signalLevel <= 31) {
1914                    SignalStrength signalStrength = new SignalStrength(signalLevel, -1, -1, -1,
1915                            -1, -1, -1, true);
1916                    Intent intent = new Intent();
1917                    Bundle data = new Bundle();
1918                    signalStrength.fillInNotifierBundle(data);
1919                    intent.putExtras(data);
1920                    mBluetoothPhoneState.updateSignalState(intent);
1921                }
1922
1923                if (SystemProperties.getBoolean(DEBUG_HANDSFREE_CLCC, false)) {
1924                    log(getClccResult().toString());
1925                }
1926                try {
1927                    sleep(1000);  // 1 second
1928                } catch (InterruptedException e) {
1929                    break;
1930                }
1931
1932                int inBandRing =
1933                    SystemProperties.getInt(DEBUG_UNSOL_INBAND_RINGTONE, -1);
1934                if (inBandRing == 0 || inBandRing == 1) {
1935                    AtCommandResult result =
1936                        new AtCommandResult(AtCommandResult.UNSOLICITED);
1937                    result.addResponse("+BSIR: " + inBandRing);
1938                    sendURC(result.toString());
1939                }
1940            }
1941        }
1942    }
1943
1944    private static void log(String msg) {
1945        Log.d(TAG, msg);
1946    }
1947}
1948
1949
1950
1951