| /* |
| * 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.os.*; |
| import android.database.Cursor; |
| import android.provider.Telephony; |
| import android.text.util.Regex; |
| import android.util.EventLog; |
| import android.util.Log; |
| |
| import java.util.ArrayList; |
| import com.android.internal.telephony.Phone; |
| |
| /** |
| * {@hide} |
| */ |
| public class PdpConnection extends Handler { |
| |
| private static final String LOG_TAG = "GSM"; |
| private static final boolean DBG = true; |
| private static final boolean FAKE_FAIL = false; |
| |
| public enum PdpState { |
| ACTIVE, /* has active pdp context */ |
| ACTIVATING, /* during connecting process */ |
| INACTIVE; /* has empty pdp context */ |
| |
| public String toString() { |
| switch (this) { |
| case ACTIVE: return "active"; |
| case ACTIVATING: return "setting up"; |
| default: return "inactive"; |
| } |
| } |
| |
| public boolean isActive() { |
| return this == ACTIVE; |
| } |
| |
| public boolean isInactive() { |
| return this == INACTIVE; |
| } |
| } |
| |
| public enum PdpFailCause { |
| NONE, |
| BAD_APN, |
| BAD_PAP_SECRET, |
| BARRED, |
| USER_AUTHENTICATION, |
| SERVICE_OPTION_NOT_SUPPORTED, |
| SERVICE_OPTION_NOT_SUBSCRIBED, |
| SIM_LOCKED, |
| RADIO_OFF, |
| NO_SIGNAL, |
| NO_DATA_PLAN, |
| RADIO_NOT_AVIALABLE, |
| SUSPENED_TEMPORARY, |
| RADIO_ERROR_RETRY, |
| UNKNOWN; |
| |
| public boolean isPermanentFail() { |
| return (this == RADIO_OFF); |
| } |
| |
| public String toString() { |
| switch (this) { |
| case NONE: return "no error"; |
| case BAD_APN: return "bad apn"; |
| case BAD_PAP_SECRET:return "bad pap secret"; |
| case BARRED: return "barred"; |
| case USER_AUTHENTICATION: return "error user autentication"; |
| case SERVICE_OPTION_NOT_SUPPORTED: return "data not supported"; |
| case SERVICE_OPTION_NOT_SUBSCRIBED: return "datt not subcribed"; |
| case SIM_LOCKED: return "sim locked"; |
| case RADIO_OFF: return "radio is off"; |
| case NO_SIGNAL: return "no signal"; |
| case NO_DATA_PLAN: return "no data plan"; |
| case RADIO_NOT_AVIALABLE: return "radio not available"; |
| case SUSPENED_TEMPORARY: return "suspend temporary"; |
| case RADIO_ERROR_RETRY: return "transient radio error"; |
| default: return "unknown data error"; |
| } |
| } |
| } |
| |
| /** Fail cause of last PDP activate, from RIL_LastPDPActivateFailCause */ |
| private static final int PDP_FAIL_RIL_BARRED = 8; |
| private static final int PDP_FAIL_RIL_BAD_APN = 27; |
| private static final int PDP_FAIL_RIL_USER_AUTHENTICATION = 29; |
| private static final int PDP_FAIL_RIL_SERVICE_OPTION_NOT_SUPPORTED = 32; |
| private static final int PDP_FAIL_RIL_SERVICE_OPTION_NOT_SUBSCRIBED = 33; |
| private static final int PDP_FAIL_RIL_ERROR_UNSPECIFIED = 0xffff; |
| |
| //***** Event codes |
| private static final int EVENT_SETUP_PDP_DONE = 1; |
| private static final int EVENT_GET_LAST_FAIL_DONE = 2; |
| private static final int EVENT_LINK_STATE_CHANGED = 3; |
| private static final int EVENT_DEACTIVATE_DONE = 4; |
| private static final int EVENT_FORCE_RETRY = 5; |
| |
| //***** Instance Variables |
| private GSMPhone phone; |
| private String pdp_name; |
| private PdpState state; |
| private Message onConnectCompleted; |
| private Message onDisconnect; |
| private int cid; |
| private long createTime; |
| private long lastFailTime; |
| private PdpFailCause lastFailCause; |
| private ApnSetting apn; |
| private String interfaceName; |
| private String ipAddress; |
| private String gatewayAddress; |
| private String[] dnsServers; |
| |
| private static final String NULL_IP = "0.0.0.0"; |
| |
| // dataLink is only used to support pppd link |
| DataLink dataLink; |
| // receivedDisconnectReq is set when disconnect pdp link during activating |
| private boolean receivedDisconnectReq; |
| |
| //***** Constructor |
| PdpConnection(GSMPhone phone) |
| { |
| this.phone = phone; |
| this.state = PdpState.INACTIVE; |
| onConnectCompleted = null; |
| onDisconnect = null; |
| this.cid = -1; |
| this.createTime = -1; |
| this.lastFailTime = -1; |
| this.lastFailCause = PdpFailCause.NONE; |
| this.apn = null; |
| this.dataLink = null; |
| receivedDisconnectReq = false; |
| this.dnsServers = new String[2]; |
| |
| if (SystemProperties.get("ro.radio.use-ppp","no").equals("yes")) { |
| dataLink = new PppLink(phone.mDataConnection); |
| dataLink.setOnLinkChange(this, EVENT_LINK_STATE_CHANGED, null); |
| } |
| } |
| |
| /** |
| * Setup PDP connection for provided apn |
| * @param apn for this connection |
| * @param onCompleted notify success or not after down |
| */ |
| void connect(ApnSetting apn, Message onCompleted) { |
| if (DBG) log("Connecting to carrier: '" + apn.carrier |
| + "' APN: '" + apn.apn |
| + "' proxy: '" + apn.proxy + "' port: '" + apn.port); |
| |
| setHttpProxy (apn.proxy, apn.port); |
| |
| state = PdpState.ACTIVATING; |
| this.apn = apn; |
| onConnectCompleted = onCompleted; |
| createTime = -1; |
| lastFailTime = -1; |
| lastFailCause = PdpFailCause.NONE; |
| receivedDisconnectReq = false; |
| |
| if (FAKE_FAIL) { |
| // for debug before baseband implement error in setup PDP |
| if (apn.apn.equalsIgnoreCase("badapn")){ |
| notifyFail(PdpFailCause.BAD_APN, onConnectCompleted); |
| return; |
| } |
| } |
| |
| phone.mCM.setupDefaultPDP(apn.apn, apn.user, apn.password, |
| obtainMessage(EVENT_SETUP_PDP_DONE)); |
| } |
| |
| void disconnect(Message msg) { |
| onDisconnect = msg; |
| if (state == PdpState.ACTIVE) { |
| if (dataLink != null) { |
| dataLink.disconnect(); |
| } |
| |
| if (phone.mCM.getRadioState().isOn()) { |
| phone.mCM.deactivateDefaultPDP(cid, obtainMessage(EVENT_DEACTIVATE_DONE, msg)); |
| } |
| } else if (state == PdpState.ACTIVATING) { |
| receivedDisconnectReq = true; |
| } else { |
| // state == INACTIVE. Nothing to do, so notify immediately. |
| notifyDisconnect(msg); |
| } |
| } |
| |
| private void |
| setHttpProxy(String httpProxy, String httpPort) |
| { |
| if (httpProxy == null || httpProxy.length() == 0) { |
| phone.setSystemProperty("net.gprs.http-proxy", null); |
| return; |
| } |
| |
| if (httpPort == null || httpPort.length() == 0) { |
| httpPort = "8080"; // Default to port 8080 |
| } |
| |
| phone.setSystemProperty("net.gprs.http-proxy", |
| "http://" + httpProxy + ":" + httpPort + "/"); |
| } |
| |
| public String toString() { |
| return "State=" + state + " Apn=" + apn + |
| " create=" + createTime + " lastFail=" + lastFailTime + |
| " lastFailCause=" + lastFailCause; |
| } |
| |
| public long getConnectionTime() { |
| return createTime; |
| } |
| |
| public long getLastFailTime() { |
| return lastFailTime; |
| } |
| |
| public PdpFailCause getLastFailCause() { |
| return lastFailCause; |
| } |
| |
| public ApnSetting getApn() { |
| return apn; |
| } |
| |
| String getInterface() { |
| return interfaceName; |
| } |
| |
| String getIpAddress() { |
| return ipAddress; |
| } |
| |
| String getGatewayAddress() { |
| return gatewayAddress; |
| } |
| |
| String[] getDnsServers() { |
| return dnsServers; |
| } |
| |
| public PdpState getState() { |
| return state; |
| } |
| |
| private void notifyFail(PdpFailCause cause, Message onCompleted) { |
| if (onCompleted == null) return; |
| |
| state = PdpState.INACTIVE; |
| lastFailCause = cause; |
| lastFailTime = System.currentTimeMillis(); |
| onConnectCompleted = null; |
| |
| if (DBG) log("Notify PDP fail at " + lastFailTime |
| + " due to " + lastFailCause); |
| |
| AsyncResult.forMessage(onCompleted, cause, new Exception()); |
| onCompleted.sendToTarget(); |
| } |
| |
| private void notifySuccess(Message onCompleted) { |
| if (onCompleted == null) return; |
| |
| state = PdpState.ACTIVE; |
| createTime = System.currentTimeMillis(); |
| onConnectCompleted = null; |
| onCompleted.arg1 = cid; |
| |
| if (DBG) log("Notify PDP success at " + createTime); |
| |
| AsyncResult.forMessage(onCompleted); |
| onCompleted.sendToTarget(); |
| } |
| |
| private void notifyDisconnect(Message msg) { |
| if (DBG) log("Notify PDP disconnect"); |
| |
| if (msg != null) { |
| AsyncResult.forMessage(msg); |
| msg.sendToTarget(); |
| } |
| clearSettings(); |
| } |
| |
| void clearSettings() { |
| state = PdpState.INACTIVE; |
| receivedDisconnectReq = false; |
| createTime = -1; |
| lastFailTime = -1; |
| lastFailCause = PdpFailCause.NONE; |
| apn = null; |
| onConnectCompleted = null; |
| interfaceName = null; |
| ipAddress = null; |
| gatewayAddress = null; |
| dnsServers[0] = null; |
| dnsServers[1] = null; |
| } |
| |
| private void onLinkStateChanged(DataLink.LinkState linkState) { |
| switch (linkState) { |
| case LINK_UP: |
| notifySuccess(onConnectCompleted); |
| break; |
| |
| case LINK_DOWN: |
| case LINK_EXITED: |
| phone.mCM.getLastPdpFailCause( |
| obtainMessage (EVENT_GET_LAST_FAIL_DONE)); |
| break; |
| } |
| } |
| |
| private PdpFailCause getFailCauseFromRequest(int rilCause) { |
| PdpFailCause cause; |
| |
| switch (rilCause) { |
| case PDP_FAIL_RIL_BARRED: |
| cause = PdpFailCause.BARRED; |
| break; |
| case PDP_FAIL_RIL_BAD_APN: |
| cause = PdpFailCause.BAD_APN; |
| break; |
| case PDP_FAIL_RIL_USER_AUTHENTICATION: |
| cause = PdpFailCause.USER_AUTHENTICATION; |
| break; |
| case PDP_FAIL_RIL_SERVICE_OPTION_NOT_SUPPORTED: |
| cause = PdpFailCause.SERVICE_OPTION_NOT_SUPPORTED; |
| break; |
| case PDP_FAIL_RIL_SERVICE_OPTION_NOT_SUBSCRIBED: |
| cause = PdpFailCause.SERVICE_OPTION_NOT_SUBSCRIBED; |
| break; |
| default: |
| cause = PdpFailCause.UNKNOWN; |
| } |
| return cause; |
| } |
| |
| |
| private void log(String s) { |
| Log.d(LOG_TAG, "[PdpConnection] " + s); |
| } |
| |
| @Override |
| public void handleMessage(Message msg) { |
| AsyncResult ar; |
| |
| switch (msg.what) { |
| case EVENT_SETUP_PDP_DONE: |
| ar = (AsyncResult) msg.obj; |
| |
| if (ar.exception != null) { |
| Log.e(LOG_TAG, "PDP Context Init failed " + ar.exception); |
| |
| if (receivedDisconnectReq) { |
| // Don't bother reporting the error if there's already a |
| // pending disconnect request, since DataConnectionTracker |
| // has already updated its state. |
| notifyDisconnect(onDisconnect); |
| } else { |
| if ( ar.exception instanceof CommandException && |
| ((CommandException) (ar.exception)).getCommandError() |
| == CommandException.Error.RADIO_NOT_AVAILABLE) { |
| notifyFail(PdpFailCause.RADIO_NOT_AVIALABLE, |
| onConnectCompleted); |
| } else { |
| phone.mCM.getLastPdpFailCause( |
| obtainMessage(EVENT_GET_LAST_FAIL_DONE)); |
| } |
| } |
| } else { |
| if (receivedDisconnectReq) { |
| // Don't bother reporting success if there's already a |
| // pending disconnect request, since DataConnectionTracker |
| // has already updated its state. |
| // Set ACTIVE so that disconnect does the right thing. |
| state = PdpState.ACTIVE; |
| disconnect(onDisconnect); |
| } else { |
| String[] response = ((String[]) ar.result); |
| cid = Integer.parseInt(response[0]); |
| |
| if (response.length > 2) { |
| interfaceName = response[1]; |
| ipAddress = response[2]; |
| String prefix = "net." + interfaceName + "."; |
| gatewayAddress = SystemProperties.get(prefix + "gw"); |
| dnsServers[0] = SystemProperties.get(prefix + "dns1"); |
| dnsServers[1] = SystemProperties.get(prefix + "dns2"); |
| if (DBG) { |
| log("interface=" + interfaceName + " ipAddress=" + ipAddress |
| + " gateway=" + gatewayAddress + " DNS1=" + dnsServers[0] |
| + " DNS2=" + dnsServers[1]); |
| } |
| |
| if (NULL_IP.equals(dnsServers[0]) && NULL_IP.equals(dnsServers[1]) |
| && !phone.isDnsCheckDisabled()) { |
| // Work around a race condition where QMI does not fill in DNS: |
| // Deactivate PDP and let DataConnectionTracker retry. |
| // Do not apply the race condition workaround for MMS APN |
| // if Proxy is an IP-address. |
| // Otherwise, the default APN will not be restored anymore. |
| if (!apn.types[0].equals(Phone.APN_TYPE_MMS) |
| || !isIpAddress(apn.mmsProxy)) { |
| EventLog.writeEvent(TelephonyEventLog.EVENT_LOG_BAD_DNS_ADDRESS, |
| dnsServers[0]); |
| phone.mCM.deactivateDefaultPDP(cid, |
| obtainMessage(EVENT_FORCE_RETRY)); |
| break; |
| } |
| } |
| } |
| |
| if (dataLink != null) { |
| dataLink.connect(); |
| } else { |
| onLinkStateChanged(DataLink.LinkState.LINK_UP); |
| } |
| |
| if (DBG) log("PDP setup on cid = " + cid); |
| } |
| } |
| break; |
| case EVENT_FORCE_RETRY: |
| if (receivedDisconnectReq) { |
| notifyDisconnect(onDisconnect); |
| } else { |
| ar = (AsyncResult) msg.obj; |
| notifyFail(PdpFailCause.RADIO_ERROR_RETRY, onConnectCompleted); |
| } |
| break; |
| case EVENT_GET_LAST_FAIL_DONE: |
| if (receivedDisconnectReq) { |
| // Don't bother reporting the error if there's already a |
| // pending disconnect request, since DataConnectionTracker |
| // has already updated its state. |
| notifyDisconnect(onDisconnect); |
| } else { |
| ar = (AsyncResult) msg.obj; |
| PdpFailCause cause = PdpFailCause.UNKNOWN; |
| |
| if (ar.exception == null) { |
| int rilFailCause = ((int[]) (ar.result))[0]; |
| cause = getFailCauseFromRequest(rilFailCause); |
| } |
| notifyFail(cause, onConnectCompleted); |
| } |
| |
| break; |
| case EVENT_LINK_STATE_CHANGED: |
| ar = (AsyncResult) msg.obj; |
| DataLink.LinkState ls = (DataLink.LinkState) ar.result; |
| onLinkStateChanged(ls); |
| break; |
| case EVENT_DEACTIVATE_DONE: |
| ar = (AsyncResult) msg.obj; |
| notifyDisconnect((Message) ar.userObj); |
| break; |
| } |
| } |
| |
| private boolean isIpAddress(String address) { |
| if (address == null) return false; |
| |
| return Regex.IP_ADDRESS_PATTERN.matcher(apn.mmsProxy).matches(); |
| } |
| } |