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