FileDocCategorySizeDatePackage
PdpConnection.javaAPI DocAndroid 1.5 API17232Wed May 06 22:42:02 BST 2009com.android.internal.telephony.gsm

PdpConnection.java

/*
 * 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();
    }
}