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.ICarrierMessagingCallback;
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 
MmsRequest(RequestManager requestManager, int subId, String creator, Bundle configOverrides, Context context)87     public MmsRequest(RequestManager requestManager, int subId, String creator,
88             Bundle configOverrides, Context context) {
89         mRequestManager = requestManager;
90         mSubId = subId;
91         mCreator = creator;
92         mMmsConfigOverrides = configOverrides;
93         mMmsConfig = null;
94         mContext = context;
95     }
96 
getSubId()97     public int getSubId() {
98         return mSubId;
99     }
100 
ensureMmsConfigLoaded()101     private boolean ensureMmsConfigLoaded() {
102         if (mMmsConfig == null) {
103             // Not yet retrieved from mms config manager. Try getting it.
104             final Bundle config = MmsConfigManager.getInstance().getMmsConfigBySubId(mSubId);
105             if (config != null) {
106                 mMmsConfig = config;
107                 // TODO: Make MmsConfigManager authoritative for user agent and don't consult
108                 // TelephonyManager.
109                 final TelephonyManager telephonyManager =
110                         (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
111                 final String userAgent = telephonyManager.getMmsUserAgent();
112                 if (!TextUtils.isEmpty(userAgent)) {
113                     config.putString(SmsManager.MMS_CONFIG_USER_AGENT, userAgent);
114                 }
115                 final String userAgentProfileUrl = telephonyManager.getMmsUAProfUrl();
116                 if (!TextUtils.isEmpty(userAgentProfileUrl)) {
117                     config.putString(SmsManager.MMS_CONFIG_UA_PROF_URL, userAgentProfileUrl);
118                 }
119                 // Apply overrides
120                 if (mMmsConfigOverrides != null) {
121                     mMmsConfig.putAll(mMmsConfigOverrides);
122                 }
123             }
124         }
125         return mMmsConfig != null;
126     }
127 
128     /**
129      * Execute the request
130      *
131      * @param context The context
132      * @param networkManager The network manager to use
133      */
execute(Context context, MmsNetworkManager networkManager)134     public void execute(Context context, MmsNetworkManager networkManager) {
135         final String requestId = this.toString();
136         LogUtil.i(requestId, "Executing...");
137         int result = SmsManager.MMS_ERROR_UNSPECIFIED;
138         int httpStatusCode = 0;
139         byte[] response = null;
140         // TODO: add mms data channel check back to fast fail if no way to send mms,
141         // when telephony provides such API.
142         if (!ensureMmsConfigLoaded()) { // Check mms config
143             LogUtil.e(requestId, "mms config is not loaded yet");
144             result = SmsManager.MMS_ERROR_CONFIGURATION_ERROR;
145         } else if (!prepareForHttpRequest()) { // Prepare request, like reading pdu data from user
146             LogUtil.e(requestId, "Failed to prepare for request");
147             result = SmsManager.MMS_ERROR_IO_ERROR;
148         } else { // Execute
149             long retryDelaySecs = 2;
150             // Try multiple times of MMS HTTP request
151             for (int i = 0; i < RETRY_TIMES; i++) {
152                 try {
153                     networkManager.acquireNetwork(requestId);
154                     final String apnName = networkManager.getApnName();
155                     LogUtil.d(requestId, "APN name is " + apnName);
156                     try {
157                         ApnSettings apn = null;
158                         try {
159                             apn = ApnSettings.load(context, apnName, mSubId, requestId);
160                         } catch (ApnException e) {
161                             // If no APN could be found, fall back to trying without the APN name
162                             if (apnName == null) {
163                                 // If the APN name was already null then don't need to retry
164                                 throw (e);
165                             }
166                             LogUtil.i(requestId, "No match with APN name: "
167                                     + apnName + ", try with no name");
168                             apn = ApnSettings.load(context, null, mSubId, requestId);
169                         }
170                         LogUtil.i(requestId, "Using " + apn.toString());
171                         response = doHttp(context, networkManager, apn);
172                         result = Activity.RESULT_OK;
173                         // Success
174                         break;
175                     } finally {
176                         networkManager.releaseNetwork(requestId);
177                     }
178                 } catch (ApnException e) {
179                     LogUtil.e(requestId, "APN failure", e);
180                     result = SmsManager.MMS_ERROR_INVALID_APN;
181                     break;
182                 } catch (MmsNetworkException e) {
183                     LogUtil.e(requestId, "MMS network acquiring failure", e);
184                     result = SmsManager.MMS_ERROR_UNABLE_CONNECT_MMS;
185                     // Retry
186                 } catch (MmsHttpException e) {
187                     LogUtil.e(requestId, "HTTP or network I/O failure", e);
188                     result = SmsManager.MMS_ERROR_HTTP_FAILURE;
189                     httpStatusCode = e.getStatusCode();
190                     // Retry
191                 } catch (Exception e) {
192                     LogUtil.e(requestId, "Unexpected failure", e);
193                     result = SmsManager.MMS_ERROR_UNSPECIFIED;
194                     break;
195                 }
196                 try {
197                     Thread.sleep(retryDelaySecs * 1000, 0/*nano*/);
198                 } catch (InterruptedException e) {}
199                 retryDelaySecs <<= 1;
200             }
201         }
202         processResult(context, result, response, httpStatusCode);
203     }
204 
205     /**
206      * Process the result of the completed request, including updating the message status
207      * in database and sending back the result via pending intents.
208      *  @param context The context
209      * @param result The result code of execution
210      * @param response The response body
211      * @param httpStatusCode The optional http status code in case of http failure
212      */
processResult(Context context, int result, byte[] response, int httpStatusCode)213     public void processResult(Context context, int result, byte[] response, int httpStatusCode) {
214         final Uri messageUri = persistIfRequired(context, result, response);
215 
216         // Return MMS HTTP request result via PendingIntent
217         final PendingIntent pendingIntent = getPendingIntent();
218         if (pendingIntent != null) {
219             boolean succeeded = true;
220             // Extra information to send back with the pending intent
221             Intent fillIn = new Intent();
222             if (response != null) {
223                 succeeded = transferResponse(fillIn, response);
224             }
225             if (messageUri != null) {
226                 fillIn.putExtra("uri", messageUri.toString());
227             }
228             if (result == SmsManager.MMS_ERROR_HTTP_FAILURE && httpStatusCode != 0) {
229                 fillIn.putExtra(SmsManager.EXTRA_MMS_HTTP_STATUS, httpStatusCode);
230             }
231             try {
232                 if (!succeeded) {
233                     result = SmsManager.MMS_ERROR_IO_ERROR;
234                 }
235                 pendingIntent.send(context, result, fillIn);
236             } catch (PendingIntent.CanceledException e) {
237                 LogUtil.e(this.toString(), "Sending pending intent canceled", e);
238             }
239         }
240 
241         revokeUriPermission(context);
242     }
243 
244     /**
245      * Returns true if sending / downloading using the carrier app has failed and completes the
246      * action using platform API's, otherwise false.
247      */
maybeFallbackToRegularDelivery(int carrierMessagingAppResult)248     protected boolean maybeFallbackToRegularDelivery(int carrierMessagingAppResult) {
249         if (carrierMessagingAppResult
250                 == CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK
251                 || carrierMessagingAppResult
252                         == CarrierMessagingService.DOWNLOAD_STATUS_RETRY_ON_CARRIER_NETWORK) {
253             LogUtil.d(this.toString(), "Sending/downloading MMS by IP failed.");
254             mRequestManager.addSimRequest(MmsRequest.this);
255             return true;
256         } else {
257             return false;
258         }
259     }
260 
261     /**
262      * Converts from {@code carrierMessagingAppResult} to a platform result code.
263      */
toSmsManagerResult(int carrierMessagingAppResult)264     protected static int toSmsManagerResult(int carrierMessagingAppResult) {
265         switch (carrierMessagingAppResult) {
266             case CarrierMessagingService.SEND_STATUS_OK:
267                 return Activity.RESULT_OK;
268             case CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK:
269                 return SmsManager.MMS_ERROR_RETRY;
270             default:
271                 return SmsManager.MMS_ERROR_UNSPECIFIED;
272         }
273     }
274 
275     @Override
toString()276     public String toString() {
277         return getClass().getSimpleName() + '@' + Integer.toHexString(hashCode());
278     }
279 
280 
getRequestId()281     protected String getRequestId() {
282         return this.toString();
283     }
284 
285     /**
286      * Making the HTTP request to MMSC
287      *
288      * @param context The context
289      * @param netMgr The current {@link MmsNetworkManager}
290      * @param apn The APN setting
291      * @return The HTTP response data
292      * @throws MmsHttpException If any network error happens
293      */
doHttp(Context context, MmsNetworkManager netMgr, ApnSettings apn)294     protected abstract byte[] doHttp(Context context, MmsNetworkManager netMgr, ApnSettings apn)
295             throws MmsHttpException;
296 
297     /**
298      * @return The PendingIntent associate with the MMS sending invocation
299      */
getPendingIntent()300     protected abstract PendingIntent getPendingIntent();
301 
302     /**
303      * @return The queue should be used by this request, 0 is sending and 1 is downloading
304      */
getQueueType()305     protected abstract int getQueueType();
306 
307     /**
308      * Persist message into telephony if required (i.e. when auto-persisting is on or
309      * the calling app is non-default sms app for sending)
310      *
311      * @param context The context
312      * @param result The result code of execution
313      * @param response The response body
314      * @return The persisted URI of the message or null if we don't persist or fail
315      */
persistIfRequired(Context context, int result, byte[] response)316     protected abstract Uri persistIfRequired(Context context, int result, byte[] response);
317 
318     /**
319      * Prepare to make the HTTP request - will download message for sending
320      * @return true if preparation succeeds (and request can proceed) else false
321      */
prepareForHttpRequest()322     protected abstract boolean prepareForHttpRequest();
323 
324     /**
325      * Transfer the received response to the caller
326      *
327      * @param fillIn the intent that will be returned to the caller
328      * @param response the pdu to transfer
329      * @return true if response transfer succeeds else false
330      */
transferResponse(Intent fillIn, byte[] response)331     protected abstract boolean transferResponse(Intent fillIn, byte[] response);
332 
333     /**
334      * Revoke the content URI permission granted by the MMS app to the phone package.
335      *
336      * @param context The context
337      */
revokeUriPermission(Context context)338     protected abstract void revokeUriPermission(Context context);
339 
340     /**
341      * Base class for handling carrier app send / download result.
342      */
343     protected abstract class CarrierMmsActionCallback extends ICarrierMessagingCallback.Stub {
344         @Override
onSendSmsComplete(int result, int messageRef)345         public void onSendSmsComplete(int result, int messageRef) {
346             LogUtil.e("Unexpected onSendSmsComplete call with result: " + result);
347         }
348 
349         @Override
onSendMultipartSmsComplete(int result, int[] messageRefs)350         public void onSendMultipartSmsComplete(int result, int[] messageRefs) {
351             LogUtil.e("Unexpected onSendMultipartSmsComplete call with result: " + result);
352         }
353 
354         @Override
onFilterComplete(int result)355         public void onFilterComplete(int result) {
356             LogUtil.e("Unexpected onFilterComplete call with result: " + result);
357         }
358     }
359 }
360