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