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