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