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 NTIZ 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            // aquired 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 implemnted, 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
876    private void
877    pollState() {
878        pollingContext = new int[1];
879        pollingContext[0] = 0;
880
881        switch (cm.getRadioState()) {
882        case RADIO_UNAVAILABLE:
883            newSS.setStateOutOfService();
884            newCellLoc.setStateInvalid();
885            setSignalStrengthDefaultValues();
886            mGotCountryCode = false;
887
888            pollStateDone();
889            break;
890
891        case RADIO_OFF:
892            newSS.setStateOff();
893            newCellLoc.setStateInvalid();
894            setSignalStrengthDefaultValues();
895            mGotCountryCode = false;
896
897            pollStateDone();
898            break;
899
900        case SIM_NOT_READY:
901        case SIM_LOCKED_OR_ABSENT:
902        case SIM_READY:
903            log("Radio Technology Change ongoing, setting SS to off");
904            newSS.setStateOff();
905            newCellLoc.setStateInvalid();
906            setSignalStrengthDefaultValues();
907            mGotCountryCode = false;
908
909            // NOTE: pollStateDone() is not needed in this case
910            break;
911
912        default:
913            // Issue all poll-related commands at once, then count
914            // down the responses which are allowed to arrive
915            // out-of-order.
916
917            pollingContext[0]++;
918            // RIL_REQUEST_OPERATOR is necessary for CDMA
919            cm.getOperator(
920                    obtainMessage(EVENT_POLL_STATE_OPERATOR_CDMA, pollingContext));
921
922            pollingContext[0]++;
923            // RIL_REQUEST_REGISTRATION_STATE is necessary for CDMA
924            cm.getRegistrationState(
925                    obtainMessage(EVENT_POLL_STATE_REGISTRATION_CDMA, pollingContext));
926
927            break;
928        }
929    }
930
931    private static String networkTypeToString(int type) {
932        String ret = "unknown";
933
934        switch (type) {
935        case DATA_ACCESS_CDMA_IS95A:
936        case DATA_ACCESS_CDMA_IS95B:
937            ret = "CDMA";
938            break;
939        case DATA_ACCESS_CDMA_1xRTT:
940            ret = "CDMA - 1xRTT";
941            break;
942        case DATA_ACCESS_CDMA_EvDo_0:
943            ret = "CDMA - EvDo rev. 0";
944            break;
945        case DATA_ACCESS_CDMA_EvDo_A:
946            ret = "CDMA - EvDo rev. A";
947            break;
948        default:
949            if (DBG) {
950                Log.e(LOG_TAG, "Wrong network. Can not return a string.");
951            }
952        break;
953        }
954
955        return ret;
956    }
957
958    private void fixTimeZone(String isoCountryCode) {
959        TimeZone zone = null;
960        // If the offset is (0, false) and the time zone property
961        // is set, use the time zone property rather than GMT.
962        String zoneName = SystemProperties.get(TIMEZONE_PROPERTY);
963        if ((mZoneOffset == 0) && (mZoneDst == false) && (zoneName != null)
964                && (zoneName.length() > 0)
965                && (Arrays.binarySearch(GMT_COUNTRY_CODES, isoCountryCode) < 0)) {
966            // For NITZ string without time zone,
967            // need adjust time to reflect default time zone setting
968            zone = TimeZone.getDefault();
969            long tzOffset;
970            tzOffset = zone.getOffset(System.currentTimeMillis());
971            if (getAutoTime()) {
972                setAndBroadcastNetworkSetTime(System.currentTimeMillis() - tzOffset);
973            } else {
974                // Adjust the saved NITZ time to account for tzOffset.
975                mSavedTime = mSavedTime - tzOffset;
976            }
977        } else if (isoCountryCode.equals("")) {
978            // Country code not found. This is likely a test network.
979            // Get a TimeZone based only on the NITZ parameters (best guess).
980            zone = getNitzTimeZone(mZoneOffset, mZoneDst, mZoneTime);
981        } else {
982            zone = TimeUtils.getTimeZone(mZoneOffset, mZoneDst, mZoneTime, isoCountryCode);
983        }
984
985        mNeedFixZone = false;
986
987        if (zone != null) {
988            if (getAutoTime()) {
989                setAndBroadcastNetworkSetTimeZone(zone.getID());
990            }
991            saveNitzTimeZone(zone.getID());
992        }
993    }
994
995    private void pollStateDone() {
996        if (DBG) log("Poll ServiceState done: oldSS=[" + ss + "] newSS=[" + newSS + "]");
997
998        boolean hasRegistered =
999            ss.getState() != ServiceState.STATE_IN_SERVICE
1000            && newSS.getState() == ServiceState.STATE_IN_SERVICE;
1001
1002        boolean hasDeregistered =
1003            ss.getState() == ServiceState.STATE_IN_SERVICE
1004            && newSS.getState() != ServiceState.STATE_IN_SERVICE;
1005
1006        boolean hasCdmaDataConnectionAttached =
1007            this.cdmaDataConnectionState != ServiceState.STATE_IN_SERVICE
1008            && this.newCdmaDataConnectionState == ServiceState.STATE_IN_SERVICE;
1009
1010        boolean hasCdmaDataConnectionDetached =
1011            this.cdmaDataConnectionState == ServiceState.STATE_IN_SERVICE
1012            && this.newCdmaDataConnectionState != ServiceState.STATE_IN_SERVICE;
1013
1014        boolean hasCdmaDataConnectionChanged =
1015                       cdmaDataConnectionState != newCdmaDataConnectionState;
1016
1017        boolean hasNetworkTypeChanged = networkType != newNetworkType;
1018
1019        boolean hasChanged = !newSS.equals(ss);
1020
1021        boolean hasRoamingOn = !ss.getRoaming() && newSS.getRoaming();
1022
1023        boolean hasRoamingOff = ss.getRoaming() && !newSS.getRoaming();
1024
1025        boolean hasLocationChanged = !newCellLoc.equals(cellLoc);
1026
1027        // Add an event log when connection state changes
1028        if (ss.getState() != newSS.getState() ||
1029                cdmaDataConnectionState != newCdmaDataConnectionState) {
1030            EventLog.writeEvent(EventLogTags.CDMA_SERVICE_STATE_CHANGE,
1031                    ss.getState(), cdmaDataConnectionState,
1032                    newSS.getState(), newCdmaDataConnectionState);
1033        }
1034
1035        ServiceState tss;
1036        tss = ss;
1037        ss = newSS;
1038        newSS = tss;
1039        // clean slate for next time
1040        newSS.setStateOutOfService();
1041
1042        CdmaCellLocation tcl = cellLoc;
1043        cellLoc = newCellLoc;
1044        newCellLoc = tcl;
1045
1046        cdmaDataConnectionState = newCdmaDataConnectionState;
1047        networkType = newNetworkType;
1048
1049        newSS.setStateOutOfService(); // clean slate for next time
1050
1051        if (hasNetworkTypeChanged) {
1052            phone.setSystemProperty(TelephonyProperties.PROPERTY_DATA_NETWORK_TYPE,
1053                    networkTypeToString(networkType));
1054        }
1055
1056        if (hasRegistered) {
1057            networkAttachedRegistrants.notifyRegistrants();
1058        }
1059
1060        if (hasChanged) {
1061            if (cm.getRadioState().isNVReady()) {
1062                String eriText;
1063                // Now the CDMAPhone sees the new ServiceState so it can get the new ERI text
1064                if (ss.getState() == ServiceState.STATE_IN_SERVICE) {
1065                    eriText = phone.getCdmaEriText();
1066                } else {
1067                    // Note that ServiceState.STATE_OUT_OF_SERVICE is valid used for
1068                    // mRegistrationState 0,2,3 and 4
1069                    eriText = phone.getContext().getText(
1070                            com.android.internal.R.string.roamingTextSearching).toString();
1071                }
1072                ss.setCdmaEriText(eriText);
1073            }
1074
1075            String operatorNumeric;
1076
1077            phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ALPHA,
1078                    ss.getOperatorAlphaLong());
1079
1080            operatorNumeric = ss.getOperatorNumeric();
1081            phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC, operatorNumeric);
1082
1083            if (operatorNumeric == null) {
1084                phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY, "");
1085            } else {
1086                String isoCountryCode = "";
1087                try{
1088                    isoCountryCode = MccTable.countryCodeForMcc(Integer.parseInt(
1089                            operatorNumeric.substring(0,3)));
1090                } catch ( NumberFormatException ex){
1091                    Log.w(LOG_TAG, "countryCodeForMcc error" + ex);
1092                } catch ( StringIndexOutOfBoundsException ex) {
1093                    Log.w(LOG_TAG, "countryCodeForMcc error" + ex);
1094                }
1095
1096                phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY,
1097                        isoCountryCode);
1098                mGotCountryCode = true;
1099                if (mNeedFixZone) {
1100                    fixTimeZone(isoCountryCode);
1101                }
1102            }
1103
1104            phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ISROAMING,
1105                    ss.getRoaming() ? "true" : "false");
1106
1107            updateSpnDisplay();
1108            phone.notifyServiceStateChanged(ss);
1109        }
1110
1111        if (hasCdmaDataConnectionAttached) {
1112            cdmaDataConnectionAttachedRegistrants.notifyRegistrants();
1113        }
1114
1115        if (hasCdmaDataConnectionDetached) {
1116            cdmaDataConnectionDetachedRegistrants.notifyRegistrants();
1117        }
1118
1119        if (hasCdmaDataConnectionChanged || hasNetworkTypeChanged) {
1120            phone.notifyDataConnection(null);
1121        }
1122
1123        if (hasRoamingOn) {
1124            roamingOnRegistrants.notifyRegistrants();
1125        }
1126
1127        if (hasRoamingOff) {
1128            roamingOffRegistrants.notifyRegistrants();
1129        }
1130
1131        if (hasLocationChanged) {
1132            phone.notifyLocationChanged();
1133        }
1134    }
1135
1136    /**
1137     * Returns a TimeZone object based only on parameters from the NITZ string.
1138     */
1139    private TimeZone getNitzTimeZone(int offset, boolean dst, long when) {
1140        TimeZone guess = findTimeZone(offset, dst, when);
1141        if (guess == null) {
1142            // Couldn't find a proper timezone.  Perhaps the DST data is wrong.
1143            guess = findTimeZone(offset, !dst, when);
1144        }
1145        if (DBG) log("getNitzTimeZone returning " + (guess == null ? guess : guess.getID()));
1146        return guess;
1147    }
1148
1149    private TimeZone findTimeZone(int offset, boolean dst, long when) {
1150        int rawOffset = offset;
1151        if (dst) {
1152            rawOffset -= 3600000;
1153        }
1154        String[] zones = TimeZone.getAvailableIDs(rawOffset);
1155        TimeZone guess = null;
1156        Date d = new Date(when);
1157        for (String zone : zones) {
1158            TimeZone tz = TimeZone.getTimeZone(zone);
1159            if (tz.getOffset(when) == offset &&
1160                    tz.inDaylightTime(d) == dst) {
1161                guess = tz;
1162                break;
1163            }
1164        }
1165
1166        return guess;
1167    }
1168
1169    /**
1170     * TODO: This code is exactly the same as in GsmServiceStateTracker
1171     * and has a TODO to not poll signal strength if screen is off.
1172     * This code should probably be hoisted to the base class so
1173     * the fix, when added, works for both.
1174     */
1175    private void
1176    queueNextSignalStrengthPoll() {
1177        if (dontPollSignalStrength || (cm.getRadioState().isGsm())) {
1178            // The radio is telling us about signal strength changes
1179            // we don't have to ask it
1180            return;
1181        }
1182
1183        Message msg;
1184
1185        msg = obtainMessage();
1186        msg.what = EVENT_POLL_SIGNAL_STRENGTH;
1187
1188        // TODO Don't poll signal strength if screen is off
1189        sendMessageDelayed(msg, POLL_PERIOD_MILLIS);
1190    }
1191
1192    /**
1193     *  send signal-strength-changed notification if changed
1194     *  Called both for solicited and unsolicited signal strength updates
1195     */
1196    private void
1197    onSignalStrengthResult(AsyncResult ar) {
1198        SignalStrength oldSignalStrength = mSignalStrength;
1199
1200        if (ar.exception != null) {
1201            // Most likely radio is resetting/disconnected change to default values.
1202            setSignalStrengthDefaultValues();
1203        } else {
1204            int[] ints = (int[])ar.result;
1205            int offset = 2;
1206            int cdmaDbm = (ints[offset] > 0) ? -ints[offset] : -120;
1207            int cdmaEcio = (ints[offset+1] > 0) ? -ints[offset+1] : -160;
1208            int evdoRssi = (ints[offset+2] > 0) ? -ints[offset+2] : -120;
1209            int evdoEcio = (ints[offset+3] > 0) ? -ints[offset+3] : -1;
1210            int evdoSnr  = ((ints[offset+4] > 0) && (ints[offset+4] <= 8)) ? ints[offset+4] : -1;
1211
1212            //log(String.format("onSignalStrengthResult cdmaDbm=%d cdmaEcio=%d evdoRssi=%d evdoEcio=%d evdoSnr=%d",
1213            //        cdmaDbm, cdmaEcio, evdoRssi, evdoEcio, evdoSnr));
1214            mSignalStrength = new SignalStrength(99, -1, cdmaDbm, cdmaEcio,
1215                    evdoRssi, evdoEcio, evdoSnr, false);
1216        }
1217
1218        try {
1219            phone.notifySignalStrength();
1220        } catch (NullPointerException ex) {
1221            log("onSignalStrengthResult() Phone already destroyed: " + ex
1222                    + "SignalStrength not notified");
1223        }
1224    }
1225
1226
1227    private int radioTechnologyToDataServiceState(int code) {
1228        int retVal = ServiceState.STATE_OUT_OF_SERVICE;
1229        switch(code) {
1230        case 0:
1231        case 1:
1232        case 2:
1233        case 3:
1234        case 4:
1235        case 5:
1236            break;
1237        case 6: // RADIO_TECHNOLOGY_1xRTT
1238        case 7: // RADIO_TECHNOLOGY_EVDO_0
1239        case 8: // RADIO_TECHNOLOGY_EVDO_A
1240            retVal = ServiceState.STATE_IN_SERVICE;
1241            break;
1242        default:
1243            Log.e(LOG_TAG, "Wrong radioTechnology code.");
1244        break;
1245        }
1246        return(retVal);
1247    }
1248
1249    /** code is registration state 0-5 from TS 27.007 7.2 */
1250    private int
1251    regCodeToServiceState(int code) {
1252        switch (code) {
1253        case 0: // Not searching and not registered
1254            return ServiceState.STATE_OUT_OF_SERVICE;
1255        case 1:
1256            return ServiceState.STATE_IN_SERVICE;
1257        case 2: // 2 is "searching", fall through
1258        case 3: // 3 is "registration denied", fall through
1259        case 4: // 4 is "unknown" no vaild in current baseband
1260            return ServiceState.STATE_OUT_OF_SERVICE;
1261        case 5:// 5 is "Registered, roaming"
1262            return ServiceState.STATE_IN_SERVICE;
1263
1264        default:
1265            Log.w(LOG_TAG, "unexpected service state " + code);
1266        return ServiceState.STATE_OUT_OF_SERVICE;
1267        }
1268    }
1269
1270    /**
1271     * @return The current CDMA data connection state. ServiceState.RADIO_TECHNOLOGY_1xRTT or
1272     * ServiceState.RADIO_TECHNOLOGY_EVDO is the same as "attached" and
1273     * ServiceState.RADIO_TECHNOLOGY_UNKNOWN is the same as detached.
1274     */
1275    /*package*/ int getCurrentCdmaDataConnectionState() {
1276        return cdmaDataConnectionState;
1277    }
1278
1279    /**
1280     * code is registration state 0-5 from TS 27.007 7.2
1281     * returns true if registered roam, false otherwise
1282     */
1283    private boolean
1284    regCodeIsRoaming (int code) {
1285        // 5 is  "in service -- roam"
1286        return 5 == code;
1287    }
1288
1289    /**
1290     * Determine whether a roaming indicator is in the carrier-specified list of ERIs for
1291     * home system
1292     *
1293     * @param roamInd roaming indicator in String
1294     * @return true if the roamInd is in the carrier-specified list of ERIs for home network
1295     */
1296    private boolean isRoamIndForHomeSystem(String roamInd) {
1297        // retrieve the carrier-specified list of ERIs for home system
1298        String homeRoamIndcators = SystemProperties.get("ro.cdma.homesystem");
1299
1300        if (!TextUtils.isEmpty(homeRoamIndcators)) {
1301            // searches through the comma-separated list for a match,
1302            // return true if one is found.
1303            for (String homeRoamInd : homeRoamIndcators.split(",")) {
1304                if (homeRoamInd.equals(roamInd)) {
1305                    return true;
1306                }
1307            }
1308            // no matches found against the list!
1309            return false;
1310        }
1311
1312        // no system property found for the roaming indicators for home system
1313        return false;
1314    }
1315
1316    /**
1317     * Set roaming state when cdmaRoaming is true and ons is different from spn
1318     * @param cdmaRoaming TS 27.007 7.2 CREG registered roaming
1319     * @param s ServiceState hold current ons
1320     * @return true for roaming state set
1321     */
1322    private
1323    boolean isRoamingBetweenOperators(boolean cdmaRoaming, ServiceState s) {
1324        String spn = SystemProperties.get(TelephonyProperties.PROPERTY_ICC_OPERATOR_ALPHA, "empty");
1325
1326        // NOTE: in case of RUIM we should completely ignore the ERI data file and
1327        // mOperatorAlphaLong is set from RIL_REQUEST_OPERATOR response 0 (alpha ONS)
1328        String onsl = s.getOperatorAlphaLong();
1329        String onss = s.getOperatorAlphaShort();
1330
1331        boolean equalsOnsl = onsl != null && spn.equals(onsl);
1332        boolean equalsOnss = onss != null && spn.equals(onss);
1333
1334        return cdmaRoaming && !(equalsOnsl || equalsOnss);
1335    }
1336
1337
1338    /**
1339     * nitzReceiveTime is time_t that the NITZ time was posted
1340     */
1341
1342    private
1343    void setTimeFromNITZString (String nitz, long nitzReceiveTime)
1344    {
1345        // "yy/mm/dd,hh:mm:ss(+/-)tz"
1346        // tz is in number of quarter-hours
1347
1348        long start = SystemClock.elapsedRealtime();
1349        Log.i(LOG_TAG, "NITZ: " + nitz + "," + nitzReceiveTime +
1350                        " start=" + start + " delay=" + (start - nitzReceiveTime));
1351
1352        try {
1353            /* NITZ time (hour:min:sec) will be in UTC but it supplies the timezone
1354             * offset as well (which we won't worry about until later) */
1355            Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
1356
1357            c.clear();
1358            c.set(Calendar.DST_OFFSET, 0);
1359
1360            String[] nitzSubs = nitz.split("[/:,+-]");
1361
1362            int year = 2000 + Integer.parseInt(nitzSubs[0]);
1363            c.set(Calendar.YEAR, year);
1364
1365            // month is 0 based!
1366            int month = Integer.parseInt(nitzSubs[1]) - 1;
1367            c.set(Calendar.MONTH, month);
1368
1369            int date = Integer.parseInt(nitzSubs[2]);
1370            c.set(Calendar.DATE, date);
1371
1372            int hour = Integer.parseInt(nitzSubs[3]);
1373            c.set(Calendar.HOUR, hour);
1374
1375            int minute = Integer.parseInt(nitzSubs[4]);
1376            c.set(Calendar.MINUTE, minute);
1377
1378            int second = Integer.parseInt(nitzSubs[5]);
1379            c.set(Calendar.SECOND, second);
1380
1381            boolean sign = (nitz.indexOf('-') == -1);
1382
1383            int tzOffset = Integer.parseInt(nitzSubs[6]);
1384
1385            int dst = (nitzSubs.length >= 8 ) ? Integer.parseInt(nitzSubs[7])
1386                                              : 0;
1387
1388            // The zone offset received from NITZ is for current local time,
1389            // so DST correction is already applied.  Don't add it again.
1390            //
1391            // tzOffset += dst * 4;
1392            //
1393            // We could unapply it if we wanted the raw offset.
1394
1395            tzOffset = (sign ? 1 : -1) * tzOffset * 15 * 60 * 1000;
1396
1397            TimeZone    zone = null;
1398
1399            // As a special extension, the Android emulator appends the name of
1400            // the host computer's timezone to the nitz string. this is zoneinfo
1401            // timezone name of the form Area!Location or Area!Location!SubLocation
1402            // so we need to convert the ! into /
1403            if (nitzSubs.length >= 9) {
1404                String  tzname = nitzSubs[8].replace('!','/');
1405                zone = TimeZone.getTimeZone( tzname );
1406            }
1407
1408            String iso = SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY);
1409
1410            if (zone == null) {
1411
1412                if (mGotCountryCode) {
1413                    if (iso != null && iso.length() > 0) {
1414                        zone = TimeUtils.getTimeZone(tzOffset, dst != 0,
1415                                c.getTimeInMillis(),
1416                                iso);
1417                    } else {
1418                        // We don't have a valid iso country code.  This is
1419                        // most likely because we're on a test network that's
1420                        // using a bogus MCC (eg, "001"), so get a TimeZone
1421                        // based only on the NITZ parameters.
1422                        zone = getNitzTimeZone(tzOffset, (dst != 0), c.getTimeInMillis());
1423                    }
1424                }
1425            }
1426
1427            if (zone == null) {
1428                // We got the time before the country, so we don't know
1429                // how to identify the DST rules yet.  Save the information
1430                // and hope to fix it up later.
1431
1432                mNeedFixZone = true;
1433                mZoneOffset  = tzOffset;
1434                mZoneDst     = dst != 0;
1435                mZoneTime    = c.getTimeInMillis();
1436            }
1437
1438            if (zone != null) {
1439                if (getAutoTime()) {
1440                    setAndBroadcastNetworkSetTimeZone(zone.getID());
1441                }
1442                saveNitzTimeZone(zone.getID());
1443            }
1444
1445            String ignore = SystemProperties.get("gsm.ignore-nitz");
1446            if (ignore != null && ignore.equals("yes")) {
1447                Log.i(LOG_TAG, "NITZ: Not setting clock because gsm.ignore-nitz is set");
1448                return;
1449            }
1450
1451            try {
1452                mWakeLock.acquire();
1453
1454                /**
1455                 * Correct the NITZ time by how long its taken to get here.
1456                 */
1457                long millisSinceNitzReceived
1458                        = SystemClock.elapsedRealtime() - nitzReceiveTime;
1459
1460                if (millisSinceNitzReceived < 0) {
1461                    // Sanity check: something is wrong
1462                    Log.i(LOG_TAG, "NITZ: not setting time, clock has rolled "
1463                                        + "backwards since NITZ time was received, "
1464                                        + nitz);
1465                    return;
1466                }
1467
1468                if (millisSinceNitzReceived > Integer.MAX_VALUE) {
1469                    // If the time is this far off, something is wrong > 24 days!
1470                    Log.i(LOG_TAG, "NITZ: not setting time, processing has taken "
1471                                    + (millisSinceNitzReceived / (1000 * 60 * 60 * 24))
1472                                    + " days");
1473                    return;
1474                }
1475
1476                // Note: with range checks above, cast to int is safe
1477                c.add(Calendar.MILLISECOND, (int)millisSinceNitzReceived);
1478
1479                if (getAutoTime()) {
1480                    /**
1481                     * Update system time automatically
1482                     */
1483                    long gained = c.getTimeInMillis() - System.currentTimeMillis();
1484                    long timeSinceLastUpdate = SystemClock.elapsedRealtime() - mSavedAtTime;
1485                    int nitzUpdateSpacing = Settings.Secure.getInt(cr,
1486                            Settings.Secure.NITZ_UPDATE_SPACING, mNitzUpdateSpacing);
1487                    int nitzUpdateDiff = Settings.Secure.getInt(cr,
1488                            Settings.Secure.NITZ_UPDATE_DIFF, mNitzUpdateDiff);
1489
1490                    if ((mSavedAtTime == 0) || (timeSinceLastUpdate > nitzUpdateSpacing)
1491                            || (Math.abs(gained) > nitzUpdateDiff)) {
1492                        Log.i(LOG_TAG, "NITZ: Auto updating time of day to " + c.getTime()
1493                                + " NITZ receive delay=" + millisSinceNitzReceived
1494                                + "ms gained=" + gained + "ms from " + nitz);
1495
1496                        setAndBroadcastNetworkSetTime(c.getTimeInMillis());
1497                    } else {
1498                        Log.i(LOG_TAG, "NITZ: ignore, a previous update was "
1499                                + timeSinceLastUpdate + "ms ago and gained=" + gained + "ms");
1500                        return;
1501                    }
1502                }
1503
1504                /**
1505                 * Update properties and save the time we did the update
1506                 */
1507                Log.i(LOG_TAG, "NITZ: update nitz time property");
1508                SystemProperties.set("gsm.nitz.time", String.valueOf(c.getTimeInMillis()));
1509                mSavedTime = c.getTimeInMillis();
1510                mSavedAtTime = SystemClock.elapsedRealtime();
1511            } finally {
1512                long end = SystemClock.elapsedRealtime();
1513                Log.i(LOG_TAG, "NITZ: end=" + end + " dur=" + (end - start));
1514                mWakeLock.release();
1515            }
1516        } catch (RuntimeException ex) {
1517            Log.e(LOG_TAG, "NITZ: Parsing NITZ time " + nitz, ex);
1518        }
1519    }
1520
1521    private boolean getAutoTime() {
1522        try {
1523            return Settings.System.getInt(cr, Settings.System.AUTO_TIME) > 0;
1524        } catch (SettingNotFoundException snfe) {
1525            return true;
1526        }
1527    }
1528
1529    private void saveNitzTimeZone(String zoneId) {
1530        mSavedTimeZone = zoneId;
1531    }
1532
1533    /**
1534     * Set the timezone and send out a sticky broadcast so the system can
1535     * determine if the timezone was set by the carrier.
1536     *
1537     * @param zoneId timezone set by carrier
1538     */
1539    private void setAndBroadcastNetworkSetTimeZone(String zoneId) {
1540        AlarmManager alarm =
1541            (AlarmManager) phone.getContext().getSystemService(Context.ALARM_SERVICE);
1542        alarm.setTimeZone(zoneId);
1543        Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE);
1544        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
1545        intent.putExtra("time-zone", zoneId);
1546        phone.getContext().sendStickyBroadcast(intent);
1547    }
1548
1549    /**
1550     * Set the time and Send out a sticky broadcast so the system can determine
1551     * if the time was set by the carrier.
1552     *
1553     * @param time time set by network
1554     */
1555    private void setAndBroadcastNetworkSetTime(long time) {
1556        SystemClock.setCurrentTimeMillis(time);
1557        Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIME);
1558        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
1559        intent.putExtra("time", time);
1560        phone.getContext().sendStickyBroadcast(intent);
1561    }
1562
1563     private void revertToNitz() {
1564        if (Settings.System.getInt(cr, Settings.System.AUTO_TIME, 0) == 0) {
1565            return;
1566        }
1567        Log.d(LOG_TAG, "Reverting to NITZ: tz='" + mSavedTimeZone
1568                + "' mSavedTime=" + mSavedTime
1569                + " mSavedAtTime=" + mSavedAtTime);
1570        if (mSavedTimeZone != null && mSavedTime != 0 && mSavedAtTime != 0) {
1571            setAndBroadcastNetworkSetTimeZone(mSavedTimeZone);
1572            setAndBroadcastNetworkSetTime(mSavedTime
1573                    + (SystemClock.elapsedRealtime() - mSavedAtTime));
1574        }
1575    }
1576
1577    private boolean isSidsAllZeros() {
1578        if (mHomeSystemId != null) {
1579            for (int i=0; i < mHomeSystemId.length; i++) {
1580                if (mHomeSystemId[i] != 0) {
1581                    return false;
1582                }
1583            }
1584        }
1585        return true;
1586    }
1587
1588    /**
1589     * Check whether a specified system ID that matches one of the home system IDs.
1590     */
1591    private boolean isHomeSid(int sid) {
1592        if (mHomeSystemId != null) {
1593            for (int i=0; i < mHomeSystemId.length; i++) {
1594                if (sid == mHomeSystemId[i]) {
1595                    return true;
1596                }
1597            }
1598        }
1599        return false;
1600    }
1601
1602    /**
1603     * @return true if phone is camping on a technology
1604     * that could support voice and data simultaneously.
1605     */
1606    boolean isConcurrentVoiceAndData() {
1607        // Note: it needs to be confirmed which CDMA network types
1608        // can support voice and data calls concurrently.
1609        // For the time-being, the return value will be false.
1610        return false;
1611    }
1612
1613    protected void log(String s) {
1614        Log.d(LOG_TAG, "[CdmaServiceStateTracker] " + s);
1615    }
1616
1617    public String getMdnNumber() {
1618        return mMdn;
1619    }
1620
1621    public String getCdmaMin() {
1622         return mMin;
1623    }
1624
1625    /** Returns null if NV is not yet ready */
1626    public String getPrlVersion() {
1627        return mPrlVersion;
1628    }
1629
1630    /**
1631     * Returns IMSI as MCC + MNC + MIN
1632     */
1633    String getImsi() {
1634        // TODO: When RUIM is enabled, IMSI will come from RUIM not build-time props.
1635        String operatorNumeric = SystemProperties.get(
1636                TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC, "");
1637
1638        if (!TextUtils.isEmpty(operatorNumeric) && getCdmaMin() != null) {
1639            return (operatorNumeric + getCdmaMin());
1640        } else {
1641            return null;
1642        }
1643    }
1644
1645    /**
1646     * Check if subscription data has been assigned to mMin
1647     *
1648     * return true if MIN info is ready; false otherwise.
1649     */
1650    public boolean isMinInfoReady() {
1651        return mIsMinInfoReady;
1652    }
1653
1654    /**
1655     * process the pending request to turn radio off after data is disconnected
1656     *
1657     * return true if there is pending request to process; false otherwise.
1658     */
1659    public boolean processPendingRadioPowerOffAfterDataOff() {
1660        synchronized(this) {
1661            if (mPendingRadioPowerOffAfterDataOff) {
1662                if (DBG) log("Process pending request to turn radio off.");
1663                removeMessages(EVENT_SET_RADIO_POWER_OFF);
1664                hangupAndPowerOff();
1665                mPendingRadioPowerOffAfterDataOff = false;
1666                return true;
1667            }
1668            return false;
1669        }
1670    }
1671
1672    private void hangupAndPowerOff() {
1673        // hang up all active voice calls
1674        phone.mCT.ringingCall.hangupIfAlive();
1675        phone.mCT.backgroundCall.hangupIfAlive();
1676        phone.mCT.foregroundCall.hangupIfAlive();
1677        cm.setRadioPower(false, null);
1678    }
1679}
1680