1/*
2 * Copyright (C) 2014 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.mms.service;
18
19import android.content.Context;
20import android.database.Cursor;
21import android.database.sqlite.SqliteWrapper;
22import android.net.NetworkUtils;
23import android.net.Uri;
24import android.provider.Telephony;
25import android.text.TextUtils;
26
27import com.android.internal.telephony.PhoneConstants;
28import com.android.mms.service.exception.ApnException;
29
30import java.net.URI;
31import java.net.URISyntaxException;
32
33/**
34 * APN settings used for MMS transactions
35 */
36public class ApnSettings {
37    // MMSC URL
38    private final String mServiceCenter;
39    // MMSC proxy address
40    private final String mProxyAddress;
41    // MMSC proxy port
42    private final int mProxyPort;
43    // Debug text for this APN: a concatenation of interesting columns of this APN
44    private final String mDebugText;
45
46    private static final String[] APN_PROJECTION = {
47            Telephony.Carriers.TYPE,
48            Telephony.Carriers.MMSC,
49            Telephony.Carriers.MMSPROXY,
50            Telephony.Carriers.MMSPORT,
51            Telephony.Carriers.NAME,
52            Telephony.Carriers.APN,
53            Telephony.Carriers.BEARER_BITMASK,
54            Telephony.Carriers.PROTOCOL,
55            Telephony.Carriers.ROAMING_PROTOCOL,
56            Telephony.Carriers.AUTH_TYPE,
57            Telephony.Carriers.MVNO_TYPE,
58            Telephony.Carriers.MVNO_MATCH_DATA,
59            Telephony.Carriers.PROXY,
60            Telephony.Carriers.PORT,
61            Telephony.Carriers.SERVER,
62            Telephony.Carriers.USER,
63            Telephony.Carriers.PASSWORD,
64    };
65    private static final int COLUMN_TYPE         = 0;
66    private static final int COLUMN_MMSC         = 1;
67    private static final int COLUMN_MMSPROXY     = 2;
68    private static final int COLUMN_MMSPORT      = 3;
69    private static final int COLUMN_NAME         = 4;
70    private static final int COLUMN_APN          = 5;
71    private static final int COLUMN_BEARER       = 6;
72    private static final int COLUMN_PROTOCOL     = 7;
73    private static final int COLUMN_ROAMING_PROTOCOL = 8;
74    private static final int COLUMN_AUTH_TYPE    = 9;
75    private static final int COLUMN_MVNO_TYPE    = 10;
76    private static final int COLUMN_MVNO_MATCH_DATA = 11;
77    private static final int COLUMN_PROXY        = 12;
78    private static final int COLUMN_PORT         = 13;
79    private static final int COLUMN_SERVER       = 14;
80    private static final int COLUMN_USER         = 15;
81    private static final int COLUMN_PASSWORD     = 16;
82
83
84    /**
85     * Load APN settings from system
86     *  @param context
87     * @param apnName the optional APN name to match
88     * @param requestId the request ID for logging
89     */
90    public static ApnSettings load(Context context, String apnName, int subId, String requestId)
91            throws ApnException {
92        LogUtil.i(requestId, "Loading APN using name " + apnName);
93        // TODO: CURRENT semantics is currently broken in telephony. Revive this when it is fixed.
94        //String selection = Telephony.Carriers.CURRENT + " IS NOT NULL";
95        String selection = null;
96        String[] selectionArgs = null;
97        apnName = apnName != null ? apnName.trim() : null;
98        if (!TextUtils.isEmpty(apnName)) {
99            //selection += " AND " + Telephony.Carriers.APN + "=?";
100            selection = Telephony.Carriers.APN + "=?";
101            selectionArgs = new String[]{ apnName };
102        }
103        Cursor cursor = null;
104        try {
105            cursor = SqliteWrapper.query(
106                    context,
107                    context.getContentResolver(),
108                    Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "/subId/" + subId),
109                    APN_PROJECTION,
110                    selection,
111                    selectionArgs,
112                    null/*sortOrder*/);
113            if (cursor != null) {
114                String mmscUrl = null;
115                String proxyAddress = null;
116                int proxyPort = -1;
117                while (cursor.moveToNext()) {
118                    // Read values from APN settings
119                    if (isValidApnType(
120                            cursor.getString(COLUMN_TYPE), PhoneConstants.APN_TYPE_MMS)) {
121                        mmscUrl = trimWithNullCheck(cursor.getString(COLUMN_MMSC));
122                        if (TextUtils.isEmpty(mmscUrl)) {
123                            continue;
124                        }
125                        mmscUrl = NetworkUtils.trimV4AddrZeros(mmscUrl);
126                        try {
127                            new URI(mmscUrl);
128                        } catch (URISyntaxException e) {
129                            throw new ApnException("Invalid MMSC url " + mmscUrl);
130                        }
131                        proxyAddress = trimWithNullCheck(cursor.getString(COLUMN_MMSPROXY));
132                        if (!TextUtils.isEmpty(proxyAddress)) {
133                            proxyAddress = NetworkUtils.trimV4AddrZeros(proxyAddress);
134                            final String portString =
135                                    trimWithNullCheck(cursor.getString(COLUMN_MMSPORT));
136                            if (portString != null) {
137                                try {
138                                    proxyPort = Integer.parseInt(portString);
139                                } catch (NumberFormatException e) {
140                                    LogUtil.e(requestId, "Invalid port " + portString);
141                                    throw new ApnException("Invalid port " + portString);
142                                }
143                            }
144                        }
145                        return new ApnSettings(
146                                mmscUrl, proxyAddress, proxyPort, getDebugText(cursor));
147                    }
148                }
149
150            }
151        } finally {
152            if (cursor != null) {
153                cursor.close();
154            }
155        }
156        throw new ApnException("Can not find valid APN");
157    }
158
159    private static String getDebugText(Cursor cursor) {
160        final StringBuilder sb = new StringBuilder();
161        sb.append("APN [");
162        for (int i = 0; i < cursor.getColumnCount(); i++) {
163            final String name = cursor.getColumnName(i);
164            final String value = cursor.getString(i);
165            if (TextUtils.isEmpty(value)) {
166                continue;
167            }
168            if (i > 0) {
169                sb.append(' ');
170            }
171            sb.append(name).append('=').append(value);
172        }
173        sb.append("]");
174        return sb.toString();
175    }
176
177    private static String trimWithNullCheck(String value) {
178        return value != null ? value.trim() : null;
179    }
180
181    public ApnSettings(String mmscUrl, String proxyAddr, int proxyPort, String debugText) {
182        mServiceCenter = mmscUrl;
183        mProxyAddress = proxyAddr;
184        mProxyPort = proxyPort;
185        mDebugText = debugText;
186   }
187
188    public String getMmscUrl() {
189        return mServiceCenter;
190    }
191
192    public String getProxyAddress() {
193        return mProxyAddress;
194    }
195
196    public int getProxyPort() {
197        return mProxyPort;
198    }
199
200    public boolean isProxySet() {
201        return !TextUtils.isEmpty(mProxyAddress);
202    }
203
204    private static boolean isValidApnType(String types, String requestType) {
205        // If APN type is unspecified, assume APN_TYPE_ALL.
206        if (TextUtils.isEmpty(types)) {
207            return true;
208        }
209        for (String type : types.split(",")) {
210            type = type.trim();
211            if (type.equals(requestType) || type.equals(PhoneConstants.APN_TYPE_ALL)) {
212                return true;
213            }
214        }
215        return false;
216    }
217
218    public String toString() {
219        return mDebugText;
220    }
221}
222