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 
17 package com.android.mms.service;
18 
19 import android.content.Context;
20 import android.database.Cursor;
21 import android.net.NetworkUtils;
22 import android.net.Uri;
23 import android.provider.Telephony;
24 import android.telephony.data.ApnSetting;
25 import android.text.TextUtils;
26 
27 import com.android.mms.service.exception.ApnException;
28 
29 import java.net.URI;
30 import java.net.URISyntaxException;
31 
32 /**
33  * APN settings used for MMS transactions
34  */
35 public class ApnSettings {
36 
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      *
87      * @param apnName   the optional APN name to match
88      * @param requestId the request ID for logging
89      */
load(Context context, String apnName, int subId, String requestId)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 
104         try (Cursor cursor = context.getContentResolver().query(
105                     Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "/subId/" + subId),
106                     APN_PROJECTION,
107                     selection,
108                     selectionArgs,
109                     null/*sortOrder*/)) {
110 
111             ApnSettings settings = getApnSettingsFromCursor(cursor, requestId);
112             if (settings != null) {
113                 return settings;
114             }
115         }
116         throw new ApnException("Can not find valid APN");
117     }
118 
getApnSettingsFromCursor(Cursor cursor, String requestId)119     private static ApnSettings getApnSettingsFromCursor(Cursor cursor, String requestId)
120             throws ApnException {
121         if (cursor == null) {
122             return null;
123         }
124 
125         // Default proxy port to 80
126         int proxyPort = 80;
127         while (cursor.moveToNext()) {
128             // Read values from APN settings
129             if (isValidApnType(
130                     cursor.getString(COLUMN_TYPE), ApnSetting.TYPE_MMS_STRING)) {
131                 String mmscUrl = trimWithNullCheck(cursor.getString(COLUMN_MMSC));
132                 if (TextUtils.isEmpty(mmscUrl)) {
133                     continue;
134                 }
135                 mmscUrl = NetworkUtils.trimV4AddrZeros(mmscUrl);
136                 try {
137                     new URI(mmscUrl);
138                 } catch (URISyntaxException e) {
139                     throw new ApnException("Invalid MMSC url " + mmscUrl);
140                 }
141                 String proxyAddress = trimWithNullCheck(cursor.getString(COLUMN_MMSPROXY));
142                 if (!TextUtils.isEmpty(proxyAddress)) {
143                     proxyAddress = NetworkUtils.trimV4AddrZeros(proxyAddress);
144                     final String portString =
145                             trimWithNullCheck(cursor.getString(COLUMN_MMSPORT));
146                     if (!TextUtils.isEmpty(portString)) {
147                         try {
148                             proxyPort = Integer.parseInt(portString);
149                         } catch (NumberFormatException e) {
150                             LogUtil.e(requestId, "Invalid port " + portString + ", use 80");
151                         }
152                     }
153                 }
154                 return new ApnSettings(
155                         mmscUrl, proxyAddress, proxyPort, getDebugText(cursor));
156             }
157         }
158         return null;
159     }
160 
getDebugText(Cursor cursor)161     private static String getDebugText(Cursor cursor) {
162         final StringBuilder sb = new StringBuilder();
163         sb.append("APN [");
164         for (int i = 0; i < cursor.getColumnCount(); i++) {
165             final String name = cursor.getColumnName(i);
166             final String value = cursor.getString(i);
167             if (TextUtils.isEmpty(value)) {
168                 continue;
169             }
170             if (i > 0) {
171                 sb.append(' ');
172             }
173             sb.append(name).append('=').append(value);
174         }
175         sb.append("]");
176         return sb.toString();
177     }
178 
trimWithNullCheck(String value)179     private static String trimWithNullCheck(String value) {
180         return value != null ? value.trim() : null;
181     }
182 
ApnSettings(String mmscUrl, String proxyAddr, int proxyPort, String debugText)183     public ApnSettings(String mmscUrl, String proxyAddr, int proxyPort, String debugText) {
184         mServiceCenter = mmscUrl;
185         mProxyAddress = proxyAddr;
186         mProxyPort = proxyPort;
187         mDebugText = debugText;
188     }
189 
getMmscUrl()190     public String getMmscUrl() {
191         return mServiceCenter;
192     }
193 
getProxyAddress()194     public String getProxyAddress() {
195         return mProxyAddress;
196     }
197 
getProxyPort()198     public int getProxyPort() {
199         return mProxyPort;
200     }
201 
isProxySet()202     public boolean isProxySet() {
203         return !TextUtils.isEmpty(mProxyAddress);
204     }
205 
isValidApnType(String types, String requestType)206     private static boolean isValidApnType(String types, String requestType) {
207         // If APN type is unspecified, assume TYPE_ALL_STRING.
208         if (TextUtils.isEmpty(types)) {
209             return true;
210         }
211         for (String type : types.split(",")) {
212             type = type.trim();
213             if (type.equals(requestType) || type.equals(ApnSetting.TYPE_ALL_STRING)) {
214                 return true;
215             }
216         }
217         return false;
218     }
219 
toString()220     public String toString() {
221         return mDebugText;
222     }
223 }
224