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.internal.telephony.cdma;
18
19import android.app.AlarmManager;
20import android.content.ContentResolver;
21import android.content.Context;
22import android.content.Intent;
23import android.database.ContentObserver;
24import android.os.AsyncResult;
25import android.os.Handler;
26import android.os.Message;
27import android.os.PowerManager;
28import android.os.Registrant;
29import android.os.RegistrantList;
30import android.os.SystemClock;
31import android.os.SystemProperties;
32import android.provider.Settings;
33import android.provider.Settings.SettingNotFoundException;
34import android.provider.Telephony.Intents;
35import android.telephony.ServiceState;
36import android.telephony.SignalStrength;
37import android.telephony.cdma.CdmaCellLocation;
38import android.text.TextUtils;
39import android.util.EventLog;
40import android.util.Log;
41import android.util.Config;
42import android.util.TimeUtils;
43
44import com.android.internal.telephony.CommandException;
45import com.android.internal.telephony.CommandsInterface;
46import com.android.internal.telephony.DataConnectionTracker;
47import com.android.internal.telephony.EventLogTags;
48import com.android.internal.telephony.IccCard;
49import com.android.internal.telephony.MccTable;
50import com.android.internal.telephony.ServiceStateTracker;
51import com.android.internal.telephony.TelephonyIntents;
52import com.android.internal.telephony.TelephonyProperties;
53
54import java.util.Arrays;
55import java.util.Calendar;
56import java.util.Date;
57import java.util.TimeZone;
58
59/**
60 * {@hide}
61 */
62final class CdmaServiceStateTracker extends ServiceStateTracker {
63    static final String LOG_TAG = "CDMA";
64
65    CDMAPhone phone;
66    CdmaCellLocation cellLoc;
67    CdmaCellLocation newCellLoc;
68
69     /** if time between NITZ updates is less than mNitzUpdateSpacing the update may be ignored. */
70    private static final int NITZ_UPDATE_SPACING_DEFAULT = 1000 * 60 * 10;
71    private int mNitzUpdateSpacing = SystemProperties.getInt("ro.nitz_update_spacing",
72            NITZ_UPDATE_SPACING_DEFAULT);
73
74    /** If mNitzUpdateSpacing hasn't been exceeded but update is > mNitzUpdate do the update */
75    private static final int NITZ_UPDATE_DIFF_DEFAULT = 2000;
76    private int mNitzUpdateDiff = SystemProperties.getInt("ro.nitz_update_diff",
77            NITZ_UPDATE_DIFF_DEFAULT);
78
79    /**
80     *  Values correspond to ServiceStateTracker.DATA_ACCESS_ definitions.
81     */
82    private int networkType = 0;
83    private int newNetworkType = 0;
84
85    private boolean mCdmaRoaming = false;
86    private int mRoamingIndicator;
87    private boolean mIsInPrl;
88    private int mDefaultRoamingIndicator;
89
90    /**
91     * Initially assume no data connection.
92     */
93    private int cdmaDataConnectionState = ServiceState.STATE_OUT_OF_SERVICE;
94    private int newCdmaDataConnectionState = ServiceState.STATE_OUT_OF_SERVICE;
95    private int mRegistrationState = -1;
96    private RegistrantList cdmaDataConnectionAttachedRegistrants = new RegistrantList();
97    private RegistrantList cdmaDataConnectionDetachedRegistrants = new RegistrantList();
98    private RegistrantList cdmaForSubscriptionInfoReadyRegistrants = new RegistrantList();
99
100    /**
101     * Sometimes we get the NITZ time before we know what country we
102     * are in. Keep the time zone information from the NITZ string so
103     * we can fix the time zone once know the country.
104     */
105    private boolean mNeedFixZone = false;
106    private int mZoneOffset;
107    private boolean mZoneDst;
108    private long mZoneTime;
109    private boolean mGotCountryCode = false;
110    String mSavedTimeZone;
111    long mSavedTime;
112    long mSavedAtTime;
113
114    /**
115     * We can't register for SIM_RECORDS_LOADED immediately because the
116     * SIMRecords object may not be instantiated yet.
117     */
118    private boolean mNeedToRegForRuimLoaded = false;
119
120    /** Wake lock used while setting time of day. */
121    private PowerManager.WakeLock mWakeLock;
122    private static final String WAKELOCK_TAG = "ServiceStateTracker";
123
124    /** Track of SPN display rules, so we only broadcast intent if something changes. */
125    private String curSpn = null;
126    private int curSpnRule = 0;
127
128    /** Contains the name of the registered network in CDMA (either ONS or ERI text). */
129    private String curPlmn = null;
130
131    private String mMdn;
132    private int mHomeSystemId[] = null;
133    private int mHomeNetworkId[] = null;
134    private String mMin;
135    private String mPrlVersion;
136    private boolean mIsMinInfoReady = false;
137
138    private boolean isEriTextLoaded = false;
139    private boolean isSubscriptionFromRuim = false;
140
141    private boolean mPendingRadioPowerOffAfterDataOff = false;
142
143    /* Used only for debugging purposes. */
144    private String mRegistrationDeniedReason;
145
146    private ContentResolver cr;
147    private String currentCarrier = null;
148
149    private ContentObserver mAutoTimeObserver = new ContentObserver(new Handler()) {
150        @Override
151        public void onChange(boolean selfChange) {
152            Log.i("CdmaServiceStateTracker", "Auto time state called ...");
153            revertToNitz();
154
155        }
156    };
157
158    public CdmaServiceStateTracker(CDMAPhone phone) {
159        super();
160
161        this.phone = phone;
162        cr = phone.getContext().getContentResolver();
163        cm = phone.mCM;
164        ss = new ServiceState();
165        newSS = new ServiceState();
166        cellLoc = new CdmaCellLocation();
167        newCellLoc = new CdmaCellLocation();
168        mSignalStrength = new SignalStrength();
169
170        PowerManager powerManager =
171                (PowerManager)phone.getContext().getSystemService(Context.POWER_SERVICE);
172        mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
173
174        cm.registerForAvailable(this, EVENT_RADIO_AVAILABLE, null);
175        cm.registerForRadioStateChanged(this, EVENT_RADIO_STATE_CHANGED, null);
176
177        cm.registerForNetworkStateChanged(this, EVENT_NETWORK_STATE_CHANGED_CDMA, null);
178        cm.setOnNITZTime(this, EVENT_NITZ_TIME, null);
179        cm.setOnSignalStrengthUpdate(this, EVENT_SIGNAL_STRENGTH_UPDATE, null);
180
181        cm.registerForRUIMReady(this, EVENT_RUIM_READY, null);
182
183        cm.registerForNVReady(this, EVENT_NV_READY, null);
184        phone.registerForEriFileLoaded(this, EVENT_ERI_FILE_LOADED, null);
185        cm.registerForCdmaOtaProvision(this,EVENT_OTA_PROVISION_STATUS_CHANGE, null);
186
187        // System setting property AIRPLANE_MODE_ON is set in Settings.
188        int airplaneMode = Settings.System.getInt(cr, Settings.System.AIRPLANE_MODE_ON, 0);
189        mDesiredPowerState = ! (airplaneMode > 0);
190
191        cr.registerContentObserver(
192                Settings.System.getUriFor(Settings.System.AUTO_TIME), true,
193                mAutoTimeObserver);
194        setSignalStrengthDefaultValues();
195
196        mNeedToRegForRuimLoaded = true;
197    }
198
199    public void dispose() {
200        // Unregister for all events.
201        cm.unregisterForAvailable(this);
202        cm.unregisterForRadioStateChanged(this);
203        cm.unregisterForNetworkStateChanged(this);
204        cm.unregisterForRUIMReady(this);
205        cm.unregisterForNVReady(this);
206        cm.unregisterForCdmaOtaProvision(this);
207        phone.unregisterForEriFileLoaded(this);
208        phone.mRuimRecords.unregisterForRecordsLoaded(this);
209        cm.unSetOnSignalStrengthUpdate(this);
210        cm.unSetOnNITZTime(this);
211        cr.unregisterContentObserver(this.mAutoTimeObserver);
212    }
213
214    @Override
215    protected void finalize() {
216        if (DBG) log("CdmaServiceStateTracker finalized");
217    }
218
219    void registerForNetworkAttach(Handler h, int what, Object obj) {
220        Registrant r = new Registrant(h, what, obj);
221        networkAttachedRegistrants.add(r);
222
223        if (ss.getState() == ServiceState.STATE_IN_SERVICE) {
224            r.notifyRegistrant();
225        }
226    }
227
228    void unregisterForNetworkAttach(Handler h) {
229        networkAttachedRegistrants.remove(h);
230    }
231
232    /**
233     * Registration point for transition into Data attached.
234     * @param h handler to notify
235     * @param what what code of message when delivered
236     * @param obj placed in Message.obj
237     */
238    void registerForCdmaDataConnectionAttached(Handler h, int what, Object obj) {
239        Registrant r = new Registrant(h, what, obj);
240        cdmaDataConnectionAttachedRegistrants.add(r);
241
242        if (cdmaDataConnectionState == ServiceState.STATE_IN_SERVICE) {
243            r.notifyRegistrant();
244        }
245    }
246
247    void unregisterForCdmaDataConnectionAttached(Handler h) {
248        cdmaDataConnectionAttachedRegistrants.remove(h);
249    }
250
251    /**
252     * Registration point for transition into Data detached.
253     * @param h handler to notify
254     * @param what what code of message when delivered
255     * @param obj placed in Message.obj
256     */
257    void registerForCdmaDataConnectionDetached(Handler h, int what, Object obj) {
258        Registrant r = new Registrant(h, what, obj);
259        cdmaDataConnectionDetachedRegistrants.add(r);
260
261        if (cdmaDataConnectionState != ServiceState.STATE_IN_SERVICE) {
262            r.notifyRegistrant();
263        }
264    }
265
266    void unregisterForCdmaDataConnectionDetached(Handler h) {
267        cdmaDataConnectionDetachedRegistrants.remove(h);
268    }
269
270    /**
271     * Registration point for subscription info ready
272     * @param h handler to notify
273     * @param what what code of message when delivered
274     * @param obj placed in Message.obj
275     */
276    public void registerForSubscriptionInfoReady(Handler h, int what, Object obj) {
277        Registrant r = new Registrant(h, what, obj);
278        cdmaForSubscriptionInfoReadyRegistrants.add(r);
279
280        if (isMinInfoReady()) {
281            r.notifyRegistrant();
282        }
283    }
284
285    public void unregisterForSubscriptionInfoReady(Handler h) {
286        cdmaForSubscriptionInfoReadyRegistrants.remove(h);
287    }
288
289    @Override
290    public void handleMessage (Message msg) {
291        AsyncResult ar;
292        int[] ints;
293        String[] strings;
294
295        switch (msg.what) {
296        case EVENT_RADIO_AVAILABLE:
297            break;
298
299        case EVENT_RUIM_READY:
300            // The RUIM is now ready i.e if it was locked it has been
301            // unlocked. At this stage, the radio is already powered on.
302            isSubscriptionFromRuim = true;
303            if (mNeedToRegForRuimLoaded) {
304                phone.mRuimRecords.registerForRecordsLoaded(this,
305                        EVENT_RUIM_RECORDS_LOADED, null);
306                mNeedToRegForRuimLoaded = false;
307            }
308
309            cm.getCDMASubscription(obtainMessage(EVENT_POLL_STATE_CDMA_SUBSCRIPTION));
310            if (DBG) log("Receive EVENT_RUIM_READY and Send Request getCDMASubscription.");
311
312            // Restore the previous network selection.
313            pollState();
314
315            // Signal strength polling stops when radio is off.
316            queueNextSignalStrengthPoll();
317            break;
318
319        case EVENT_NV_READY:
320            isSubscriptionFromRuim = false;
321            // For Non-RUIM phones, the subscription information is stored in
322            // Non Volatile. Here when Non-Volatile is ready, we can poll the CDMA
323            // subscription info.
324            cm.getCDMASubscription( obtainMessage(EVENT_POLL_STATE_CDMA_SUBSCRIPTION));
325            pollState();
326            // Signal strength polling stops when radio is off.
327            queueNextSignalStrengthPoll();
328            break;
329
330        case EVENT_RADIO_STATE_CHANGED:
331            // This will do nothing in the 'radio not available' case.
332            setPowerStateToDesired();
333            pollState();
334            break;
335
336        case EVENT_NETWORK_STATE_CHANGED_CDMA:
337            pollState();
338            break;
339
340        case EVENT_GET_SIGNAL_STRENGTH:
341            // This callback is called when signal strength is polled
342            // all by itself.
343
344            if (!(cm.getRadioState().isOn()) || (cm.getRadioState().isGsm())) {
345                // Polling will continue when radio turns back on.
346                return;
347            }
348            ar = (AsyncResult) msg.obj;
349            onSignalStrengthResult(ar);
350            queueNextSignalStrengthPoll();
351
352            break;
353
354        case EVENT_GET_LOC_DONE_CDMA:
355            ar = (AsyncResult) msg.obj;
356
357            if (ar.exception == null) {
358                String states[] = (String[])ar.result;
359                int baseStationId = -1;
360                int baseStationLatitude = CdmaCellLocation.INVALID_LAT_LONG;
361                int baseStationLongitude = CdmaCellLocation.INVALID_LAT_LONG;
362                int systemId = -1;
363                int networkId = -1;
364
365                if (states.length > 9) {
366                    try {
367                        if (states[4] != null) {
368                            baseStationId = Integer.parseInt(states[4]);
369                        }
370                        if (states[5] != null) {
371                            baseStationLatitude = Integer.parseInt(states[5]);
372                        }
373                        if (states[6] != null) {
374                            baseStationLongitude = Integer.parseInt(states[6]);
375                        }
376                        // Some carriers only return lat-lngs of 0,0
377                        if (baseStationLatitude == 0 && baseStationLongitude == 0) {
378                            baseStationLatitude  = CdmaCellLocation.INVALID_LAT_LONG;
379                            baseStationLongitude = CdmaCellLocation.INVALID_LAT_LONG;
380                        }
381                        if (states[8] != null) {
382                            systemId = Integer.parseInt(states[8]);
383                        }
384                        if (states[9] != null) {
385                            networkId = Integer.parseInt(states[9]);
386                        }
387                    } catch (NumberFormatException ex) {
388                        Log.w(LOG_TAG, "error parsing cell location data: " + ex);
389                    }
390                }
391
392                cellLoc.setCellLocationData(baseStationId, baseStationLatitude,
393                        baseStationLongitude, systemId, networkId);
394                phone.notifyLocationChanged();
395            }
396
397            // Release any temporary cell lock, which could have been
398            // acquired to allow a single-shot location update.
399            disableSingleLocationUpdate();
400            break;
401
402        case EVENT_POLL_STATE_REGISTRATION_CDMA:
403        case EVENT_POLL_STATE_OPERATOR_CDMA:
404            ar = (AsyncResult) msg.obj;
405            handlePollStateResult(msg.what, ar);
406            break;
407
408        case EVENT_POLL_STATE_CDMA_SUBSCRIPTION: // Handle RIL_CDMA_SUBSCRIPTION
409            ar = (AsyncResult) msg.obj;
410
411            if (ar.exception == null) {
412                String cdmaSubscription[] = (String[])ar.result;
413                if (cdmaSubscription != null && cdmaSubscription.length >= 5) {
414                    mMdn = cdmaSubscription[0];
415                    if (cdmaSubscription[1] != null) {
416                        String[] sid = cdmaSubscription[1].split(",");
417                        mHomeSystemId = new int[sid.length];
418                        for (int i = 0; i < sid.length; i++) {
419                            try {
420                                mHomeSystemId[i] = Integer.parseInt(sid[i]);
421                            } catch (NumberFormatException ex) {
422                                Log.e(LOG_TAG, "error parsing system id: ", ex);
423                            }
424                        }
425                    }
426                    Log.d(LOG_TAG,"GET_CDMA_SUBSCRIPTION SID=" + cdmaSubscription[1] );
427
428                    if (cdmaSubscription[2] != null) {
429                        String[] nid = cdmaSubscription[2].split(",");
430                        mHomeNetworkId = new int[nid.length];
431                        for (int i = 0; i < nid.length; i++) {
432                            try {
433                                mHomeNetworkId[i] = Integer.parseInt(nid[i]);
434                            } catch (NumberFormatException ex) {
435                                Log.e(LOG_TAG, "error parsing network id: ", ex);
436                            }
437                        }
438                    }
439                    Log.d(LOG_TAG,"GET_CDMA_SUBSCRIPTION NID=" + cdmaSubscription[2] );
440                    mMin = cdmaSubscription[3];
441                    mPrlVersion = cdmaSubscription[4];
442                    Log.d(LOG_TAG,"GET_CDMA_SUBSCRIPTION MDN=" + mMdn);
443                    //Notify apps subscription info is ready
444                    if (cdmaForSubscriptionInfoReadyRegistrants != null) {
445                        cdmaForSubscriptionInfoReadyRegistrants.notifyRegistrants();
446                    }
447                    if (!mIsMinInfoReady) {
448                        mIsMinInfoReady = true;
449                    }
450                    phone.getIccCard().broadcastIccStateChangedIntent(IccCard.INTENT_VALUE_ICC_IMSI,
451                            null);
452                } else {
453                    Log.w(LOG_TAG,"error parsing cdmaSubscription params num="
454                            + cdmaSubscription.length);
455                }
456            }
457            break;
458
459        case EVENT_POLL_SIGNAL_STRENGTH:
460            // Just poll signal strength...not part of pollState()
461
462            cm.getSignalStrength(obtainMessage(EVENT_GET_SIGNAL_STRENGTH));
463            break;
464
465        case EVENT_NITZ_TIME:
466            ar = (AsyncResult) msg.obj;
467
468            String nitzString = (String)((Object[])ar.result)[0];
469            long nitzReceiveTime = ((Long)((Object[])ar.result)[1]).longValue();
470
471            setTimeFromNITZString(nitzString, nitzReceiveTime);
472            break;
473
474        case EVENT_SIGNAL_STRENGTH_UPDATE:
475            // This is a notification from CommandsInterface.setOnSignalStrengthUpdate.
476
477            ar = (AsyncResult) msg.obj;
478
479            // The radio is telling us about signal strength changes,
480            // so we don't have to ask it.
481            dontPollSignalStrength = true;
482
483            onSignalStrengthResult(ar);
484            break;
485
486        case EVENT_RUIM_RECORDS_LOADED:
487            updateSpnDisplay();
488            break;
489
490        case EVENT_LOCATION_UPDATES_ENABLED:
491            ar = (AsyncResult) msg.obj;
492
493            if (ar.exception == null) {
494                cm.getRegistrationState(obtainMessage(EVENT_GET_LOC_DONE_CDMA, null));
495            }
496            break;
497
498        case EVENT_ERI_FILE_LOADED:
499            // Repoll the state once the ERI file has been loaded.
500            if (DBG) log("[CdmaServiceStateTracker] ERI file has been loaded, repolling.");
501            pollState();
502            break;
503
504        case EVENT_OTA_PROVISION_STATUS_CHANGE:
505            ar = (AsyncResult)msg.obj;
506            if (ar.exception == null) {
507                ints = (int[]) ar.result;
508                int otaStatus = ints[0];
509                if (otaStatus == phone.CDMA_OTA_PROVISION_STATUS_COMMITTED
510                    || otaStatus == phone.CDMA_OTA_PROVISION_STATUS_OTAPA_STOPPED) {
511                    Log.d(LOG_TAG, "Received OTA_PROGRAMMING Complete,Reload MDN ");
512                    cm.getCDMASubscription( obtainMessage(EVENT_POLL_STATE_CDMA_SUBSCRIPTION));
513                }
514            }
515            break;
516
517        case EVENT_SET_RADIO_POWER_OFF:
518            synchronized(this) {
519                if (mPendingRadioPowerOffAfterDataOff) {
520                    if (DBG) log("EVENT_SET_RADIO_OFF, turn radio off now.");
521                    hangupAndPowerOff();
522                    mPendingRadioPowerOffAfterDataOff = false;
523                }
524            }
525            break;
526
527        default:
528            Log.e(LOG_TAG, "Unhandled message with number: " + msg.what);
529        break;
530        }
531    }
532
533    //***** Private Instance Methods
534
535    @Override
536    protected void setPowerStateToDesired() {
537        // If we want it on and it's off, turn it on
538        if (mDesiredPowerState
539            && cm.getRadioState() == CommandsInterface.RadioState.RADIO_OFF) {
540            cm.setRadioPower(true, null);
541        } else if (!mDesiredPowerState && cm.getRadioState().isOn()) {
542            DataConnectionTracker dcTracker = phone.mDataConnection;
543            if (! dcTracker.isDataConnectionAsDesired()) {
544                EventLog.writeEvent(EventLogTags.DATA_NETWORK_STATUS_ON_RADIO_OFF,
545                        dcTracker.getStateInString(),
546                        dcTracker.getAnyDataEnabled() ? 1 : 0);
547            }
548
549            // If it's on and available and we want it off gracefully
550            powerOffRadioSafely();
551        } // Otherwise, we're in the desired state
552    }
553
554    @Override
555    protected void powerOffRadioSafely(){
556        // clean data connection
557        DataConnectionTracker dcTracker = phone.mDataConnection;
558
559        Message msg = dcTracker.obtainMessage(DataConnectionTracker.EVENT_CLEAN_UP_CONNECTION);
560        msg.arg1 = 1; // tearDown is true
561        msg.obj = CDMAPhone.REASON_RADIO_TURNED_OFF;
562        dcTracker.sendMessage(msg);
563
564        synchronized(this) {
565            if (!mPendingRadioPowerOffAfterDataOff) {
566                DataConnectionTracker.State currentState = dcTracker.getState();
567                if (currentState != DataConnectionTracker.State.CONNECTED
568                        && currentState != DataConnectionTracker.State.DISCONNECTING
569                        && currentState != DataConnectionTracker.State.INITING) {
570                    if (DBG) log("Data disconnected, turn off radio right away.");
571                    hangupAndPowerOff();
572                }
573                else if (sendEmptyMessageDelayed(EVENT_SET_RADIO_POWER_OFF, 30000)) {
574                    if (DBG) {
575                        log("Wait up to 30 sec for data to disconnect, then turn off radio.");
576                    }
577                    mPendingRadioPowerOffAfterDataOff = true;
578                } else {
579                    Log.w(LOG_TAG, "Cannot send delayed Msg, turn off radio right away.");
580                    hangupAndPowerOff();
581                }
582            }
583        }
584    }
585
586    @Override
587    protected void updateSpnDisplay() {
588        String spn = "";
589        boolean showSpn = false;
590        String plmn = "";
591        boolean showPlmn = false;
592        int rule = 0;
593        if (cm.getRadioState().isRUIMReady()) {
594            // TODO RUIM SPN is not implemented, EF_SPN has to be read and Display Condition
595            //   Character Encoding, Language Indicator and SPN has to be set
596            // rule = phone.mRuimRecords.getDisplayRule(ss.getOperatorNumeric());
597            // spn = phone.mSIMRecords.getServiceProvideName();
598            plmn = ss.getOperatorAlphaLong(); // mOperatorAlphaLong contains the ONS
599            // showSpn = (rule & ...
600            showPlmn = true; // showPlmn = (rule & ...
601
602        } else {
603            // In this case there is no SPN available from RUIM, we show the ERI text
604            plmn = ss.getOperatorAlphaLong(); // mOperatorAlphaLong contains the ERI text
605            showPlmn = true;
606        }
607
608        if (rule != curSpnRule
609                || !TextUtils.equals(spn, curSpn)
610                || !TextUtils.equals(plmn, curPlmn)) {
611            Intent intent = new Intent(Intents.SPN_STRINGS_UPDATED_ACTION);
612            intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
613            intent.putExtra(Intents.EXTRA_SHOW_SPN, showSpn);
614            intent.putExtra(Intents.EXTRA_SPN, spn);
615            intent.putExtra(Intents.EXTRA_SHOW_PLMN, showPlmn);
616            intent.putExtra(Intents.EXTRA_PLMN, plmn);
617            phone.getContext().sendStickyBroadcast(intent);
618        }
619
620        curSpnRule = rule;
621        curSpn = spn;
622        curPlmn = plmn;
623    }
624
625    /**
626     * Handle the result of one of the pollState()-related requests
627     */
628
629    @Override
630    protected void handlePollStateResult (int what, AsyncResult ar) {
631        int ints[];
632        String states[];
633
634        // Ignore stale requests from last poll.
635        if (ar.userObj != pollingContext) return;
636
637        if (ar.exception != null) {
638            CommandException.Error err=null;
639
640            if (ar.exception instanceof CommandException) {
641                err = ((CommandException)(ar.exception)).getCommandError();
642            }
643
644            if (err == CommandException.Error.RADIO_NOT_AVAILABLE) {
645                // Radio has crashed or turned off.
646                cancelPollState();
647                return;
648            }
649
650            if (!cm.getRadioState().isOn()) {
651                // Radio has crashed or turned off.
652                cancelPollState();
653                return;
654            }
655
656            if (err != CommandException.Error.OP_NOT_ALLOWED_BEFORE_REG_NW &&
657                    err != CommandException.Error.OP_NOT_ALLOWED_BEFORE_REG_NW) {
658                Log.e(LOG_TAG,
659                        "RIL implementation has returned an error where it must succeed",
660                        ar.exception);
661            }
662        } else try {
663            switch (what) {
664            case EVENT_POLL_STATE_REGISTRATION_CDMA: // Handle RIL_REQUEST_REGISTRATION_STATE.
665                states = (String[])ar.result;
666
667                int registrationState = 4;     //[0] registrationState
668                int radioTechnology = -1;      //[3] radioTechnology
669                int baseStationId = -1;        //[4] baseStationId
670                //[5] baseStationLatitude
671                int baseStationLatitude = CdmaCellLocation.INVALID_LAT_LONG;
672                //[6] baseStationLongitude
673                int baseStationLongitude = CdmaCellLocation.INVALID_LAT_LONG;
674                int cssIndicator = 0;          //[7] init with 0, because it is treated as a boolean
675                int systemId = 0;              //[8] systemId
676                int networkId = 0;             //[9] networkId
677                int roamingIndicator = -1;     //[10] Roaming indicator
678                int systemIsInPrl = 0;         //[11] Indicates if current system is in PRL
679                int defaultRoamingIndicator = 0;  //[12] Is default roaming indicator from PRL
680                int reasonForDenial = 0;       //[13] Denial reason if registrationState = 3
681
682                if (states.length == 14) {
683                    try {
684                        if (states[0] != null) {
685                            registrationState = Integer.parseInt(states[0]);
686                        }
687                        if (states[3] != null) {
688                            radioTechnology = Integer.parseInt(states[3]);
689                        }
690                        if (states[4] != null) {
691                            baseStationId = Integer.parseInt(states[4]);
692                        }
693                        if (states[5] != null) {
694                            baseStationLatitude = Integer.parseInt(states[5]);
695                        }
696                        if (states[6] != null) {
697                            baseStationLongitude = Integer.parseInt(states[6]);
698                        }
699                        // Some carriers only return lat-lngs of 0,0
700                        if (baseStationLatitude == 0 && baseStationLongitude == 0) {
701                            baseStationLatitude  = CdmaCellLocation.INVALID_LAT_LONG;
702                            baseStationLongitude = CdmaCellLocation.INVALID_LAT_LONG;
703                        }
704                        if (states[7] != null) {
705                            cssIndicator = Integer.parseInt(states[7]);
706                        }
707                        if (states[8] != null) {
708                            systemId = Integer.parseInt(states[8]);
709                        }
710                        if (states[9] != null) {
711                            networkId = Integer.parseInt(states[9]);
712                        }
713                        if (states[10] != null) {
714                            roamingIndicator = Integer.parseInt(states[10]);
715                        }
716                        if (states[11] != null) {
717                            systemIsInPrl = Integer.parseInt(states[11]);
718                        }
719                        if (states[12] != null) {
720                            defaultRoamingIndicator = Integer.parseInt(states[12]);
721                        }
722                        if (states[13] != null) {
723                            reasonForDenial = Integer.parseInt(states[13]);
724                        }
725                    } catch (NumberFormatException ex) {
726                        Log.w(LOG_TAG, "error parsing RegistrationState: " + ex);
727                    }
728                } else {
729                    throw new RuntimeException("Warning! Wrong number of parameters returned from "
730                                         + "RIL_REQUEST_REGISTRATION_STATE: expected 14 got "
731                                         + states.length);
732                }
733
734                mRegistrationState = registrationState;
735                // When registration state is roaming and TSB58
736                // roaming indicator is not in the carrier-specified
737                // list of ERIs for home system, mCdmaRoaming is true.
738                mCdmaRoaming =
739                        regCodeIsRoaming(registrationState) && !isRoamIndForHomeSystem(states[10]);
740                newSS.setState (regCodeToServiceState(registrationState));
741
742                this.newCdmaDataConnectionState =
743                        radioTechnologyToDataServiceState(radioTechnology);
744                newSS.setRadioTechnology(radioTechnology);
745                newNetworkType = radioTechnology;
746
747                newSS.setCssIndicator(cssIndicator);
748                newSS.setSystemAndNetworkId(systemId, networkId);
749                mRoamingIndicator = roamingIndicator;
750                mIsInPrl = (systemIsInPrl == 0) ? false : true;
751                mDefaultRoamingIndicator = defaultRoamingIndicator;
752
753
754                // Values are -1 if not available.
755                newCellLoc.setCellLocationData(baseStationId, baseStationLatitude,
756                        baseStationLongitude, systemId, networkId);
757
758                if (reasonForDenial == 0) {
759                    mRegistrationDeniedReason = ServiceStateTracker.REGISTRATION_DENIED_GEN;
760                } else if (reasonForDenial == 1) {
761                    mRegistrationDeniedReason = ServiceStateTracker.REGISTRATION_DENIED_AUTH;
762                } else {
763                    mRegistrationDeniedReason = "";
764                }
765
766                if (mRegistrationState == 3) {
767                    if (DBG) log("Registration denied, " + mRegistrationDeniedReason);
768                }
769                break;
770
771            case EVENT_POLL_STATE_OPERATOR_CDMA: // Handle RIL_REQUEST_OPERATOR
772                String opNames[] = (String[])ar.result;
773
774                if (opNames != null && opNames.length >= 3) {
775                    if (cm.getRadioState().isNVReady()) {
776                        // In CDMA in case on NV, the ss.mOperatorAlphaLong is set later with the
777                        // ERI text, so here it is ignored what is coming from the modem.
778                        newSS.setOperatorName(null, opNames[1], opNames[2]);
779                    } else {
780                        newSS.setOperatorName(opNames[0], opNames[1], opNames[2]);
781                    }
782                } else {
783                    Log.w(LOG_TAG, "error parsing opNames");
784                }
785                break;
786
787            default:
788                Log.e(LOG_TAG, "RIL response handle in wrong phone!"
789                    + " Expected CDMA RIL request and get GSM RIL request.");
790            break;
791            }
792
793        } catch (RuntimeException ex) {
794            Log.e(LOG_TAG, "Exception while polling service state. "
795                    + "Probably malformed RIL response.", ex);
796        }
797
798        pollingContext[0]--;
799
800        if (pollingContext[0] == 0) {
801            boolean namMatch = false;
802            if (!isSidsAllZeros() && isHomeSid(newSS.getSystemId())) {
803                namMatch = true;
804            }
805
806            // Setting SS Roaming (general)
807            if (isSubscriptionFromRuim) {
808                newSS.setRoaming(isRoamingBetweenOperators(mCdmaRoaming, newSS));
809            } else {
810                newSS.setRoaming(mCdmaRoaming);
811            }
812
813            // Setting SS CdmaRoamingIndicator and CdmaDefaultRoamingIndicator
814            newSS.setCdmaDefaultRoamingIndicator(mDefaultRoamingIndicator);
815            newSS.setCdmaRoamingIndicator(mRoamingIndicator);
816            boolean isPrlLoaded = true;
817            if (TextUtils.isEmpty(mPrlVersion)) {
818                isPrlLoaded = false;
819            }
820            if (!isPrlLoaded) {
821                newSS.setCdmaRoamingIndicator(EriInfo.ROAMING_INDICATOR_FLASH);
822            } else if (!isSidsAllZeros()) {
823                if (!namMatch && !mIsInPrl) {
824                    // Use default
825                    newSS.setCdmaRoamingIndicator(mDefaultRoamingIndicator);
826                } else if (namMatch && !mIsInPrl) {
827                    newSS.setCdmaRoamingIndicator(EriInfo.ROAMING_INDICATOR_FLASH);
828                } else if (!namMatch && mIsInPrl) {
829                    // Use the one from PRL/ERI
830                    newSS.setCdmaRoamingIndicator(mRoamingIndicator);
831                } else {
832                    // It means namMatch && mIsInPrl
833                    if ((mRoamingIndicator <= 2)) {
834                        newSS.setCdmaRoamingIndicator(EriInfo.ROAMING_INDICATOR_OFF);
835                    } else {
836                        // Use the one from PRL/ERI
837                        newSS.setCdmaRoamingIndicator(mRoamingIndicator);
838                    }
839                }
840            }
841
842            int roamingIndicator = newSS.getCdmaRoamingIndicator();
843            newSS.setCdmaEriIconIndex(phone.mEriManager.getCdmaEriIconIndex(roamingIndicator,
844                    mDefaultRoamingIndicator));
845            newSS.setCdmaEriIconMode(phone.mEriManager.getCdmaEriIconMode(roamingIndicator,
846                    mDefaultRoamingIndicator));
847
848            // NOTE: Some operator may require overriding mCdmaRoaming
849            // (set by the modem), depending on the mRoamingIndicator.
850
851            if (DBG) {
852                log("Set CDMA Roaming Indicator to: " + newSS.getCdmaRoamingIndicator()
853                    + ". mCdmaRoaming = " + mCdmaRoaming + ", isPrlLoaded = " + isPrlLoaded
854                    + ". namMatch = " + namMatch + " , mIsInPrl = " + mIsInPrl
855                    + ", mRoamingIndicator = " + mRoamingIndicator
856                    + ", mDefaultRoamingIndicator= " + mDefaultRoamingIndicator);
857            }
858            pollStateDone();
859        }
860
861    }
862
863    private void setSignalStrengthDefaultValues() {
864        mSignalStrength = new SignalStrength(99, -1, -1, -1, -1, -1, -1, false);
865    }
866
867    /**
868     * A complete "service state" from our perspective is
869     * composed of a handful of separate requests to the radio.
870     *
871     * We make all of these requests at once, but then abandon them
872     * and start over again if the radio notifies us that some
873     * event has changed
874     */
875    private void
876    pollState() {
877        pollingContext = new int[1];
878        pollingContext[0] = 0;
879
880        switch (cm.getRadioState()) {
881        case RADIO_UNAVAILABLE:
882            newSS.setStateOutOfService();
883            newCellLoc.setStateInvalid();
884            setSignalStrengthDefaultValues();
885            mGotCountryCode = false;
886
887            pollStateDone();
888            break;
889
890        case RADIO_OFF:
891            newSS.setStateOff();
892            newCellLoc.setStateInvalid();
893            setSignalStrengthDefaultValues();
894            mGotCountryCode = false;
895
896            pollStateDone();
897            break;
898
899        case SIM_NOT_READY:
900        case SIM_LOCKED_OR_ABSENT:
901        case SIM_READY:
902            log("Radio Technology Change ongoing, setting SS to off");
903            newSS.setStateOff();
904            newCellLoc.setStateInvalid();
905            setSignalStrengthDefaultValues();
906            mGotCountryCode = false;
907
908            // NOTE: pollStateDone() is not needed in this case
909            break;
910
911        default:
912            // Issue all poll-related commands at once, then count
913            // down the responses which are allowed to arrive
914            // out-of-order.
915
916            pollingContext[0]++;
917            // RIL_REQUEST_OPERATOR is necessary for CDMA
918            cm.getOperator(
919                    obtainMessage(EVENT_POLL_STATE_OPERATOR_CDMA, pollingContext));
920
921            pollingContext[0]++;
922            // RIL_REQUEST_REGISTRATION_STATE is necessary for CDMA
923            cm.getRegistrationState(
924                    obtainMessage(EVENT_POLL_STATE_REGISTRATION_CDMA, pollingContext));
925
926            break;
927        }
928    }
929
930    private static String networkTypeToString(int type) {
931        String ret = "unknown";
932
933        switch (type) {
934        case DATA_ACCESS_CDMA_IS95A:
935        case DATA_ACCESS_CDMA_IS95B:
936            ret = "CDMA";
937            break;
938        case DATA_ACCESS_CDMA_1xRTT:
939            ret = "CDMA - 1xRTT";
940            break;
941        case DATA_ACCESS_CDMA_EvDo_0:
942            ret = "CDMA - EvDo rev. 0";
943            break;
944        case DATA_ACCESS_CDMA_EvDo_A:
945            ret = "CDMA - EvDo rev. A";
946            break;
947        case DATA_ACCESS_CDMA_EvDo_B:
948            ret = "CDMA - EvDo rev. B";
949            break;
950        default:
951            if (DBG) {
952                Log.e(LOG_TAG, "Wrong network. Can not return a string.");
953            }
954        break;
955        }
956
957        return ret;
958    }
959
960    private void fixTimeZone(String isoCountryCode) {
961        TimeZone zone = null;
962        // If the offset is (0, false) and the time zone property
963        // is set, use the time zone property rather than GMT.
964        String zoneName = SystemProperties.get(TIMEZONE_PROPERTY);
965        if ((mZoneOffset == 0) && (mZoneDst == false) && (zoneName != null)
966                && (zoneName.length() > 0)
967                && (Arrays.binarySearch(GMT_COUNTRY_CODES, isoCountryCode) < 0)) {
968            // For NITZ string without time zone,
969            // need adjust time to reflect default time zone setting
970            zone = TimeZone.getDefault();
971            long tzOffset;
972            tzOffset = zone.getOffset(System.currentTimeMillis());
973            if (getAutoTime()) {
974                setAndBroadcastNetworkSetTime(System.currentTimeMillis() - tzOffset);
975            } else {
976                // Adjust the saved NITZ time to account for tzOffset.
977                mSavedTime = mSavedTime - tzOffset;
978            }
979        } else if (isoCountryCode.equals("")) {
980            // Country code not found. This is likely a test network.
981            // Get a TimeZone based only on the NITZ parameters (best guess).
982            zone = getNitzTimeZone(mZoneOffset, mZoneDst, mZoneTime);
983        } else {
984            zone = TimeUtils.getTimeZone(mZoneOffset, mZoneDst, mZoneTime, isoCountryCode);
985        }
986
987        mNeedFixZone = false;
988
989        if (zone != null) {
990            if (getAutoTime()) {
991                setAndBroadcastNetworkSetTimeZone(zone.getID());
992            }
993            saveNitzTimeZone(zone.getID());
994        }
995    }
996
997    private void pollStateDone() {
998        if (DBG) log("Poll ServiceState done: oldSS=[" + ss + "] newSS=[" + newSS + "]");
999
1000        boolean hasRegistered =
1001            ss.getState() != ServiceState.STATE_IN_SERVICE
1002            && newSS.getState() == ServiceState.STATE_IN_SERVICE;
1003
1004        boolean hasDeregistered =
1005            ss.getState() == ServiceState.STATE_IN_SERVICE
1006            && newSS.getState() != ServiceState.STATE_IN_SERVICE;
1007
1008        boolean hasCdmaDataConnectionAttached =
1009            this.cdmaDataConnectionState != ServiceState.STATE_IN_SERVICE
1010            && this.newCdmaDataConnectionState == ServiceState.STATE_IN_SERVICE;
1011
1012        boolean hasCdmaDataConnectionDetached =
1013            this.cdmaDataConnectionState == ServiceState.STATE_IN_SERVICE
1014            && this.newCdmaDataConnectionState != ServiceState.STATE_IN_SERVICE;
1015
1016        boolean hasCdmaDataConnectionChanged =
1017                       cdmaDataConnectionState != newCdmaDataConnectionState;
1018
1019        boolean hasNetworkTypeChanged = networkType != newNetworkType;
1020
1021        boolean hasChanged = !newSS.equals(ss);
1022
1023        boolean hasRoamingOn = !ss.getRoaming() && newSS.getRoaming();
1024
1025        boolean hasRoamingOff = ss.getRoaming() && !newSS.getRoaming();
1026
1027        boolean hasLocationChanged = !newCellLoc.equals(cellLoc);
1028
1029        // Add an event log when connection state changes
1030        if (ss.getState() != newSS.getState() ||
1031                cdmaDataConnectionState != newCdmaDataConnectionState) {
1032            EventLog.writeEvent(EventLogTags.CDMA_SERVICE_STATE_CHANGE,
1033                    ss.getState(), cdmaDataConnectionState,
1034                    newSS.getState(), newCdmaDataConnectionState);
1035        }
1036
1037        ServiceState tss;
1038        tss = ss;
1039        ss = newSS;
1040        newSS = tss;
1041        // clean slate for next time
1042        newSS.setStateOutOfService();
1043
1044        CdmaCellLocation tcl = cellLoc;
1045        cellLoc = newCellLoc;
1046        newCellLoc = tcl;
1047
1048        cdmaDataConnectionState = newCdmaDataConnectionState;
1049        networkType = newNetworkType;
1050
1051        newSS.setStateOutOfService(); // clean slate for next time
1052
1053        if (hasNetworkTypeChanged) {
1054            phone.setSystemProperty(TelephonyProperties.PROPERTY_DATA_NETWORK_TYPE,
1055                    networkTypeToString(networkType));
1056        }
1057
1058        if (hasRegistered) {
1059            networkAttachedRegistrants.notifyRegistrants();
1060        }
1061
1062        if (hasChanged) {
1063            if (cm.getRadioState().isNVReady()) {
1064                String eriText;
1065                // Now the CDMAPhone sees the new ServiceState so it can get the new ERI text
1066                if (ss.getState() == ServiceState.STATE_IN_SERVICE) {
1067                    eriText = phone.getCdmaEriText();
1068                } else {
1069                    // Note that ServiceState.STATE_OUT_OF_SERVICE is valid used for
1070                    // mRegistrationState 0,2,3 and 4
1071                    eriText = phone.getContext().getText(
1072                            com.android.internal.R.string.roamingTextSearching).toString();
1073                }
1074                ss.setCdmaEriText(eriText);
1075            }
1076
1077            String operatorNumeric;
1078
1079            phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ALPHA,
1080                    ss.getOperatorAlphaLong());
1081
1082            operatorNumeric = ss.getOperatorNumeric();
1083            phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC, operatorNumeric);
1084
1085            if (operatorNumeric == null) {
1086                phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY, "");
1087            } else {
1088                String isoCountryCode = "";
1089                try{
1090                    isoCountryCode = MccTable.countryCodeForMcc(Integer.parseInt(
1091                            operatorNumeric.substring(0,3)));
1092                } catch ( NumberFormatException ex){
1093                    Log.w(LOG_TAG, "countryCodeForMcc error" + ex);
1094                } catch ( StringIndexOutOfBoundsException ex) {
1095                    Log.w(LOG_TAG, "countryCodeForMcc error" + ex);
1096                }
1097
1098                phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY,
1099                        isoCountryCode);
1100                mGotCountryCode = true;
1101                if (mNeedFixZone) {
1102                    fixTimeZone(isoCountryCode);
1103                }
1104            }
1105
1106            phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ISROAMING,
1107                    ss.getRoaming() ? "true" : "false");
1108
1109            updateSpnDisplay();
1110            phone.notifyServiceStateChanged(ss);
1111        }
1112
1113        if (hasCdmaDataConnectionAttached) {
1114            cdmaDataConnectionAttachedRegistrants.notifyRegistrants();
1115        }
1116
1117        if (hasCdmaDataConnectionDetached) {
1118            cdmaDataConnectionDetachedRegistrants.notifyRegistrants();
1119        }
1120
1121        if (hasCdmaDataConnectionChanged || hasNetworkTypeChanged) {
1122            phone.notifyDataConnection(null);
1123        }
1124
1125        if (hasRoamingOn) {
1126            roamingOnRegistrants.notifyRegistrants();
1127        }
1128
1129        if (hasRoamingOff) {
1130            roamingOffRegistrants.notifyRegistrants();
1131        }
1132
1133        if (hasLocationChanged) {
1134            phone.notifyLocationChanged();
1135        }
1136    }
1137
1138    /**
1139     * Returns a TimeZone object based only on parameters from the NITZ string.
1140     */
1141    private TimeZone getNitzTimeZone(int offset, boolean dst, long when) {
1142        TimeZone guess = findTimeZone(offset, dst, when);
1143        if (guess == null) {
1144            // Couldn't find a proper timezone.  Perhaps the DST data is wrong.
1145            guess = findTimeZone(offset, !dst, when);
1146        }
1147        if (DBG) log("getNitzTimeZone returning " + (guess == null ? guess : guess.getID()));
1148        return guess;
1149    }
1150
1151    private TimeZone findTimeZone(int offset, boolean dst, long when) {
1152        int rawOffset = offset;
1153        if (dst) {
1154            rawOffset -= 3600000;
1155        }
1156        String[] zones = TimeZone.getAvailableIDs(rawOffset);
1157        TimeZone guess = null;
1158        Date d = new Date(when);
1159        for (String zone : zones) {
1160            TimeZone tz = TimeZone.getTimeZone(zone);
1161            if (tz.getOffset(when) == offset &&
1162                    tz.inDaylightTime(d) == dst) {
1163                guess = tz;
1164                break;
1165            }
1166        }
1167
1168        return guess;
1169    }
1170
1171    /**
1172     * TODO: This code is exactly the same as in GsmServiceStateTracker
1173     * and has a TODO to not poll signal strength if screen is off.
1174     * This code should probably be hoisted to the base class so
1175     * the fix, when added, works for both.
1176     */
1177    private void
1178    queueNextSignalStrengthPoll() {
1179        if (dontPollSignalStrength || (cm.getRadioState().isGsm())) {
1180            // The radio is telling us about signal strength changes
1181            // we don't have to ask it
1182            return;
1183        }
1184
1185        Message msg;
1186
1187        msg = obtainMessage();
1188        msg.what = EVENT_POLL_SIGNAL_STRENGTH;
1189
1190        // TODO Don't poll signal strength if screen is off
1191        sendMessageDelayed(msg, POLL_PERIOD_MILLIS);
1192    }
1193
1194    /**
1195     *  send signal-strength-changed notification if changed
1196     *  Called both for solicited and unsolicited signal strength updates
1197     */
1198    private void
1199    onSignalStrengthResult(AsyncResult ar) {
1200        SignalStrength oldSignalStrength = mSignalStrength;
1201
1202        if (ar.exception != null) {
1203            // Most likely radio is resetting/disconnected change to default values.
1204            setSignalStrengthDefaultValues();
1205        } else {
1206            int[] ints = (int[])ar.result;
1207            int offset = 2;
1208            int cdmaDbm = (ints[offset] > 0) ? -ints[offset] : -120;
1209            int cdmaEcio = (ints[offset+1] > 0) ? -ints[offset+1] : -160;
1210            int evdoRssi = (ints[offset+2] > 0) ? -ints[offset+2] : -120;
1211            int evdoEcio = (ints[offset+3] > 0) ? -ints[offset+3] : -1;
1212            int evdoSnr  = ((ints[offset+4] > 0) && (ints[offset+4] <= 8)) ? ints[offset+4] : -1;
1213
1214            //log(String.format("onSignalStrengthResult cdmaDbm=%d cdmaEcio=%d evdoRssi=%d evdoEcio=%d evdoSnr=%d",
1215            //        cdmaDbm, cdmaEcio, evdoRssi, evdoEcio, evdoSnr));
1216            mSignalStrength = new SignalStrength(99, -1, cdmaDbm, cdmaEcio,
1217                    evdoRssi, evdoEcio, evdoSnr, false);
1218        }
1219
1220        try {
1221            phone.notifySignalStrength();
1222        } catch (NullPointerException ex) {
1223            log("onSignalStrengthResult() Phone already destroyed: " + ex
1224                    + "SignalStrength not notified");
1225        }
1226    }
1227
1228
1229    private int radioTechnologyToDataServiceState(int code) {
1230        int retVal = ServiceState.STATE_OUT_OF_SERVICE;
1231        switch(code) {
1232        case 0:
1233        case 1:
1234        case 2:
1235        case 3:
1236        case 4:
1237        case 5:
1238            break;
1239        case 6: // RADIO_TECHNOLOGY_1xRTT
1240        case 7: // RADIO_TECHNOLOGY_EVDO_0
1241        case 8: // RADIO_TECHNOLOGY_EVDO_A
1242        case 12: // RADIO_TECHNOLOGY_EVDO_B
1243            retVal = ServiceState.STATE_IN_SERVICE;
1244            break;
1245        default:
1246            Log.e(LOG_TAG, "Wrong radioTechnology code.");
1247        break;
1248        }
1249        return(retVal);
1250    }
1251
1252    /** code is registration state 0-5 from TS 27.007 7.2 */
1253    private int
1254    regCodeToServiceState(int code) {
1255        switch (code) {
1256        case 0: // Not searching and not registered
1257            return ServiceState.STATE_OUT_OF_SERVICE;
1258        case 1:
1259            return ServiceState.STATE_IN_SERVICE;
1260        case 2: // 2 is "searching", fall through
1261        case 3: // 3 is "registration denied", fall through
1262        case 4: // 4 is "unknown", not valid in current baseband
1263            return ServiceState.STATE_OUT_OF_SERVICE;
1264        case 5:// 5 is "Registered, roaming"
1265            return ServiceState.STATE_IN_SERVICE;
1266
1267        default:
1268            Log.w(LOG_TAG, "unexpected service state " + code);
1269        return ServiceState.STATE_OUT_OF_SERVICE;
1270        }
1271    }
1272
1273    /**
1274     * @return The current CDMA data connection state. ServiceState.RADIO_TECHNOLOGY_1xRTT or
1275     * ServiceState.RADIO_TECHNOLOGY_EVDO is the same as "attached" and
1276     * ServiceState.RADIO_TECHNOLOGY_UNKNOWN is the same as detached.
1277     */
1278    /*package*/ int getCurrentCdmaDataConnectionState() {
1279        return cdmaDataConnectionState;
1280    }
1281
1282    /**
1283     * code is registration state 0-5 from TS 27.007 7.2
1284     * returns true if registered roam, false otherwise
1285     */
1286    private boolean
1287    regCodeIsRoaming (int code) {
1288        // 5 is  "in service -- roam"
1289        return 5 == code;
1290    }
1291
1292    /**
1293     * Determine whether a roaming indicator is in the carrier-specified list of ERIs for
1294     * home system
1295     *
1296     * @param roamInd roaming indicator in String
1297     * @return true if the roamInd is in the carrier-specified list of ERIs for home network
1298     */
1299    private boolean isRoamIndForHomeSystem(String roamInd) {
1300        // retrieve the carrier-specified list of ERIs for home system
1301        String homeRoamIndicators = SystemProperties.get("ro.cdma.homesystem");
1302
1303        if (!TextUtils.isEmpty(homeRoamIndicators)) {
1304            // searches through the comma-separated list for a match,
1305            // return true if one is found.
1306            for (String homeRoamInd : homeRoamIndicators.split(",")) {
1307                if (homeRoamInd.equals(roamInd)) {
1308                    return true;
1309                }
1310            }
1311            // no matches found against the list!
1312            return false;
1313        }
1314
1315        // no system property found for the roaming indicators for home system
1316        return false;
1317    }
1318
1319    /**
1320     * Set roaming state when cdmaRoaming is true and ons is different from spn
1321     * @param cdmaRoaming TS 27.007 7.2 CREG registered roaming
1322     * @param s ServiceState hold current ons
1323     * @return true for roaming state set
1324     */
1325    private
1326    boolean isRoamingBetweenOperators(boolean cdmaRoaming, ServiceState s) {
1327        String spn = SystemProperties.get(TelephonyProperties.PROPERTY_ICC_OPERATOR_ALPHA, "empty");
1328
1329        // NOTE: in case of RUIM we should completely ignore the ERI data file and
1330        // mOperatorAlphaLong is set from RIL_REQUEST_OPERATOR response 0 (alpha ONS)
1331        String onsl = s.getOperatorAlphaLong();
1332        String onss = s.getOperatorAlphaShort();
1333
1334        boolean equalsOnsl = onsl != null && spn.equals(onsl);
1335        boolean equalsOnss = onss != null && spn.equals(onss);
1336
1337        return cdmaRoaming && !(equalsOnsl || equalsOnss);
1338    }
1339
1340
1341    /**
1342     * nitzReceiveTime is time_t that the NITZ time was posted
1343     */
1344
1345    private
1346    void setTimeFromNITZString (String nitz, long nitzReceiveTime)
1347    {
1348        // "yy/mm/dd,hh:mm:ss(+/-)tz"
1349        // tz is in number of quarter-hours
1350
1351        long start = SystemClock.elapsedRealtime();
1352        Log.i(LOG_TAG, "NITZ: " + nitz + "," + nitzReceiveTime +
1353                        " start=" + start + " delay=" + (start - nitzReceiveTime));
1354
1355        try {
1356            /* NITZ time (hour:min:sec) will be in UTC but it supplies the timezone
1357             * offset as well (which we won't worry about until later) */
1358            Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
1359
1360            c.clear();
1361            c.set(Calendar.DST_OFFSET, 0);
1362
1363            String[] nitzSubs = nitz.split("[/:,+-]");
1364
1365            int year = 2000 + Integer.parseInt(nitzSubs[0]);
1366            c.set(Calendar.YEAR, year);
1367
1368            // month is 0 based!
1369            int month = Integer.parseInt(nitzSubs[1]) - 1;
1370            c.set(Calendar.MONTH, month);
1371
1372            int date = Integer.parseInt(nitzSubs[2]);
1373            c.set(Calendar.DATE, date);
1374
1375            int hour = Integer.parseInt(nitzSubs[3]);
1376            c.set(Calendar.HOUR, hour);
1377
1378            int minute = Integer.parseInt(nitzSubs[4]);
1379            c.set(Calendar.MINUTE, minute);
1380
1381            int second = Integer.parseInt(nitzSubs[5]);
1382            c.set(Calendar.SECOND, second);
1383
1384            boolean sign = (nitz.indexOf('-') == -1);
1385
1386            int tzOffset = Integer.parseInt(nitzSubs[6]);
1387
1388            int dst = (nitzSubs.length >= 8 ) ? Integer.parseInt(nitzSubs[7])
1389                                              : 0;
1390
1391            // The zone offset received from NITZ is for current local time,
1392            // so DST correction is already applied.  Don't add it again.
1393            //
1394            // tzOffset += dst * 4;
1395            //
1396            // We could unapply it if we wanted the raw offset.
1397
1398            tzOffset = (sign ? 1 : -1) * tzOffset * 15 * 60 * 1000;
1399
1400            TimeZone    zone = null;
1401
1402            // As a special extension, the Android emulator appends the name of
1403            // the host computer's timezone to the nitz string. this is zoneinfo
1404            // timezone name of the form Area!Location or Area!Location!SubLocation
1405            // so we need to convert the ! into /
1406            if (nitzSubs.length >= 9) {
1407                String  tzname = nitzSubs[8].replace('!','/');
1408                zone = TimeZone.getTimeZone( tzname );
1409            }
1410
1411            String iso = SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY);
1412
1413            if (zone == null) {
1414
1415                if (mGotCountryCode) {
1416                    if (iso != null && iso.length() > 0) {
1417                        zone = TimeUtils.getTimeZone(tzOffset, dst != 0,
1418                                c.getTimeInMillis(),
1419                                iso);
1420                    } else {
1421                        // We don't have a valid iso country code.  This is
1422                        // most likely because we're on a test network that's
1423                        // using a bogus MCC (eg, "001"), so get a TimeZone
1424                        // based only on the NITZ parameters.
1425                        zone = getNitzTimeZone(tzOffset, (dst != 0), c.getTimeInMillis());
1426                    }
1427                }
1428            }
1429
1430            if (zone == null) {
1431                // We got the time before the country, so we don't know
1432                // how to identify the DST rules yet.  Save the information
1433                // and hope to fix it up later.
1434
1435                mNeedFixZone = true;
1436                mZoneOffset  = tzOffset;
1437                mZoneDst     = dst != 0;
1438                mZoneTime    = c.getTimeInMillis();
1439            }
1440
1441            if (zone != null) {
1442                if (getAutoTime()) {
1443                    setAndBroadcastNetworkSetTimeZone(zone.getID());
1444                }
1445                saveNitzTimeZone(zone.getID());
1446            }
1447
1448            String ignore = SystemProperties.get("gsm.ignore-nitz");
1449            if (ignore != null && ignore.equals("yes")) {
1450                Log.i(LOG_TAG, "NITZ: Not setting clock because gsm.ignore-nitz is set");
1451                return;
1452            }
1453
1454            try {
1455                mWakeLock.acquire();
1456
1457                /**
1458                 * Correct the NITZ time by how long its taken to get here.
1459                 */
1460                long millisSinceNitzReceived
1461                        = SystemClock.elapsedRealtime() - nitzReceiveTime;
1462
1463                if (millisSinceNitzReceived < 0) {
1464                    // Sanity check: something is wrong
1465                    Log.i(LOG_TAG, "NITZ: not setting time, clock has rolled "
1466                                        + "backwards since NITZ time was received, "
1467                                        + nitz);
1468                    return;
1469                }
1470
1471                if (millisSinceNitzReceived > Integer.MAX_VALUE) {
1472                    // If the time is this far off, something is wrong > 24 days!
1473                    Log.i(LOG_TAG, "NITZ: not setting time, processing has taken "
1474                                    + (millisSinceNitzReceived / (1000 * 60 * 60 * 24))
1475                                    + " days");
1476                    return;
1477                }
1478
1479                // Note: with range checks above, cast to int is safe
1480                c.add(Calendar.MILLISECOND, (int)millisSinceNitzReceived);
1481
1482                if (getAutoTime()) {
1483                    /**
1484                     * Update system time automatically
1485                     */
1486                    long gained = c.getTimeInMillis() - System.currentTimeMillis();
1487                    long timeSinceLastUpdate = SystemClock.elapsedRealtime() - mSavedAtTime;
1488                    int nitzUpdateSpacing = Settings.Secure.getInt(cr,
1489                            Settings.Secure.NITZ_UPDATE_SPACING, mNitzUpdateSpacing);
1490                    int nitzUpdateDiff = Settings.Secure.getInt(cr,
1491                            Settings.Secure.NITZ_UPDATE_DIFF, mNitzUpdateDiff);
1492
1493                    if ((mSavedAtTime == 0) || (timeSinceLastUpdate > nitzUpdateSpacing)
1494                            || (Math.abs(gained) > nitzUpdateDiff)) {
1495                        Log.i(LOG_TAG, "NITZ: Auto updating time of day to " + c.getTime()
1496                                + " NITZ receive delay=" + millisSinceNitzReceived
1497                                + "ms gained=" + gained + "ms from " + nitz);
1498
1499                        setAndBroadcastNetworkSetTime(c.getTimeInMillis());
1500                    } else {
1501                        Log.i(LOG_TAG, "NITZ: ignore, a previous update was "
1502                                + timeSinceLastUpdate + "ms ago and gained=" + gained + "ms");
1503                        return;
1504                    }
1505                }
1506
1507                /**
1508                 * Update properties and save the time we did the update
1509                 */
1510                Log.i(LOG_TAG, "NITZ: update nitz time property");
1511                SystemProperties.set("gsm.nitz.time", String.valueOf(c.getTimeInMillis()));
1512                mSavedTime = c.getTimeInMillis();
1513                mSavedAtTime = SystemClock.elapsedRealtime();
1514            } finally {
1515                long end = SystemClock.elapsedRealtime();
1516                Log.i(LOG_TAG, "NITZ: end=" + end + " dur=" + (end - start));
1517                mWakeLock.release();
1518            }
1519        } catch (RuntimeException ex) {
1520            Log.e(LOG_TAG, "NITZ: Parsing NITZ time " + nitz, ex);
1521        }
1522    }
1523
1524    private boolean getAutoTime() {
1525        try {
1526            return Settings.System.getInt(cr, Settings.System.AUTO_TIME) > 0;
1527        } catch (SettingNotFoundException snfe) {
1528            return true;
1529        }
1530    }
1531
1532    private void saveNitzTimeZone(String zoneId) {
1533        mSavedTimeZone = zoneId;
1534    }
1535
1536    /**
1537     * Set the timezone and send out a sticky broadcast so the system can
1538     * determine if the timezone was set by the carrier.
1539     *
1540     * @param zoneId timezone set by carrier
1541     */
1542    private void setAndBroadcastNetworkSetTimeZone(String zoneId) {
1543        AlarmManager alarm =
1544            (AlarmManager) phone.getContext().getSystemService(Context.ALARM_SERVICE);
1545        alarm.setTimeZone(zoneId);
1546        Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE);
1547        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
1548        intent.putExtra("time-zone", zoneId);
1549        phone.getContext().sendStickyBroadcast(intent);
1550    }
1551
1552    /**
1553     * Set the time and Send out a sticky broadcast so the system can determine
1554     * if the time was set by the carrier.
1555     *
1556     * @param time time set by network
1557     */
1558    private void setAndBroadcastNetworkSetTime(long time) {
1559        SystemClock.setCurrentTimeMillis(time);
1560        Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIME);
1561        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
1562        intent.putExtra("time", time);
1563        phone.getContext().sendStickyBroadcast(intent);
1564    }
1565
1566     private void revertToNitz() {
1567        if (Settings.System.getInt(cr, Settings.System.AUTO_TIME, 0) == 0) {
1568            return;
1569        }
1570        Log.d(LOG_TAG, "Reverting to NITZ: tz='" + mSavedTimeZone
1571                + "' mSavedTime=" + mSavedTime
1572                + " mSavedAtTime=" + mSavedAtTime);
1573        if (mSavedTimeZone != null && mSavedTime != 0 && mSavedAtTime != 0) {
1574            setAndBroadcastNetworkSetTimeZone(mSavedTimeZone);
1575            setAndBroadcastNetworkSetTime(mSavedTime
1576                    + (SystemClock.elapsedRealtime() - mSavedAtTime));
1577        }
1578    }
1579
1580    private boolean isSidsAllZeros() {
1581        if (mHomeSystemId != null) {
1582            for (int i=0; i < mHomeSystemId.length; i++) {
1583                if (mHomeSystemId[i] != 0) {
1584                    return false;
1585                }
1586            }
1587        }
1588        return true;
1589    }
1590
1591    /**
1592     * Check whether a specified system ID that matches one of the home system IDs.
1593     */
1594    private boolean isHomeSid(int sid) {
1595        if (mHomeSystemId != null) {
1596            for (int i=0; i < mHomeSystemId.length; i++) {
1597                if (sid == mHomeSystemId[i]) {
1598                    return true;
1599                }
1600            }
1601        }
1602        return false;
1603    }
1604
1605    /**
1606     * @return true if phone is camping on a technology
1607     * that could support voice and data simultaneously.
1608     */
1609    boolean isConcurrentVoiceAndData() {
1610        // Note: it needs to be confirmed which CDMA network types
1611        // can support voice and data calls concurrently.
1612        // For the time-being, the return value will be false.
1613        return false;
1614    }
1615
1616    protected void log(String s) {
1617        Log.d(LOG_TAG, "[CdmaServiceStateTracker] " + s);
1618    }
1619
1620    public String getMdnNumber() {
1621        return mMdn;
1622    }
1623
1624    public String getCdmaMin() {
1625         return mMin;
1626    }
1627
1628    /** Returns null if NV is not yet ready */
1629    public String getPrlVersion() {
1630        return mPrlVersion;
1631    }
1632
1633    /**
1634     * Returns IMSI as MCC + MNC + MIN
1635     */
1636    String getImsi() {
1637        // TODO: When RUIM is enabled, IMSI will come from RUIM not build-time props.
1638        String operatorNumeric = SystemProperties.get(
1639                TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC, "");
1640
1641        if (!TextUtils.isEmpty(operatorNumeric) && getCdmaMin() != null) {
1642            return (operatorNumeric + getCdmaMin());
1643        } else {
1644            return null;
1645        }
1646    }
1647
1648    /**
1649     * Check if subscription data has been assigned to mMin
1650     *
1651     * return true if MIN info is ready; false otherwise.
1652     */
1653    public boolean isMinInfoReady() {
1654        return mIsMinInfoReady;
1655    }
1656
1657    /**
1658     * process the pending request to turn radio off after data is disconnected
1659     *
1660     * return true if there is pending request to process; false otherwise.
1661     */
1662    public boolean processPendingRadioPowerOffAfterDataOff() {
1663        synchronized(this) {
1664            if (mPendingRadioPowerOffAfterDataOff) {
1665                if (DBG) log("Process pending request to turn radio off.");
1666                removeMessages(EVENT_SET_RADIO_POWER_OFF);
1667                hangupAndPowerOff();
1668                mPendingRadioPowerOffAfterDataOff = false;
1669                return true;
1670            }
1671            return false;
1672        }
1673    }
1674
1675    private void hangupAndPowerOff() {
1676        // hang up all active voice calls
1677        phone.mCT.ringingCall.hangupIfAlive();
1678        phone.mCT.backgroundCall.hangupIfAlive();
1679        phone.mCT.foregroundCall.hangupIfAlive();
1680        cm.setRadioPower(false, null);
1681    }
1682}
1683