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