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.app.Activity;
20 import android.app.PendingIntent;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.net.Uri;
24 import android.os.Bundle;
25 import android.service.carrier.CarrierMessagingService;
26 import android.service.carrier.CarrierMessagingServiceWrapper.CarrierMessagingCallbackWrapper;
27 import android.telephony.SmsManager;
28 import android.telephony.TelephonyManager;
29 import android.text.TextUtils;
30 
31 import com.android.mms.service.exception.ApnException;
32 import com.android.mms.service.exception.MmsHttpException;
33 import com.android.mms.service.exception.MmsNetworkException;
34 
35 /**
36  * Base class for MMS requests. This has the common logic of sending/downloading MMS.
37  */
38 public abstract class MmsRequest {
39     private static final int RETRY_TIMES = 3;
40 
41     /**
42      * Interface for certain functionalities from MmsService
43      */
44     public static interface RequestManager {
45         /**
46          * Enqueue an MMS request
47          *
48          * @param request the request to enqueue
49          */
addSimRequest(MmsRequest request)50         public void addSimRequest(MmsRequest request);
51 
52         /*
53          * @return Whether to auto persist received MMS
54          */
getAutoPersistingPref()55         public boolean getAutoPersistingPref();
56 
57         /**
58          * Read pdu (up to maxSize bytes) from supplied content uri
59          * @param contentUri content uri from which to read
60          * @param maxSize maximum number of bytes to read
61          * @return read pdu (else null in case of error or too big)
62          */
readPduFromContentUri(final Uri contentUri, final int maxSize)63         public byte[] readPduFromContentUri(final Uri contentUri, final int maxSize);
64 
65         /**
66          * Write pdu to supplied content uri
67          * @param contentUri content uri to which bytes should be written
68          * @param pdu pdu bytes to write
69          * @return true in case of success (else false)
70          */
writePduToContentUri(final Uri contentUri, final byte[] pdu)71         public boolean writePduToContentUri(final Uri contentUri, final byte[] pdu);
72     }
73 
74     // The reference to the pending requests manager (i.e. the MmsService)
75     protected RequestManager mRequestManager;
76     // The SIM id
77     protected int mSubId;
78     // The creator app
79     protected String mCreator;
80     // MMS config
81     protected Bundle mMmsConfig;
82     // MMS config overrides that will be applied to mMmsConfig when we eventually load it.
83     protected Bundle mMmsConfigOverrides;
84     // Context used to get TelephonyManager.
85     protected Context mContext;
86     protected long mMessageId;
87 
MmsRequest(RequestManager requestManager, int subId, String creator, Bundle configOverrides, Context context, long messageId)88     public MmsRequest(RequestManager requestManager, int subId, String creator,
89             Bundle configOverrides, Context context, long messageId) {
90         mRequestManager = requestManager;
91         mSubId = subId;
92         mCreator = creator;
93         mMmsConfigOverrides = configOverrides;
94         mMmsConfig = null;
95         mContext = context;
96         mMessageId = messageId;
97     }
98 
getSubId()99     public int getSubId() {
100         return mSubId;
101     }
102 
ensureMmsConfigLoaded()103     private boolean ensureMmsConfigLoaded() {
104         if (mMmsConfig == null) {
105             // Not yet retrieved from mms config manager. Try getting it.
106             final Bundle config = MmsConfigManager.getInstance().getMmsConfigBySubId(mSubId);
107             if (config != null) {
108                 mMmsConfig = config;
109                 // TODO: Make MmsConfigManager authoritative for user agent and don't consult
110                 // TelephonyManager.
111                 final TelephonyManager telephonyManager = ((TelephonyManager) mContext
112                         .getSystemService(Context.TELEPHONY_SERVICE))
113                         .createForSubscriptionId(mSubId);
114                 final String userAgent = telephonyManager.getMmsUserAgent();
115                 if (!TextUtils.isEmpty(userAgent)) {
116                     config.putString(SmsManager.MMS_CONFIG_USER_AGENT, userAgent);
117                 }
118                 final String userAgentProfileUrl = telephonyManager.getMmsUAProfUrl();
119                 if (!TextUtils.isEmpty(userAgentProfileUrl)) {
120                     config.putString(SmsManager.MMS_CONFIG_UA_PROF_URL, userAgentProfileUrl);
121                 }
122                 // Apply overrides
123                 if (mMmsConfigOverrides != null) {
124                     mMmsConfig.putAll(mMmsConfigOverrides);
125                 }
126             }
127         }
128         return mMmsConfig != null;
129     }
130 
131     /**
132      * Execute the request
133      *
134      * @param context The context
135      * @param networkManager The network manager to use
136      */
execute(Context context, MmsNetworkManager networkManager)137     public void execute(Context context, MmsNetworkManager networkManager) {
138         final String requestId = this.getRequestId();
139         LogUtil.i(requestId, "Executing...");
140         int result = SmsManager.MMS_ERROR_UNSPECIFIED;
141         int httpStatusCode = 0;
142         byte[] response = null;
143         // TODO: add mms data channel check back to fast fail if no way to send mms,
144         // when telephony provides such API.
145         if (!ensureMmsConfigLoaded()) { // Check mms config
146             LogUtil.e(requestId, "mms config is not loaded yet");
147             result = SmsManager.MMS_ERROR_CONFIGURATION_ERROR;
148         } else if (!prepareForHttpRequest()) { // Prepare request, like reading pdu data from user
149             LogUtil.e(requestId, "Failed to prepare for request");
150             result = SmsManager.MMS_ERROR_IO_ERROR;
151         } else { // Execute
152             long retryDelaySecs = 2;
153             // Try multiple times of MMS HTTP request, depending on the error.
154             for (int i = 0; i < RETRY_TIMES; i++) {
155                 try {
156                     networkManager.acquireNetwork(requestId);
157                     final String apnName = networkManager.getApnName();
158                     LogUtil.d(requestId, "APN name is " + apnName);
159                     try {
160                         ApnSettings apn = null;
161                         try {
162                             apn = ApnSettings.load(context, apnName, mSubId, requestId);
163                         } catch (ApnException e) {
164                             // If no APN could be found, fall back to trying without the APN name
165                             if (apnName == null) {
166                                 // If the APN name was already null then don't need to retry
167                                 throw (e);
168                             }
169                             LogUtil.i(requestId, "No match with APN name: "
170                                     + apnName + ", try with no name");
171                             apn = ApnSettings.load(context, null, mSubId, requestId);
172                         }
173                         LogUtil.i(requestId, "Using " + apn.toString());
174                         response = doHttp(context, networkManager, apn);
175                         result = Activity.RESULT_OK;
176                         // Success
177                         break;
178                     } finally {
179                         networkManager.releaseNetwork(requestId, this instanceof DownloadRequest);
180                     }
181                 } catch (ApnException e) {
182                     LogUtil.e(requestId, "APN failure", e);
183                     result = SmsManager.MMS_ERROR_INVALID_APN;
184                     break;
185                 } catch (MmsNetworkException e) {
186                     LogUtil.e(requestId, "MMS network acquiring failure", e);
187                     result = SmsManager.MMS_ERROR_UNABLE_CONNECT_MMS;
188                     break;
189                 } catch (MmsHttpException e) {
190                     LogUtil.e(requestId, "HTTP or network I/O failure", e);
191                     result = SmsManager.MMS_ERROR_HTTP_FAILURE;
192                     httpStatusCode = e.getStatusCode();
193                     // Retry
194                 } catch (Exception e) {
195                     LogUtil.e(requestId, "Unexpected failure", e);
196                     result = SmsManager.MMS_ERROR_UNSPECIFIED;
197                     break;
198                 }
199                 try {
200                     Thread.sleep(retryDelaySecs * 1000, 0/*nano*/);
201                 } catch (InterruptedException e) {}
202                 retryDelaySecs <<= 1;
203             }
204         }
205         processResult(context, result, response, httpStatusCode);
206     }
207 
208     /**
209      * Process the result of the completed request, including updating the message status
210      * in database and sending back the result via pending intents.
211      * @param context The context
212      * @param result The result code of execution
213      * @param response The response body
214      * @param httpStatusCode The optional http status code in case of http failure
215      */
processResult(Context context, int result, byte[] response, int httpStatusCode)216     public void processResult(Context context, int result, byte[] response, int httpStatusCode) {
217         final Uri messageUri = persistIfRequired(context, result, response);
218 
219         final String requestId = this.getRequestId();
220         // As noted in the @param comment above, the httpStatusCode is only set when there's
221         // an http failure. On success, such as an http code of 200, the value here will be 0.
222         // It's disconcerting in the log to see httpStatusCode: 0 when the mms succeeded. That
223         // is why an httpStatusCode of zero is now reported in the log as "success".
224         LogUtil.i(requestId, "processResult: " + result + ", httpStatusCode: "
225                 + (httpStatusCode != 0 ? httpStatusCode : "success (0)"));
226 
227         // Return MMS HTTP request result via PendingIntent
228         final PendingIntent pendingIntent = getPendingIntent();
229         if (pendingIntent != null) {
230             boolean succeeded = true;
231             // Extra information to send back with the pending intent
232             Intent fillIn = new Intent();
233             if (response != null) {
234                 succeeded = transferResponse(fillIn, response);
235             }
236             if (messageUri != null) {
237                 fillIn.putExtra("uri", messageUri.toString());
238             }
239             if (result == SmsManager.MMS_ERROR_HTTP_FAILURE && httpStatusCode != 0) {
240                 fillIn.putExtra(SmsManager.EXTRA_MMS_HTTP_STATUS, httpStatusCode);
241             }
242             try {
243                 if (!succeeded) {
244                     result = SmsManager.MMS_ERROR_IO_ERROR;
245                 }
246                 pendingIntent.send(context, result, fillIn);
247             } catch (PendingIntent.CanceledException e) {
248                 LogUtil.e(requestId, "Sending pending intent canceled", e);
249             }
250         }
251 
252         revokeUriPermission(context);
253     }
254 
255     /**
256      * Returns true if sending / downloading using the carrier app has failed and completes the
257      * action using platform API's, otherwise false.
258      */
maybeFallbackToRegularDelivery(int carrierMessagingAppResult)259     protected boolean maybeFallbackToRegularDelivery(int carrierMessagingAppResult) {
260         if (carrierMessagingAppResult
261                 == CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK
262                 || carrierMessagingAppResult
263                         == CarrierMessagingService.DOWNLOAD_STATUS_RETRY_ON_CARRIER_NETWORK) {
264             LogUtil.d(this.toString(), "Sending/downloading MMS by IP failed. messageId: "
265                     + mMessageId);
266             mRequestManager.addSimRequest(MmsRequest.this);
267             return true;
268         } else {
269             return false;
270         }
271     }
272 
273     /**
274      * Converts from {@code carrierMessagingAppResult} to a platform result code.
275      */
toSmsManagerResult(int carrierMessagingAppResult)276     protected static int toSmsManagerResult(int carrierMessagingAppResult) {
277         switch (carrierMessagingAppResult) {
278             case CarrierMessagingService.SEND_STATUS_OK:
279                 return Activity.RESULT_OK;
280             case CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK:
281                 return SmsManager.MMS_ERROR_RETRY;
282             default:
283                 return SmsManager.MMS_ERROR_UNSPECIFIED;
284         }
285     }
286 
287     @Override
toString()288     public String toString() {
289         return getClass().getSimpleName() + '@' + Integer.toHexString(hashCode())
290                 + " messageId: " + mMessageId;
291     }
292 
293 
getRequestId()294     protected String getRequestId() {
295         return this.toString();
296     }
297 
298     /**
299      * Making the HTTP request to MMSC
300      *
301      * @param context The context
302      * @param netMgr The current {@link MmsNetworkManager}
303      * @param apn The APN setting
304      * @return The HTTP response data
305      * @throws MmsHttpException If any network error happens
306      */
doHttp(Context context, MmsNetworkManager netMgr, ApnSettings apn)307     protected abstract byte[] doHttp(Context context, MmsNetworkManager netMgr, ApnSettings apn)
308             throws MmsHttpException;
309 
310     /**
311      * @return The PendingIntent associate with the MMS sending invocation
312      */
getPendingIntent()313     protected abstract PendingIntent getPendingIntent();
314 
315     /**
316      * @return The queue should be used by this request, 0 is sending and 1 is downloading
317      */
getQueueType()318     protected abstract int getQueueType();
319 
320     /**
321      * Persist message into telephony if required (i.e. when auto-persisting is on or
322      * the calling app is non-default sms app for sending)
323      *
324      * @param context The context
325      * @param result The result code of execution
326      * @param response The response body
327      * @return The persisted URI of the message or null if we don't persist or fail
328      */
persistIfRequired(Context context, int result, byte[] response)329     protected abstract Uri persistIfRequired(Context context, int result, byte[] response);
330 
331     /**
332      * Prepare to make the HTTP request - will download message for sending
333      * @return true if preparation succeeds (and request can proceed) else false
334      */
prepareForHttpRequest()335     protected abstract boolean prepareForHttpRequest();
336 
337     /**
338      * Transfer the received response to the caller
339      *
340      * @param fillIn the intent that will be returned to the caller
341      * @param response the pdu to transfer
342      * @return true if response transfer succeeds else false
343      */
transferResponse(Intent fillIn, byte[] response)344     protected abstract boolean transferResponse(Intent fillIn, byte[] response);
345 
346     /**
347      * Revoke the content URI permission granted by the MMS app to the phone package.
348      *
349      * @param context The context
350      */
revokeUriPermission(Context context)351     protected abstract void revokeUriPermission(Context context);
352 
353     /**
354      * Base class for handling carrier app send / download result.
355      */
356     protected abstract class CarrierMmsActionCallback extends CarrierMessagingCallbackWrapper {
357         @Override
onSendSmsComplete(int result, int messageRef)358         public void onSendSmsComplete(int result, int messageRef) {
359             LogUtil.e("Unexpected onSendSmsComplete call for messageId " + mMessageId
360                     + " with result: " + result);
361         }
362 
363         @Override
onSendMultipartSmsComplete(int result, int[] messageRefs)364         public void onSendMultipartSmsComplete(int result, int[] messageRefs) {
365             LogUtil.e("Unexpected onSendMultipartSmsComplete call for messageId " + mMessageId
366                     + " with result: " + result);
367         }
368 
369         @Override
onFilterComplete(int result)370         public void onFilterComplete(int result) {
371             LogUtil.e("Unexpected onFilterComplete call for messageId " + mMessageId
372                     + " with result: " + result);
373         }
374     }
375 }
376