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