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