blob: e4cfb23f412befd8da7a3da7dcc5e148b7c6f2c3 [file] [log] [blame]
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.telephony;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.Message;
import android.os.Registrant;
import android.os.RegistrantList;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.util.TimeUtils;
import java.io.FileDescriptor;
import java.io.PrintWriter;
/**
* {@hide}
*/
public abstract class ServiceStateTracker extends Handler {
protected CommandsInterface cm;
public ServiceState ss;
protected ServiceState newSS;
public SignalStrength mSignalStrength;
// TODO - this should not be public
public RestrictedState mRestrictedState = new RestrictedState();
/* The otaspMode passed to PhoneStateListener#onOtaspChanged */
static public final int OTASP_UNINITIALIZED = 0;
static public final int OTASP_UNKNOWN = 1;
static public final int OTASP_NEEDED = 2;
static public final int OTASP_NOT_NEEDED = 3;
/**
* A unique identifier to track requests associated with a poll
* and ignore stale responses. The value is a count-down of
* expected responses in this pollingContext.
*/
protected int[] pollingContext;
protected boolean mDesiredPowerState;
/**
* Values correspond to ServiceState.RIL_RADIO_TECHNOLOGY_ definitions.
*/
protected int mRilRadioTechnology = 0;
protected int mNewRilRadioTechnology = 0;
/**
* By default, strength polling is enabled. However, if we're
* getting unsolicited signal strength updates from the radio, set
* value to true and don't bother polling any more.
*/
protected boolean dontPollSignalStrength = false;
protected RegistrantList mRoamingOnRegistrants = new RegistrantList();
protected RegistrantList mRoamingOffRegistrants = new RegistrantList();
protected RegistrantList mAttachedRegistrants = new RegistrantList();
protected RegistrantList mDetachedRegistrants = new RegistrantList();
protected RegistrantList mNetworkAttachedRegistrants = new RegistrantList();
protected RegistrantList mPsRestrictEnabledRegistrants = new RegistrantList();
protected RegistrantList mPsRestrictDisabledRegistrants = new RegistrantList();
/* Radio power off pending flag and tag counter */
private boolean mPendingRadioPowerOffAfterDataOff = false;
private int mPendingRadioPowerOffAfterDataOffTag = 0;
protected static final boolean DBG = true;
/** Signal strength poll rate. */
protected static final int POLL_PERIOD_MILLIS = 20 * 1000;
/** Waiting period before recheck gprs and voice registration. */
public static final int DEFAULT_GPRS_CHECK_PERIOD_MILLIS = 60 * 1000;
/** GSM events */
protected static final int EVENT_RADIO_STATE_CHANGED = 1;
protected static final int EVENT_NETWORK_STATE_CHANGED = 2;
protected static final int EVENT_GET_SIGNAL_STRENGTH = 3;
protected static final int EVENT_POLL_STATE_REGISTRATION = 4;
protected static final int EVENT_POLL_STATE_GPRS = 5;
protected static final int EVENT_POLL_STATE_OPERATOR = 6;
protected static final int EVENT_POLL_SIGNAL_STRENGTH = 10;
protected static final int EVENT_NITZ_TIME = 11;
protected static final int EVENT_SIGNAL_STRENGTH_UPDATE = 12;
protected static final int EVENT_RADIO_AVAILABLE = 13;
protected static final int EVENT_POLL_STATE_NETWORK_SELECTION_MODE = 14;
protected static final int EVENT_GET_LOC_DONE = 15;
protected static final int EVENT_SIM_RECORDS_LOADED = 16;
protected static final int EVENT_SIM_READY = 17;
protected static final int EVENT_LOCATION_UPDATES_ENABLED = 18;
protected static final int EVENT_GET_PREFERRED_NETWORK_TYPE = 19;
protected static final int EVENT_SET_PREFERRED_NETWORK_TYPE = 20;
protected static final int EVENT_RESET_PREFERRED_NETWORK_TYPE = 21;
protected static final int EVENT_CHECK_REPORT_GPRS = 22;
protected static final int EVENT_RESTRICTED_STATE_CHANGED = 23;
/** CDMA events */
protected static final int EVENT_POLL_STATE_REGISTRATION_CDMA = 24;
protected static final int EVENT_POLL_STATE_OPERATOR_CDMA = 25;
protected static final int EVENT_RUIM_READY = 26;
protected static final int EVENT_RUIM_RECORDS_LOADED = 27;
protected static final int EVENT_POLL_SIGNAL_STRENGTH_CDMA = 28;
protected static final int EVENT_GET_SIGNAL_STRENGTH_CDMA = 29;
protected static final int EVENT_NETWORK_STATE_CHANGED_CDMA = 30;
protected static final int EVENT_GET_LOC_DONE_CDMA = 31;
protected static final int EVENT_SIGNAL_STRENGTH_UPDATE_CDMA = 32;
protected static final int EVENT_NV_LOADED = 33;
protected static final int EVENT_POLL_STATE_CDMA_SUBSCRIPTION = 34;
protected static final int EVENT_NV_READY = 35;
protected static final int EVENT_ERI_FILE_LOADED = 36;
protected static final int EVENT_OTA_PROVISION_STATUS_CHANGE = 37;
protected static final int EVENT_SET_RADIO_POWER_OFF = 38;
protected static final int EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED = 39;
protected static final int EVENT_CDMA_PRL_VERSION_CHANGED = 40;
protected static final int EVENT_RADIO_ON = 41;
protected static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
/**
* List of ISO codes for countries that can have an offset of
* GMT+0 when not in daylight savings time. This ignores some
* small places such as the Canary Islands (Spain) and
* Danmarkshavn (Denmark). The list must be sorted by code.
*/
protected static final String[] GMT_COUNTRY_CODES = {
"bf", // Burkina Faso
"ci", // Cote d'Ivoire
"eh", // Western Sahara
"fo", // Faroe Islands, Denmark
"gb", // United Kingdom of Great Britain and Northern Ireland
"gh", // Ghana
"gm", // Gambia
"gn", // Guinea
"gw", // Guinea Bissau
"ie", // Ireland
"lr", // Liberia
"is", // Iceland
"ma", // Morocco
"ml", // Mali
"mr", // Mauritania
"pt", // Portugal
"sl", // Sierra Leone
"sn", // Senegal
"st", // Sao Tome and Principe
"tg", // Togo
};
/** Reason for registration denial. */
protected static final String REGISTRATION_DENIED_GEN = "General";
protected static final String REGISTRATION_DENIED_AUTH = "Authentication Failure";
public ServiceStateTracker() {
}
public boolean getDesiredPowerState() {
return mDesiredPowerState;
}
/**
* Registration point for combined roaming on
* combined roaming is true when roaming is true and ONS differs SPN
*
* @param h handler to notify
* @param what what code of message when delivered
* @param obj placed in Message.obj
*/
public void registerForRoamingOn(Handler h, int what, Object obj) {
Registrant r = new Registrant(h, what, obj);
mRoamingOnRegistrants.add(r);
if (ss.getRoaming()) {
r.notifyRegistrant();
}
}
public void unregisterForRoamingOn(Handler h) {
mRoamingOnRegistrants.remove(h);
}
/**
* Registration point for combined roaming off
* combined roaming is true when roaming is true and ONS differs SPN
*
* @param h handler to notify
* @param what what code of message when delivered
* @param obj placed in Message.obj
*/
public void registerForRoamingOff(Handler h, int what, Object obj) {
Registrant r = new Registrant(h, what, obj);
mRoamingOffRegistrants.add(r);
if (!ss.getRoaming()) {
r.notifyRegistrant();
}
}
public void unregisterForRoamingOff(Handler h) {
mRoamingOffRegistrants.remove(h);
}
/**
* Re-register network by toggling preferred network type.
* This is a work-around to deregister and register network since there is
* no ril api to set COPS=2 (deregister) only.
*
* @param onComplete is dispatched when this is complete. it will be
* an AsyncResult, and onComplete.obj.exception will be non-null
* on failure.
*/
public void reRegisterNetwork(Message onComplete) {
cm.getPreferredNetworkType(
obtainMessage(EVENT_GET_PREFERRED_NETWORK_TYPE, onComplete));
}
public void
setRadioPower(boolean power) {
mDesiredPowerState = power;
setPowerStateToDesired();
}
/**
* These two flags manage the behavior of the cell lock -- the
* lock should be held if either flag is true. The intention is
* to allow temporary acquisition of the lock to get a single
* update. Such a lock grab and release can thus be made to not
* interfere with more permanent lock holds -- in other words, the
* lock will only be released if both flags are false, and so
* releases by temporary users will only affect the lock state if
* there is no continuous user.
*/
private boolean mWantContinuousLocationUpdates;
private boolean mWantSingleLocationUpdate;
public void enableSingleLocationUpdate() {
if (mWantSingleLocationUpdate || mWantContinuousLocationUpdates) return;
mWantSingleLocationUpdate = true;
cm.setLocationUpdates(true, obtainMessage(EVENT_LOCATION_UPDATES_ENABLED));
}
public void enableLocationUpdates() {
if (mWantSingleLocationUpdate || mWantContinuousLocationUpdates) return;
mWantContinuousLocationUpdates = true;
cm.setLocationUpdates(true, obtainMessage(EVENT_LOCATION_UPDATES_ENABLED));
}
protected void disableSingleLocationUpdate() {
mWantSingleLocationUpdate = false;
if (!mWantSingleLocationUpdate && !mWantContinuousLocationUpdates) {
cm.setLocationUpdates(false, null);
}
}
public void disableLocationUpdates() {
mWantContinuousLocationUpdates = false;
if (!mWantSingleLocationUpdate && !mWantContinuousLocationUpdates) {
cm.setLocationUpdates(false, null);
}
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case EVENT_SET_RADIO_POWER_OFF:
synchronized(this) {
if (mPendingRadioPowerOffAfterDataOff &&
(msg.arg1 == mPendingRadioPowerOffAfterDataOffTag)) {
if (DBG) log("EVENT_SET_RADIO_OFF, turn radio off now.");
hangupAndPowerOff();
mPendingRadioPowerOffAfterDataOffTag += 1;
mPendingRadioPowerOffAfterDataOff = false;
} else {
log("EVENT_SET_RADIO_OFF is stale arg1=" + msg.arg1 +
"!= tag=" + mPendingRadioPowerOffAfterDataOffTag);
}
}
break;
default:
log("Unhandled message with number: " + msg.what);
break;
}
}
protected abstract Phone getPhone();
protected abstract void handlePollStateResult(int what, AsyncResult ar);
protected abstract void updateSpnDisplay();
protected abstract void setPowerStateToDesired();
protected abstract void log(String s);
protected abstract void loge(String s);
public abstract int getCurrentDataConnectionState();
public abstract boolean isConcurrentVoiceAndDataAllowed();
/**
* Registration point for transition into DataConnection attached.
* @param h handler to notify
* @param what what code of message when delivered
* @param obj placed in Message.obj
*/
public void registerForDataConnectionAttached(Handler h, int what, Object obj) {
Registrant r = new Registrant(h, what, obj);
mAttachedRegistrants.add(r);
if (getCurrentDataConnectionState() == ServiceState.STATE_IN_SERVICE) {
r.notifyRegistrant();
}
}
public void unregisterForDataConnectionAttached(Handler h) {
mAttachedRegistrants.remove(h);
}
/**
* Registration point for transition into DataConnection detached.
* @param h handler to notify
* @param what what code of message when delivered
* @param obj placed in Message.obj
*/
public void registerForDataConnectionDetached(Handler h, int what, Object obj) {
Registrant r = new Registrant(h, what, obj);
mDetachedRegistrants.add(r);
if (getCurrentDataConnectionState() != ServiceState.STATE_IN_SERVICE) {
r.notifyRegistrant();
}
}
public void unregisterForDataConnectionDetached(Handler h) {
mDetachedRegistrants.remove(h);
}
/**
* Registration point for transition into network attached.
* @param h handler to notify
* @param what what code of message when delivered
* @param obj in Message.obj
*/
public void registerForNetworkAttached(Handler h, int what, Object obj) {
Registrant r = new Registrant(h, what, obj);
mNetworkAttachedRegistrants.add(r);
if (ss.getState() == ServiceState.STATE_IN_SERVICE) {
r.notifyRegistrant();
}
}
public void unregisterForNetworkAttached(Handler h) {
mNetworkAttachedRegistrants.remove(h);
}
/**
* Registration point for transition into packet service restricted zone.
* @param h handler to notify
* @param what what code of message when delivered
* @param obj placed in Message.obj
*/
public void registerForPsRestrictedEnabled(Handler h, int what, Object obj) {
Registrant r = new Registrant(h, what, obj);
mPsRestrictEnabledRegistrants.add(r);
if (mRestrictedState.isPsRestricted()) {
r.notifyRegistrant();
}
}
public void unregisterForPsRestrictedEnabled(Handler h) {
mPsRestrictEnabledRegistrants.remove(h);
}
/**
* Registration point for transition out of packet service restricted zone.
* @param h handler to notify
* @param what what code of message when delivered
* @param obj placed in Message.obj
*/
public void registerForPsRestrictedDisabled(Handler h, int what, Object obj) {
Registrant r = new Registrant(h, what, obj);
mPsRestrictDisabledRegistrants.add(r);
if (mRestrictedState.isPsRestricted()) {
r.notifyRegistrant();
}
}
public void unregisterForPsRestrictedDisabled(Handler h) {
mPsRestrictDisabledRegistrants.remove(h);
}
/**
* Clean up existing voice and data connection then turn off radio power.
*
* Hang up the existing voice calls to decrease call drop rate.
*/
public void powerOffRadioSafely(DataConnectionTracker dcTracker) {
synchronized (this) {
if (!mPendingRadioPowerOffAfterDataOff) {
// To minimize race conditions we call cleanUpAllConnections on
// both if else paths instead of before this isDisconnected test.
if (dcTracker.isDisconnected()) {
// To minimize race conditions we do this after isDisconnected
dcTracker.cleanUpAllConnections(Phone.REASON_RADIO_TURNED_OFF);
if (DBG) log("Data disconnected, turn off radio right away.");
hangupAndPowerOff();
} else {
dcTracker.cleanUpAllConnections(Phone.REASON_RADIO_TURNED_OFF);
Message msg = Message.obtain(this);
msg.what = EVENT_SET_RADIO_POWER_OFF;
msg.arg1 = ++mPendingRadioPowerOffAfterDataOffTag;
if (sendMessageDelayed(msg, 30000)) {
if (DBG) log("Wait upto 30s for data to disconnect, then turn off radio.");
mPendingRadioPowerOffAfterDataOff = true;
} else {
log("Cannot send delayed Msg, turn off radio right away.");
hangupAndPowerOff();
}
}
}
}
}
/**
* process the pending request to turn radio off after data is disconnected
*
* return true if there is pending request to process; false otherwise.
*/
public boolean processPendingRadioPowerOffAfterDataOff() {
synchronized(this) {
if (mPendingRadioPowerOffAfterDataOff) {
if (DBG) log("Process pending request to turn radio off.");
mPendingRadioPowerOffAfterDataOffTag += 1;
hangupAndPowerOff();
mPendingRadioPowerOffAfterDataOff = false;
return true;
}
return false;
}
}
/**
* Hang up all voice call and turn off radio. Implemented by derived class.
*/
protected abstract void hangupAndPowerOff();
/** Cancel a pending (if any) pollState() operation */
protected void cancelPollState() {
// This will effectively cancel the rest of the poll requests.
pollingContext = new int[1];
}
/**
* Return true if time zone needs fixing.
*
* @param phoneBase
* @param operatorNumeric
* @param prevOperatorNumeric
* @param needToFixTimeZone
* @return true if time zone needs to be fixed
*/
protected boolean shouldFixTimeZoneNow(PhoneBase phoneBase, String operatorNumeric,
String prevOperatorNumeric, boolean needToFixTimeZone) {
// Return false if the mcc isn't valid as we don't know where we are.
// Return true if we have an IccCard and the mcc changed or we
// need to fix it because when the NITZ time came in we didn't
// know the country code.
// If mcc is invalid then we'll return false
int mcc;
try {
mcc = Integer.parseInt(operatorNumeric.substring(0, 3));
} catch (Exception e) {
if (DBG) {
log("shouldFixTimeZoneNow: no mcc, operatorNumeric=" + operatorNumeric +
" retVal=false");
}
return false;
}
// If prevMcc is invalid will make it different from mcc
// so we'll return true if the card exists.
int prevMcc;
try {
prevMcc = Integer.parseInt(prevOperatorNumeric.substring(0, 3));
} catch (Exception e) {
prevMcc = mcc + 1;
}
// Determine if the Icc card exists
IccCard iccCard = phoneBase.getIccCard();
boolean iccCardExist = (iccCard != null) && iccCard.getState().iccCardExist();
// Determine retVal
boolean retVal = ((iccCardExist && (mcc != prevMcc)) || needToFixTimeZone);
if (DBG) {
long ctm = System.currentTimeMillis();
log("shouldFixTimeZoneNow: retVal=" + retVal +
" iccCard=" + iccCard +
" iccCard.state=" + (iccCard == null ? "null" : iccCard.getState().toString()) +
" iccCardExist=" + iccCardExist +
" operatorNumeric=" + operatorNumeric + " mcc=" + mcc +
" prevOperatorNumeric=" + prevOperatorNumeric + " prevMcc=" + prevMcc +
" needToFixTimeZone=" + needToFixTimeZone +
" ltod=" + TimeUtils.logTimeOfDay(ctm));
}
return retVal;
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("ServiceStateTracker:");
pw.println(" ss=" + ss);
pw.println(" newSS=" + newSS);
pw.println(" mSignalStrength=" + mSignalStrength);
pw.println(" mRestrictedState=" + mRestrictedState);
pw.println(" pollingContext=" + pollingContext);
pw.println(" mDesiredPowerState=" + mDesiredPowerState);
pw.println(" mRilRadioTechnology=" + mRilRadioTechnology);
pw.println(" mNewRilRadioTechnology=" + mNewRilRadioTechnology);
pw.println(" dontPollSignalStrength=" + dontPollSignalStrength);
pw.println(" mPendingRadioPowerOffAfterDataOff=" + mPendingRadioPowerOffAfterDataOff);
pw.println(" mPendingRadioPowerOffAfterDataOffTag=" + mPendingRadioPowerOffAfterDataOffTag);
}
}