GsmServiceStateTracker.java revision 8c6b883cd3c835df32cab4cbf395ebf648bb7d7e
1/*
2 * Copyright (C) 2006 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.gsm;
18
19import static com.android.internal.telephony.TelephonyProperties.PROPERTY_DATA_NETWORK_TYPE;
20import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_ALPHA;
21import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC;
22import static com.android.internal.telephony.TelephonyProperties.PROPERTY_OPERATOR_ALPHA;
23import static com.android.internal.telephony.TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY;
24import static com.android.internal.telephony.TelephonyProperties.PROPERTY_OPERATOR_ISROAMING;
25import static com.android.internal.telephony.TelephonyProperties.PROPERTY_OPERATOR_NUMERIC;
26import android.app.AlarmManager;
27import android.app.Notification;
28import android.app.NotificationManager;
29import android.app.PendingIntent;
30import android.content.ContentResolver;
31import android.content.Context;
32import android.content.Intent;
33import android.database.ContentObserver;
34import android.os.AsyncResult;
35import android.os.Handler;
36import android.os.Message;
37import android.os.PowerManager;
38import android.os.Registrant;
39import android.os.RegistrantList;
40import android.os.SystemClock;
41import android.os.SystemProperties;
42import android.provider.Checkin;
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.gsm.GsmCellLocation;
49import android.text.TextUtils;
50import android.util.Config;
51import android.util.EventLog;
52import android.util.Log;
53import android.util.TimeUtils;
54
55import com.android.internal.telephony.CommandException;
56import com.android.internal.telephony.CommandsInterface;
57import com.android.internal.telephony.DataConnectionTracker;
58import com.android.internal.telephony.IccCard;
59import com.android.internal.telephony.RILConstants;
60import com.android.internal.telephony.ServiceStateTracker;
61import com.android.internal.telephony.TelephonyEventLog;
62import com.android.internal.telephony.TelephonyIntents;
63
64import java.util.Arrays;
65import java.util.Calendar;
66import java.util.Date;
67import java.util.TimeZone;
68
69/**
70 * {@hide}
71 */
72final class GsmServiceStateTracker extends ServiceStateTracker {
73
74    //***** Instance Variables
75    GSMPhone phone;
76    GsmCellLocation cellLoc;
77    GsmCellLocation newCellLoc;
78    int mPreferredNetworkType;
79    RestrictedState rs;
80
81    private int gprsState = ServiceState.STATE_OUT_OF_SERVICE;
82    private int newGPRSState = ServiceState.STATE_OUT_OF_SERVICE;
83
84    /**
85     *  The access technology currently in use: DATA_ACCESS_
86     */
87    private int networkType = 0;
88    private int newNetworkType = 0;
89    /* gsm roaming status solely based on TS 27.007 7.2 CREG */
90    private boolean mGsmRoaming = false;
91    /* data roaming status solely based on TS 27.007 10.1.19 CGREG */
92    private boolean mDataRoaming = false;
93    private boolean newDataRoaming = false;
94
95    private RegistrantList gprsAttachedRegistrants = new RegistrantList();
96    private RegistrantList gprsDetachedRegistrants = new RegistrantList();
97    private RegistrantList psRestrictEnabledRegistrants = new RegistrantList();
98    private RegistrantList psRestrictDisabledRegistrants = new RegistrantList();
99
100    // Sometimes we get the NITZ time before we know what country we are in.
101    // Keep the time zone information from the NITZ string so we can fix
102    // the time zone once know the country.
103    private boolean mNeedFixZone = false;
104    private int mZoneOffset;
105    private boolean mZoneDst;
106    private long mZoneTime;
107    private boolean mGotCountryCode = false;
108    private ContentResolver cr;
109
110    String mSavedTimeZone;
111    long mSavedTime;
112    long mSavedAtTime;
113
114    // We can't register for SIM_RECORDS_LOADED immediately because the
115    // SIMRecords object may not be instantiated yet.
116    private boolean mNeedToRegForSimLoaded;
117
118    // Started the recheck process after finding gprs should registerd but not
119    private boolean mStartedGprsRegCheck = false;
120    // Already sent the event-log for no gprs register
121    private boolean mReportedGprsNoReg = false;
122
123    /**
124     * The Notification object given to the NotificationManager.
125     */
126    private Notification mNotification;
127
128    // Wake lock used while setting time of day.
129    private PowerManager.WakeLock mWakeLock;
130    private static final String WAKELOCK_TAG = "ServiceStateTracker";
131
132    // Keep track of SPN display rules, so we only broadcast intent if something changes.
133    private String curSpn = null;
134    private String curPlmn = null;
135    private int curSpnRule = 0;
136
137    //***** Constants
138
139    static final boolean DBG = true;
140    static final String LOG_TAG = "GSM";
141
142    // waiting period before recheck gprs and voice registration
143    static final int DEFAULT_GPRS_CHECK_PERIOD_MILLIS = 60 * 1000;
144
145    // notification type
146    static final int PS_ENABLED = 1001;             // Access Control blocks data service
147    static final int PS_DISABLED = 1002;            // Access Control enables data service
148    static final int CS_ENABLED = 1003;             // Access Control blocks all voice/sms service
149    static final int CS_DISABLED = 1004;            // Access Control enables all voice/sms service
150    static final int CS_NORMAL_ENABLED = 1005;      // Access Control blocks normal voice/sms service
151    static final int CS_EMERGENCY_ENABLED = 1006;   // Access Control blocks emergency call service
152
153    // notification id
154    static final int PS_NOTIFICATION = 888; //id to update and cancel PS restricted
155    static final int CS_NOTIFICATION = 999; //id to update and cancel CS restricted
156
157    private ContentObserver mAutoTimeObserver = new ContentObserver(new Handler()) {
158        @Override
159        public void onChange(boolean selfChange) {
160            Log.i("GsmServiceStateTracker", "Auto time state changed");
161            revertToNitz();
162        }
163    };
164
165
166    //***** Constructors
167
168    public GsmServiceStateTracker(GSMPhone phone) {
169        super();
170
171        this.phone = phone;
172        cm = phone.mCM;
173        ss = new ServiceState();
174        newSS = new ServiceState();
175        cellLoc = new GsmCellLocation();
176        newCellLoc = new GsmCellLocation();
177        rs = new RestrictedState();
178        mSignalStrength = new SignalStrength();
179
180        PowerManager powerManager =
181                (PowerManager)phone.getContext().getSystemService(Context.POWER_SERVICE);
182        mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
183
184        cm.registerForAvailable(this, EVENT_RADIO_AVAILABLE, null);
185        cm.registerForRadioStateChanged(this, EVENT_RADIO_STATE_CHANGED, null);
186
187        cm.registerForNetworkStateChanged(this, EVENT_NETWORK_STATE_CHANGED, null);
188        cm.setOnNITZTime(this, EVENT_NITZ_TIME, null);
189        cm.setOnSignalStrengthUpdate(this, EVENT_SIGNAL_STRENGTH_UPDATE, null);
190        cm.setOnRestrictedStateChanged(this, EVENT_RESTRICTED_STATE_CHANGED, null);
191        cm.registerForSIMReady(this, EVENT_SIM_READY, null);
192
193        // system setting property AIRPLANE_MODE_ON is set in Settings.
194        int airplaneMode = Settings.System.getInt(
195                phone.getContext().getContentResolver(),
196                Settings.System.AIRPLANE_MODE_ON, 0);
197        mDesiredPowerState = ! (airplaneMode > 0);
198
199        cr = phone.getContext().getContentResolver();
200        cr.registerContentObserver(
201                Settings.System.getUriFor(Settings.System.AUTO_TIME), true,
202                mAutoTimeObserver);
203        setSignalStrengthDefaultValues();
204        mNeedToRegForSimLoaded = true;
205    }
206
207    public void dispose() {
208        //Unregister for all events
209        cm.unregisterForAvailable(this);
210        cm.unregisterForRadioStateChanged(this);
211        cm.unregisterForNetworkStateChanged(this);
212        cm.unregisterForSIMReady(this);
213
214        phone.mSIMRecords.unregisterForRecordsLoaded(this);
215        cm.unSetOnSignalStrengthUpdate(this);
216        cm.unSetOnRestrictedStateChanged(this);
217        cm.unSetOnNITZTime(this);
218        cr.unregisterContentObserver(this.mAutoTimeObserver);
219    }
220
221    protected void finalize() {
222        if(DBG) Log.d(LOG_TAG, "GsmServiceStateTracker finalized");
223    }
224
225    /**
226     * Registration point for transition into GPRS attached.
227     * @param h handler to notify
228     * @param what what code of message when delivered
229     * @param obj placed in Message.obj
230     */
231    /*protected*/ void registerForGprsAttached(Handler h, int what, Object obj) {
232        Registrant r = new Registrant(h, what, obj);
233        gprsAttachedRegistrants.add(r);
234
235        if (gprsState == ServiceState.STATE_IN_SERVICE) {
236            r.notifyRegistrant();
237        }
238    }
239
240    /*protected*/ void unregisterForGprsAttached(Handler h) {
241        gprsAttachedRegistrants.remove(h);
242    }
243
244    /*protected*/  void registerForNetworkAttach(Handler h, int what, Object obj) {
245        Registrant r = new Registrant(h, what, obj);
246        networkAttachedRegistrants.add(r);
247
248        if (ss.getState() == ServiceState.STATE_IN_SERVICE) {
249            r.notifyRegistrant();
250        }
251    }
252
253    /*protected*/  void unregisterForNetworkAttach(Handler h) {
254        networkAttachedRegistrants.remove(h);
255    }
256    /**
257     * Registration point for transition into GPRS detached.
258     * @param h handler to notify
259     * @param what what code of message when delivered
260     * @param obj placed in Message.obj
261     */
262    /*protected*/ void registerForGprsDetached(Handler h, int what, Object obj) {
263        Registrant r = new Registrant(h, what, obj);
264        gprsDetachedRegistrants.add(r);
265
266        if (gprsState == ServiceState.STATE_OUT_OF_SERVICE) {
267            r.notifyRegistrant();
268        }
269    }
270
271    /*protected*/  void unregisterForGprsDetached(Handler h) {
272        gprsDetachedRegistrants.remove(h);
273    }
274
275    /**
276     * Registration point for transition into packet service restricted zone.
277     * @param h handler to notify
278     * @param what what code of message when delivered
279     * @param obj placed in Message.obj
280     */
281    /*protected*/  void registerForPsRestrictedEnabled(Handler h, int what, Object obj) {
282        Log.d(LOG_TAG, "[DSAC DEB] " + "registerForPsRestrictedEnabled ");
283        Registrant r = new Registrant(h, what, obj);
284        psRestrictEnabledRegistrants.add(r);
285
286        if (rs.isPsRestricted()) {
287            r.notifyRegistrant();
288        }
289    }
290
291    /*protected*/  void unregisterForPsRestrictedEnabled(Handler h) {
292        psRestrictEnabledRegistrants.remove(h);
293    }
294
295    /**
296     * Registration point for transition out of packet service restricted zone.
297     * @param h handler to notify
298     * @param what what code of message when delivered
299     * @param obj placed in Message.obj
300     */
301    /*protected*/  void registerForPsRestrictedDisabled(Handler h, int what, Object obj) {
302        Log.d(LOG_TAG, "[DSAC DEB] " + "registerForPsRestrictedDisabled ");
303        Registrant r = new Registrant(h, what, obj);
304        psRestrictDisabledRegistrants.add(r);
305
306        if (rs.isPsRestricted()) {
307            r.notifyRegistrant();
308        }
309    }
310
311    /*protected*/  void unregisterForPsRestrictedDisabled(Handler h) {
312        psRestrictDisabledRegistrants.remove(h);
313    }
314
315    /*protected*/ boolean getDataRoaming() {
316        return mDataRoaming;
317    }
318
319    //***** Called from GSMPhone
320    public void
321    getLacAndCid(Message onComplete) {
322        cm.getRegistrationState(obtainMessage(
323                        EVENT_GET_LOC_DONE, onComplete));
324    }
325
326
327    //***** Overridden from ServiceStateTracker
328    public void
329    handleMessage (Message msg) {
330        AsyncResult ar;
331        int[] ints;
332        String[] strings;
333        Message message;
334
335        switch (msg.what) {
336            case EVENT_RADIO_AVAILABLE:
337                //this is unnecessary
338                //setPowerStateToDesired();
339                break;
340
341            case EVENT_SIM_READY:
342                // The SIM is now ready i.e if it was locked
343                // it has been unlocked. At this stage, the radio is already
344                // powered on.
345                if (mNeedToRegForSimLoaded) {
346                    phone.mSIMRecords.registerForRecordsLoaded(this,
347                            EVENT_SIM_RECORDS_LOADED, null);
348                    mNeedToRegForSimLoaded = false;
349                }
350                // restore the previous network selection.
351                phone.restoreSavedNetworkSelection(null);
352                pollState();
353                // Signal strength polling stops when radio is off
354                queueNextSignalStrengthPoll();
355                break;
356
357            case EVENT_RADIO_STATE_CHANGED:
358                // This will do nothing in the radio not
359                // available case
360                setPowerStateToDesired();
361                pollState();
362                break;
363
364            case EVENT_NETWORK_STATE_CHANGED:
365                pollState();
366                break;
367
368            case EVENT_GET_SIGNAL_STRENGTH:
369                // This callback is called when signal strength is polled
370                // all by itself
371
372                if (!(cm.getRadioState().isOn()) || (cm.getRadioState().isCdma())) {
373                    // Polling will continue when radio turns back on and not CDMA
374                    return;
375                }
376                ar = (AsyncResult) msg.obj;
377                onSignalStrengthResult(ar);
378                queueNextSignalStrengthPoll();
379
380                break;
381
382            case EVENT_GET_LOC_DONE:
383                ar = (AsyncResult) msg.obj;
384
385                if (ar.exception == null) {
386                    String states[] = (String[])ar.result;
387                    int lac = -1;
388                    int cid = -1;
389                    if (states.length >= 3) {
390                        try {
391                            if (states[1] != null && states[1].length() > 0) {
392                                lac = Integer.parseInt(states[1], 16);
393                            }
394                            if (states[2] != null && states[2].length() > 0) {
395                                cid = Integer.parseInt(states[2], 16);
396                            }
397                        } catch (NumberFormatException ex) {
398                            Log.w(LOG_TAG, "error parsing location: " + ex);
399                        }
400                    }
401
402                    // only update if lac or cid changed
403                    if (cellLoc.getCid() != cid || cellLoc.getLac() != lac) {
404                        cellLoc.setLacAndCid(lac, cid);
405                        phone.notifyLocationChanged();
406                    }
407                }
408
409                if (ar.userObj != null) {
410                    AsyncResult.forMessage(((Message) ar.userObj)).exception
411                            = ar.exception;
412                    ((Message) ar.userObj).sendToTarget();
413                }
414                break;
415
416            case EVENT_POLL_STATE_REGISTRATION:
417            case EVENT_POLL_STATE_GPRS:
418            case EVENT_POLL_STATE_OPERATOR:
419            case EVENT_POLL_STATE_NETWORK_SELECTION_MODE:
420                ar = (AsyncResult) msg.obj;
421
422                handlePollStateResult(msg.what, ar);
423                break;
424
425            case EVENT_POLL_SIGNAL_STRENGTH:
426                // Just poll signal strength...not part of pollState()
427
428                cm.getSignalStrength(obtainMessage(EVENT_GET_SIGNAL_STRENGTH));
429                break;
430
431            case EVENT_NITZ_TIME:
432                ar = (AsyncResult) msg.obj;
433
434                String nitzString = (String)((Object[])ar.result)[0];
435                long nitzReceiveTime = ((Long)((Object[])ar.result)[1]).longValue();
436
437                setTimeFromNITZString(nitzString, nitzReceiveTime);
438                break;
439
440            case EVENT_SIGNAL_STRENGTH_UPDATE:
441                // This is a notification from
442                // CommandsInterface.setOnSignalStrengthUpdate
443
444                ar = (AsyncResult) msg.obj;
445
446                // The radio is telling us about signal strength changes
447                // we don't have to ask it
448                dontPollSignalStrength = true;
449
450                onSignalStrengthResult(ar);
451                break;
452
453            case EVENT_SIM_RECORDS_LOADED:
454                updateSpnDisplay();
455                break;
456
457            case EVENT_LOCATION_UPDATES_ENABLED:
458                ar = (AsyncResult) msg.obj;
459
460                if (ar.exception == null) {
461                    getLacAndCid(null);
462                }
463                break;
464
465            case EVENT_SET_PREFERRED_NETWORK_TYPE:
466                ar = (AsyncResult) msg.obj;
467                // Don't care the result, only use for dereg network (COPS=2)
468                message = obtainMessage(EVENT_RESET_PREFERRED_NETWORK_TYPE, ar.userObj);
469                cm.setPreferredNetworkType(mPreferredNetworkType, message);
470                break;
471
472            case EVENT_RESET_PREFERRED_NETWORK_TYPE:
473                ar = (AsyncResult) msg.obj;
474                if (ar.userObj != null) {
475                    AsyncResult.forMessage(((Message) ar.userObj)).exception
476                            = ar.exception;
477                    ((Message) ar.userObj).sendToTarget();
478                }
479                break;
480
481            case EVENT_GET_PREFERRED_NETWORK_TYPE:
482                ar = (AsyncResult) msg.obj;
483
484                if (ar.exception == null) {
485                    mPreferredNetworkType = ((int[])ar.result)[0];
486                } else {
487                    mPreferredNetworkType = RILConstants.NETWORK_MODE_GLOBAL;
488                }
489
490                message = obtainMessage(EVENT_SET_PREFERRED_NETWORK_TYPE, ar.userObj);
491                int toggledNetworkType = RILConstants.NETWORK_MODE_GLOBAL;
492
493                cm.setPreferredNetworkType(toggledNetworkType, message);
494                break;
495
496            case EVENT_CHECK_REPORT_GPRS:
497                if (ss != null && !isGprsConsistant(gprsState, ss.getState())) {
498
499                    // Can't register data sevice while voice service is ok
500                    // i.e. CREG is ok while CGREG is not
501                    // possible a network or baseband side error
502                    int cid = -1;
503                    GsmCellLocation loc = ((GsmCellLocation)phone.getCellLocation());
504                    if (loc != null) cid = loc.getCid();
505
506                    EventLog.List val = new EventLog.List(ss.getOperatorNumeric(), cid);
507                    EventLog.writeEvent(TelephonyEventLog.EVENT_LOG_CGREG_FAIL, val);
508                    mReportedGprsNoReg = true;
509                }
510                mStartedGprsRegCheck = false;
511                break;
512
513            case EVENT_RESTRICTED_STATE_CHANGED:
514                // This is a notification from
515                // CommandsInterface.setOnRestrictedStateChanged
516
517                Log.d(LOG_TAG, "[DSAC DEB] " + "EVENT_RESTRICTED_STATE_CHANGED");
518
519                ar = (AsyncResult) msg.obj;
520
521                onRestrictedStateChanged(ar);
522                break;
523
524            default:
525                Log.e(LOG_TAG, "Unhandled message with number: " + msg.what);
526            break;
527        }
528    }
529
530    //***** Private Instance Methods
531
532    protected void setPowerStateToDesired()
533    {
534        // If we want it on and it's off, turn it on
535        if (mDesiredPowerState
536            && cm.getRadioState() == CommandsInterface.RadioState.RADIO_OFF) {
537            cm.setRadioPower(true, null);
538        } else if (!mDesiredPowerState && cm.getRadioState().isOn()) {
539            DataConnectionTracker dcTracker = phone.mDataConnection;
540            if (! dcTracker.isDataConnectionAsDesired()) {
541
542                EventLog.List val = new EventLog.List(
543                        dcTracker.getStateInString(),
544                        (dcTracker.getAnyDataEnabled() ? 1 : 0) );
545                EventLog.writeEvent(TelephonyEventLog.EVENT_LOG_DATA_STATE_RADIO_OFF, val);
546            }
547            Message msg = dcTracker.obtainMessage(DataConnectionTracker.EVENT_CLEAN_UP_CONNECTION);
548            msg.arg1 = 1; // tearDown is true
549            msg.obj = GSMPhone.REASON_RADIO_TURNED_OFF;
550            dcTracker.sendMessage(msg);
551
552            // poll data state up to 15 times, with a 100ms delay
553            // totaling 1.5 sec. Normal data disable action will finish in 100ms.
554            for (int i = 0; i < MAX_NUM_DATA_STATE_READS; i++) {
555                if (dcTracker.getState() != DataConnectionTracker.State.CONNECTED
556                        && dcTracker.getState() != DataConnectionTracker.State.DISCONNECTING) {
557                    Log.d(LOG_TAG, "Data shutdown complete.");
558                    break;
559                }
560                SystemClock.sleep(DATA_STATE_POLL_SLEEP_MS);
561            }
562            // If it's on and available and we want it off..
563            cm.setRadioPower(false, null);
564        } // Otherwise, we're in the desired state
565    }
566
567    protected void updateSpnDisplay() {
568        int rule = phone.mSIMRecords.getDisplayRule(ss.getOperatorNumeric());
569        String spn = phone.mSIMRecords.getServiceProviderName();
570        String plmn = ss.getOperatorAlphaLong();
571
572        if (rule != curSpnRule
573                || !TextUtils.equals(spn, curSpn)
574                || !TextUtils.equals(plmn, curPlmn)) {
575            boolean showSpn =
576                (rule & SIMRecords.SPN_RULE_SHOW_SPN) == SIMRecords.SPN_RULE_SHOW_SPN;
577            boolean showPlmn =
578                (rule & SIMRecords.SPN_RULE_SHOW_PLMN) == SIMRecords.SPN_RULE_SHOW_PLMN;
579            Intent intent = new Intent(Intents.SPN_STRINGS_UPDATED_ACTION);
580            intent.putExtra(Intents.EXTRA_SHOW_SPN, showSpn);
581            intent.putExtra(Intents.EXTRA_SPN, spn);
582            intent.putExtra(Intents.EXTRA_SHOW_PLMN, showPlmn);
583            intent.putExtra(Intents.EXTRA_PLMN, plmn);
584            phone.getContext().sendStickyBroadcast(intent);
585        }
586        curSpnRule = rule;
587        curSpn = spn;
588        curPlmn = plmn;
589    }
590
591    /**
592     * Handle the result of one of the pollState()-related requests
593     */
594
595    protected void
596    handlePollStateResult (int what, AsyncResult ar) {
597        int ints[];
598        String states[];
599
600        // Ignore stale requests from last poll
601        if (ar.userObj != pollingContext) return;
602
603        if (ar.exception != null) {
604            CommandException.Error err=null;
605
606            if (ar.exception instanceof CommandException) {
607                err = ((CommandException)(ar.exception)).getCommandError();
608            }
609
610            if (err == CommandException.Error.RADIO_NOT_AVAILABLE) {
611                // Radio has crashed or turned off
612                cancelPollState();
613                return;
614            }
615
616            if (!cm.getRadioState().isOn()) {
617                // Radio has crashed or turned off
618                cancelPollState();
619                return;
620            }
621
622            if (err != CommandException.Error.OP_NOT_ALLOWED_BEFORE_REG_NW &&
623                    err != CommandException.Error.OP_NOT_ALLOWED_BEFORE_REG_NW) {
624                Log.e(LOG_TAG,
625                        "RIL implementation has returned an error where it must succeed" +
626                        ar.exception);
627            }
628        } else try {
629            switch (what) {
630                case EVENT_POLL_STATE_REGISTRATION:
631                    states = (String[])ar.result;
632                    int lac = -1;
633                    int cid = -1;
634                    int regState = -1;
635                    if (states.length > 0) {
636                        try {
637                            regState = Integer.parseInt(states[0]);
638                            if (states.length >= 3) {
639                                if (states[1] != null && states[1].length() > 0) {
640                                    lac = Integer.parseInt(states[1], 16);
641                                }
642                                if (states[2] != null && states[2].length() > 0) {
643                                    cid = Integer.parseInt(states[2], 16);
644                                }
645                            }
646                        } catch (NumberFormatException ex) {
647                            Log.w(LOG_TAG, "error parsing RegistrationState: " + ex);
648                        }
649                    }
650
651                    mGsmRoaming = regCodeIsRoaming(regState);
652                    newSS.setState (regCodeToServiceState(regState));
653
654                    // LAC and CID are -1 if not avail
655                    newCellLoc.setLacAndCid(lac, cid);
656                break;
657
658                case EVENT_POLL_STATE_GPRS:
659                    states = (String[])ar.result;
660
661                    int type = 0;
662                    regState = -1;
663                    if (states.length > 0) {
664                        try {
665                            regState = Integer.parseInt(states[0]);
666
667                            // states[3] (if present) is the current radio technology
668                            if (states.length >= 4 && states[3] != null) {
669                                type = Integer.parseInt(states[3]);
670                            }
671                        } catch (NumberFormatException ex) {
672                            Log.w(LOG_TAG, "error parsing GprsRegistrationState: " + ex);
673                        }
674                    }
675                    newGPRSState = regCodeToServiceState(regState);
676                    newDataRoaming = regCodeIsRoaming(regState);
677                    newNetworkType = type;
678                break;
679
680                case EVENT_POLL_STATE_OPERATOR:
681                    String opNames[] = (String[])ar.result;
682
683                    if (opNames != null && opNames.length >= 3) {
684                        newSS.setOperatorName (
685                                opNames[0], opNames[1], opNames[2]);
686                    }
687                break;
688
689                case EVENT_POLL_STATE_NETWORK_SELECTION_MODE:
690                    ints = (int[])ar.result;
691                    newSS.setIsManualSelection(ints[0] == 1);
692                break;
693            }
694
695        } catch (RuntimeException ex) {
696            Log.e(LOG_TAG, "Exception while polling service state. "
697                            + "Probably malformed RIL response.", ex);
698        }
699
700        pollingContext[0]--;
701
702        if (pollingContext[0] == 0) {
703            newSS.setRoaming(isRoamingBetweenOperators(mGsmRoaming, newSS));
704            // when both roaming indicators are true but not roaming between
705            // operators, roaming should set to false.
706            if (newDataRoaming && mGsmRoaming && !newSS.getRoaming()) {
707                newDataRoaming = false;
708            }
709            pollStateDone();
710        }
711
712    }
713
714    private void setSignalStrengthDefaultValues() {
715        mSignalStrength = new SignalStrength(99, -1, -1, -1, -1, -1, -1, true);
716    }
717
718    /**
719     * A complete "service state" from our perspective is
720     * composed of a handful of separate requests to the radio.
721     *
722     * We make all of these requests at once, but then abandon them
723     * and start over again if the radio notifies us that some
724     * event has changed
725     */
726
727    private void
728    pollState() {
729        pollingContext = new int[1];
730        pollingContext[0] = 0;
731
732        switch (cm.getRadioState()) {
733            case RADIO_UNAVAILABLE:
734                newSS.setStateOutOfService();
735                newCellLoc.setStateInvalid();
736                setSignalStrengthDefaultValues();
737                mGotCountryCode = false;
738                newDataRoaming = false;
739
740                pollStateDone();
741            break;
742
743            case RADIO_OFF:
744                newSS.setStateOff();
745                newCellLoc.setStateInvalid();
746                setSignalStrengthDefaultValues();
747                mGotCountryCode = false;
748                newDataRoaming = false;
749
750                pollStateDone();
751            break;
752
753            case RUIM_NOT_READY:
754            case RUIM_READY:
755            case RUIM_LOCKED_OR_ABSENT:
756            case NV_NOT_READY:
757            case NV_READY:
758                Log.d(LOG_TAG, "Radio Technology Change ongoing, setting SS to off");
759                newSS.setStateOff();
760                newCellLoc.setStateInvalid();
761                setSignalStrengthDefaultValues();
762                mGotCountryCode = false;
763                newDataRoaming = false;
764                mDataRoaming = false;
765
766                //NOTE: pollStateDone() is not needed in this case
767                break;
768
769            default:
770                // Issue all poll-related commands at once
771                // then count down the responses, which
772                // are allowed to arrive out-of-order
773
774                pollingContext[0]++;
775                cm.getOperator(
776                    obtainMessage(
777                        EVENT_POLL_STATE_OPERATOR, pollingContext));
778
779                pollingContext[0]++;
780                cm.getGPRSRegistrationState(
781                    obtainMessage(
782                        EVENT_POLL_STATE_GPRS, pollingContext));
783
784                pollingContext[0]++;
785                cm.getRegistrationState(
786                    obtainMessage(
787                        EVENT_POLL_STATE_REGISTRATION, pollingContext));
788
789                pollingContext[0]++;
790                cm.getNetworkSelectionMode(
791                    obtainMessage(
792                        EVENT_POLL_STATE_NETWORK_SELECTION_MODE, pollingContext));
793            break;
794        }
795    }
796
797    private static String networkTypeToString(int type) {
798        //Network Type from GPRS_REGISTRATION_STATE
799        String ret = "unknown";
800
801        switch (type) {
802            case DATA_ACCESS_GPRS:
803                ret = "GPRS";
804                break;
805            case DATA_ACCESS_EDGE:
806                ret = "EDGE";
807                break;
808            case DATA_ACCESS_UMTS:
809                ret = "UMTS";
810                break;
811            default:
812                Log.e(LOG_TAG, "Wrong network type: " + Integer.toString(type));
813                break;
814        }
815
816        return ret;
817    }
818
819    private void
820    pollStateDone() {
821        if (DBG) {
822            Log.d(LOG_TAG, "Poll ServiceState done: " +
823                " oldSS=[" + ss + "] newSS=[" + newSS +
824                "] oldGprs=" + gprsState + " newGprs=" + newGPRSState +
825                " oldType=" + networkTypeToString(networkType) +
826                " newType=" + networkTypeToString(newNetworkType));
827        }
828
829        boolean hasRegistered =
830            ss.getState() != ServiceState.STATE_IN_SERVICE
831            && newSS.getState() == ServiceState.STATE_IN_SERVICE;
832
833        boolean hasDeregistered =
834            ss.getState() == ServiceState.STATE_IN_SERVICE
835            && newSS.getState() != ServiceState.STATE_IN_SERVICE;
836
837        boolean hasGprsAttached =
838                gprsState != ServiceState.STATE_IN_SERVICE
839                && newGPRSState == ServiceState.STATE_IN_SERVICE;
840
841        boolean hasGprsDetached =
842                gprsState == ServiceState.STATE_IN_SERVICE
843                && newGPRSState != ServiceState.STATE_IN_SERVICE;
844
845        boolean hasNetworkTypeChanged = networkType != newNetworkType;
846
847        boolean hasChanged = !newSS.equals(ss);
848
849        boolean hasRoamingOn = !mDataRoaming && newDataRoaming;
850
851        boolean hasRoamingOff = mDataRoaming && !newDataRoaming;
852
853        boolean hasLocationChanged = !newCellLoc.equals(cellLoc);
854
855        ServiceState tss;
856        tss = ss;
857        ss = newSS;
858        newSS = tss;
859        // clean slate for next time
860        newSS.setStateOutOfService();
861
862        GsmCellLocation tcl = cellLoc;
863        cellLoc = newCellLoc;
864        newCellLoc = tcl;
865
866        gprsState = newGPRSState;
867        networkType = newNetworkType;
868        mDataRoaming = newDataRoaming;
869
870        newSS.setStateOutOfService(); // clean slate for next time
871
872        if (hasNetworkTypeChanged) {
873            phone.setSystemProperty(PROPERTY_DATA_NETWORK_TYPE,
874                    networkTypeToString(networkType));
875        }
876
877        if (hasRegistered) {
878            Checkin.updateStats(phone.getContext().getContentResolver(),
879                    Checkin.Stats.Tag.PHONE_GSM_REGISTERED, 1, 0.0);
880            networkAttachedRegistrants.notifyRegistrants();
881        }
882
883        if (hasChanged) {
884            String operatorNumeric;
885
886            phone.setSystemProperty(PROPERTY_OPERATOR_ALPHA,
887                ss.getOperatorAlphaLong());
888
889            operatorNumeric = ss.getOperatorNumeric();
890            phone.setSystemProperty(PROPERTY_OPERATOR_NUMERIC, operatorNumeric);
891
892            if (operatorNumeric == null) {
893                phone.setSystemProperty(PROPERTY_OPERATOR_ISO_COUNTRY, "");
894            } else {
895                String iso = "";
896                try{
897                    iso = MccTable.countryCodeForMcc(Integer.parseInt(
898                            operatorNumeric.substring(0,3)));
899                } catch ( NumberFormatException ex){
900                    Log.w(LOG_TAG, "countryCodeForMcc error" + ex);
901                } catch ( StringIndexOutOfBoundsException ex) {
902                    Log.w(LOG_TAG, "countryCodeForMcc error" + ex);
903                }
904
905                phone.setSystemProperty(PROPERTY_OPERATOR_ISO_COUNTRY, iso);
906                mGotCountryCode = true;
907
908                if (mNeedFixZone) {
909                    TimeZone zone = null;
910                    // If the offset is (0, false) and the timezone property
911                    // is set, use the timezone property rather than
912                    // GMT.
913                    String zoneName = SystemProperties.get(TIMEZONE_PROPERTY);
914                    if ((mZoneOffset == 0) && (mZoneDst == false) &&
915                        (zoneName != null) && (zoneName.length() > 0) &&
916                        (Arrays.binarySearch(GMT_COUNTRY_CODES, iso) < 0)) {
917                        zone = TimeZone.getDefault();
918                        // For NITZ string without timezone,
919                        // need adjust time to reflect default timezone setting
920                        long tzOffset;
921                        tzOffset = zone.getOffset(System.currentTimeMillis());
922                        if (getAutoTime()) {
923                            setAndBroadcastNetworkSetTime(System.currentTimeMillis() - tzOffset);
924                        } else {
925                            // Adjust the saved NITZ time to account for tzOffset.
926                            mSavedTime = mSavedTime - tzOffset;
927                        }
928                    } else if (iso.equals("")){
929                        // Country code not found.  This is likely a test network.
930                        // Get a TimeZone based only on the NITZ parameters (best guess).
931                        zone = getNitzTimeZone(mZoneOffset, mZoneDst, mZoneTime);
932                    } else {
933                        zone = TimeUtils.getTimeZone(mZoneOffset,
934                            mZoneDst, mZoneTime, iso);
935                    }
936
937                    mNeedFixZone = false;
938
939                    if (zone != null) {
940                        if (getAutoTime()) {
941                            setAndBroadcastNetworkSetTimeZone(zone.getID());
942                        }
943                        saveNitzTimeZone(zone.getID());
944                    }
945                }
946            }
947
948            phone.setSystemProperty(PROPERTY_OPERATOR_ISROAMING,
949                ss.getRoaming() ? "true" : "false");
950
951            updateSpnDisplay();
952            phone.notifyServiceStateChanged(ss);
953        }
954
955        if (hasGprsAttached) {
956            gprsAttachedRegistrants.notifyRegistrants();
957        }
958
959        if (hasGprsDetached) {
960            gprsDetachedRegistrants.notifyRegistrants();
961        }
962
963        if (hasNetworkTypeChanged) {
964            phone.notifyDataConnection(null);
965        }
966
967        if (hasRoamingOn) {
968            roamingOnRegistrants.notifyRegistrants();
969        }
970
971        if (hasRoamingOff) {
972            roamingOffRegistrants.notifyRegistrants();
973        }
974
975        if (hasLocationChanged) {
976            phone.notifyLocationChanged();
977        }
978
979        if (! isGprsConsistant(gprsState, ss.getState())) {
980            if (!mStartedGprsRegCheck && !mReportedGprsNoReg) {
981                mStartedGprsRegCheck = true;
982
983                int check_period = Settings.Gservices.getInt(
984                        phone.getContext().getContentResolver(),
985                        Settings.Gservices.GPRS_REGISTER_CHECK_PERIOD_MS,
986                        DEFAULT_GPRS_CHECK_PERIOD_MILLIS);
987                sendMessageDelayed(obtainMessage(EVENT_CHECK_REPORT_GPRS),
988                        check_period);
989            }
990        } else {
991            mReportedGprsNoReg = false;
992        }
993    }
994
995    /**
996     * Check if GPRS got registred while voice is registered
997     *
998     * @param gprsState for GPRS registration state, i.e. CGREG in GSM
999     * @param serviceState for voice registration state, i.e. CREG in GSM
1000     * @return false if device only register to voice but not gprs
1001     */
1002    private boolean isGprsConsistant (int gprsState, int serviceState) {
1003        return !((serviceState == ServiceState.STATE_IN_SERVICE) &&
1004                (gprsState != ServiceState.STATE_IN_SERVICE));
1005    }
1006
1007    /**
1008     * Returns a TimeZone object based only on parameters from the NITZ string.
1009     */
1010    private TimeZone getNitzTimeZone(int offset, boolean dst, long when) {
1011        TimeZone guess = findTimeZone(offset, dst, when);
1012        if (guess == null) {
1013            // Couldn't find a proper timezone.  Perhaps the DST data is wrong.
1014            guess = findTimeZone(offset, !dst, when);
1015        }
1016        if (DBG) {
1017            Log.d(LOG_TAG, "getNitzTimeZone returning "
1018                    + (guess == null ? guess : guess.getID()));
1019        }
1020        return guess;
1021    }
1022
1023    private TimeZone findTimeZone(int offset, boolean dst, long when) {
1024        int rawOffset = offset;
1025        if (dst) {
1026            rawOffset -= 3600000;
1027        }
1028        String[] zones = TimeZone.getAvailableIDs(rawOffset);
1029        TimeZone guess = null;
1030        Date d = new Date(when);
1031        for (String zone : zones) {
1032            TimeZone tz = TimeZone.getTimeZone(zone);
1033            if (tz.getOffset(when) == offset &&
1034                tz.inDaylightTime(d) == dst) {
1035                guess = tz;
1036                break;
1037            }
1038        }
1039
1040        return guess;
1041    }
1042
1043    private void
1044    queueNextSignalStrengthPoll() {
1045        if (dontPollSignalStrength || (cm.getRadioState().isCdma())) {
1046            // The radio is telling us about signal strength changes
1047            // we don't have to ask it
1048            return;
1049        }
1050
1051        Message msg;
1052
1053        msg = obtainMessage();
1054        msg.what = EVENT_POLL_SIGNAL_STRENGTH;
1055
1056        long nextTime;
1057
1058        // TODO Done't poll signal strength if screen is off
1059        sendMessageDelayed(msg, POLL_PERIOD_MILLIS);
1060    }
1061
1062    /**
1063     *  send signal-strength-changed notification if changed
1064     *  Called both for solicited and unsolicited signal stength updates
1065     */
1066    private void
1067    onSignalStrengthResult(AsyncResult ar) {
1068        SignalStrength oldSignalStrength = mSignalStrength;
1069        int rssi = 99;
1070
1071        if (ar.exception != null) {
1072            // -1 = unknown
1073            // most likely radio is resetting/disconnected
1074            setSignalStrengthDefaultValues();
1075        } else {
1076            int[] ints = (int[])ar.result;
1077
1078            // bug 658816 seems to be a case where the result is 0-length
1079            if (ints.length != 0) {
1080                rssi = ints[0];
1081            } else {
1082                Log.e(LOG_TAG, "Bogus signal strength response");
1083                rssi = 99;
1084            }
1085        }
1086
1087        mSignalStrength = new SignalStrength(rssi, -1, -1, -1,
1088                -1, -1, -1, true);
1089
1090        if (!mSignalStrength.equals(oldSignalStrength)) {
1091            try { // This takes care of delayed EVENT_POLL_SIGNAL_STRENGTH (scheduled after
1092                  // POLL_PERIOD_MILLIS) during Radio Technology Change)
1093                phone.notifySignalStrength();
1094           } catch (NullPointerException ex) {
1095                log("onSignalStrengthResult() Phone already destroyed: " + ex
1096                        + "SignalStrength not notified");
1097           }
1098        }
1099    }
1100
1101    /**
1102     * Set restricted state based on the OnRestrictedStateChanged notification
1103     * If any voice or packet restricted state changes, trigger a UI
1104     * notification and notify registrants when sim is ready.
1105     *
1106     * @param ar an int value of RIL_RESTRICTED_STATE_*
1107     */
1108    private void onRestrictedStateChanged(AsyncResult ar)
1109    {
1110        Log.d(LOG_TAG, "[DSAC DEB] " + "onRestrictedStateChanged");
1111        RestrictedState newRs = new RestrictedState();
1112
1113        Log.d(LOG_TAG, "[DSAC DEB] " + "current rs at enter "+ rs);
1114
1115        if (ar.exception == null) {
1116            int[] ints = (int[])ar.result;
1117            int state = ints[0];
1118
1119            newRs.setCsEmergencyRestricted(
1120                    ((state & RILConstants.RIL_RESTRICTED_STATE_CS_EMERGENCY) != 0) ||
1121                    ((state & RILConstants.RIL_RESTRICTED_STATE_CS_ALL) != 0) );
1122            //ignore the normal call and data restricted state before SIM READY
1123            if (phone.getIccCard().getState() == IccCard.State.READY) {
1124                newRs.setCsNormalRestricted(
1125                        ((state & RILConstants.RIL_RESTRICTED_STATE_CS_NORMAL) != 0) ||
1126                        ((state & RILConstants.RIL_RESTRICTED_STATE_CS_ALL) != 0) );
1127                newRs.setPsRestricted(
1128                        (state & RILConstants.RIL_RESTRICTED_STATE_PS_ALL)!= 0);
1129            }
1130
1131            Log.d(LOG_TAG, "[DSAC DEB] " + "new rs "+ newRs);
1132
1133            if (!rs.isPsRestricted() && newRs.isPsRestricted()) {
1134                psRestrictEnabledRegistrants.notifyRegistrants();
1135                setNotification(PS_ENABLED);
1136            } else if (rs.isPsRestricted() && !newRs.isPsRestricted()) {
1137                psRestrictDisabledRegistrants.notifyRegistrants();
1138                setNotification(PS_DISABLED);
1139            }
1140
1141            /**
1142             * There are two kind of cs restriction, normal and emergency. So
1143             * there are 4 x 4 combinations in current and new restricted states
1144             * and we only need to notify when state is changed.
1145             */
1146            if (rs.isCsRestricted()) {
1147                if (!newRs.isCsRestricted()) {
1148                    // remove all restriction
1149                    setNotification(CS_DISABLED);
1150                } else if (!newRs.isCsNormalRestricted()) {
1151                    // remove normal restriction
1152                    setNotification(CS_EMERGENCY_ENABLED);
1153                } else if (!newRs.isCsEmergencyRestricted()) {
1154                    // remove emergency restriction
1155                    setNotification(CS_NORMAL_ENABLED);
1156                }
1157            } else if (rs.isCsEmergencyRestricted() && !rs.isCsNormalRestricted()) {
1158                if (!newRs.isCsRestricted()) {
1159                    // remove all restriction
1160                    setNotification(CS_DISABLED);
1161                } else if (newRs.isCsRestricted()) {
1162                    // enable all restriction
1163                    setNotification(CS_ENABLED);
1164                } else if (newRs.isCsNormalRestricted()) {
1165                    // remove emergency restriction and enable normal restriction
1166                    setNotification(CS_NORMAL_ENABLED);
1167                }
1168            } else if (!rs.isCsEmergencyRestricted() && rs.isCsNormalRestricted()) {
1169                if (!newRs.isCsRestricted()) {
1170                    // remove all restriction
1171                    setNotification(CS_DISABLED);
1172                } else if (newRs.isCsRestricted()) {
1173                    // enable all restriction
1174                    setNotification(CS_ENABLED);
1175                } else if (newRs.isCsEmergencyRestricted()) {
1176                    // remove normal restriction and enable emergency restriction
1177                    setNotification(CS_EMERGENCY_ENABLED);
1178                }
1179            } else {
1180                if (newRs.isCsRestricted()) {
1181                    // enable all restriction
1182                    setNotification(CS_ENABLED);
1183                } else if (newRs.isCsEmergencyRestricted()) {
1184                    // enable emergency restriction
1185                    setNotification(CS_EMERGENCY_ENABLED);
1186                } else if (newRs.isCsNormalRestricted()) {
1187                    // enable normal restriction
1188                    setNotification(CS_NORMAL_ENABLED);
1189                }
1190            }
1191
1192            rs = newRs;
1193        }
1194        Log.d(LOG_TAG, "[DSAC DEB] " + "current rs at return "+ rs);
1195    }
1196
1197    /** code is registration state 0-5 from TS 27.007 7.2 */
1198    private int
1199    regCodeToServiceState(int code) {
1200        switch (code) {
1201            case 0:
1202            case 2: // 2 is "searching"
1203            case 3: // 3 is "registration denied"
1204            case 4: // 4 is "unknown" no vaild in current baseband
1205                return ServiceState.STATE_OUT_OF_SERVICE;
1206
1207            case 1:
1208                return ServiceState.STATE_IN_SERVICE;
1209
1210            case 5:
1211                // in service, roam
1212                return ServiceState.STATE_IN_SERVICE;
1213
1214            default:
1215                Log.w(LOG_TAG, "unexpected service state " + code);
1216                return ServiceState.STATE_OUT_OF_SERVICE;
1217        }
1218    }
1219
1220
1221    /**
1222     * code is registration state 0-5 from TS 27.007 7.2
1223     * returns true if registered roam, false otherwise
1224     */
1225    private boolean
1226    regCodeIsRoaming (int code) {
1227        // 5 is  "in service -- roam"
1228        return 5 == code;
1229    }
1230
1231    /**
1232     * Set roaming state when gsmRoaming is true and, if operator mcc is the
1233     * same as sim mcc, ons is different from spn
1234     * @param gsmRoaming TS 27.007 7.2 CREG registered roaming
1235     * @param s ServiceState hold current ons
1236     * @return true for roaming state set
1237     */
1238    private
1239    boolean isRoamingBetweenOperators(boolean gsmRoaming, ServiceState s) {
1240        String spn = SystemProperties.get(PROPERTY_ICC_OPERATOR_ALPHA, "empty");
1241
1242        String onsl = s.getOperatorAlphaLong();
1243        String onss = s.getOperatorAlphaShort();
1244
1245        boolean equalsOnsl = onsl != null && spn.equals(onsl);
1246        boolean equalsOnss = onss != null && spn.equals(onss);
1247
1248        String simNumeric = SystemProperties.get(PROPERTY_ICC_OPERATOR_NUMERIC, "");
1249        String  operatorNumeric = s.getOperatorNumeric();
1250
1251        boolean equalsMcc = true;
1252        try {
1253            equalsMcc = simNumeric.substring(0, 3).
1254                    equals(operatorNumeric.substring(0, 3));
1255        } catch (Exception e){
1256        }
1257
1258        return gsmRoaming && !(equalsMcc && (equalsOnsl || equalsOnss));
1259    }
1260
1261    private static
1262    int twoDigitsAt(String s, int offset) {
1263        int a, b;
1264
1265        a = Character.digit(s.charAt(offset), 10);
1266        b = Character.digit(s.charAt(offset+1), 10);
1267
1268        if (a < 0 || b < 0) {
1269
1270            throw new RuntimeException("invalid format");
1271        }
1272
1273        return a*10 + b;
1274    }
1275
1276    /**
1277     * @return The current GPRS state. IN_SERVICE is the same as "attached"
1278     * and OUT_OF_SERVICE is the same as detached.
1279     */
1280    /*package*/ int getCurrentGprsState() {
1281        return gprsState;
1282    }
1283
1284    /**
1285     * @return true if phone is camping on a technology (eg UMTS)
1286     * that could support voice and data simultaniously.
1287     */
1288    boolean isConcurrentVoiceAndData() {
1289        return (networkType == DATA_ACCESS_UMTS);
1290    }
1291
1292    /**
1293     * Provides the name of the algorithmic time zone for the specified
1294     * offset.  Taken from TimeZone.java.
1295     */
1296    private static String displayNameFor(int off) {
1297        off = off / 1000 / 60;
1298
1299        char[] buf = new char[9];
1300        buf[0] = 'G';
1301        buf[1] = 'M';
1302        buf[2] = 'T';
1303
1304        if (off < 0) {
1305            buf[3] = '-';
1306            off = -off;
1307        } else {
1308            buf[3] = '+';
1309        }
1310
1311        int hours = off / 60;
1312        int minutes = off % 60;
1313
1314        buf[4] = (char) ('0' + hours / 10);
1315        buf[5] = (char) ('0' + hours % 10);
1316
1317        buf[6] = ':';
1318
1319        buf[7] = (char) ('0' + minutes / 10);
1320        buf[8] = (char) ('0' + minutes % 10);
1321
1322        return new String(buf);
1323    }
1324
1325    /**
1326     * nitzReceiveTime is time_t that the NITZ time was posted
1327     */
1328
1329    private
1330    void setTimeFromNITZString (String nitz, long nitzReceiveTime)
1331    {
1332        // "yy/mm/dd,hh:mm:ss(+/-)tz"
1333        // tz is in number of quarter-hours
1334
1335        long start = SystemClock.elapsedRealtime();
1336        Log.i(LOG_TAG, "NITZ: " + nitz + "," + nitzReceiveTime +
1337                        " start=" + start + " delay=" + (start - nitzReceiveTime));
1338
1339        try {
1340            /* NITZ time (hour:min:sec) will be in UTC but it supplies the timezone
1341             * offset as well (which we won't worry about until later) */
1342            Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
1343
1344            c.clear();
1345            c.set(Calendar.DST_OFFSET, 0);
1346
1347            String[] nitzSubs = nitz.split("[/:,+-]");
1348
1349            int year = 2000 + Integer.parseInt(nitzSubs[0]);
1350            c.set(Calendar.YEAR, year);
1351
1352            // month is 0 based!
1353            int month = Integer.parseInt(nitzSubs[1]) - 1;
1354            c.set(Calendar.MONTH, month);
1355
1356            int date = Integer.parseInt(nitzSubs[2]);
1357            c.set(Calendar.DATE, date);
1358
1359            int hour = Integer.parseInt(nitzSubs[3]);
1360            c.set(Calendar.HOUR, hour);
1361
1362            int minute = Integer.parseInt(nitzSubs[4]);
1363            c.set(Calendar.MINUTE, minute);
1364
1365            int second = Integer.parseInt(nitzSubs[5]);
1366            c.set(Calendar.SECOND, second);
1367
1368            boolean sign = (nitz.indexOf('-') == -1);
1369
1370            int tzOffset = Integer.parseInt(nitzSubs[6]);
1371
1372            int dst = (nitzSubs.length >= 8 ) ? Integer.parseInt(nitzSubs[7])
1373                                              : 0;
1374
1375            // The zone offset received from NITZ is for current local time,
1376            // so DST correction is already applied.  Don't add it again.
1377            //
1378            // tzOffset += dst * 4;
1379            //
1380            // We could unapply it if we wanted the raw offset.
1381
1382            tzOffset = (sign ? 1 : -1) * tzOffset * 15 * 60 * 1000;
1383
1384            TimeZone    zone = null;
1385
1386            // As a special extension, the Android emulator appends the name of
1387            // the host computer's timezone to the nitz string. this is zoneinfo
1388            // timezone name of the form Area!Location or Area!Location!SubLocation
1389            // so we need to convert the ! into /
1390            if (nitzSubs.length >= 9) {
1391                String  tzname = nitzSubs[8].replace('!','/');
1392                zone = TimeZone.getTimeZone( tzname );
1393            }
1394
1395            String iso = SystemProperties.get(PROPERTY_OPERATOR_ISO_COUNTRY);
1396
1397            if (zone == null) {
1398
1399                if (mGotCountryCode) {
1400                    if (iso != null && iso.length() > 0) {
1401                        zone = TimeUtils.getTimeZone(tzOffset, dst != 0,
1402                                c.getTimeInMillis(),
1403                                iso);
1404                    } else {
1405                        // We don't have a valid iso country code.  This is
1406                        // most likely because we're on a test network that's
1407                        // using a bogus MCC (eg, "001"), so get a TimeZone
1408                        // based only on the NITZ parameters.
1409                        zone = getNitzTimeZone(tzOffset, (dst != 0), c.getTimeInMillis());
1410                    }
1411                }
1412            }
1413
1414            if (zone == null) {
1415                // We got the time before the country, so we don't know
1416                // how to identify the DST rules yet.  Save the information
1417                // and hope to fix it up later.
1418
1419                mNeedFixZone = true;
1420                mZoneOffset  = tzOffset;
1421                mZoneDst     = dst != 0;
1422                mZoneTime    = c.getTimeInMillis();
1423            }
1424
1425            if (zone != null) {
1426                if (getAutoTime()) {
1427                    setAndBroadcastNetworkSetTimeZone(zone.getID());
1428                }
1429                saveNitzTimeZone(zone.getID());
1430            }
1431
1432            String ignore = SystemProperties.get("gsm.ignore-nitz");
1433            if (ignore != null && ignore.equals("yes")) {
1434                Log.i(LOG_TAG, "NITZ: Not setting clock because gsm.ignore-nitz is set");
1435                return;
1436            }
1437
1438            try {
1439                mWakeLock.acquire();
1440
1441                if (getAutoTime()) {
1442                    long millisSinceNitzReceived
1443                            = SystemClock.elapsedRealtime() - nitzReceiveTime;
1444
1445                    if (millisSinceNitzReceived < 0) {
1446                        // Sanity check: something is wrong
1447                        Log.i(LOG_TAG, "NITZ: not setting time, clock has rolled "
1448                                            + "backwards since NITZ time was received, "
1449                                            + nitz);
1450                        return;
1451                    }
1452
1453                    if (millisSinceNitzReceived > Integer.MAX_VALUE) {
1454                        // If the time is this far off, something is wrong > 24 days!
1455                        Log.i(LOG_TAG, "NITZ: not setting time, processing has taken "
1456                                        + (millisSinceNitzReceived / (1000 * 60 * 60 * 24))
1457                                        + " days");
1458                        return;
1459                    }
1460
1461                    // Note: with range checks above, cast to int is safe
1462                    c.add(Calendar.MILLISECOND, (int)millisSinceNitzReceived);
1463
1464                    Log.i(LOG_TAG, "NITZ: Setting time of day to " + c.getTime()
1465                        + " NITZ receive delay(ms): " + millisSinceNitzReceived
1466                        + " gained(ms): "
1467                        + (c.getTimeInMillis() - System.currentTimeMillis())
1468                        + " from " + nitz);
1469
1470                    setAndBroadcastNetworkSetTime(c.getTimeInMillis());
1471                    Log.i(LOG_TAG, "NITZ: after Setting time of day");
1472                }
1473                SystemProperties.set("gsm.nitz.time", String.valueOf(c.getTimeInMillis()));
1474                saveNitzTime(c.getTimeInMillis());
1475                if (Config.LOGV) {
1476                    long end = SystemClock.elapsedRealtime();
1477                    Log.v(LOG_TAG, "NITZ: end=" + end + " dur=" + (end - start));
1478                }
1479            } finally {
1480                mWakeLock.release();
1481            }
1482        } catch (RuntimeException ex) {
1483            Log.e(LOG_TAG, "NITZ: Parsing NITZ time " + nitz, ex);
1484        }
1485    }
1486
1487    private boolean getAutoTime() {
1488        try {
1489            return Settings.System.getInt(phone.getContext().getContentResolver(),
1490                    Settings.System.AUTO_TIME) > 0;
1491        } catch (SettingNotFoundException snfe) {
1492            return true;
1493        }
1494    }
1495
1496    private void saveNitzTimeZone(String zoneId) {
1497        mSavedTimeZone = zoneId;
1498    }
1499
1500    private void saveNitzTime(long time) {
1501        mSavedTime = time;
1502        mSavedAtTime = SystemClock.elapsedRealtime();
1503    }
1504
1505    /**
1506     * Set the timezone and send out a sticky broadcast so the system can
1507     * determine if the timezone was set by the carrier.
1508     *
1509     * @param zoneId timezone set by carrier
1510     */
1511    private void setAndBroadcastNetworkSetTimeZone(String zoneId) {
1512        AlarmManager alarm =
1513            (AlarmManager) phone.getContext().getSystemService(Context.ALARM_SERVICE);
1514        alarm.setTimeZone(zoneId);
1515        Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE);
1516        intent.putExtra("time-zone", zoneId);
1517        phone.getContext().sendStickyBroadcast(intent);
1518    }
1519
1520    /**
1521     * Set the time and Send out a sticky broadcast so the system can determine
1522     * if the time was set by the carrier.
1523     *
1524     * @param time time set by network
1525     */
1526    private void setAndBroadcastNetworkSetTime(long time) {
1527        SystemClock.setCurrentTimeMillis(time);
1528        Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIME);
1529        intent.putExtra("time", time);
1530        phone.getContext().sendStickyBroadcast(intent);
1531    }
1532
1533    private void revertToNitz() {
1534        if (Settings.System.getInt(phone.getContext().getContentResolver(),
1535                Settings.System.AUTO_TIME, 0) == 0) {
1536            return;
1537        }
1538        Log.d(LOG_TAG, "Reverting to NITZ: tz='" + mSavedTimeZone
1539                + "' mSavedTime=" + mSavedTime
1540                + " mSavedAtTime=" + mSavedAtTime);
1541        if (mSavedTimeZone != null && mSavedTime != 0 && mSavedAtTime != 0) {
1542            setAndBroadcastNetworkSetTimeZone(mSavedTimeZone);
1543            setAndBroadcastNetworkSetTime(mSavedTime
1544                    + (SystemClock.elapsedRealtime() - mSavedAtTime));
1545        }
1546    }
1547
1548    /**
1549     * Post a notification to NotificationManager for restricted state
1550     *
1551     * @param notifyType is one state of PS/CS_*_ENABLE/DISABLE
1552     */
1553    private void setNotification(int notifyType) {
1554
1555        Log.d(LOG_TAG, "[DSAC DEB] " + "create notification " + notifyType);
1556        Context context = phone.getContext();
1557
1558        mNotification = new Notification();
1559        mNotification.when = System.currentTimeMillis();
1560        mNotification.flags = Notification.FLAG_AUTO_CANCEL;
1561        mNotification.icon = com.android.internal.R.drawable.stat_sys_warning;
1562        Intent intent = new Intent();
1563        mNotification.contentIntent = PendingIntent
1564        .getActivity(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
1565
1566        CharSequence details = "";
1567        CharSequence title = context.getText(com.android.internal.R.string.RestrictedChangedTitle);
1568        int notificationId = CS_NOTIFICATION;
1569
1570        switch (notifyType) {
1571        case PS_ENABLED:
1572            notificationId = PS_NOTIFICATION;
1573            details = context.getText(com.android.internal.R.string.RestrictedOnData);;
1574            break;
1575        case PS_DISABLED:
1576            notificationId = PS_NOTIFICATION;
1577            break;
1578        case CS_ENABLED:
1579            details = context.getText(com.android.internal.R.string.RestrictedOnAll);;
1580            break;
1581        case CS_NORMAL_ENABLED:
1582            details = context.getText(com.android.internal.R.string.RestrictedOnNormal);;
1583            break;
1584        case CS_EMERGENCY_ENABLED:
1585            details = context.getText(com.android.internal.R.string.RestrictedOnEmergency);;
1586            break;
1587        case CS_DISABLED:
1588            // do nothing and cancel the notification later
1589            break;
1590        }
1591
1592        Log.d(LOG_TAG, "[DSAC DEB] " + "put notification " + title + " / " +details);
1593        mNotification.tickerText = title;
1594        mNotification.setLatestEventInfo(context, title, details,
1595                mNotification.contentIntent);
1596
1597        NotificationManager notificationManager = (NotificationManager)
1598            context.getSystemService(Context.NOTIFICATION_SERVICE);
1599
1600        if (notifyType == PS_DISABLED || notifyType == CS_DISABLED) {
1601            // cancel previous post notification
1602            notificationManager.cancel(notificationId);
1603        } else {
1604            // update restricted state notification
1605            notificationManager.notify(notificationId, mNotification);
1606        }
1607    }
1608
1609    private void log(String s) {
1610        Log.d(LOG_TAG, "[GsmServiceStateTracker] " + s);
1611    }
1612}
1613