1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.telephony.gsm;
18
19import android.os.*;
20import android.text.util.Regex;
21import android.util.EventLog;
22import android.util.Log;
23
24import com.android.internal.telephony.CommandException;
25import com.android.internal.telephony.CommandsInterface;
26import com.android.internal.telephony.DataConnection;
27import com.android.internal.telephony.DataLink;
28import com.android.internal.telephony.Phone;
29import com.android.internal.telephony.RILConstants;
30import com.android.internal.telephony.TelephonyEventLog;
31
32/**
33 * {@hide}
34 */
35public class PdpConnection extends DataConnection {
36
37    private static final String LOG_TAG = "GSM";
38    private static final boolean DBG  = true;
39
40    /** Fail cause of last PDP activate, from RIL_LastPDPActivateFailCause */
41    private static final int PDP_FAIL_OPERATOR_BARRED = 0x08;
42    private static final int PDP_FAIL_INSUFFICIENT_RESOURCES = 0x1A;
43    private static final int PDP_FAIL_MISSING_UKNOWN_APN = 0x1B;
44    private static final int PDP_FAIL_UNKNOWN_PDP_ADDRESS_TYPE = 0x1C;
45    private static final int PDP_FAIL_USER_AUTHENTICATION = 0x1D;
46    private static final int PDP_FAIL_ACTIVATION_REJECT_GGSN = 0x1E;
47    private static final int PDP_FAIL_ACTIVATION_REJECT_UNSPECIFIED = 0x1F;
48    private static final int PDP_FAIL_SERVICE_OPTION_NOT_SUPPORTED = 0x20;
49    private static final int PDP_FAIL_SERVICE_OPTION_NOT_SUBSCRIBED = 0x21;
50    private static final int PDP_FAIL_SERVICE_OPTION_OUT_OF_ORDER = 0x22;
51    private static final int PDP_FAIL_NSAPI_IN_USE      = 0x23;
52    private static final int PDP_FAIL_PROTOCOL_ERRORS   = 0x6F;
53    private static final int PDP_FAIL_ERROR_UNSPECIFIED = 0xffff;
54
55    private static final int PDP_FAIL_REGISTRATION_FAIL = -1;
56    private static final int PDP_FAIL_GPRS_REGISTRATION_FAIL = -2;
57
58    //***** Instance Variables
59    private String pdp_name;
60    private ApnSetting apn;
61
62    //***** Constructor
63    PdpConnection(GSMPhone phone) {
64        super(phone);
65    }
66
67    /**
68     * Setup PDP connection for provided apn
69     * @param apn for this connection
70     * @param onCompleted notify success or not after down
71     */
72    void connect(ApnSetting apn, Message onCompleted) {
73        if (DBG) log("Connecting to carrier: '" + apn.carrier
74                + "' APN: '" + apn.apn
75                + "' proxy: '" + apn.proxy + "' port: '" + apn.port);
76
77        setHttpProxy (apn.proxy, apn.port);
78
79        state = State.ACTIVATING;
80        this.apn = apn;
81        onConnectCompleted = onCompleted;
82        createTime = -1;
83        lastFailTime = -1;
84        lastFailCause = FailCause.NONE;
85        receivedDisconnectReq = false;
86
87        int authType = apn.authType;
88        if (authType == -1) {
89            authType = (apn.user != null) ? RILConstants.SETUP_DATA_AUTH_PAP_CHAP :
90                RILConstants.SETUP_DATA_AUTH_NONE;
91        }
92        phone.mCM.setupDataCall(Integer.toString(RILConstants.SETUP_DATA_TECH_GSM),
93                Integer.toString(RILConstants.DATA_PROFILE_DEFAULT), apn.apn, apn.user,
94                apn.password, Integer.toString(authType),
95                obtainMessage(EVENT_SETUP_DATA_CONNECTION_DONE));
96    }
97
98    private void tearDownData(Message msg) {
99        if (phone.mCM.getRadioState().isOn()) {
100            phone.mCM.deactivateDataCall(cid, obtainMessage(EVENT_DEACTIVATE_DONE, msg));
101        }
102    }
103
104    protected void disconnect(Message msg) {
105        onDisconnect = msg;
106        if (state == State.ACTIVE) {
107            tearDownData(msg);
108        } else if (state == State.ACTIVATING) {
109            receivedDisconnectReq = true;
110        } else {
111            // state == INACTIVE.  Nothing to do, so notify immediately.
112            notifyDisconnect(msg);
113        }
114    }
115
116    public void clearSettings() {
117        super.clearSettings();
118        apn = null;
119    }
120
121    public String toString() {
122        return "State=" + state + " Apn=" + apn +
123               " create=" + createTime + " lastFail=" + lastFailTime +
124               " lastFailCause=" + lastFailCause;
125    }
126
127
128    protected void notifyFail(FailCause cause, Message onCompleted) {
129        if (onCompleted == null) return;
130
131        state = State.INACTIVE;
132        lastFailCause = cause;
133        lastFailTime = System.currentTimeMillis();
134        onConnectCompleted = null;
135
136        if (DBG) {
137            log("Notify PDP fail at " + lastFailTime +
138                    " due to " + lastFailCause);
139        }
140
141        AsyncResult.forMessage(onCompleted, cause, new Exception());
142        onCompleted.sendToTarget();
143    }
144
145    protected void notifySuccess(Message onCompleted) {
146        if (onCompleted == null) {
147            return;
148        }
149
150        state = State.ACTIVE;
151        createTime = System.currentTimeMillis();
152        onConnectCompleted = null;
153        onCompleted.arg1 = cid;
154
155        if (DBG) log("Notify PDP success at " + createTime);
156
157        AsyncResult.forMessage(onCompleted);
158        onCompleted.sendToTarget();
159    }
160
161    protected void notifyDisconnect(Message msg) {
162        if (DBG) log("Notify PDP disconnect");
163
164        if (msg != null) {
165            AsyncResult.forMessage(msg);
166            msg.sendToTarget();
167        }
168        clearSettings();
169    }
170
171    protected void onLinkStateChanged(DataLink.LinkState linkState) {
172        switch (linkState) {
173            case LINK_UP:
174                notifySuccess(onConnectCompleted);
175                break;
176
177            case LINK_DOWN:
178            case LINK_EXITED:
179                phone.mCM.getLastPdpFailCause(
180                        obtainMessage (EVENT_GET_LAST_FAIL_DONE));
181                break;
182        }
183    }
184
185    protected FailCause getFailCauseFromRequest(int rilCause) {
186        FailCause cause;
187
188        switch (rilCause) {
189            case PDP_FAIL_OPERATOR_BARRED:
190                cause = FailCause.OPERATOR_BARRED;
191                break;
192            case PDP_FAIL_INSUFFICIENT_RESOURCES:
193                cause = FailCause.INSUFFICIENT_RESOURCES;
194                break;
195            case PDP_FAIL_MISSING_UKNOWN_APN:
196                cause = FailCause.MISSING_UKNOWN_APN;
197                break;
198            case PDP_FAIL_UNKNOWN_PDP_ADDRESS_TYPE:
199                cause = FailCause.UNKNOWN_PDP_ADDRESS;
200                break;
201            case PDP_FAIL_USER_AUTHENTICATION:
202                cause = FailCause.USER_AUTHENTICATION;
203                break;
204            case PDP_FAIL_ACTIVATION_REJECT_GGSN:
205                cause = FailCause.ACTIVATION_REJECT_GGSN;
206                break;
207            case PDP_FAIL_ACTIVATION_REJECT_UNSPECIFIED:
208                cause = FailCause.ACTIVATION_REJECT_UNSPECIFIED;
209                break;
210            case PDP_FAIL_SERVICE_OPTION_OUT_OF_ORDER:
211                cause = FailCause.SERVICE_OPTION_OUT_OF_ORDER;
212                break;
213            case PDP_FAIL_SERVICE_OPTION_NOT_SUPPORTED:
214                cause = FailCause.SERVICE_OPTION_NOT_SUPPORTED;
215                break;
216            case PDP_FAIL_SERVICE_OPTION_NOT_SUBSCRIBED:
217                cause = FailCause.SERVICE_OPTION_NOT_SUBSCRIBED;
218                break;
219            case PDP_FAIL_NSAPI_IN_USE:
220                cause = FailCause.NSAPI_IN_USE;
221                break;
222            case PDP_FAIL_PROTOCOL_ERRORS:
223                cause = FailCause.PROTOCOL_ERRORS;
224                break;
225            case PDP_FAIL_ERROR_UNSPECIFIED:
226                cause = FailCause.UNKNOWN;
227                break;
228            case PDP_FAIL_REGISTRATION_FAIL:
229                cause = FailCause.REGISTRATION_FAIL;
230                break;
231            case PDP_FAIL_GPRS_REGISTRATION_FAIL:
232                cause = FailCause.GPRS_REGISTRATION_FAIL;
233                break;
234            default:
235                cause = FailCause.UNKNOWN;
236        }
237        return cause;
238    }
239
240    protected void log(String s) {
241        Log.d(LOG_TAG, "[PdpConnection] " + s);
242    }
243
244    @Override
245    protected void onDeactivated(AsyncResult ar) {
246        notifyDisconnect((Message) ar.userObj);
247        if (DBG) log("PDP Connection Deactivated");
248    }
249
250    @Override
251    protected void onSetupConnectionCompleted(AsyncResult ar) {
252        if (ar.exception != null) {
253            Log.e(LOG_TAG, "PDP Context Init failed " + ar.exception);
254
255            if (receivedDisconnectReq) {
256                // Don't bother reporting the error if there's already a
257                // pending disconnect request, since DataConnectionTracker
258                // has already updated its state.
259                notifyDisconnect(onDisconnect);
260            } else {
261                if ( ar.exception instanceof CommandException &&
262                        ((CommandException) (ar.exception)).getCommandError()
263                        == CommandException.Error.RADIO_NOT_AVAILABLE) {
264                    notifyFail(FailCause.RADIO_NOT_AVAILABLE,
265                            onConnectCompleted);
266                } else {
267                    phone.mCM.getLastPdpFailCause(
268                            obtainMessage(EVENT_GET_LAST_FAIL_DONE));
269                }
270            }
271        } else {
272            if (receivedDisconnectReq) {
273                // Don't bother reporting success if there's already a
274                // pending disconnect request, since DataConnectionTracker
275                // has already updated its state.
276                tearDownData(onDisconnect);
277            } else {
278                String[] response = ((String[]) ar.result);
279                cid = Integer.parseInt(response[0]);
280
281                if (response.length > 2) {
282                    interfaceName = response[1];
283                    ipAddress = response[2];
284                    String prefix = "net." + interfaceName + ".";
285                    gatewayAddress = SystemProperties.get(prefix + "gw");
286                    dnsServers[0] = SystemProperties.get(prefix + "dns1");
287                    dnsServers[1] = SystemProperties.get(prefix + "dns2");
288                    if (DBG) {
289                        log("interface=" + interfaceName + " ipAddress=" + ipAddress
290                            + " gateway=" + gatewayAddress + " DNS1=" + dnsServers[0]
291                            + " DNS2=" + dnsServers[1]);
292                    }
293
294                    if (NULL_IP.equals(dnsServers[0]) && NULL_IP.equals(dnsServers[1])
295                                        && !((GSMPhone) phone).isDnsCheckDisabled()) {
296                        // Work around a race condition where QMI does not fill in DNS:
297                        // Deactivate PDP and let DataConnectionTracker retry.
298                        // Do not apply the race condition workaround for MMS APN
299                        // if Proxy is an IP-address.
300                        // Otherwise, the default APN will not be restored anymore.
301                        if (!apn.types[0].equals(Phone.APN_TYPE_MMS)
302                                || !isIpAddress(apn.mmsProxy)) {
303                            EventLog.writeEvent(TelephonyEventLog.EVENT_LOG_BAD_DNS_ADDRESS,
304                                    dnsServers[0]);
305                            phone.mCM.deactivateDataCall(cid, obtainMessage(EVENT_FORCE_RETRY));
306                            return;
307                        }
308                    }
309                }
310
311                onLinkStateChanged(DataLink.LinkState.LINK_UP);
312
313                if (DBG) log("PDP setup on cid = " + cid);
314            }
315        }
316    }
317
318    private boolean isIpAddress(String address) {
319        if (address == null) return false;
320
321        return Regex.IP_ADDRESS_PATTERN.matcher(apn.mmsProxy).matches();
322    }
323
324    public ApnSetting getApn() {
325        return this.apn;
326    }
327}
328