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