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