| /* |
| * 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.gsm; |
| |
| import android.app.AlarmManager; |
| import android.app.Notification; |
| import android.app.NotificationManager; |
| import android.app.PendingIntent; |
| import android.content.BroadcastReceiver; |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.res.Resources; |
| import android.database.ContentObserver; |
| import android.os.AsyncResult; |
| import android.os.Build; |
| import android.os.Handler; |
| import android.os.Message; |
| import android.os.PersistableBundle; |
| import android.os.PowerManager; |
| import android.os.RemoteException; |
| import android.os.ServiceManager; |
| import android.os.SystemClock; |
| import android.os.SystemProperties; |
| import android.os.UserHandle; |
| import android.provider.Settings; |
| import android.provider.Settings.SettingNotFoundException; |
| import android.telephony.CellIdentityGsm; |
| import android.telephony.CellIdentityLte; |
| import android.telephony.CellIdentityWcdma; |
| import android.telephony.CellInfo; |
| import android.telephony.CellInfoGsm; |
| import android.telephony.CellInfoLte; |
| import android.telephony.CellInfoWcdma; |
| import android.telephony.CellLocation; |
| import android.telephony.Rlog; |
| import android.telephony.ServiceState; |
| import android.telephony.SignalStrength; |
| import android.telephony.gsm.GsmCellLocation; |
| import android.telephony.TelephonyManager; |
| import android.text.TextUtils; |
| import android.util.EventLog; |
| import android.util.TimeUtils; |
| |
| import com.android.internal.telephony.CommandException; |
| import com.android.internal.telephony.CommandsInterface; |
| import com.android.internal.telephony.EventLogTags; |
| import com.android.internal.telephony.ICarrierConfigLoader; |
| import com.android.internal.telephony.MccTable; |
| import com.android.internal.telephony.ProxyController; |
| import com.android.internal.telephony.Phone; |
| import com.android.internal.telephony.RILConstants; |
| import com.android.internal.telephony.RestrictedState; |
| import com.android.internal.telephony.ServiceStateTracker; |
| import android.telephony.SubscriptionManager; |
| import com.android.internal.telephony.TelephonyIntents; |
| import com.android.internal.telephony.TelephonyProperties; |
| import com.android.internal.telephony.dataconnection.DcTrackerBase; |
| import com.android.internal.telephony.imsphone.ImsPhone; |
| import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState; |
| import com.android.internal.telephony.uicc.IccRecords; |
| import com.android.internal.telephony.uicc.SIMRecords; |
| import com.android.internal.telephony.uicc.UiccCardApplication; |
| import com.android.internal.telephony.uicc.UiccController; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Calendar; |
| import java.util.Date; |
| import java.util.List; |
| import java.util.TimeZone; |
| |
| /** |
| * {@hide} |
| */ |
| final class GsmServiceStateTracker extends ServiceStateTracker { |
| static final String LOG_TAG = "GsmSST"; |
| static final boolean VDBG = false; |
| //CAF_MSIM make it private ?? |
| private static final int EVENT_ALL_DATA_DISCONNECTED = 1001; |
| private GSMPhone mPhone; |
| GsmCellLocation mCellLoc; |
| GsmCellLocation mNewCellLoc; |
| int mPreferredNetworkType; |
| |
| private int mMaxDataCalls = 1; |
| private int mNewMaxDataCalls = 1; |
| private int mReasonDataDenied = -1; |
| private int mNewReasonDataDenied = -1; |
| |
| /** |
| * GSM roaming status solely based on TS 27.007 7.2 CREG. Only used by |
| * handlePollStateResult to store CREG roaming result. |
| */ |
| private boolean mGsmRoaming = false; |
| |
| /** |
| * Data roaming status solely based on TS 27.007 10.1.19 CGREG. Only used by |
| * handlePollStateResult to store CGREG roaming result. |
| */ |
| private boolean mDataRoaming = false; |
| |
| /** |
| * Mark when service state is in emergency call only mode |
| */ |
| private boolean mEmergencyOnly = false; |
| |
| /** |
| * Sometimes we get the NITZ time before we know what country we |
| * are in. Keep the time zone information from the NITZ string so |
| * we can fix the time zone once know the country. |
| */ |
| private boolean mNeedFixZoneAfterNitz = false; |
| private int mZoneOffset; |
| private boolean mZoneDst; |
| private long mZoneTime; |
| private boolean mGotCountryCode = false; |
| private ContentResolver mCr; |
| |
| /** Boolean is true is setTimeFromNITZString was called */ |
| private boolean mNitzUpdatedTime = false; |
| /** Time stamp after 19 January 2038 is not supported under 32 bit */ |
| private static final int MAX_NITZ_YEAR = 2037; |
| |
| String mSavedTimeZone; |
| long mSavedTime; |
| long mSavedAtTime; |
| |
| /** Started the recheck process after finding gprs should registered but not. */ |
| private boolean mStartedGprsRegCheck = false; |
| |
| /** Already sent the event-log for no gprs register. */ |
| private boolean mReportedGprsNoReg = false; |
| |
| /** |
| * The Notification object given to the NotificationManager. |
| */ |
| private Notification mNotification; |
| |
| /** Wake lock used while setting time of day. */ |
| private PowerManager.WakeLock mWakeLock; |
| private static final String WAKELOCK_TAG = "ServiceStateTracker"; |
| |
| /** Notification type. */ |
| static final int PS_ENABLED = 1001; // Access Control blocks data service |
| static final int PS_DISABLED = 1002; // Access Control enables data service |
| static final int CS_ENABLED = 1003; // Access Control blocks all voice/sms service |
| static final int CS_DISABLED = 1004; // Access Control enables all voice/sms service |
| static final int CS_NORMAL_ENABLED = 1005; // Access Control blocks normal voice/sms service |
| static final int CS_EMERGENCY_ENABLED = 1006; // Access Control blocks emergency call service |
| |
| /** Notification id. */ |
| static final int PS_NOTIFICATION = 888; // Id to update and cancel PS restricted |
| static final int CS_NOTIFICATION = 999; // Id to update and cancel CS restricted |
| |
| private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| if (!mPhone.mIsTheCurrentActivePhone) { |
| Rlog.e(LOG_TAG, "Received Intent " + intent + |
| " while being destroyed. Ignoring."); |
| return; |
| } |
| |
| if (intent.getAction().equals(Intent.ACTION_LOCALE_CHANGED)) { |
| // update emergency string whenever locale changed |
| updateSpnDisplay(); |
| } else if (intent.getAction().equals(ACTION_RADIO_OFF)) { |
| mAlarmSwitch = false; |
| DcTrackerBase dcTracker = mPhone.mDcTracker; |
| powerOffRadioSafely(dcTracker); |
| } |
| } |
| }; |
| |
| private ContentObserver mAutoTimeObserver = new ContentObserver(new Handler()) { |
| @Override |
| public void onChange(boolean selfChange) { |
| Rlog.i("GsmServiceStateTracker", "Auto time state changed"); |
| revertToNitzTime(); |
| } |
| }; |
| |
| private ContentObserver mAutoTimeZoneObserver = new ContentObserver(new Handler()) { |
| @Override |
| public void onChange(boolean selfChange) { |
| Rlog.i("GsmServiceStateTracker", "Auto time zone state changed"); |
| revertToNitzTimeZone(); |
| } |
| }; |
| |
| public GsmServiceStateTracker(GSMPhone phone) { |
| super(phone, phone.mCi, new CellInfoGsm()); |
| |
| mPhone = phone; |
| mCellLoc = new GsmCellLocation(); |
| mNewCellLoc = new GsmCellLocation(); |
| |
| PowerManager powerManager = |
| (PowerManager)phone.getContext().getSystemService(Context.POWER_SERVICE); |
| mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG); |
| |
| mCi.registerForAvailable(this, EVENT_RADIO_AVAILABLE, null); |
| mCi.registerForRadioStateChanged(this, EVENT_RADIO_STATE_CHANGED, null); |
| |
| mCi.registerForVoiceNetworkStateChanged(this, EVENT_NETWORK_STATE_CHANGED, null); |
| mCi.setOnNITZTime(this, EVENT_NITZ_TIME, null); |
| mCi.setOnRestrictedStateChanged(this, EVENT_RESTRICTED_STATE_CHANGED, null); |
| |
| // system setting property AIRPLANE_MODE_ON is set in Settings. |
| int airplaneMode = Settings.Global.getInt( |
| phone.getContext().getContentResolver(), |
| Settings.Global.AIRPLANE_MODE_ON, 0); |
| mDesiredPowerState = ! (airplaneMode > 0); |
| |
| mCr = phone.getContext().getContentResolver(); |
| mCr.registerContentObserver( |
| Settings.Global.getUriFor(Settings.Global.AUTO_TIME), true, |
| mAutoTimeObserver); |
| mCr.registerContentObserver( |
| Settings.Global.getUriFor(Settings.Global.AUTO_TIME_ZONE), true, |
| mAutoTimeZoneObserver); |
| |
| setSignalStrengthDefaultValues(); |
| |
| // Query signal strength from the modem after service tracker is created (i.e. boot up, |
| // switching between GSM and CDMA phone), because the unsolicited signal strength |
| // information might come late or even never come. This will get the accurate signal |
| // strength information displayed on the UI. |
| mCi.getSignalStrength(obtainMessage(EVENT_GET_SIGNAL_STRENGTH)); |
| |
| // Monitor locale change |
| IntentFilter filter = new IntentFilter(); |
| filter.addAction(Intent.ACTION_LOCALE_CHANGED); |
| phone.getContext().registerReceiver(mIntentReceiver, filter); |
| |
| filter = new IntentFilter(); |
| Context context = phone.getContext(); |
| filter.addAction(ACTION_RADIO_OFF); |
| context.registerReceiver(mIntentReceiver, filter); |
| } |
| |
| @Override |
| public void dispose() { |
| checkCorrectThread(); |
| log("ServiceStateTracker dispose"); |
| |
| // Unregister for all events. |
| mCi.unregisterForAvailable(this); |
| mCi.unregisterForRadioStateChanged(this); |
| mCi.unregisterForVoiceNetworkStateChanged(this); |
| if (mUiccApplcation != null) {mUiccApplcation.unregisterForReady(this);} |
| if (mIccRecords != null) {mIccRecords.unregisterForRecordsLoaded(this);} |
| mCi.unSetOnRestrictedStateChanged(this); |
| mCi.unSetOnNITZTime(this); |
| mCr.unregisterContentObserver(mAutoTimeObserver); |
| mCr.unregisterContentObserver(mAutoTimeZoneObserver); |
| mPhone.getContext().unregisterReceiver(mIntentReceiver); |
| super.dispose(); |
| } |
| |
| @Override |
| protected void finalize() { |
| if(DBG) log("finalize"); |
| } |
| |
| @Override |
| protected Phone getPhone() { |
| return mPhone; |
| } |
| |
| @Override |
| public void handleMessage (Message msg) { |
| AsyncResult ar; |
| int[] ints; |
| String[] strings; |
| Message message; |
| |
| if (!mPhone.mIsTheCurrentActivePhone) { |
| Rlog.e(LOG_TAG, "Received message " + msg + |
| "[" + msg.what + "] while being destroyed. Ignoring."); |
| return; |
| } |
| switch (msg.what) { |
| case EVENT_RADIO_AVAILABLE: |
| //this is unnecessary |
| //setPowerStateToDesired(); |
| break; |
| |
| case EVENT_SIM_READY: |
| // Reset the mPreviousSubId so we treat a SIM power bounce |
| // as a first boot. See b/19194287 |
| mOnSubscriptionsChangedListener.mPreviousSubId.set(-1); |
| pollState(); |
| // Signal strength polling stops when radio is off |
| queueNextSignalStrengthPoll(); |
| break; |
| |
| case EVENT_RADIO_STATE_CHANGED: |
| // This will do nothing in the radio not |
| // available case |
| setPowerStateToDesired(); |
| pollState(); |
| break; |
| |
| case EVENT_NETWORK_STATE_CHANGED: |
| pollState(); |
| break; |
| |
| case EVENT_GET_SIGNAL_STRENGTH: |
| // This callback is called when signal strength is polled |
| // all by itself |
| |
| if (!(mCi.getRadioState().isOn())) { |
| // Polling will continue when radio turns back on |
| return; |
| } |
| ar = (AsyncResult) msg.obj; |
| onSignalStrengthResult(ar, true); |
| queueNextSignalStrengthPoll(); |
| |
| break; |
| |
| case EVENT_GET_LOC_DONE: |
| ar = (AsyncResult) msg.obj; |
| |
| if (ar.exception == null) { |
| String states[] = (String[])ar.result; |
| int lac = -1; |
| int cid = -1; |
| if (states.length >= 3) { |
| try { |
| if (states[1] != null && states[1].length() > 0) { |
| lac = Integer.parseInt(states[1], 16); |
| } |
| if (states[2] != null && states[2].length() > 0) { |
| cid = Integer.parseInt(states[2], 16); |
| } |
| } catch (NumberFormatException ex) { |
| Rlog.w(LOG_TAG, "error parsing location: " + ex); |
| } |
| } |
| mCellLoc.setLacAndCid(lac, cid); |
| mPhone.notifyLocationChanged(); |
| } |
| |
| // Release any temporary cell lock, which could have been |
| // acquired to allow a single-shot location update. |
| disableSingleLocationUpdate(); |
| break; |
| |
| case EVENT_POLL_STATE_REGISTRATION: |
| case EVENT_POLL_STATE_GPRS: |
| case EVENT_POLL_STATE_OPERATOR: |
| case EVENT_POLL_STATE_NETWORK_SELECTION_MODE: |
| ar = (AsyncResult) msg.obj; |
| |
| handlePollStateResult(msg.what, ar); |
| break; |
| |
| case EVENT_POLL_SIGNAL_STRENGTH: |
| // Just poll signal strength...not part of pollState() |
| |
| mCi.getSignalStrength(obtainMessage(EVENT_GET_SIGNAL_STRENGTH)); |
| break; |
| |
| case EVENT_NITZ_TIME: |
| ar = (AsyncResult) msg.obj; |
| |
| String nitzString = (String)((Object[])ar.result)[0]; |
| long nitzReceiveTime = ((Long)((Object[])ar.result)[1]).longValue(); |
| |
| setTimeFromNITZString(nitzString, nitzReceiveTime); |
| break; |
| |
| case EVENT_SIGNAL_STRENGTH_UPDATE: |
| // This is a notification from |
| // CommandsInterface.setOnSignalStrengthUpdate |
| |
| ar = (AsyncResult) msg.obj; |
| |
| // The radio is telling us about signal strength changes |
| // we don't have to ask it |
| mDontPollSignalStrength = true; |
| |
| onSignalStrengthResult(ar, true); |
| break; |
| |
| case EVENT_SIM_RECORDS_LOADED: |
| log("EVENT_SIM_RECORDS_LOADED: what=" + msg.what); |
| // Gsm doesn't support OTASP so its not needed |
| mPhone.notifyOtaspChanged(OTASP_NOT_NEEDED); |
| |
| updatePhoneObject(); |
| updateSpnDisplay(); |
| break; |
| |
| case EVENT_LOCATION_UPDATES_ENABLED: |
| ar = (AsyncResult) msg.obj; |
| |
| if (ar.exception == null) { |
| mCi.getVoiceRegistrationState(obtainMessage(EVENT_GET_LOC_DONE, null)); |
| } |
| break; |
| |
| case EVENT_SET_PREFERRED_NETWORK_TYPE: |
| ar = (AsyncResult) msg.obj; |
| // Don't care the result, only use for dereg network (COPS=2) |
| message = obtainMessage(EVENT_RESET_PREFERRED_NETWORK_TYPE, ar.userObj); |
| mCi.setPreferredNetworkType(mPreferredNetworkType, message); |
| break; |
| |
| case EVENT_RESET_PREFERRED_NETWORK_TYPE: |
| ar = (AsyncResult) msg.obj; |
| if (ar.userObj != null) { |
| AsyncResult.forMessage(((Message) ar.userObj)).exception |
| = ar.exception; |
| ((Message) ar.userObj).sendToTarget(); |
| } |
| break; |
| |
| case EVENT_GET_PREFERRED_NETWORK_TYPE: |
| ar = (AsyncResult) msg.obj; |
| |
| if (ar.exception == null) { |
| mPreferredNetworkType = ((int[])ar.result)[0]; |
| } else { |
| mPreferredNetworkType = RILConstants.NETWORK_MODE_GLOBAL; |
| } |
| |
| message = obtainMessage(EVENT_SET_PREFERRED_NETWORK_TYPE, ar.userObj); |
| int toggledNetworkType = RILConstants.NETWORK_MODE_GLOBAL; |
| |
| mCi.setPreferredNetworkType(toggledNetworkType, message); |
| break; |
| |
| case EVENT_CHECK_REPORT_GPRS: |
| if (mSS != null && !isGprsConsistent(mSS.getDataRegState(), mSS.getVoiceRegState())) { |
| |
| // Can't register data service while voice service is ok |
| // i.e. CREG is ok while CGREG is not |
| // possible a network or baseband side error |
| GsmCellLocation loc = ((GsmCellLocation)mPhone.getCellLocation()); |
| EventLog.writeEvent(EventLogTags.DATA_NETWORK_REGISTRATION_FAIL, |
| mSS.getOperatorNumeric(), loc != null ? loc.getCid() : -1); |
| mReportedGprsNoReg = true; |
| } |
| mStartedGprsRegCheck = false; |
| break; |
| |
| case EVENT_RESTRICTED_STATE_CHANGED: |
| // This is a notification from |
| // CommandsInterface.setOnRestrictedStateChanged |
| |
| if (DBG) log("EVENT_RESTRICTED_STATE_CHANGED"); |
| |
| ar = (AsyncResult) msg.obj; |
| |
| onRestrictedStateChanged(ar); |
| break; |
| |
| case EVENT_ALL_DATA_DISCONNECTED: |
| int dds = SubscriptionManager.getDefaultDataSubId(); |
| ProxyController.getInstance().unregisterForAllDataDisconnected(dds, this); |
| synchronized(this) { |
| if (mPendingRadioPowerOffAfterDataOff) { |
| if (DBG) log("EVENT_ALL_DATA_DISCONNECTED, turn radio off now."); |
| hangupAndPowerOff(); |
| mPendingRadioPowerOffAfterDataOff = false; |
| } else { |
| log("EVENT_ALL_DATA_DISCONNECTED is stale"); |
| } |
| } |
| break; |
| |
| case EVENT_CHANGE_IMS_STATE: |
| if (DBG) log("EVENT_CHANGE_IMS_STATE:"); |
| |
| setPowerStateToDesired(); |
| break; |
| |
| case EVENT_IMS_CAPABILITY_CHANGED: |
| if (DBG) log("EVENT_IMS_CAPABILITY_CHANGED"); |
| updateSpnDisplay(); |
| break; |
| |
| default: |
| super.handleMessage(msg); |
| break; |
| } |
| } |
| |
| @Override |
| protected void setPowerStateToDesired() { |
| |
| if (DBG) { |
| log("mDeviceShuttingDown = " + mDeviceShuttingDown); |
| log("mDesiredPowerState = " + mDesiredPowerState); |
| log("getRadioState = " + mCi.getRadioState()); |
| log("mPowerOffDelayNeed = " + mPowerOffDelayNeed); |
| log("mAlarmSwitch = " + mAlarmSwitch); |
| } |
| |
| if (mAlarmSwitch) { |
| if(DBG) log("mAlarmSwitch == true"); |
| Context context = mPhone.getContext(); |
| AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); |
| am.cancel(mRadioOffIntent); |
| mAlarmSwitch = false; |
| } |
| |
| // If we want it on and it's off, turn it on |
| if (mDesiredPowerState |
| && mCi.getRadioState() == CommandsInterface.RadioState.RADIO_OFF) { |
| mCi.setRadioPower(true, null); |
| } else if (!mDesiredPowerState && mCi.getRadioState().isOn()) { |
| // If it's on and available and we want it off gracefully |
| if (mPowerOffDelayNeed) { |
| if (mImsRegistrationOnOff && !mAlarmSwitch) { |
| if(DBG) log("mImsRegistrationOnOff == true"); |
| Context context = mPhone.getContext(); |
| AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); |
| |
| Intent intent = new Intent(ACTION_RADIO_OFF); |
| mRadioOffIntent = PendingIntent.getBroadcast(context, 0, intent, 0); |
| |
| mAlarmSwitch = true; |
| if (DBG) log("Alarm setting"); |
| am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, |
| SystemClock.elapsedRealtime() + 3000, mRadioOffIntent); |
| } else { |
| DcTrackerBase dcTracker = mPhone.mDcTracker; |
| powerOffRadioSafely(dcTracker); |
| } |
| } else { |
| DcTrackerBase dcTracker = mPhone.mDcTracker; |
| powerOffRadioSafely(dcTracker); |
| } |
| } else if (mDeviceShuttingDown && mCi.getRadioState().isAvailable()) { |
| mCi.requestShutdown(null); |
| } |
| } |
| |
| @Override |
| protected void hangupAndPowerOff() { |
| // hang up all active voice calls |
| if (mPhone.isInCall()) { |
| mPhone.mCT.mRingingCall.hangupIfAlive(); |
| mPhone.mCT.mBackgroundCall.hangupIfAlive(); |
| mPhone.mCT.mForegroundCall.hangupIfAlive(); |
| } |
| |
| mCi.setRadioPower(false, null); |
| } |
| |
| @Override |
| protected void updateSpnDisplay() { |
| // The values of plmn/showPlmn change in different scenarios. |
| // 1) No service but emergency call allowed -> expected |
| // to show "Emergency call only" |
| // EXTRA_SHOW_PLMN = true |
| // EXTRA_PLMN = "Emergency call only" |
| |
| // 2) No service at all --> expected to show "No service" |
| // EXTRA_SHOW_PLMN = true |
| // EXTRA_PLMN = "No service" |
| |
| // 3) Normal operation in either home or roaming service |
| // EXTRA_SHOW_PLMN = depending on IccRecords rule |
| // EXTRA_PLMN = plmn |
| |
| // 4) No service due to power off, aka airplane mode |
| // EXTRA_SHOW_PLMN = false |
| // EXTRA_PLMN = null |
| |
| IccRecords iccRecords = mIccRecords; |
| String plmn = null; |
| boolean showPlmn = false; |
| int rule = (iccRecords != null) ? iccRecords.getDisplayRule(mSS.getOperatorNumeric()) : 0; |
| if (mSS.getVoiceRegState() == ServiceState.STATE_OUT_OF_SERVICE |
| || mSS.getVoiceRegState() == ServiceState.STATE_EMERGENCY_ONLY) { |
| showPlmn = true; |
| if (mEmergencyOnly) { |
| // No service but emergency call allowed |
| plmn = Resources.getSystem(). |
| getText(com.android.internal.R.string.emergency_calls_only).toString(); |
| } else { |
| // No service at all |
| plmn = Resources.getSystem(). |
| getText(com.android.internal.R.string.lockscreen_carrier_default).toString(); |
| } |
| if (DBG) log("updateSpnDisplay: radio is on but out " + |
| "of service, set plmn='" + plmn + "'"); |
| } else if (mSS.getVoiceRegState() == ServiceState.STATE_IN_SERVICE) { |
| // In either home or roaming service |
| plmn = mSS.getOperatorAlphaLong(); |
| showPlmn = !TextUtils.isEmpty(plmn) && |
| ((rule & SIMRecords.SPN_RULE_SHOW_PLMN) |
| == SIMRecords.SPN_RULE_SHOW_PLMN); |
| } else { |
| // Power off state, such as airplane mode, show plmn as "No service" |
| showPlmn = true; |
| plmn = Resources.getSystem(). |
| getText(com.android.internal.R.string.lockscreen_carrier_default).toString(); |
| if (DBG) log("updateSpnDisplay: radio is off w/ showPlmn=" |
| + showPlmn + " plmn=" + plmn); |
| } |
| |
| // The value of spn/showSpn are same in different scenarios. |
| // EXTRA_SHOW_SPN = depending on IccRecords rule and radio/IMS state |
| // EXTRA_SPN = spn |
| // EXTRA_DATA_SPN = dataSpn |
| String spn = (iccRecords != null) ? iccRecords.getServiceProviderName() : ""; |
| String dataSpn = spn; |
| boolean showSpn = !TextUtils.isEmpty(spn) |
| && ((rule & SIMRecords.SPN_RULE_SHOW_SPN) |
| == SIMRecords.SPN_RULE_SHOW_SPN); |
| |
| if (!TextUtils.isEmpty(spn) |
| && mPhone.getImsPhone() != null |
| && ((ImsPhone) mPhone.getImsPhone()).isVowifiEnabled()) { |
| // In Wi-Fi Calling mode show SPN+WiFi |
| String formatVoice = mPhone.getContext().getText( |
| com.android.internal.R.string.wfcSpnFormat).toString(); |
| String formatData = mPhone.getContext().getText( |
| com.android.internal.R.string.wfcDataSpnFormat).toString(); |
| String originalSpn = spn.trim(); |
| spn = String.format(formatVoice, originalSpn); |
| dataSpn = String.format(formatData, originalSpn); |
| showSpn = true; |
| showPlmn = false; |
| } else if (mSS.getVoiceRegState() == ServiceState.STATE_POWER_OFF |
| || (showPlmn && TextUtils.equals(spn, plmn))) { |
| // airplane mode or spn equals plmn, do not show spn |
| spn = null; |
| showSpn = false; |
| } |
| |
| int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; |
| int[] subIds = SubscriptionManager.getSubId(mPhone.getPhoneId()); |
| if (subIds != null && subIds.length > 0) { |
| subId = subIds[0]; |
| } |
| |
| // Update SPN_STRINGS_UPDATED_ACTION IFF any value changes |
| if (mSubId != subId || |
| showPlmn != mCurShowPlmn |
| || showSpn != mCurShowSpn |
| || !TextUtils.equals(spn, mCurSpn) |
| || !TextUtils.equals(dataSpn, mCurDataSpn) |
| || !TextUtils.equals(plmn, mCurPlmn)) { |
| if (DBG) { |
| log(String.format("updateSpnDisplay: changed" + |
| " sending intent rule=" + rule + |
| " showPlmn='%b' plmn='%s' showSpn='%b' spn='%s' dataSpn='%s' subId='%d'", |
| showPlmn, plmn, showSpn, spn, dataSpn, subId)); |
| } |
| Intent intent = new Intent(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION); |
| intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); |
| intent.putExtra(TelephonyIntents.EXTRA_SHOW_SPN, showSpn); |
| intent.putExtra(TelephonyIntents.EXTRA_SPN, spn); |
| intent.putExtra(TelephonyIntents.EXTRA_DATA_SPN, dataSpn); |
| intent.putExtra(TelephonyIntents.EXTRA_SHOW_PLMN, showPlmn); |
| intent.putExtra(TelephonyIntents.EXTRA_PLMN, plmn); |
| SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId()); |
| mPhone.getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL); |
| |
| if (!mSubscriptionController.setPlmnSpn(mPhone.getPhoneId(), |
| showPlmn, plmn, showSpn, spn)) { |
| mSpnUpdatePending = true; |
| } |
| } |
| |
| mSubId = subId; |
| mCurShowSpn = showSpn; |
| mCurShowPlmn = showPlmn; |
| mCurSpn = spn; |
| mCurDataSpn = dataSpn; |
| mCurPlmn = plmn; |
| } |
| |
| /** |
| * Handle the result of one of the pollState()-related requests |
| */ |
| @Override |
| protected void handlePollStateResult (int what, AsyncResult ar) { |
| int ints[]; |
| String states[]; |
| |
| // Ignore stale requests from last poll |
| if (ar.userObj != mPollingContext) return; |
| |
| if (ar.exception != null) { |
| CommandException.Error err=null; |
| |
| if (ar.exception instanceof CommandException) { |
| err = ((CommandException)(ar.exception)).getCommandError(); |
| } |
| |
| if (err == CommandException.Error.RADIO_NOT_AVAILABLE) { |
| // Radio has crashed or turned off |
| cancelPollState(); |
| return; |
| } |
| |
| if (err != CommandException.Error.OP_NOT_ALLOWED_BEFORE_REG_NW) { |
| loge("RIL implementation has returned an error where it must succeed" + |
| ar.exception); |
| } |
| } else try { |
| switch (what) { |
| case EVENT_POLL_STATE_REGISTRATION: { |
| states = (String[])ar.result; |
| int lac = -1; |
| int cid = -1; |
| int type = ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN; |
| int regState = ServiceState.RIL_REG_STATE_UNKNOWN; |
| int reasonRegStateDenied = -1; |
| int psc = -1; |
| if (states.length > 0) { |
| try { |
| regState = Integer.parseInt(states[0]); |
| if (states.length >= 3) { |
| if (states[1] != null && states[1].length() > 0) { |
| lac = Integer.parseInt(states[1], 16); |
| } |
| if (states[2] != null && states[2].length() > 0) { |
| cid = Integer.parseInt(states[2], 16); |
| } |
| |
| // states[3] (if present) is the current radio technology |
| if (states.length >= 4 && states[3] != null) { |
| type = Integer.parseInt(states[3]); |
| } |
| } |
| if (states.length > 14) { |
| if (states[14] != null && states[14].length() > 0) { |
| psc = Integer.parseInt(states[14], 16); |
| } |
| } |
| } catch (NumberFormatException ex) { |
| loge("error parsing RegistrationState: " + ex); |
| } |
| } |
| |
| mGsmRoaming = regCodeIsRoaming(regState); |
| mNewSS.setState(regCodeToServiceState(regState)); |
| mNewSS.setRilVoiceRadioTechnology(type); |
| |
| boolean isVoiceCapable = mPhoneBase.getContext().getResources() |
| .getBoolean(com.android.internal.R.bool.config_voice_capable); |
| if ((regState == ServiceState.RIL_REG_STATE_DENIED_EMERGENCY_CALL_ENABLED |
| || regState == ServiceState.RIL_REG_STATE_NOT_REG_EMERGENCY_CALL_ENABLED |
| || regState == ServiceState.RIL_REG_STATE_SEARCHING_EMERGENCY_CALL_ENABLED |
| || regState == ServiceState.RIL_REG_STATE_UNKNOWN_EMERGENCY_CALL_ENABLED) |
| && isVoiceCapable) { |
| mEmergencyOnly = true; |
| } else { |
| mEmergencyOnly = false; |
| } |
| |
| // LAC and CID are -1 if not avail |
| mNewCellLoc.setLacAndCid(lac, cid); |
| mNewCellLoc.setPsc(psc); |
| break; |
| } |
| |
| case EVENT_POLL_STATE_GPRS: { |
| states = (String[])ar.result; |
| |
| int type = 0; |
| int regState = ServiceState.RIL_REG_STATE_UNKNOWN; |
| mNewReasonDataDenied = -1; |
| mNewMaxDataCalls = 1; |
| if (states.length > 0) { |
| try { |
| regState = Integer.parseInt(states[0]); |
| |
| // states[3] (if present) is the current radio technology |
| if (states.length >= 4 && states[3] != null) { |
| type = Integer.parseInt(states[3]); |
| } |
| if ((states.length >= 5 ) && |
| (regState == ServiceState.RIL_REG_STATE_DENIED)) { |
| mNewReasonDataDenied = Integer.parseInt(states[4]); |
| } |
| if (states.length >= 6) { |
| mNewMaxDataCalls = Integer.parseInt(states[5]); |
| } |
| } catch (NumberFormatException ex) { |
| loge("error parsing GprsRegistrationState: " + ex); |
| } |
| } |
| int dataRegState = regCodeToServiceState(regState); |
| mNewSS.setDataRegState(dataRegState); |
| mDataRoaming = regCodeIsRoaming(regState); |
| mNewSS.setRilDataRadioTechnology(type); |
| if (DBG) { |
| log("handlPollStateResultMessage: GsmSST setDataRegState=" + dataRegState |
| + " regState=" + regState |
| + " dataRadioTechnology=" + type); |
| } |
| break; |
| } |
| |
| case EVENT_POLL_STATE_OPERATOR: { |
| String opNames[] = (String[])ar.result; |
| |
| if (opNames != null && opNames.length >= 3) { |
| // FIXME: Giving brandOverride higher precedence, is this desired? |
| String brandOverride = mUiccController.getUiccCard(getPhoneId()) != null ? |
| mUiccController.getUiccCard(getPhoneId()).getOperatorBrandOverride() : null; |
| if (brandOverride != null) { |
| log("EVENT_POLL_STATE_OPERATOR: use brandOverride=" + brandOverride); |
| mNewSS.setOperatorName(brandOverride, brandOverride, opNames[2]); |
| } else { |
| mNewSS.setOperatorName (opNames[0], opNames[1], opNames[2]); |
| } |
| } |
| break; |
| } |
| |
| case EVENT_POLL_STATE_NETWORK_SELECTION_MODE: { |
| ints = (int[])ar.result; |
| mNewSS.setIsManualSelection(ints[0] == 1); |
| if ((ints[0] == 1) && (!mPhone.isManualNetSelAllowed())) { |
| /* |
| * modem is currently in manual selection but manual |
| * selection is not allowed in the current mode so |
| * switch to automatic registration |
| */ |
| mPhone.setNetworkSelectionModeAutomatic (null); |
| log(" Forcing Automatic Network Selection, " + |
| "manual selection is not allowed"); |
| } |
| break; |
| } |
| } |
| } catch (RuntimeException ex) { |
| loge("Exception while polling service state. Probably malformed RIL response." + ex); |
| } |
| |
| mPollingContext[0]--; |
| |
| if (mPollingContext[0] == 0) { |
| updateRoamingState(); |
| mNewSS.setEmergencyOnly(mEmergencyOnly); |
| pollStateDone(); |
| } |
| } |
| |
| /** |
| * Query the carrier configuration to determine if there any network overrides |
| * for roaming or not roaming for the current service state. |
| */ |
| protected void updateRoamingState() { |
| /** |
| * Since the roaming state of gsm service (from +CREG) and |
| * data service (from +CGREG) could be different, the new SS |
| * is set to roaming when either is true. |
| * |
| * There are exceptions for the above rule. |
| * The new SS is not set as roaming while gsm service reports |
| * roaming but indeed it is same operator. |
| * And the operator is considered non roaming. |
| * |
| * The test for the operators is to handle special roaming |
| * agreements and MVNO's. |
| */ |
| boolean roaming = (mGsmRoaming || mDataRoaming); |
| if (mGsmRoaming && !isOperatorConsideredRoaming(mNewSS) && |
| (isSameNamedOperators(mNewSS) || isOperatorConsideredNonRoaming(mNewSS))) { |
| roaming = false; |
| } |
| |
| // Save the roaming state before carrier config possibly overrides it. |
| mNewSS.setDataRoamingFromRegistration(roaming); |
| |
| ICarrierConfigLoader configLoader = |
| (ICarrierConfigLoader) ServiceManager.getService(Context.CARRIER_CONFIG_SERVICE); |
| if (configLoader != null) { |
| try { |
| PersistableBundle b = configLoader.getConfigForSubId(mPhone.getSubId()); |
| |
| if (alwaysOnHomeNetwork(b)) { |
| log("updateRoamingState: carrier config override always on home network"); |
| roaming = false; |
| } else if (isNonRoamingInGsmNetwork(b, mNewSS.getOperatorNumeric())) { |
| log("updateRoamingState: carrier config override set non roaming:" |
| + mNewSS.getOperatorNumeric()); |
| roaming = false; |
| } else if (isRoamingInGsmNetwork(b, mNewSS.getOperatorNumeric())) { |
| log("updateRoamingState: carrier config override set roaming:" |
| + mNewSS.getOperatorNumeric()); |
| roaming = true; |
| } |
| } catch (RemoteException e) { |
| loge("updateRoamingState: unable to access carrier config service"); |
| } |
| } else { |
| log("updateRoamingState: no carrier config service available"); |
| } |
| |
| mNewSS.setVoiceRoaming(roaming); |
| mNewSS.setDataRoaming(roaming); |
| } |
| |
| /** |
| * Set both voice and data roaming type, |
| * judging from the ISO country of SIM VS network. |
| */ |
| protected void setRoamingType(ServiceState currentServiceState) { |
| final boolean isVoiceInService = |
| (currentServiceState.getVoiceRegState() == ServiceState.STATE_IN_SERVICE); |
| if (isVoiceInService) { |
| if (currentServiceState.getVoiceRoaming()) { |
| // check roaming type by MCC |
| if (inSameCountry(currentServiceState.getVoiceOperatorNumeric())) { |
| currentServiceState.setVoiceRoamingType( |
| ServiceState.ROAMING_TYPE_DOMESTIC); |
| } else { |
| currentServiceState.setVoiceRoamingType( |
| ServiceState.ROAMING_TYPE_INTERNATIONAL); |
| } |
| } else { |
| currentServiceState.setVoiceRoamingType(ServiceState.ROAMING_TYPE_NOT_ROAMING); |
| } |
| } |
| final boolean isDataInService = |
| (currentServiceState.getDataRegState() == ServiceState.STATE_IN_SERVICE); |
| final int dataRegType = currentServiceState.getRilDataRadioTechnology(); |
| if (isDataInService) { |
| if (!currentServiceState.getDataRoaming()) { |
| currentServiceState.setDataRoamingType(ServiceState.ROAMING_TYPE_NOT_ROAMING); |
| } else if (ServiceState.isGsm(dataRegType)) { |
| if (isVoiceInService) { |
| // GSM data should have the same state as voice |
| currentServiceState.setDataRoamingType(currentServiceState |
| .getVoiceRoamingType()); |
| } else { |
| // we can not decide GSM data roaming type without voice |
| currentServiceState.setDataRoamingType(ServiceState.ROAMING_TYPE_UNKNOWN); |
| } |
| } else { |
| // we can not decide 3gpp2 roaming state here |
| currentServiceState.setDataRoamingType(ServiceState.ROAMING_TYPE_UNKNOWN); |
| } |
| } |
| } |
| |
| private void setSignalStrengthDefaultValues() { |
| mSignalStrength = new SignalStrength(true); |
| } |
| |
| /** |
| * A complete "service state" from our perspective is |
| * composed of a handful of separate requests to the radio. |
| * |
| * We make all of these requests at once, but then abandon them |
| * and start over again if the radio notifies us that some |
| * event has changed |
| */ |
| @Override |
| public void pollState() { |
| mPollingContext = new int[1]; |
| mPollingContext[0] = 0; |
| |
| switch (mCi.getRadioState()) { |
| case RADIO_UNAVAILABLE: |
| mNewSS.setStateOutOfService(); |
| mNewCellLoc.setStateInvalid(); |
| setSignalStrengthDefaultValues(); |
| mGotCountryCode = false; |
| mNitzUpdatedTime = false; |
| pollStateDone(); |
| break; |
| |
| case RADIO_OFF: |
| mNewSS.setStateOff(); |
| mNewCellLoc.setStateInvalid(); |
| setSignalStrengthDefaultValues(); |
| mGotCountryCode = false; |
| mNitzUpdatedTime = false; |
| if (ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN |
| != mSS.getRilDataRadioTechnology()) { |
| pollStateDone(); |
| } |
| |
| default: |
| // Issue all poll-related commands at once |
| // then count down the responses, which |
| // are allowed to arrive out-of-order |
| |
| mPollingContext[0]++; |
| mCi.getOperator( |
| obtainMessage( |
| EVENT_POLL_STATE_OPERATOR, mPollingContext)); |
| |
| mPollingContext[0]++; |
| mCi.getDataRegistrationState( |
| obtainMessage( |
| EVENT_POLL_STATE_GPRS, mPollingContext)); |
| |
| mPollingContext[0]++; |
| mCi.getVoiceRegistrationState( |
| obtainMessage( |
| EVENT_POLL_STATE_REGISTRATION, mPollingContext)); |
| |
| mPollingContext[0]++; |
| mCi.getNetworkSelectionMode( |
| obtainMessage( |
| EVENT_POLL_STATE_NETWORK_SELECTION_MODE, mPollingContext)); |
| break; |
| } |
| } |
| |
| private void pollStateDone() { |
| if (Build.IS_DEBUGGABLE && SystemProperties.getBoolean(PROP_FORCE_ROAMING, false)) { |
| mNewSS.setVoiceRoaming(true); |
| mNewSS.setDataRoaming(true); |
| } |
| useDataRegStateForDataOnlyDevices(); |
| resetServiceStateInIwlanMode(); |
| |
| if (DBG) { |
| log("Poll ServiceState done: " + |
| " oldSS=[" + mSS + "] newSS=[" + mNewSS + "]" + |
| " oldMaxDataCalls=" + mMaxDataCalls + |
| " mNewMaxDataCalls=" + mNewMaxDataCalls + |
| " oldReasonDataDenied=" + mReasonDataDenied + |
| " mNewReasonDataDenied=" + mNewReasonDataDenied); |
| } |
| |
| boolean hasRegistered = |
| mSS.getVoiceRegState() != ServiceState.STATE_IN_SERVICE |
| && mNewSS.getVoiceRegState() == ServiceState.STATE_IN_SERVICE; |
| |
| boolean hasDeregistered = |
| mSS.getVoiceRegState() == ServiceState.STATE_IN_SERVICE |
| && mNewSS.getVoiceRegState() != ServiceState.STATE_IN_SERVICE; |
| |
| boolean hasGprsAttached = |
| mSS.getDataRegState() != ServiceState.STATE_IN_SERVICE |
| && mNewSS.getDataRegState() == ServiceState.STATE_IN_SERVICE; |
| |
| boolean hasGprsDetached = |
| mSS.getDataRegState() == ServiceState.STATE_IN_SERVICE |
| && mNewSS.getDataRegState() != ServiceState.STATE_IN_SERVICE; |
| |
| boolean hasDataRegStateChanged = |
| mSS.getDataRegState() != mNewSS.getDataRegState(); |
| |
| boolean hasVoiceRegStateChanged = |
| mSS.getVoiceRegState() != mNewSS.getVoiceRegState(); |
| |
| boolean hasRilVoiceRadioTechnologyChanged = |
| mSS.getRilVoiceRadioTechnology() != mNewSS.getRilVoiceRadioTechnology(); |
| |
| boolean hasRilDataRadioTechnologyChanged = |
| mSS.getRilDataRadioTechnology() != mNewSS.getRilDataRadioTechnology(); |
| |
| boolean hasChanged = !mNewSS.equals(mSS); |
| |
| boolean hasVoiceRoamingOn = !mSS.getVoiceRoaming() && mNewSS.getVoiceRoaming(); |
| |
| boolean hasVoiceRoamingOff = mSS.getVoiceRoaming() && !mNewSS.getVoiceRoaming(); |
| |
| boolean hasDataRoamingOn = !mSS.getDataRoaming() && mNewSS.getDataRoaming(); |
| |
| boolean hasDataRoamingOff = mSS.getDataRoaming() && !mNewSS.getDataRoaming(); |
| |
| boolean hasLocationChanged = !mNewCellLoc.equals(mCellLoc); |
| TelephonyManager tm = |
| (TelephonyManager) mPhone.getContext().getSystemService(Context.TELEPHONY_SERVICE); |
| |
| // Add an event log when connection state changes |
| if (hasVoiceRegStateChanged || hasDataRegStateChanged) { |
| EventLog.writeEvent(EventLogTags.GSM_SERVICE_STATE_CHANGE, |
| mSS.getVoiceRegState(), mSS.getDataRegState(), |
| mNewSS.getVoiceRegState(), mNewSS.getDataRegState()); |
| } |
| |
| // Add an event log when network type switched |
| // TODO: we may add filtering to reduce the event logged, |
| // i.e. check preferred network setting, only switch to 2G, etc |
| if (hasRilVoiceRadioTechnologyChanged) { |
| int cid = -1; |
| GsmCellLocation loc = mNewCellLoc; |
| if (loc != null) cid = loc.getCid(); |
| // NOTE: this code was previously located after mSS and mNewSS are swapped, so |
| // existing logs were incorrectly using the new state for "network_from" |
| // and STATE_OUT_OF_SERVICE for "network_to". To avoid confusion, use a new log tag |
| // to record the correct states. |
| EventLog.writeEvent(EventLogTags.GSM_RAT_SWITCHED_NEW, cid, |
| mSS.getRilVoiceRadioTechnology(), |
| mNewSS.getRilVoiceRadioTechnology()); |
| if (DBG) { |
| log("RAT switched " |
| + ServiceState.rilRadioTechnologyToString(mSS.getRilVoiceRadioTechnology()) |
| + " -> " |
| + ServiceState.rilRadioTechnologyToString( |
| mNewSS.getRilVoiceRadioTechnology()) + " at cell " + cid); |
| } |
| } |
| |
| // swap mSS and mNewSS to put new state in mSS |
| ServiceState tss = mSS; |
| mSS = mNewSS; |
| mNewSS = tss; |
| // clean slate for next time |
| mNewSS.setStateOutOfService(); |
| |
| // swap mCellLoc and mNewCellLoc to put new state in mCellLoc |
| GsmCellLocation tcl = mCellLoc; |
| mCellLoc = mNewCellLoc; |
| mNewCellLoc = tcl; |
| |
| mReasonDataDenied = mNewReasonDataDenied; |
| mMaxDataCalls = mNewMaxDataCalls; |
| |
| if (hasRilVoiceRadioTechnologyChanged) { |
| updatePhoneObject(); |
| } |
| |
| if (hasRilDataRadioTechnologyChanged) { |
| tm.setDataNetworkTypeForPhone(mPhone.getPhoneId(), mSS.getRilVoiceRadioTechnology()); |
| |
| if (ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN |
| == mSS.getRilDataRadioTechnology()) { |
| log("pollStateDone: IWLAN enabled"); |
| } |
| } |
| |
| if (hasRegistered) { |
| mNetworkAttachedRegistrants.notifyRegistrants(); |
| |
| if (DBG) { |
| log("pollStateDone: registering current mNitzUpdatedTime=" + |
| mNitzUpdatedTime + " changing to false"); |
| } |
| mNitzUpdatedTime = false; |
| } |
| |
| if (hasChanged) { |
| String operatorNumeric; |
| |
| updateSpnDisplay(); |
| |
| tm.setNetworkOperatorNameForPhone(mPhone.getPhoneId(), mSS.getOperatorAlphaLong()); |
| |
| String prevOperatorNumeric = tm.getNetworkOperatorForPhone(mPhone.getPhoneId()); |
| operatorNumeric = mSS.getOperatorNumeric(); |
| tm.setNetworkOperatorNumericForPhone(mPhone.getPhoneId(), operatorNumeric); |
| updateCarrierMccMncConfiguration(operatorNumeric, |
| prevOperatorNumeric, mPhone.getContext()); |
| if (operatorNumeric == null) { |
| if (DBG) log("operatorNumeric is null"); |
| tm.setNetworkCountryIsoForPhone(mPhone.getPhoneId(), ""); |
| mGotCountryCode = false; |
| mNitzUpdatedTime = false; |
| } else { |
| String iso = ""; |
| String mcc = ""; |
| try{ |
| mcc = operatorNumeric.substring(0, 3); |
| iso = MccTable.countryCodeForMcc(Integer.parseInt(mcc)); |
| } catch ( NumberFormatException ex){ |
| loge("pollStateDone: countryCodeForMcc error" + ex); |
| } catch ( StringIndexOutOfBoundsException ex) { |
| loge("pollStateDone: countryCodeForMcc error" + ex); |
| } |
| |
| tm.setNetworkCountryIsoForPhone(mPhone.getPhoneId(), iso); |
| mGotCountryCode = true; |
| |
| TimeZone zone = null; |
| |
| if (!mNitzUpdatedTime && !mcc.equals("000") && !TextUtils.isEmpty(iso) && |
| getAutoTimeZone()) { |
| |
| // Test both paths if ignore nitz is true |
| boolean testOneUniqueOffsetPath = SystemProperties.getBoolean( |
| TelephonyProperties.PROPERTY_IGNORE_NITZ, false) && |
| ((SystemClock.uptimeMillis() & 1) == 0); |
| |
| ArrayList<TimeZone> uniqueZones = TimeUtils.getTimeZonesWithUniqueOffsets(iso); |
| if ((uniqueZones.size() == 1) || testOneUniqueOffsetPath) { |
| zone = uniqueZones.get(0); |
| if (DBG) { |
| log("pollStateDone: no nitz but one TZ for iso-cc=" + iso + |
| " with zone.getID=" + zone.getID() + |
| " testOneUniqueOffsetPath=" + testOneUniqueOffsetPath); |
| } |
| setAndBroadcastNetworkSetTimeZone(zone.getID()); |
| } else { |
| if (DBG) { |
| log("pollStateDone: there are " + uniqueZones.size() + |
| " unique offsets for iso-cc='" + iso + |
| " testOneUniqueOffsetPath=" + testOneUniqueOffsetPath + |
| "', do nothing"); |
| } |
| } |
| } |
| |
| if (shouldFixTimeZoneNow(mPhone, operatorNumeric, prevOperatorNumeric, |
| mNeedFixZoneAfterNitz)) { |
| // If the offset is (0, false) and the timezone property |
| // is set, use the timezone property rather than |
| // GMT. |
| String zoneName = SystemProperties.get(TIMEZONE_PROPERTY); |
| if (DBG) { |
| log("pollStateDone: fix time zone zoneName='" + zoneName + |
| "' mZoneOffset=" + mZoneOffset + " mZoneDst=" + mZoneDst + |
| " iso-cc='" + iso + |
| "' iso-cc-idx=" + Arrays.binarySearch(GMT_COUNTRY_CODES, iso)); |
| } |
| |
| if ("".equals(iso) && mNeedFixZoneAfterNitz) { |
| // Country code not found. This is likely a test network. |
| // Get a TimeZone based only on the NITZ parameters (best guess). |
| zone = getNitzTimeZone(mZoneOffset, mZoneDst, mZoneTime); |
| if (DBG) log("pollStateDone: using NITZ TimeZone"); |
| } else |
| // "(mZoneOffset == 0) && (mZoneDst == false) && |
| // (Arrays.binarySearch(GMT_COUNTRY_CODES, iso) < 0)" |
| // means that we received a NITZ string telling |
| // it is in GMT+0 w/ DST time zone |
| // BUT iso tells is NOT, e.g, a wrong NITZ reporting |
| // local time w/ 0 offset. |
| if ((mZoneOffset == 0) && (mZoneDst == false) && |
| (zoneName != null) && (zoneName.length() > 0) && |
| (Arrays.binarySearch(GMT_COUNTRY_CODES, iso) < 0)) { |
| zone = TimeZone.getDefault(); |
| if (mNeedFixZoneAfterNitz) { |
| // For wrong NITZ reporting local time w/ 0 offset, |
| // need adjust time to reflect default timezone setting |
| long ctm = System.currentTimeMillis(); |
| long tzOffset = zone.getOffset(ctm); |
| if (DBG) { |
| log("pollStateDone: tzOffset=" + tzOffset + " ltod=" + |
| TimeUtils.logTimeOfDay(ctm)); |
| } |
| if (getAutoTime()) { |
| long adj = ctm - tzOffset; |
| if (DBG) log("pollStateDone: adj ltod=" + |
| TimeUtils.logTimeOfDay(adj)); |
| setAndBroadcastNetworkSetTime(adj); |
| } else { |
| // Adjust the saved NITZ time to account for tzOffset. |
| mSavedTime = mSavedTime - tzOffset; |
| } |
| } |
| if (DBG) log("pollStateDone: using default TimeZone"); |
| } else { |
| zone = TimeUtils.getTimeZone(mZoneOffset, mZoneDst, mZoneTime, iso); |
| if (DBG) log("pollStateDone: using getTimeZone(off, dst, time, iso)"); |
| } |
| |
| mNeedFixZoneAfterNitz = false; |
| |
| if (zone != null) { |
| log("pollStateDone: zone != null zone.getID=" + zone.getID()); |
| if (getAutoTimeZone()) { |
| setAndBroadcastNetworkSetTimeZone(zone.getID()); |
| } |
| saveNitzTimeZone(zone.getID()); |
| } else { |
| log("pollStateDone: zone == null"); |
| } |
| } |
| } |
| |
| tm.setNetworkRoamingForPhone(mPhone.getPhoneId(), mSS.getVoiceRoaming()); |
| |
| setRoamingType(mSS); |
| log("Broadcasting ServiceState : " + mSS); |
| mPhone.notifyServiceStateChanged(mSS); |
| } |
| |
| if (hasGprsAttached) { |
| mAttachedRegistrants.notifyRegistrants(); |
| } |
| |
| if (hasGprsDetached) { |
| mDetachedRegistrants.notifyRegistrants(); |
| } |
| |
| if (hasDataRegStateChanged || hasRilDataRadioTechnologyChanged) { |
| notifyDataRegStateRilRadioTechnologyChanged(); |
| |
| if (ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN |
| == mSS.getRilDataRadioTechnology()) { |
| mPhone.notifyDataConnection(Phone.REASON_IWLAN_AVAILABLE); |
| } else { |
| mPhone.notifyDataConnection(null); |
| } |
| } |
| |
| if (hasVoiceRoamingOn) { |
| mVoiceRoamingOnRegistrants.notifyRegistrants(); |
| } |
| |
| if (hasVoiceRoamingOff) { |
| mVoiceRoamingOffRegistrants.notifyRegistrants(); |
| } |
| |
| if (hasDataRoamingOn) { |
| mDataRoamingOnRegistrants.notifyRegistrants(); |
| } |
| |
| if (hasDataRoamingOff) { |
| mDataRoamingOffRegistrants.notifyRegistrants(); |
| } |
| |
| if (hasLocationChanged) { |
| mPhone.notifyLocationChanged(); |
| } |
| |
| if (! isGprsConsistent(mSS.getDataRegState(), mSS.getVoiceRegState())) { |
| if (!mStartedGprsRegCheck && !mReportedGprsNoReg) { |
| mStartedGprsRegCheck = true; |
| |
| int check_period = Settings.Global.getInt( |
| mPhone.getContext().getContentResolver(), |
| Settings.Global.GPRS_REGISTER_CHECK_PERIOD_MS, |
| DEFAULT_GPRS_CHECK_PERIOD_MILLIS); |
| sendMessageDelayed(obtainMessage(EVENT_CHECK_REPORT_GPRS), |
| check_period); |
| } |
| } else { |
| mReportedGprsNoReg = false; |
| } |
| // TODO: Add GsmCellIdenity updating, see CdmaLteServiceStateTracker. |
| } |
| |
| /** |
| * Check if GPRS got registered while voice is registered. |
| * |
| * @param dataRegState i.e. CGREG in GSM |
| * @param voiceRegState i.e. CREG in GSM |
| * @return false if device only register to voice but not gprs |
| */ |
| private boolean isGprsConsistent(int dataRegState, int voiceRegState) { |
| return !((voiceRegState == ServiceState.STATE_IN_SERVICE) && |
| (dataRegState != ServiceState.STATE_IN_SERVICE)); |
| } |
| |
| /** |
| * Returns a TimeZone object based only on parameters from the NITZ string. |
| */ |
| private TimeZone getNitzTimeZone(int offset, boolean dst, long when) { |
| TimeZone guess = findTimeZone(offset, dst, when); |
| if (guess == null) { |
| // Couldn't find a proper timezone. Perhaps the DST data is wrong. |
| guess = findTimeZone(offset, !dst, when); |
| } |
| if (DBG) log("getNitzTimeZone returning " + (guess == null ? guess : guess.getID())); |
| return guess; |
| } |
| |
| private TimeZone findTimeZone(int offset, boolean dst, long when) { |
| int rawOffset = offset; |
| if (dst) { |
| rawOffset -= 3600000; |
| } |
| String[] zones = TimeZone.getAvailableIDs(rawOffset); |
| TimeZone guess = null; |
| Date d = new Date(when); |
| for (String zone : zones) { |
| TimeZone tz = TimeZone.getTimeZone(zone); |
| if (tz.getOffset(when) == offset && |
| tz.inDaylightTime(d) == dst) { |
| guess = tz; |
| break; |
| } |
| } |
| |
| return guess; |
| } |
| |
| private void queueNextSignalStrengthPoll() { |
| if (mDontPollSignalStrength) { |
| // The radio is telling us about signal strength changes |
| // we don't have to ask it |
| return; |
| } |
| |
| Message msg; |
| |
| msg = obtainMessage(); |
| msg.what = EVENT_POLL_SIGNAL_STRENGTH; |
| |
| long nextTime; |
| |
| // TODO Don't poll signal strength if screen is off |
| sendMessageDelayed(msg, POLL_PERIOD_MILLIS); |
| } |
| |
| /** |
| * Set restricted state based on the OnRestrictedStateChanged notification |
| * If any voice or packet restricted state changes, trigger a UI |
| * notification and notify registrants when sim is ready. |
| * |
| * @param ar an int value of RIL_RESTRICTED_STATE_* |
| */ |
| private void onRestrictedStateChanged(AsyncResult ar) { |
| RestrictedState newRs = new RestrictedState(); |
| |
| if (DBG) log("onRestrictedStateChanged: E rs "+ mRestrictedState); |
| |
| if (ar.exception == null) { |
| int[] ints = (int[])ar.result; |
| int state = ints[0]; |
| |
| newRs.setCsEmergencyRestricted( |
| ((state & RILConstants.RIL_RESTRICTED_STATE_CS_EMERGENCY) != 0) || |
| ((state & RILConstants.RIL_RESTRICTED_STATE_CS_ALL) != 0) ); |
| //ignore the normal call and data restricted state before SIM READY |
| if (mUiccApplcation != null && mUiccApplcation.getState() == AppState.APPSTATE_READY) { |
| newRs.setCsNormalRestricted( |
| ((state & RILConstants.RIL_RESTRICTED_STATE_CS_NORMAL) != 0) || |
| ((state & RILConstants.RIL_RESTRICTED_STATE_CS_ALL) != 0) ); |
| newRs.setPsRestricted( |
| (state & RILConstants.RIL_RESTRICTED_STATE_PS_ALL)!= 0); |
| } |
| |
| if (DBG) log("onRestrictedStateChanged: new rs "+ newRs); |
| |
| if (!mRestrictedState.isPsRestricted() && newRs.isPsRestricted()) { |
| mPsRestrictEnabledRegistrants.notifyRegistrants(); |
| setNotification(PS_ENABLED); |
| } else if (mRestrictedState.isPsRestricted() && !newRs.isPsRestricted()) { |
| mPsRestrictDisabledRegistrants.notifyRegistrants(); |
| setNotification(PS_DISABLED); |
| } |
| |
| /** |
| * There are two kind of cs restriction, normal and emergency. So |
| * there are 4 x 4 combinations in current and new restricted states |
| * and we only need to notify when state is changed. |
| */ |
| if (mRestrictedState.isCsRestricted()) { |
| if (!newRs.isCsRestricted()) { |
| // remove all restriction |
| setNotification(CS_DISABLED); |
| } else if (!newRs.isCsNormalRestricted()) { |
| // remove normal restriction |
| setNotification(CS_EMERGENCY_ENABLED); |
| } else if (!newRs.isCsEmergencyRestricted()) { |
| // remove emergency restriction |
| setNotification(CS_NORMAL_ENABLED); |
| } |
| } else if (mRestrictedState.isCsEmergencyRestricted() && |
| !mRestrictedState.isCsNormalRestricted()) { |
| if (!newRs.isCsRestricted()) { |
| // remove all restriction |
| setNotification(CS_DISABLED); |
| } else if (newRs.isCsRestricted()) { |
| // enable all restriction |
| setNotification(CS_ENABLED); |
| } else if (newRs.isCsNormalRestricted()) { |
| // remove emergency restriction and enable normal restriction |
| setNotification(CS_NORMAL_ENABLED); |
| } |
| } else if (!mRestrictedState.isCsEmergencyRestricted() && |
| mRestrictedState.isCsNormalRestricted()) { |
| if (!newRs.isCsRestricted()) { |
| // remove all restriction |
| setNotification(CS_DISABLED); |
| } else if (newRs.isCsRestricted()) { |
| // enable all restriction |
| setNotification(CS_ENABLED); |
| } else if (newRs.isCsEmergencyRestricted()) { |
| // remove normal restriction and enable emergency restriction |
| setNotification(CS_EMERGENCY_ENABLED); |
| } |
| } else { |
| if (newRs.isCsRestricted()) { |
| // enable all restriction |
| setNotification(CS_ENABLED); |
| } else if (newRs.isCsEmergencyRestricted()) { |
| // enable emergency restriction |
| setNotification(CS_EMERGENCY_ENABLED); |
| } else if (newRs.isCsNormalRestricted()) { |
| // enable normal restriction |
| setNotification(CS_NORMAL_ENABLED); |
| } |
| } |
| |
| mRestrictedState = newRs; |
| } |
| log("onRestrictedStateChanged: X rs "+ mRestrictedState); |
| } |
| |
| /** code is registration state 0-5 from TS 27.007 7.2 */ |
| private int regCodeToServiceState(int code) { |
| switch (code) { |
| case 0: |
| case 2: // 2 is "searching" |
| case 3: // 3 is "registration denied" |
| case 4: // 4 is "unknown" no vaild in current baseband |
| case 10:// same as 0, but indicates that emergency call is possible. |
| case 12:// same as 2, but indicates that emergency call is possible. |
| case 13:// same as 3, but indicates that emergency call is possible. |
| case 14:// same as 4, but indicates that emergency call is possible. |
| return ServiceState.STATE_OUT_OF_SERVICE; |
| |
| case 1: |
| return ServiceState.STATE_IN_SERVICE; |
| |
| case 5: |
| // in service, roam |
| return ServiceState.STATE_IN_SERVICE; |
| |
| default: |
| loge("regCodeToServiceState: unexpected service state " + code); |
| return ServiceState.STATE_OUT_OF_SERVICE; |
| } |
| } |
| |
| |
| /** |
| * code is registration state 0-5 from TS 27.007 7.2 |
| * returns true if registered roam, false otherwise |
| */ |
| private boolean regCodeIsRoaming (int code) { |
| return ServiceState.RIL_REG_STATE_ROAMING == code; |
| } |
| |
| /** |
| * Set roaming state if operator mcc is the same as sim mcc |
| * and ons is different from spn |
| * |
| * @param s ServiceState hold current ons |
| * @return true if same operator |
| */ |
| private boolean isSameNamedOperators(ServiceState s) { |
| String spn = ((TelephonyManager) mPhone.getContext(). |
| getSystemService(Context.TELEPHONY_SERVICE)). |
| getSimOperatorNameForPhone(getPhoneId()); |
| |
| String onsl = s.getOperatorAlphaLong(); |
| String onss = s.getOperatorAlphaShort(); |
| |
| boolean equalsOnsl = onsl != null && spn != null && !spn.isEmpty() && spn.equals(onsl); |
| boolean equalsOnss = onss != null && spn != null && !spn.isEmpty() && spn.equals(onss); |
| |
| return currentMccEqualsSimMcc(s) && (equalsOnsl || equalsOnss); |
| } |
| |
| /** |
| * Compare SIM MCC with Operator MCC |
| * |
| * @param s ServiceState hold current ons |
| * @return true if both are same |
| */ |
| private boolean currentMccEqualsSimMcc(ServiceState s) { |
| String simNumeric = ((TelephonyManager) mPhone.getContext(). |
| getSystemService(Context.TELEPHONY_SERVICE)). |
| getSimOperatorNumericForPhone(getPhoneId()); |
| String operatorNumeric = s.getOperatorNumeric(); |
| boolean equalsMcc = true; |
| |
| try { |
| equalsMcc = simNumeric.substring(0, 3). |
| equals(operatorNumeric.substring(0, 3)); |
| } catch (Exception e){ |
| } |
| return equalsMcc; |
| } |
| |
| /** |
| * Do not set roaming state in case of oprators considered non-roaming. |
| * |
| + Can use mcc or mcc+mnc as item of config_operatorConsideredNonRoaming. |
| * For example, 302 or 21407. If mcc or mcc+mnc match with operator, |
| * don't set roaming state. |
| * |
| * @param s ServiceState hold current ons |
| * @return false for roaming state set |
| */ |
| private boolean isOperatorConsideredNonRoaming(ServiceState s) { |
| String operatorNumeric = s.getOperatorNumeric(); |
| String[] numericArray = mPhone.getContext().getResources().getStringArray( |
| com.android.internal.R.array.config_operatorConsideredNonRoaming); |
| |
| if (numericArray.length == 0 || operatorNumeric == null) { |
| return false; |
| } |
| |
| for (String numeric : numericArray) { |
| if (operatorNumeric.startsWith(numeric)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private boolean isOperatorConsideredRoaming(ServiceState s) { |
| String operatorNumeric = s.getOperatorNumeric(); |
| String[] numericArray = mPhone.getContext().getResources().getStringArray( |
| com.android.internal.R.array.config_sameNamedOperatorConsideredRoaming); |
| |
| if (numericArray.length == 0 || operatorNumeric == null) { |
| return false; |
| } |
| |
| for (String numeric : numericArray) { |
| if (operatorNumeric.startsWith(numeric)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * @return The current GPRS state. IN_SERVICE is the same as "attached" |
| * and OUT_OF_SERVICE is the same as detached. |
| */ |
| @Override |
| public int getCurrentDataConnectionState() { |
| return mSS.getDataRegState(); |
| } |
| |
| /** |
| * @return true if phone is camping on a technology (eg UMTS) |
| * that could support voice and data simultaneously. |
| */ |
| @Override |
| public boolean isConcurrentVoiceAndDataAllowed() { |
| return (mSS.getRilVoiceRadioTechnology() >= ServiceState.RIL_RADIO_TECHNOLOGY_UMTS); |
| } |
| |
| /** |
| * @return the current cell location information. Prefer Gsm location |
| * information if available otherwise return LTE location information |
| */ |
| public CellLocation getCellLocation() { |
| if ((mCellLoc.getLac() >= 0) && (mCellLoc.getCid() >= 0)) { |
| if (DBG) log("getCellLocation(): X good mCellLoc=" + mCellLoc); |
| return mCellLoc; |
| } else { |
| List<CellInfo> result = getAllCellInfo(); |
| if (result != null) { |
| // A hack to allow tunneling of LTE information via GsmCellLocation |
| // so that older Network Location Providers can return some information |
| // on LTE only networks, see bug 9228974. |
| // |
| // We'll search the return CellInfo array preferring GSM/WCDMA |
| // data, but if there is none we'll tunnel the first LTE information |
| // in the list. |
| // |
| // The tunnel'd LTE information is returned as follows: |
| // LAC = TAC field |
| // CID = CI field |
| // PSC = 0. |
| GsmCellLocation cellLocOther = new GsmCellLocation(); |
| for (CellInfo ci : result) { |
| if (ci instanceof CellInfoGsm) { |
| CellInfoGsm cellInfoGsm = (CellInfoGsm)ci; |
| CellIdentityGsm cellIdentityGsm = cellInfoGsm.getCellIdentity(); |
| cellLocOther.setLacAndCid(cellIdentityGsm.getLac(), |
| cellIdentityGsm.getCid()); |
| cellLocOther.setPsc(cellIdentityGsm.getPsc()); |
| if (DBG) log("getCellLocation(): X ret GSM info=" + cellLocOther); |
| return cellLocOther; |
| } else if (ci instanceof CellInfoWcdma) { |
| CellInfoWcdma cellInfoWcdma = (CellInfoWcdma)ci; |
| CellIdentityWcdma cellIdentityWcdma = cellInfoWcdma.getCellIdentity(); |
| cellLocOther.setLacAndCid(cellIdentityWcdma.getLac(), |
| cellIdentityWcdma.getCid()); |
| cellLocOther.setPsc(cellIdentityWcdma.getPsc()); |
| if (DBG) log("getCellLocation(): X ret WCDMA info=" + cellLocOther); |
| return cellLocOther; |
| } else if ((ci instanceof CellInfoLte) && |
| ((cellLocOther.getLac() < 0) || (cellLocOther.getCid() < 0))) { |
| // We'll return the first good LTE info we get if there is no better answer |
| CellInfoLte cellInfoLte = (CellInfoLte)ci; |
| CellIdentityLte cellIdentityLte = cellInfoLte.getCellIdentity(); |
| if ((cellIdentityLte.getTac() != Integer.MAX_VALUE) |
| && (cellIdentityLte.getCi() != Integer.MAX_VALUE)) { |
| cellLocOther.setLacAndCid(cellIdentityLte.getTac(), |
| cellIdentityLte.getCi()); |
| cellLocOther.setPsc(0); |
| if (DBG) { |
| log("getCellLocation(): possible LTE cellLocOther=" + cellLocOther); |
| } |
| } |
| } |
| } |
| if (DBG) { |
| log("getCellLocation(): X ret best answer cellLocOther=" + cellLocOther); |
| } |
| return cellLocOther; |
| } else { |
| if (DBG) { |
| log("getCellLocation(): X empty mCellLoc and CellInfo mCellLoc=" + mCellLoc); |
| } |
| return mCellLoc; |
| } |
| } |
| } |
| |
| /** |
| * nitzReceiveTime is time_t that the NITZ time was posted |
| */ |
| private void setTimeFromNITZString (String nitz, long nitzReceiveTime) { |
| // "yy/mm/dd,hh:mm:ss(+/-)tz" |
| // tz is in number of quarter-hours |
| |
| long start = SystemClock.elapsedRealtime(); |
| if (DBG) {log("NITZ: " + nitz + "," + nitzReceiveTime + |
| " start=" + start + " delay=" + (start - nitzReceiveTime)); |
| } |
| |
| try { |
| /* NITZ time (hour:min:sec) will be in UTC but it supplies the timezone |
| * offset as well (which we won't worry about until later) */ |
| Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT")); |
| |
| c.clear(); |
| c.set(Calendar.DST_OFFSET, 0); |
| |
| String[] nitzSubs = nitz.split("[/:,+-]"); |
| |
| int year = 2000 + Integer.parseInt(nitzSubs[0]); |
| if (year > MAX_NITZ_YEAR) { |
| if (DBG) loge("NITZ year: " + year + " exceeds limit, skip NITZ time update"); |
| return; |
| } |
| c.set(Calendar.YEAR, year); |
| |
| // month is 0 based! |
| int month = Integer.parseInt(nitzSubs[1]) - 1; |
| c.set(Calendar.MONTH, month); |
| |
| int date = Integer.parseInt(nitzSubs[2]); |
| c.set(Calendar.DATE, date); |
| |
| int hour = Integer.parseInt(nitzSubs[3]); |
| c.set(Calendar.HOUR, hour); |
| |
| int minute = Integer.parseInt(nitzSubs[4]); |
| c.set(Calendar.MINUTE, minute); |
| |
| int second = Integer.parseInt(nitzSubs[5]); |
| c.set(Calendar.SECOND, second); |
| |
| boolean sign = (nitz.indexOf('-') == -1); |
| |
| int tzOffset = Integer.parseInt(nitzSubs[6]); |
| |
| int dst = (nitzSubs.length >= 8 ) ? Integer.parseInt(nitzSubs[7]) |
| : 0; |
| |
| // The zone offset received from NITZ is for current local time, |
| // so DST correction is already applied. Don't add it again. |
| // |
| // tzOffset += dst * 4; |
| // |
| // We could unapply it if we wanted the raw offset. |
| |
| tzOffset = (sign ? 1 : -1) * tzOffset * 15 * 60 * 1000; |
| |
| TimeZone zone = null; |
| |
| // As a special extension, the Android emulator appends the name of |
| // the host computer's timezone to the nitz string. this is zoneinfo |
| // timezone name of the form Area!Location or Area!Location!SubLocation |
| // so we need to convert the ! into / |
| if (nitzSubs.length >= 9) { |
| String tzname = nitzSubs[8].replace('!','/'); |
| zone = TimeZone.getTimeZone( tzname ); |
| } |
| |
| String iso = ((TelephonyManager) mPhone.getContext(). |
| getSystemService(Context.TELEPHONY_SERVICE)). |
| getNetworkCountryIsoForPhone(mPhone.getPhoneId()); |
| |
| if (zone == null) { |
| |
| if (mGotCountryCode) { |
| if (iso != null && iso.length() > 0) { |
| zone = TimeUtils.getTimeZone(tzOffset, dst != 0, |
| c.getTimeInMillis(), |
| iso); |
| } else { |
| // We don't have a valid iso country code. This is |
| // most likely because we're on a test network that's |
| // using a bogus MCC (eg, "001"), so get a TimeZone |
| // based only on the NITZ parameters. |
| zone = getNitzTimeZone(tzOffset, (dst != 0), c.getTimeInMillis()); |
| } |
| } |
| } |
| |
| if ((zone == null) || (mZoneOffset != tzOffset) || (mZoneDst != (dst != 0))){ |
| // We got the time before the country or the zone has changed |
| // so we don't know how to identify the DST rules yet. Save |
| // the information and hope to fix it up later. |
| |
| mNeedFixZoneAfterNitz = true; |
| mZoneOffset = tzOffset; |
| mZoneDst = dst != 0; |
| mZoneTime = c.getTimeInMillis(); |
| } |
| |
| if (zone != null) { |
| if (getAutoTimeZone()) { |
| setAndBroadcastNetworkSetTimeZone(zone.getID()); |
| } |
| saveNitzTimeZone(zone.getID()); |
| } |
| |
| String ignore = SystemProperties.get("gsm.ignore-nitz"); |
| if (ignore != null && ignore.equals("yes")) { |
| log("NITZ: Not setting clock because gsm.ignore-nitz is set"); |
| return; |
| } |
| |
| try { |
| mWakeLock.acquire(); |
| |
| if (getAutoTime()) { |
| long millisSinceNitzReceived |
| = SystemClock.elapsedRealtime() - nitzReceiveTime; |
| |
| if (millisSinceNitzReceived < 0) { |
| // Sanity check: something is wrong |
| if (DBG) { |
| log("NITZ: not setting time, clock has rolled " |
| + "backwards since NITZ time was received, " |
| + nitz); |
| } |
| return; |
| } |
| |
| if (millisSinceNitzReceived > Integer.MAX_VALUE) { |
| // If the time is this far off, something is wrong > 24 days! |
| if (DBG) { |
| log("NITZ: not setting time, processing has taken " |
| + (millisSinceNitzReceived / (1000 * 60 * 60 * 24)) |
| + " days"); |
| } |
| return; |
| } |
| |
| // Note: with range checks above, cast to int is safe |
| c.add(Calendar.MILLISECOND, (int)millisSinceNitzReceived); |
| |
| if (DBG) { |
| log("NITZ: Setting time of day to " + c.getTime() |
| + " NITZ receive delay(ms): " + millisSinceNitzReceived |
| + " gained(ms): " |
| + (c.getTimeInMillis() - System.currentTimeMillis()) |
| + " from " + nitz); |
| } |
| |
| setAndBroadcastNetworkSetTime(c.getTimeInMillis()); |
| Rlog.i(LOG_TAG, "NITZ: after Setting time of day"); |
| } |
| SystemProperties.set("gsm.nitz.time", String.valueOf(c.getTimeInMillis())); |
| saveNitzTime(c.getTimeInMillis()); |
| if (VDBG) { |
| long end = SystemClock.elapsedRealtime(); |
| log("NITZ: end=" + end + " dur=" + (end - start)); |
| } |
| mNitzUpdatedTime = true; |
| } finally { |
| mWakeLock.release(); |
| } |
| } catch (RuntimeException ex) { |
| loge("NITZ: Parsing NITZ time " + nitz + " ex=" + ex); |
| } |
| } |
| |
| private boolean getAutoTime() { |
| try { |
| return Settings.Global.getInt(mPhone.getContext().getContentResolver(), |
| Settings.Global.AUTO_TIME) > 0; |
| } catch (SettingNotFoundException snfe) { |
| return true; |
| } |
| } |
| |
| private boolean getAutoTimeZone() { |
| try { |
| return Settings.Global.getInt(mPhone.getContext().getContentResolver(), |
| Settings.Global.AUTO_TIME_ZONE) > 0; |
| } catch (SettingNotFoundException snfe) { |
| return true; |
| } |
| } |
| |
| private void saveNitzTimeZone(String zoneId) { |
| mSavedTimeZone = zoneId; |
| } |
| |
| private void saveNitzTime(long time) { |
| mSavedTime = time; |
| mSavedAtTime = SystemClock.elapsedRealtime(); |
| } |
| |
| /** |
| * Set the timezone and send out a sticky broadcast so the system can |
| * determine if the timezone was set by the carrier. |
| * |
| * @param zoneId timezone set by carrier |
| */ |
| private void setAndBroadcastNetworkSetTimeZone(String zoneId) { |
| if (DBG) log("setAndBroadcastNetworkSetTimeZone: setTimeZone=" + zoneId); |
| AlarmManager alarm = |
| (AlarmManager) mPhone.getContext().getSystemService(Context.ALARM_SERVICE); |
| alarm.setTimeZone(zoneId); |
| Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE); |
| intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); |
| intent.putExtra("time-zone", zoneId); |
| mPhone.getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL); |
| if (DBG) { |
| log("setAndBroadcastNetworkSetTimeZone: call alarm.setTimeZone and broadcast zoneId=" + |
| zoneId); |
| } |
| } |
| |
| /** |
| * Set the time and Send out a sticky broadcast so the system can determine |
| * if the time was set by the carrier. |
| * |
| * @param time time set by network |
| */ |
| private void setAndBroadcastNetworkSetTime(long time) { |
| if (DBG) log("setAndBroadcastNetworkSetTime: time=" + time + "ms"); |
| SystemClock.setCurrentTimeMillis(time); |
| Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIME); |
| intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); |
| intent.putExtra("time", time); |
| mPhone.getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL); |
| } |
| |
| private void revertToNitzTime() { |
| if (Settings.Global.getInt(mPhone.getContext().getContentResolver(), |
| Settings.Global.AUTO_TIME, 0) == 0) { |
| return; |
| } |
| if (DBG) { |
| log("Reverting to NITZ Time: mSavedTime=" + mSavedTime |
| + " mSavedAtTime=" + mSavedAtTime); |
| } |
| if (mSavedTime != 0 && mSavedAtTime != 0) { |
| setAndBroadcastNetworkSetTime(mSavedTime |
| + (SystemClock.elapsedRealtime() - mSavedAtTime)); |
| } |
| } |
| |
| private void revertToNitzTimeZone() { |
| if (Settings.Global.getInt(mPhone.getContext().getContentResolver(), |
| Settings.Global.AUTO_TIME_ZONE, 0) == 0) { |
| return; |
| } |
| if (DBG) log("Reverting to NITZ TimeZone: tz='" + mSavedTimeZone); |
| if (mSavedTimeZone != null) { |
| setAndBroadcastNetworkSetTimeZone(mSavedTimeZone); |
| } |
| } |
| |
| /** |
| * Post a notification to NotificationManager for restricted state |
| * |
| * @param notifyType is one state of PS/CS_*_ENABLE/DISABLE |
| */ |
| private void setNotification(int notifyType) { |
| if (DBG) log("setNotification: create notification " + notifyType); |
| |
| // Needed because sprout RIL sends these when they shouldn't? |
| boolean isSetNotification = mPhone.getContext().getResources().getBoolean( |
| com.android.internal.R.bool.config_user_notification_of_restrictied_mobile_access); |
| if (!isSetNotification) { |
| if (DBG) log("Ignore all the notifications"); |
| return; |
| } |
| |
| Context context = mPhone.getContext(); |
| |
| |
| CharSequence details = ""; |
| CharSequence title = context.getText(com.android.internal.R.string.RestrictedChangedTitle); |
| int notificationId = CS_NOTIFICATION; |
| |
| switch (notifyType) { |
| case PS_ENABLED: |
| long dataSubId = SubscriptionManager.getDefaultDataSubId(); |
| if (dataSubId != mPhone.getSubId()) { |
| return; |
| } |
| notificationId = PS_NOTIFICATION; |
| details = context.getText(com.android.internal.R.string.RestrictedOnData); |
| break; |
| case PS_DISABLED: |
| notificationId = PS_NOTIFICATION; |
| break; |
| case CS_ENABLED: |
| details = context.getText(com.android.internal.R.string.RestrictedOnAllVoice); |
| break; |
| case CS_NORMAL_ENABLED: |
| details = context.getText(com.android.internal.R.string.RestrictedOnNormal); |
| break; |
| case CS_EMERGENCY_ENABLED: |
| details = context.getText(com.android.internal.R.string.RestrictedOnEmergency); |
| break; |
| case CS_DISABLED: |
| // do nothing and cancel the notification later |
| break; |
| } |
| |
| if (DBG) log("setNotification: put notification " + title + " / " +details); |
| mNotification = new Notification.Builder(context) |
| .setWhen(System.currentTimeMillis()) |
| .setAutoCancel(true) |
| .setSmallIcon(com.android.internal.R.drawable.stat_sys_warning) |
| .setTicker(title) |
| .setColor(context.getResources().getColor( |
| com.android.internal.R.color.system_notification_accent_color)) |
| .setContentTitle(title) |
| .setContentText(details) |
| .build(); |
| |
| NotificationManager notificationManager = (NotificationManager) |
| context.getSystemService(Context.NOTIFICATION_SERVICE); |
| |
| if (notifyType == PS_DISABLED || notifyType == CS_DISABLED) { |
| // cancel previous post notification |
| notificationManager.cancel(notificationId); |
| } else { |
| // update restricted state notification |
| notificationManager.notify(notificationId, mNotification); |
| } |
| } |
| |
| private UiccCardApplication getUiccCardApplication() { |
| return mUiccController.getUiccCardApplication(mPhone.getPhoneId(), |
| UiccController.APP_FAM_3GPP); |
| } |
| |
| @Override |
| protected void onUpdateIccAvailability() { |
| if (mUiccController == null ) { |
| return; |
| } |
| |
| UiccCardApplication newUiccApplication = getUiccCardApplication(); |
| |
| if (mUiccApplcation != newUiccApplication) { |
| if (mUiccApplcation != null) { |
| log("Removing stale icc objects."); |
| mUiccApplcation.unregisterForReady(this); |
| if (mIccRecords != null) { |
| mIccRecords.unregisterForRecordsLoaded(this); |
| } |
| mIccRecords = null; |
| mUiccApplcation = null; |
| } |
| if (newUiccApplication != null) { |
| log("New card found"); |
| mUiccApplcation = newUiccApplication; |
| mIccRecords = mUiccApplcation.getIccRecords(); |
| mUiccApplcation.registerForReady(this, EVENT_SIM_READY, null); |
| if (mIccRecords != null) { |
| mIccRecords.registerForRecordsLoaded(this, EVENT_SIM_RECORDS_LOADED, null); |
| } |
| } |
| } |
| } |
| @Override |
| protected void log(String s) { |
| Rlog.d(LOG_TAG, "[GsmSST] " + s); |
| } |
| |
| @Override |
| protected void loge(String s) { |
| Rlog.e(LOG_TAG, "[GsmSST] " + s); |
| } |
| |
| @Override |
| public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { |
| pw.println("GsmServiceStateTracker extends:"); |
| super.dump(fd, pw, args); |
| pw.println(" mPhone=" + mPhone); |
| pw.println(" mSS=" + mSS); |
| pw.println(" mNewSS=" + mNewSS); |
| pw.println(" mCellLoc=" + mCellLoc); |
| pw.println(" mNewCellLoc=" + mNewCellLoc); |
| pw.println(" mPreferredNetworkType=" + mPreferredNetworkType); |
| pw.println(" mMaxDataCalls=" + mMaxDataCalls); |
| pw.println(" mNewMaxDataCalls=" + mNewMaxDataCalls); |
| pw.println(" mReasonDataDenied=" + mReasonDataDenied); |
| pw.println(" mNewReasonDataDenied=" + mNewReasonDataDenied); |
| pw.println(" mGsmRoaming=" + mGsmRoaming); |
| pw.println(" mDataRoaming=" + mDataRoaming); |
| pw.println(" mEmergencyOnly=" + mEmergencyOnly); |
| pw.println(" mNeedFixZoneAfterNitz=" + mNeedFixZoneAfterNitz); |
| pw.flush(); |
| pw.println(" mZoneOffset=" + mZoneOffset); |
| pw.println(" mZoneDst=" + mZoneDst); |
| pw.println(" mZoneTime=" + mZoneTime); |
| pw.println(" mGotCountryCode=" + mGotCountryCode); |
| pw.println(" mNitzUpdatedTime=" + mNitzUpdatedTime); |
| pw.println(" mSavedTimeZone=" + mSavedTimeZone); |
| pw.println(" mSavedTime=" + mSavedTime); |
| pw.println(" mSavedAtTime=" + mSavedAtTime); |
| pw.println(" mStartedGprsRegCheck=" + mStartedGprsRegCheck); |
| pw.println(" mReportedGprsNoReg=" + mReportedGprsNoReg); |
| pw.println(" mNotification=" + mNotification); |
| pw.println(" mWakeLock=" + mWakeLock); |
| pw.println(" mCurSpn=" + mCurSpn); |
| pw.println(" mCurDataSpn=" + mCurDataSpn); |
| pw.println(" mCurShowSpn=" + mCurShowSpn); |
| pw.println(" mCurPlmn=" + mCurPlmn); |
| pw.println(" mCurShowPlmn=" + mCurShowPlmn); |
| pw.flush(); |
| } |
| |
| |
| /** |
| * Clean up existing voice and data connection then turn off radio power. |
| * |
| * Hang up the existing voice calls to decrease call drop rate. |
| */ |
| @Override |
| public void powerOffRadioSafely(DcTrackerBase dcTracker) { |
| synchronized (this) { |
| if (!mPendingRadioPowerOffAfterDataOff) { |
| int dds = SubscriptionManager.getDefaultDataSubId(); |
| // To minimize race conditions we call cleanUpAllConnections on |
| // both if else paths instead of before this isDisconnected test. |
| if (dcTracker.isDisconnected() |
| && (dds == mPhone.getSubId() |
| || (dds != mPhone.getSubId() |
| && ProxyController.getInstance().isDataDisconnected(dds)))) { |
| // 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 { |
| // hang up all active voice calls first |
| if (mPhone.isInCall()) { |
| mPhone.mCT.mRingingCall.hangupIfAlive(); |
| mPhone.mCT.mBackgroundCall.hangupIfAlive(); |
| mPhone.mCT.mForegroundCall.hangupIfAlive(); |
| } |
| dcTracker.cleanUpAllConnections(Phone.REASON_RADIO_TURNED_OFF); |
| if (dds != mPhone.getSubId() |
| && !ProxyController.getInstance().isDataDisconnected(dds)) { |
| if (DBG) log("Data is active on DDS. Wait for all data disconnect"); |
| // Data is not disconnected on DDS. Wait for the data disconnect complete |
| // before sending the RADIO_POWER off. |
| ProxyController.getInstance().registerForAllDataDisconnected(dds, this, |
| EVENT_ALL_DATA_DISCONNECTED, null); |
| mPendingRadioPowerOffAfterDataOff = true; |
| } |
| 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(); |
| mPendingRadioPowerOffAfterDataOff = false; |
| } |
| } |
| } |
| } |
| |
| } |
| |
| public void setImsRegistrationState(boolean registered){ |
| if (mImsRegistrationOnOff && !registered) { |
| if (mAlarmSwitch) { |
| mImsRegistrationOnOff = registered; |
| |
| Context context = mPhone.getContext(); |
| AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); |
| am.cancel(mRadioOffIntent); |
| mAlarmSwitch = false; |
| |
| sendMessage(obtainMessage(EVENT_CHANGE_IMS_STATE)); |
| return; |
| } |
| } |
| mImsRegistrationOnOff = registered; |
| } |
| |
| public void onImsCapabilityChanged() { |
| sendMessage(obtainMessage(EVENT_IMS_CAPABILITY_CHANGED)); |
| } |
| } |