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;
18
19import android.content.Context;
20import android.os.AsyncResult;
21import android.os.Handler;
22import android.os.Looper;
23import android.os.Message;
24import android.os.Registrant;
25import android.os.RegistrantList;
26import android.os.SystemClock;
27import android.telephony.CellInfo;
28import android.telephony.ServiceState;
29import android.telephony.SignalStrength;
30import android.util.TimeUtils;
31
32import java.io.FileDescriptor;
33import java.io.PrintWriter;
34import java.util.List;
35
36import com.android.internal.telephony.IccCardApplicationStatus.AppState;
37import com.android.internal.telephony.uicc.UiccController;
38
39/**
40 * {@hide}
41 */
42public abstract class ServiceStateTracker extends Handler {
43
44    protected CommandsInterface cm;
45    protected UiccController mUiccController = null;
46    protected UiccCardApplication mUiccApplcation = null;
47    protected IccRecords mIccRecords = null;
48
49    protected PhoneBase mPhoneBase;
50
51    public ServiceState ss = new ServiceState();
52    protected ServiceState newSS = new ServiceState();
53
54    protected CellInfo mLastCellInfo = null;
55
56    // This is final as subclasses alias to a more specific type
57    // so we don't want the reference to change.
58    protected final CellInfo mCellInfo;
59
60    protected SignalStrength mSignalStrength = new SignalStrength();
61
62    // TODO - this should not be public, right now used externally GsmConnetion.
63    public RestrictedState mRestrictedState = new RestrictedState();
64
65    /* The otaspMode passed to PhoneStateListener#onOtaspChanged */
66    static public final int OTASP_UNINITIALIZED = 0;
67    static public final int OTASP_UNKNOWN = 1;
68    static public final int OTASP_NEEDED = 2;
69    static public final int OTASP_NOT_NEEDED = 3;
70
71    /**
72     * A unique identifier to track requests associated with a poll
73     * and ignore stale responses.  The value is a count-down of
74     * expected responses in this pollingContext.
75     */
76    protected int[] pollingContext;
77    protected boolean mDesiredPowerState;
78
79    /**
80     *  Values correspond to ServiceState.RIL_RADIO_TECHNOLOGY_ definitions.
81     */
82    protected int mRilRadioTechnology = 0;
83    protected int mNewRilRadioTechnology = 0;
84
85    /**
86     * By default, strength polling is enabled.  However, if we're
87     * getting unsolicited signal strength updates from the radio, set
88     * value to true and don't bother polling any more.
89     */
90    protected boolean dontPollSignalStrength = false;
91
92    protected RegistrantList mRoamingOnRegistrants = new RegistrantList();
93    protected RegistrantList mRoamingOffRegistrants = new RegistrantList();
94    protected RegistrantList mAttachedRegistrants = new RegistrantList();
95    protected RegistrantList mDetachedRegistrants = new RegistrantList();
96    protected RegistrantList mNetworkAttachedRegistrants = new RegistrantList();
97    protected RegistrantList mPsRestrictEnabledRegistrants = new RegistrantList();
98    protected RegistrantList mPsRestrictDisabledRegistrants = new RegistrantList();
99
100    /* Radio power off pending flag and tag counter */
101    private boolean mPendingRadioPowerOffAfterDataOff = false;
102    private int mPendingRadioPowerOffAfterDataOffTag = 0;
103
104    protected  static final boolean DBG = true;
105
106    /** Signal strength poll rate. */
107    protected static final int POLL_PERIOD_MILLIS = 20 * 1000;
108
109    /** Waiting period before recheck gprs and voice registration. */
110    public static final int DEFAULT_GPRS_CHECK_PERIOD_MILLIS = 60 * 1000;
111
112    /** GSM events */
113    protected static final int EVENT_RADIO_STATE_CHANGED               = 1;
114    protected static final int EVENT_NETWORK_STATE_CHANGED             = 2;
115    protected static final int EVENT_GET_SIGNAL_STRENGTH               = 3;
116    protected static final int EVENT_POLL_STATE_REGISTRATION           = 4;
117    protected static final int EVENT_POLL_STATE_GPRS                   = 5;
118    protected static final int EVENT_POLL_STATE_OPERATOR               = 6;
119    protected static final int EVENT_POLL_SIGNAL_STRENGTH              = 10;
120    protected static final int EVENT_NITZ_TIME                         = 11;
121    protected static final int EVENT_SIGNAL_STRENGTH_UPDATE            = 12;
122    protected static final int EVENT_RADIO_AVAILABLE                   = 13;
123    protected static final int EVENT_POLL_STATE_NETWORK_SELECTION_MODE = 14;
124    protected static final int EVENT_GET_LOC_DONE                      = 15;
125    protected static final int EVENT_SIM_RECORDS_LOADED                = 16;
126    protected static final int EVENT_SIM_READY                         = 17;
127    protected static final int EVENT_LOCATION_UPDATES_ENABLED          = 18;
128    protected static final int EVENT_GET_PREFERRED_NETWORK_TYPE        = 19;
129    protected static final int EVENT_SET_PREFERRED_NETWORK_TYPE        = 20;
130    protected static final int EVENT_RESET_PREFERRED_NETWORK_TYPE      = 21;
131    protected static final int EVENT_CHECK_REPORT_GPRS                 = 22;
132    protected static final int EVENT_RESTRICTED_STATE_CHANGED          = 23;
133
134    /** CDMA events */
135    protected static final int EVENT_POLL_STATE_REGISTRATION_CDMA      = 24;
136    protected static final int EVENT_POLL_STATE_OPERATOR_CDMA          = 25;
137    protected static final int EVENT_RUIM_READY                        = 26;
138    protected static final int EVENT_RUIM_RECORDS_LOADED               = 27;
139    protected static final int EVENT_POLL_SIGNAL_STRENGTH_CDMA         = 28;
140    protected static final int EVENT_GET_SIGNAL_STRENGTH_CDMA          = 29;
141    protected static final int EVENT_NETWORK_STATE_CHANGED_CDMA        = 30;
142    protected static final int EVENT_GET_LOC_DONE_CDMA                 = 31;
143    //protected static final int EVENT_UNUSED                            = 32;
144    protected static final int EVENT_NV_LOADED                         = 33;
145    protected static final int EVENT_POLL_STATE_CDMA_SUBSCRIPTION      = 34;
146    protected static final int EVENT_NV_READY                          = 35;
147    protected static final int EVENT_ERI_FILE_LOADED                   = 36;
148    protected static final int EVENT_OTA_PROVISION_STATUS_CHANGE       = 37;
149    protected static final int EVENT_SET_RADIO_POWER_OFF               = 38;
150    protected static final int EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED  = 39;
151    protected static final int EVENT_CDMA_PRL_VERSION_CHANGED          = 40;
152    protected static final int EVENT_RADIO_ON                          = 41;
153    protected static final int EVENT_ICC_CHANGED                       = 42;
154
155    protected static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
156
157    /**
158     * List of ISO codes for countries that can have an offset of
159     * GMT+0 when not in daylight savings time.  This ignores some
160     * small places such as the Canary Islands (Spain) and
161     * Danmarkshavn (Denmark).  The list must be sorted by code.
162    */
163    protected static final String[] GMT_COUNTRY_CODES = {
164        "bf", // Burkina Faso
165        "ci", // Cote d'Ivoire
166        "eh", // Western Sahara
167        "fo", // Faroe Islands, Denmark
168        "gb", // United Kingdom of Great Britain and Northern Ireland
169        "gh", // Ghana
170        "gm", // Gambia
171        "gn", // Guinea
172        "gw", // Guinea Bissau
173        "ie", // Ireland
174        "lr", // Liberia
175        "is", // Iceland
176        "ma", // Morocco
177        "ml", // Mali
178        "mr", // Mauritania
179        "pt", // Portugal
180        "sl", // Sierra Leone
181        "sn", // Senegal
182        "st", // Sao Tome and Principe
183        "tg", // Togo
184    };
185
186    /** Reason for registration denial. */
187    protected static final String REGISTRATION_DENIED_GEN  = "General";
188    protected static final String REGISTRATION_DENIED_AUTH = "Authentication Failure";
189
190    protected ServiceStateTracker(PhoneBase phoneBase, CommandsInterface ci, CellInfo cellInfo) {
191        mPhoneBase = phoneBase;
192        mCellInfo = cellInfo;
193        cm = ci;
194        mUiccController = UiccController.getInstance();
195        mUiccController.registerForIccChanged(this, EVENT_ICC_CHANGED, null);
196        cm.setOnSignalStrengthUpdate(this, EVENT_SIGNAL_STRENGTH_UPDATE, null);
197    }
198
199    public void dispose() {
200        cm.unSetOnSignalStrengthUpdate(this);
201    }
202
203    public boolean getDesiredPowerState() {
204        return mDesiredPowerState;
205    }
206
207    private SignalStrength mLastSignalStrength = null;
208    protected boolean notifySignalStrength() {
209        boolean notified = false;
210        synchronized(mCellInfo) {
211            if (!mSignalStrength.equals(mLastSignalStrength)) {
212                try {
213                    mPhoneBase.notifySignalStrength();
214                    notified = true;
215                } catch (NullPointerException ex) {
216                    loge("updateSignalStrength() Phone already destroyed: " + ex
217                            + "SignalStrength not notified");
218                }
219            }
220        }
221        return notified;
222    }
223
224    /**
225     * Registration point for combined roaming on
226     * combined roaming is true when roaming is true and ONS differs SPN
227     *
228     * @param h handler to notify
229     * @param what what code of message when delivered
230     * @param obj placed in Message.obj
231     */
232    public  void registerForRoamingOn(Handler h, int what, Object obj) {
233        Registrant r = new Registrant(h, what, obj);
234        mRoamingOnRegistrants.add(r);
235
236        if (ss.getRoaming()) {
237            r.notifyRegistrant();
238        }
239    }
240
241    public  void unregisterForRoamingOn(Handler h) {
242        mRoamingOnRegistrants.remove(h);
243    }
244
245    /**
246     * Registration point for combined roaming off
247     * combined roaming is true when roaming is true and ONS differs SPN
248     *
249     * @param h handler to notify
250     * @param what what code of message when delivered
251     * @param obj placed in Message.obj
252     */
253    public  void registerForRoamingOff(Handler h, int what, Object obj) {
254        Registrant r = new Registrant(h, what, obj);
255        mRoamingOffRegistrants.add(r);
256
257        if (!ss.getRoaming()) {
258            r.notifyRegistrant();
259        }
260    }
261
262    public  void unregisterForRoamingOff(Handler h) {
263        mRoamingOffRegistrants.remove(h);
264    }
265
266    /**
267     * Re-register network by toggling preferred network type.
268     * This is a work-around to deregister and register network since there is
269     * no ril api to set COPS=2 (deregister) only.
270     *
271     * @param onComplete is dispatched when this is complete.  it will be
272     * an AsyncResult, and onComplete.obj.exception will be non-null
273     * on failure.
274     */
275    public void reRegisterNetwork(Message onComplete) {
276        cm.getPreferredNetworkType(
277                obtainMessage(EVENT_GET_PREFERRED_NETWORK_TYPE, onComplete));
278    }
279
280    public void
281    setRadioPower(boolean power) {
282        mDesiredPowerState = power;
283
284        setPowerStateToDesired();
285    }
286
287    /**
288     * These two flags manage the behavior of the cell lock -- the
289     * lock should be held if either flag is true.  The intention is
290     * to allow temporary acquisition of the lock to get a single
291     * update.  Such a lock grab and release can thus be made to not
292     * interfere with more permanent lock holds -- in other words, the
293     * lock will only be released if both flags are false, and so
294     * releases by temporary users will only affect the lock state if
295     * there is no continuous user.
296     */
297    private boolean mWantContinuousLocationUpdates;
298    private boolean mWantSingleLocationUpdate;
299
300    public void enableSingleLocationUpdate() {
301        if (mWantSingleLocationUpdate || mWantContinuousLocationUpdates) return;
302        mWantSingleLocationUpdate = true;
303        cm.setLocationUpdates(true, obtainMessage(EVENT_LOCATION_UPDATES_ENABLED));
304    }
305
306    public void enableLocationUpdates() {
307        if (mWantSingleLocationUpdate || mWantContinuousLocationUpdates) return;
308        mWantContinuousLocationUpdates = true;
309        cm.setLocationUpdates(true, obtainMessage(EVENT_LOCATION_UPDATES_ENABLED));
310    }
311
312    protected void disableSingleLocationUpdate() {
313        mWantSingleLocationUpdate = false;
314        if (!mWantSingleLocationUpdate && !mWantContinuousLocationUpdates) {
315            cm.setLocationUpdates(false, null);
316        }
317    }
318
319    public void disableLocationUpdates() {
320        mWantContinuousLocationUpdates = false;
321        if (!mWantSingleLocationUpdate && !mWantContinuousLocationUpdates) {
322            cm.setLocationUpdates(false, null);
323        }
324    }
325
326    @Override
327    public void handleMessage(Message msg) {
328        switch (msg.what) {
329            case EVENT_SET_RADIO_POWER_OFF:
330                synchronized(this) {
331                    if (mPendingRadioPowerOffAfterDataOff &&
332                            (msg.arg1 == mPendingRadioPowerOffAfterDataOffTag)) {
333                        if (DBG) log("EVENT_SET_RADIO_OFF, turn radio off now.");
334                        hangupAndPowerOff();
335                        mPendingRadioPowerOffAfterDataOffTag += 1;
336                        mPendingRadioPowerOffAfterDataOff = false;
337                    } else {
338                        log("EVENT_SET_RADIO_OFF is stale arg1=" + msg.arg1 +
339                                "!= tag=" + mPendingRadioPowerOffAfterDataOffTag);
340                    }
341                }
342                break;
343
344            case EVENT_ICC_CHANGED:
345                onUpdateIccAvailability();
346                break;
347
348            default:
349                log("Unhandled message with number: " + msg.what);
350                break;
351        }
352    }
353
354    protected abstract Phone getPhone();
355    protected abstract void handlePollStateResult(int what, AsyncResult ar);
356    protected abstract void updateSpnDisplay();
357    protected abstract void setPowerStateToDesired();
358    protected abstract void onUpdateIccAvailability();
359    protected abstract void log(String s);
360    protected abstract void loge(String s);
361
362    public abstract int getCurrentDataConnectionState();
363    public abstract boolean isConcurrentVoiceAndDataAllowed();
364
365    /**
366     * Registration point for transition into DataConnection attached.
367     * @param h handler to notify
368     * @param what what code of message when delivered
369     * @param obj placed in Message.obj
370     */
371    public void registerForDataConnectionAttached(Handler h, int what, Object obj) {
372        Registrant r = new Registrant(h, what, obj);
373        mAttachedRegistrants.add(r);
374
375        if (getCurrentDataConnectionState() == ServiceState.STATE_IN_SERVICE) {
376            r.notifyRegistrant();
377        }
378    }
379    public void unregisterForDataConnectionAttached(Handler h) {
380        mAttachedRegistrants.remove(h);
381    }
382
383    /**
384     * Registration point for transition into DataConnection detached.
385     * @param h handler to notify
386     * @param what what code of message when delivered
387     * @param obj placed in Message.obj
388     */
389    public void registerForDataConnectionDetached(Handler h, int what, Object obj) {
390        Registrant r = new Registrant(h, what, obj);
391        mDetachedRegistrants.add(r);
392
393        if (getCurrentDataConnectionState() != ServiceState.STATE_IN_SERVICE) {
394            r.notifyRegistrant();
395        }
396    }
397    public void unregisterForDataConnectionDetached(Handler h) {
398        mDetachedRegistrants.remove(h);
399    }
400
401    /**
402     * Registration point for transition into network attached.
403     * @param h handler to notify
404     * @param what what code of message when delivered
405     * @param obj in Message.obj
406     */
407    public void registerForNetworkAttached(Handler h, int what, Object obj) {
408        Registrant r = new Registrant(h, what, obj);
409
410        mNetworkAttachedRegistrants.add(r);
411        if (ss.getState() == ServiceState.STATE_IN_SERVICE) {
412            r.notifyRegistrant();
413        }
414    }
415    public void unregisterForNetworkAttached(Handler h) {
416        mNetworkAttachedRegistrants.remove(h);
417    }
418
419    /**
420     * Registration point for transition into packet service restricted zone.
421     * @param h handler to notify
422     * @param what what code of message when delivered
423     * @param obj placed in Message.obj
424     */
425    public void registerForPsRestrictedEnabled(Handler h, int what, Object obj) {
426        Registrant r = new Registrant(h, what, obj);
427        mPsRestrictEnabledRegistrants.add(r);
428
429        if (mRestrictedState.isPsRestricted()) {
430            r.notifyRegistrant();
431        }
432    }
433
434    public void unregisterForPsRestrictedEnabled(Handler h) {
435        mPsRestrictEnabledRegistrants.remove(h);
436    }
437
438    /**
439     * Registration point for transition out of packet service restricted zone.
440     * @param h handler to notify
441     * @param what what code of message when delivered
442     * @param obj placed in Message.obj
443     */
444    public void registerForPsRestrictedDisabled(Handler h, int what, Object obj) {
445        Registrant r = new Registrant(h, what, obj);
446        mPsRestrictDisabledRegistrants.add(r);
447
448        if (mRestrictedState.isPsRestricted()) {
449            r.notifyRegistrant();
450        }
451    }
452
453    public void unregisterForPsRestrictedDisabled(Handler h) {
454        mPsRestrictDisabledRegistrants.remove(h);
455    }
456
457    /**
458     * Clean up existing voice and data connection then turn off radio power.
459     *
460     * Hang up the existing voice calls to decrease call drop rate.
461     */
462    public void powerOffRadioSafely(DataConnectionTracker dcTracker) {
463        synchronized (this) {
464            if (!mPendingRadioPowerOffAfterDataOff) {
465                // To minimize race conditions we call cleanUpAllConnections on
466                // both if else paths instead of before this isDisconnected test.
467                if (dcTracker.isDisconnected()) {
468                    // To minimize race conditions we do this after isDisconnected
469                    dcTracker.cleanUpAllConnections(Phone.REASON_RADIO_TURNED_OFF);
470                    if (DBG) log("Data disconnected, turn off radio right away.");
471                    hangupAndPowerOff();
472                } else {
473                    dcTracker.cleanUpAllConnections(Phone.REASON_RADIO_TURNED_OFF);
474                    Message msg = Message.obtain(this);
475                    msg.what = EVENT_SET_RADIO_POWER_OFF;
476                    msg.arg1 = ++mPendingRadioPowerOffAfterDataOffTag;
477                    if (sendMessageDelayed(msg, 30000)) {
478                        if (DBG) log("Wait upto 30s for data to disconnect, then turn off radio.");
479                        mPendingRadioPowerOffAfterDataOff = true;
480                    } else {
481                        log("Cannot send delayed Msg, turn off radio right away.");
482                        hangupAndPowerOff();
483                    }
484                }
485            }
486        }
487    }
488
489    /**
490     * process the pending request to turn radio off after data is disconnected
491     *
492     * return true if there is pending request to process; false otherwise.
493     */
494    public boolean processPendingRadioPowerOffAfterDataOff() {
495        synchronized(this) {
496            if (mPendingRadioPowerOffAfterDataOff) {
497                if (DBG) log("Process pending request to turn radio off.");
498                mPendingRadioPowerOffAfterDataOffTag += 1;
499                hangupAndPowerOff();
500                mPendingRadioPowerOffAfterDataOff = false;
501                return true;
502            }
503            return false;
504        }
505    }
506
507    /**
508     * send signal-strength-changed notification if changed Called both for
509     * solicited and unsolicited signal strength updates
510     *
511     * @return true if the signal strength changed and a notification was sent.
512     */
513    protected boolean onSignalStrengthResult(AsyncResult ar, boolean isGsm) {
514        SignalStrength oldSignalStrength = mSignalStrength;
515
516        // This signal is used for both voice and data radio signal so parse
517        // all fields
518
519        if ((ar.exception == null) && (ar.result != null)) {
520            mSignalStrength = (SignalStrength) ar.result;
521            mSignalStrength.validateInput();
522            mSignalStrength.setGsm(isGsm);
523        } else {
524            log("onSignalStrengthResult() Exception from RIL : " + ar.exception);
525            mSignalStrength = new SignalStrength(isGsm);
526        }
527
528        return notifySignalStrength();
529    }
530
531    /**
532     * Hang up all voice call and turn off radio. Implemented by derived class.
533     */
534    protected abstract void hangupAndPowerOff();
535
536    /** Cancel a pending (if any) pollState() operation */
537    protected void cancelPollState() {
538        // This will effectively cancel the rest of the poll requests.
539        pollingContext = new int[1];
540    }
541
542    /**
543     * Return true if time zone needs fixing.
544     *
545     * @param phoneBase
546     * @param operatorNumeric
547     * @param prevOperatorNumeric
548     * @param needToFixTimeZone
549     * @return true if time zone needs to be fixed
550     */
551    protected boolean shouldFixTimeZoneNow(PhoneBase phoneBase, String operatorNumeric,
552            String prevOperatorNumeric, boolean needToFixTimeZone) {
553        // Return false if the mcc isn't valid as we don't know where we are.
554        // Return true if we have an IccCard and the mcc changed or we
555        // need to fix it because when the NITZ time came in we didn't
556        // know the country code.
557
558        // If mcc is invalid then we'll return false
559        int mcc;
560        try {
561            mcc = Integer.parseInt(operatorNumeric.substring(0, 3));
562        } catch (Exception e) {
563            if (DBG) {
564                log("shouldFixTimeZoneNow: no mcc, operatorNumeric=" + operatorNumeric +
565                        " retVal=false");
566            }
567            return false;
568        }
569
570        // If prevMcc is invalid will make it different from mcc
571        // so we'll return true if the card exists.
572        int prevMcc;
573        try {
574            prevMcc = Integer.parseInt(prevOperatorNumeric.substring(0, 3));
575        } catch (Exception e) {
576            prevMcc = mcc + 1;
577        }
578
579        // Determine if the Icc card exists
580        boolean iccCardExist = false;
581        if (mUiccApplcation != null) {
582            iccCardExist = mUiccApplcation.getState() != AppState.APPSTATE_UNKNOWN;
583        }
584
585        // Determine retVal
586        boolean retVal = ((iccCardExist && (mcc != prevMcc)) || needToFixTimeZone);
587        if (DBG) {
588            long ctm = System.currentTimeMillis();
589            log("shouldFixTimeZoneNow: retVal=" + retVal +
590                    " iccCardExist=" + iccCardExist +
591                    " operatorNumeric=" + operatorNumeric + " mcc=" + mcc +
592                    " prevOperatorNumeric=" + prevOperatorNumeric + " prevMcc=" + prevMcc +
593                    " needToFixTimeZone=" + needToFixTimeZone +
594                    " ltod=" + TimeUtils.logTimeOfDay(ctm));
595        }
596        return retVal;
597    }
598
599    /**
600     * @return all available cell information or null if none.
601     */
602    public List<CellInfo> getAllCellInfo() {
603        return null;
604    }
605
606    /**
607     * @return signal strength
608     */
609    public SignalStrength getSignalStrength() {
610        synchronized(mCellInfo) {
611            return mSignalStrength;
612        }
613    }
614
615    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
616        pw.println("ServiceStateTracker:");
617        pw.println(" ss=" + ss);
618        pw.println(" newSS=" + newSS);
619        pw.println(" mCellInfo=" + mCellInfo);
620        pw.println(" mRestrictedState=" + mRestrictedState);
621        pw.println(" pollingContext=" + pollingContext);
622        pw.println(" mDesiredPowerState=" + mDesiredPowerState);
623        pw.println(" mRilRadioTechnology=" + mRilRadioTechnology);
624        pw.println(" mNewRilRadioTechnology=" + mNewRilRadioTechnology);
625        pw.println(" dontPollSignalStrength=" + dontPollSignalStrength);
626        pw.println(" mPendingRadioPowerOffAfterDataOff=" + mPendingRadioPowerOffAfterDataOff);
627        pw.println(" mPendingRadioPowerOffAfterDataOffTag=" + mPendingRadioPowerOffAfterDataOffTag);
628    }
629
630    /**
631     * Verifies the current thread is the same as the thread originally
632     * used in the initialization of this instance. Throws RuntimeException
633     * if not.
634     *
635     * @exception RuntimeException if the current thread is not
636     * the thread that originally obtained this PhoneBase instance.
637     */
638    protected void checkCorrectThread() {
639        if (Thread.currentThread() != getLooper().getThread()) {
640            throw new RuntimeException(
641                    "ServiceStateTracker must be used from within one thread");
642        }
643    }
644}
645