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