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 com.android.mms.service.exception.ApnException; 20 import com.android.mms.service.exception.MmsHttpException; 21 import com.android.mms.service.exception.MmsNetworkException; 22 23 import android.app.Activity; 24 import android.app.PendingIntent; 25 import android.content.BroadcastReceiver; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.net.ConnectivityManager; 29 import android.net.LinkProperties; 30 import android.net.Network; 31 import android.net.Uri; 32 import android.os.Bundle; 33 import android.provider.Telephony; 34 import android.telephony.SmsManager; 35 import android.util.Log; 36 37 import java.net.Inet4Address; 38 import java.net.Inet6Address; 39 import java.net.InetAddress; 40 import java.net.UnknownHostException; 41 import java.util.ArrayList; 42 import java.util.List; 43 44 /** 45 * Base class for MMS requests. This has the common logic of sending/downloading MMS. 46 */ 47 public abstract class MmsRequest { 48 private static final int RETRY_TIMES = 3; 49 50 protected static final String EXTRA_MESSAGE_REF = "messageref"; 51 52 /** 53 * Interface for certain functionalities from MmsService 54 */ 55 public static interface RequestManager { 56 /** 57 * Add a request to pending queue when it is executed by carrier app 58 * 59 * @param key The message ref key from carrier app 60 * @param request The request in pending 61 */ addPending(int key, MmsRequest request)62 public void addPending(int key, MmsRequest request); 63 64 /** 65 * Enqueue an MMS request for running 66 * 67 * @param request the request to enqueue 68 */ addRunning(MmsRequest request)69 public void addRunning(MmsRequest request); 70 71 /* 72 * @return Whether to auto persist received MMS 73 */ getAutoPersistingPref()74 public boolean getAutoPersistingPref(); 75 76 /** 77 * Read pdu (up to maxSize bytes) from supplied content uri 78 * @param contentUri content uri from which to read 79 * @param maxSize maximum number of bytes to read 80 * @return read pdu (else null in case of error or too big) 81 */ readPduFromContentUri(final Uri contentUri, final int maxSize)82 public byte[] readPduFromContentUri(final Uri contentUri, final int maxSize); 83 84 /** 85 * Write pdu to supplied content uri 86 * @param contentUri content uri to which bytes should be written 87 * @param pdu pdu bytes to write 88 * @return true in case of success (else false) 89 */ writePduToContentUri(final Uri contentUri, final byte[] pdu)90 public boolean writePduToContentUri(final Uri contentUri, final byte[] pdu); 91 } 92 93 // The URI of persisted message 94 protected Uri mMessageUri; 95 // The reference to the pending requests manager (i.e. the MmsService) 96 protected RequestManager mRequestManager; 97 // The SIM id 98 protected long mSubId; 99 // The creator app 100 protected String mCreator; 101 // MMS config 102 protected MmsConfig.Overridden mMmsConfig; 103 // MMS config overrides 104 protected Bundle mMmsConfigOverrides; 105 106 // Intent result receiver for carrier app 107 protected final BroadcastReceiver mCarrierAppResultReceiver = new BroadcastReceiver() { 108 @Override 109 public void onReceive(Context context, Intent intent) { 110 final String action = intent.getAction(); 111 if (action.equals(Telephony.Mms.Intents.MMS_SEND_ACTION) || 112 action.equals(Telephony.Mms.Intents.MMS_DOWNLOAD_ACTION)) { 113 Log.d(MmsService.TAG, "Carrier app result for " + action); 114 final int rc = getResultCode(); 115 if (rc == Activity.RESULT_OK) { 116 // Handled by carrier app, waiting for result 117 Log.d(MmsService.TAG, "Sending/downloading MMS by IP pending."); 118 final Bundle resultExtras = getResultExtras(false); 119 if (resultExtras != null && resultExtras.containsKey(EXTRA_MESSAGE_REF)) { 120 final int ref = resultExtras.getInt(EXTRA_MESSAGE_REF); 121 Log.d(MmsService.TAG, "messageref = " + ref); 122 mRequestManager.addPending(ref, MmsRequest.this); 123 } else { 124 // Bad, no message ref provided 125 Log.e(MmsService.TAG, "Can't find messageref in result extras."); 126 } 127 } else { 128 // No carrier app present, sending normally 129 Log.d(MmsService.TAG, "Sending/downloading MMS by IP failed."); 130 mRequestManager.addRunning(MmsRequest.this); 131 } 132 } else { 133 Log.e(MmsService.TAG, "unexpected BroadcastReceiver action: " + action); 134 } 135 136 } 137 }; 138 MmsRequest(RequestManager requestManager, Uri messageUri, long subId, String creator, Bundle configOverrides)139 public MmsRequest(RequestManager requestManager, Uri messageUri, long subId, 140 String creator, Bundle configOverrides) { 141 mRequestManager = requestManager; 142 mMessageUri = messageUri; 143 mSubId = subId; 144 mCreator = creator; 145 mMmsConfigOverrides = configOverrides; 146 mMmsConfig = null; 147 } 148 ensureMmsConfigLoaded()149 private boolean ensureMmsConfigLoaded() { 150 if (mMmsConfig == null) { 151 // Not yet retrieved from mms config manager. Try getting it. 152 final MmsConfig config = MmsConfigManager.getInstance().getMmsConfigBySubId(mSubId); 153 if (config != null) { 154 mMmsConfig = new MmsConfig.Overridden(config, mMmsConfigOverrides); 155 } 156 } 157 return mMmsConfig != null; 158 } 159 160 /** 161 * Execute the request 162 * 163 * @param context The context 164 * @param networkManager The network manager to use 165 */ execute(Context context, MmsNetworkManager networkManager)166 public void execute(Context context, MmsNetworkManager networkManager) { 167 int result = SmsManager.MMS_ERROR_UNSPECIFIED; 168 byte[] response = null; 169 if (!ensureMmsConfigLoaded()) { // Check mms config 170 Log.e(MmsService.TAG, "MmsRequest: mms config is not loaded yet"); 171 result = SmsManager.MMS_ERROR_CONFIGURATION_ERROR; 172 } else if (!prepareForHttpRequest()) { // Prepare request, like reading pdu data from user 173 Log.e(MmsService.TAG, "MmsRequest: failed to prepare for request"); 174 result = SmsManager.MMS_ERROR_IO_ERROR; 175 } else { // Execute 176 long retryDelaySecs = 2; 177 // Try multiple times of MMS HTTP request 178 for (int i = 0; i < RETRY_TIMES; i++) { 179 try { 180 networkManager.acquireNetwork(); 181 try { 182 final ApnSettings apn = ApnSettings.load(context, null/*apnName*/, mSubId); 183 response = doHttp(context, networkManager, apn); 184 result = Activity.RESULT_OK; 185 // Success 186 break; 187 } finally { 188 networkManager.releaseNetwork(); 189 } 190 } catch (ApnException e) { 191 Log.e(MmsService.TAG, "MmsRequest: APN failure", e); 192 result = SmsManager.MMS_ERROR_INVALID_APN; 193 break; 194 } catch (MmsNetworkException e) { 195 Log.e(MmsService.TAG, "MmsRequest: MMS network acquiring failure", e); 196 result = SmsManager.MMS_ERROR_UNABLE_CONNECT_MMS; 197 // Retry 198 } catch (MmsHttpException e) { 199 Log.e(MmsService.TAG, "MmsRequest: HTTP or network I/O failure", e); 200 result = SmsManager.MMS_ERROR_HTTP_FAILURE; 201 // Retry 202 } catch (Exception e) { 203 Log.e(MmsService.TAG, "MmsRequest: unexpected failure", e); 204 result = SmsManager.MMS_ERROR_UNSPECIFIED; 205 break; 206 } 207 try { 208 Thread.sleep(retryDelaySecs * 1000, 0/*nano*/); 209 } catch (InterruptedException e) {} 210 retryDelaySecs <<= 1; 211 } 212 } 213 processResult(context, result, response); 214 } 215 216 /** 217 * Try running MMS HTTP request for all the addresses that we can resolve to 218 * 219 * @param context The context 220 * @param netMgr The {@link com.android.mms.service.MmsNetworkManager} 221 * @param url The HTTP URL 222 * @param pdu The PDU to send 223 * @param method The HTTP method to use 224 * @param apn The APN setting to use 225 * @return The response data 226 * @throws MmsHttpException If there is any HTTP/network failure 227 */ doHttpForResolvedAddresses(Context context, MmsNetworkManager netMgr, String url, byte[] pdu, int method, ApnSettings apn)228 protected byte[] doHttpForResolvedAddresses(Context context, MmsNetworkManager netMgr, 229 String url, byte[] pdu, int method, ApnSettings apn) throws MmsHttpException { 230 MmsHttpException lastException = null; 231 final ConnectivityManager connMgr = 232 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 233 // Do HTTP on all the addresses we can resolve to 234 for (final InetAddress address : resolveDestination(connMgr, netMgr, url, apn)) { 235 try { 236 // TODO: we have to use a deprecated API here because with the new 237 // ConnectivityManager APIs in LMP, we need to either use a bound process 238 // or a bound socket. The former can not be used since we share the 239 // phone process with others. The latter is not supported by any HTTP 240 // library yet. We have to rely on this API to get things work. Once 241 // a multinet aware HTTP lib is ready, we should switch to that and 242 // remove all the unnecessary code. 243 if (!connMgr.requestRouteToHostAddress( 244 ConnectivityManager.TYPE_MOBILE_MMS, address)) { 245 throw new MmsHttpException("MmsRequest: can not request a route for host " 246 + address); 247 } 248 return HttpUtils.httpConnection( 249 context, 250 url, 251 pdu, 252 method, 253 apn.isProxySet(), 254 apn.getProxyAddress(), 255 apn.getProxyPort(), 256 netMgr, 257 address instanceof Inet6Address, 258 mMmsConfig); 259 } catch (MmsHttpException e) { 260 lastException = e; 261 Log.e(MmsService.TAG, "MmsRequest: failure in trying address " + address, e); 262 } 263 } 264 if (lastException != null) { 265 throw lastException; 266 } else { 267 // Should not reach here 268 throw new MmsHttpException("MmsRequest: unknown failure"); 269 } 270 } 271 272 /** 273 * Resolve the name of the host we are about to connect to, which can be the URL host or 274 * the proxy host. We only resolve to the supported address types (IPv4 or IPv6 or both) 275 * based on the MMS network interface's address type, i.e. we only need addresses that 276 * match the link address type. 277 * 278 * @param connMgr The connectivity manager 279 * @param netMgr The current {@link MmsNetworkManager} 280 * @param url The HTTP URL 281 * @param apn The APN setting to use 282 * @return A list of matching resolved addresses 283 * @throws MmsHttpException For any network failure 284 */ resolveDestination(ConnectivityManager connMgr, MmsNetworkManager netMgr, String url, ApnSettings apn)285 private static List<InetAddress> resolveDestination(ConnectivityManager connMgr, 286 MmsNetworkManager netMgr, String url, ApnSettings apn) throws MmsHttpException { 287 Log.d(MmsService.TAG, "MmsRequest: resolve url " + url); 288 // Find the real host to connect to 289 String host = null; 290 if (apn.isProxySet()) { 291 host = apn.getProxyAddress(); 292 } else { 293 final Uri uri = Uri.parse(url); 294 host = uri.getHost(); 295 } 296 // Find out the link address types: ipv4 or ipv6 or both 297 final int addressTypes = getMmsLinkAddressTypes(connMgr, netMgr.getNetwork()); 298 Log.d(MmsService.TAG, "MmsRequest: addressTypes=" + addressTypes); 299 // Resolve the host to a list of addresses based on supported address types 300 return resolveHostName(netMgr, host, addressTypes); 301 } 302 303 // Address type masks 304 private static final int ADDRESS_TYPE_IPV4 = 1; 305 private static final int ADDRESS_TYPE_IPV6 = 1 << 1; 306 307 /** 308 * Try to find out if we should use IPv6 or IPv4 for MMS. Basically we check if the MMS 309 * network interface has IPv6 address or not. If so, we will use IPv6. Otherwise, use 310 * IPv4. 311 * 312 * @param connMgr The connectivity manager 313 * @return A bit mask indicating what address types we have 314 */ getMmsLinkAddressTypes(ConnectivityManager connMgr, Network network)315 private static int getMmsLinkAddressTypes(ConnectivityManager connMgr, Network network) { 316 int result = 0; 317 // Return none if network is not available 318 if (network == null) { 319 return result; 320 } 321 final LinkProperties linkProperties = connMgr.getLinkProperties(network); 322 if (linkProperties != null) { 323 for (InetAddress addr : linkProperties.getAddresses()) { 324 if (addr instanceof Inet4Address) { 325 result |= ADDRESS_TYPE_IPV4; 326 } else if (addr instanceof Inet6Address) { 327 result |= ADDRESS_TYPE_IPV6; 328 } 329 } 330 } 331 return result; 332 } 333 334 /** 335 * Resolve host name to address by specified address types. 336 * 337 * @param netMgr The current {@link MmsNetworkManager} 338 * @param host The host name 339 * @param addressTypes The required address type in a bit mask 340 * (0x01: IPv4, 0x10: IPv6, 0x11: both) 341 * @return 342 * @throws MmsHttpException 343 */ resolveHostName(MmsNetworkManager netMgr, String host, int addressTypes)344 private static List<InetAddress> resolveHostName(MmsNetworkManager netMgr, String host, 345 int addressTypes) throws MmsHttpException { 346 final List<InetAddress> resolved = new ArrayList<InetAddress>(); 347 try { 348 if (addressTypes != 0) { 349 for (final InetAddress addr : netMgr.getAllByName(host)) { 350 if ((addressTypes & ADDRESS_TYPE_IPV6) != 0 351 && addr instanceof Inet6Address) { 352 // Should use IPv6 and this is IPv6 address, add it 353 resolved.add(addr); 354 } else if ((addressTypes & ADDRESS_TYPE_IPV4) != 0 355 && addr instanceof Inet4Address) { 356 // Should use IPv4 and this is IPv4 address, add it 357 resolved.add(addr); 358 } 359 } 360 } 361 if (resolved.size() < 1) { 362 throw new MmsHttpException("Failed to resolve " + host 363 + " for allowed address types: " + addressTypes); 364 } 365 return resolved; 366 } catch (final UnknownHostException e) { 367 throw new MmsHttpException("Failed to resolve " + host, e); 368 } 369 } 370 371 /** 372 * Process the result of the completed request, including updating the message status 373 * in database and sending back the result via pending intents. 374 * 375 * @param context The context 376 * @param result The result code of execution 377 * @param response The response body 378 */ processResult(Context context, int result, byte[] response)379 public void processResult(Context context, int result, byte[] response) { 380 updateStatus(context, result, response); 381 382 // Return MMS HTTP request result via PendingIntent 383 final PendingIntent pendingIntent = getPendingIntent(); 384 if (pendingIntent != null) { 385 boolean succeeded = true; 386 // Extra information to send back with the pending intent 387 Intent fillIn = new Intent(); 388 if (response != null) { 389 succeeded = transferResponse(fillIn, response); 390 } 391 if (mMessageUri != null) { 392 fillIn.putExtra("uri", mMessageUri.toString()); 393 } 394 try { 395 if (!succeeded) { 396 result = SmsManager.MMS_ERROR_IO_ERROR; 397 } 398 pendingIntent.send(context, result, fillIn); 399 } catch (PendingIntent.CanceledException e) { 400 Log.e(MmsService.TAG, "MmsRequest: sending pending intent canceled", e); 401 } 402 } 403 404 revokeUriPermission(context); 405 } 406 407 /** 408 * Making the HTTP request to MMSC 409 * 410 * @param context The context 411 * @param netMgr The current {@link MmsNetworkManager} 412 * @param apn The APN setting 413 * @return The HTTP response data 414 * @throws MmsHttpException If any network error happens 415 */ doHttp(Context context, MmsNetworkManager netMgr, ApnSettings apn)416 protected abstract byte[] doHttp(Context context, MmsNetworkManager netMgr, ApnSettings apn) 417 throws MmsHttpException; 418 419 /** 420 * @return The PendingIntent associate with the MMS sending invocation 421 */ getPendingIntent()422 protected abstract PendingIntent getPendingIntent(); 423 424 /** 425 * @return The running queue should be used by this request 426 */ getRunningQueue()427 protected abstract int getRunningQueue(); 428 429 /** 430 * Update database status of the message represented by this request 431 * 432 * @param context The context 433 * @param result The result code of execution 434 * @param response The response body 435 */ updateStatus(Context context, int result, byte[] response)436 protected abstract void updateStatus(Context context, int result, byte[] response); 437 438 /** 439 * Prepare to make the HTTP request - will download message for sending 440 * @return true if preparation succeeds (and request can proceed) else false 441 */ prepareForHttpRequest()442 protected abstract boolean prepareForHttpRequest(); 443 444 /** 445 * Transfer the received response to the caller 446 * 447 * @param fillIn the intent that will be returned to the caller 448 * @param response the pdu to transfer 449 * @return true if response transfer succeeds else false 450 */ transferResponse(Intent fillIn, byte[] response)451 protected abstract boolean transferResponse(Intent fillIn, byte[] response); 452 453 /** 454 * Revoke the content URI permission granted by the MMS app to the phone package. 455 * 456 * @param context The context 457 */ revokeUriPermission(Context context)458 protected abstract void revokeUriPermission(Context context); 459 } 460