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