• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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