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