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