blob: 66f8b72ae128a03ffadbfbcc7b3a405d79ee977a [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.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();
}
}