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