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