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