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.ActivityManager; 21 import android.app.AppOpsManager; 22 import android.app.PendingIntent; 23 import android.content.ContentValues; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.pm.UserInfo; 27 import android.database.sqlite.SQLiteException; 28 import android.net.Uri; 29 import android.os.Binder; 30 import android.os.Bundle; 31 import android.os.RemoteException; 32 import android.os.UserHandle; 33 import android.os.UserManager; 34 import android.provider.Telephony; 35 import android.service.carrier.CarrierMessagingService; 36 import android.service.carrier.CarrierMessagingServiceWrapper; 37 import android.telephony.SmsManager; 38 import android.telephony.TelephonyManager; 39 import android.text.TextUtils; 40 41 import com.android.mms.service.exception.MmsHttpException; 42 import com.android.mms.service.metrics.MmsStats; 43 44 import com.google.android.mms.MmsException; 45 import com.google.android.mms.pdu.GenericPdu; 46 import com.google.android.mms.pdu.PduHeaders; 47 import com.google.android.mms.pdu.PduParser; 48 import com.google.android.mms.pdu.PduPersister; 49 import com.google.android.mms.pdu.RetrieveConf; 50 import com.google.android.mms.util.SqliteWrapper; 51 52 /** 53 * Request to download an MMS 54 */ 55 public class DownloadRequest extends MmsRequest { 56 private static final String LOCATION_SELECTION = 57 Telephony.Mms.MESSAGE_TYPE + "=? AND " + Telephony.Mms.CONTENT_LOCATION + " =?"; 58 59 private final String mLocationUrl; 60 private final PendingIntent mDownloadedIntent; 61 private final Uri mContentUri; 62 DownloadRequest(RequestManager manager, int subId, String locationUrl, Uri contentUri, PendingIntent downloadedIntent, String creator, Bundle configOverrides, Context context, long messageId, MmsStats mmsStats, TelephonyManager telephonyManager)63 public DownloadRequest(RequestManager manager, int subId, String locationUrl, 64 Uri contentUri, PendingIntent downloadedIntent, String creator, 65 Bundle configOverrides, Context context, long messageId, MmsStats mmsStats, 66 TelephonyManager telephonyManager) { 67 super(manager, subId, creator, configOverrides, context, messageId, mmsStats, 68 telephonyManager); 69 mLocationUrl = locationUrl; 70 mDownloadedIntent = downloadedIntent; 71 mContentUri = contentUri; 72 } 73 74 @Override doHttp(Context context, MmsNetworkManager netMgr, ApnSettings apn)75 protected byte[] doHttp(Context context, MmsNetworkManager netMgr, ApnSettings apn) 76 throws MmsHttpException { 77 final String requestId = getRequestId(); 78 final MmsHttpClient mmsHttpClient = netMgr.getOrCreateHttpClient(); 79 if (mmsHttpClient == null) { 80 LogUtil.e(requestId, "MMS network is not ready! " 81 + MmsService.formatCrossStackMessageId(mMessageId)); 82 throw new MmsHttpException(0/*statusCode*/, "MMS network is not ready. " 83 + MmsService.formatCrossStackMessageId(mMessageId)); 84 } 85 return mmsHttpClient.execute( 86 mLocationUrl, 87 null/*pud*/, 88 MmsHttpClient.METHOD_GET, 89 apn.isProxySet(), 90 apn.getProxyAddress(), 91 apn.getProxyPort(), 92 mMmsConfig, 93 mSubId, 94 requestId); 95 } 96 97 @Override getPendingIntent()98 protected PendingIntent getPendingIntent() { 99 return mDownloadedIntent; 100 } 101 102 @Override getQueueType()103 protected int getQueueType() { 104 return MmsService.QUEUE_INDEX_DOWNLOAD; 105 } 106 107 @Override persistIfRequired(Context context, int result, byte[] response)108 protected Uri persistIfRequired(Context context, int result, byte[] response) { 109 final String requestId = getRequestId(); 110 // Let any mms apps running as secondary user know that a new mms has been downloaded. 111 notifyOfDownload(context); 112 113 if (!mRequestManager.getAutoPersistingPref()) { 114 return null; 115 } 116 LogUtil.d(requestId, "persistIfRequired. " 117 + MmsService.formatCrossStackMessageId(mMessageId)); 118 if (response == null || response.length < 1) { 119 LogUtil.e(requestId, "persistIfRequired: empty response. " 120 + MmsService.formatCrossStackMessageId(mMessageId)); 121 return null; 122 } 123 final long identity = Binder.clearCallingIdentity(); 124 try { 125 final boolean supportMmsContentDisposition = 126 mMmsConfig.getBoolean(SmsManager.MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION); 127 final GenericPdu pdu = (new PduParser(response, supportMmsContentDisposition)).parse(); 128 if (pdu == null || !(pdu instanceof RetrieveConf)) { 129 LogUtil.e(requestId, "persistIfRequired: invalid parsed PDU. " 130 + MmsService.formatCrossStackMessageId(mMessageId)); 131 return null; 132 } 133 final RetrieveConf retrieveConf = (RetrieveConf) pdu; 134 final int status = retrieveConf.getRetrieveStatus(); 135 if (status != PduHeaders.RETRIEVE_STATUS_OK) { 136 LogUtil.e(requestId, "persistIfRequired: retrieve failed " + status 137 + ", " + MmsService.formatCrossStackMessageId(mMessageId)); 138 // Update the retrieve status of the NotificationInd 139 final ContentValues values = new ContentValues(1); 140 values.put(Telephony.Mms.RETRIEVE_STATUS, status); 141 SqliteWrapper.update( 142 context, 143 context.getContentResolver(), 144 Telephony.Mms.CONTENT_URI, 145 values, 146 LOCATION_SELECTION, 147 new String[] { 148 Integer.toString(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND), 149 mLocationUrl 150 }); 151 return null; 152 } 153 // Store the downloaded message 154 final PduPersister persister = PduPersister.getPduPersister(context); 155 final Uri messageUri = persister.persist( 156 pdu, 157 Telephony.Mms.Inbox.CONTENT_URI, 158 true/*createThreadId*/, 159 true/*groupMmsEnabled*/, 160 null/*preOpenedFiles*/); 161 if (messageUri == null) { 162 LogUtil.e(requestId, "persistIfRequired: can not persist message. " 163 + MmsService.formatCrossStackMessageId(mMessageId)); 164 return null; 165 } 166 // Update some of the properties of the message 167 final ContentValues values = new ContentValues(); 168 values.put(Telephony.Mms.DATE, System.currentTimeMillis() / 1000L); 169 values.put(Telephony.Mms.READ, 0); 170 values.put(Telephony.Mms.SEEN, 0); 171 if (!TextUtils.isEmpty(mCreator)) { 172 values.put(Telephony.Mms.CREATOR, mCreator); 173 } 174 values.put(Telephony.Mms.SUBSCRIPTION_ID, mSubId); 175 if (SqliteWrapper.update( 176 context, 177 context.getContentResolver(), 178 messageUri, 179 values, 180 null/*where*/, 181 null/*selectionArg*/) != 1) { 182 LogUtil.e(requestId, "persistIfRequired: can not update message. " 183 + MmsService.formatCrossStackMessageId(mMessageId)); 184 } 185 // Delete the corresponding NotificationInd 186 SqliteWrapper.delete(context, 187 context.getContentResolver(), 188 Telephony.Mms.CONTENT_URI, 189 LOCATION_SELECTION, 190 new String[]{ 191 Integer.toString(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND), 192 mLocationUrl 193 }); 194 195 return messageUri; 196 } catch (MmsException e) { 197 LogUtil.e(requestId, "persistIfRequired: can not persist message. " 198 + MmsService.formatCrossStackMessageId(mMessageId), e); 199 } catch (SQLiteException e) { 200 LogUtil.e(requestId, "persistIfRequired: can not update message. " 201 + MmsService.formatCrossStackMessageId(mMessageId), e); 202 } catch (RuntimeException e) { 203 LogUtil.e(requestId, "persistIfRequired: can not parse response. " 204 + MmsService.formatCrossStackMessageId(mMessageId), e); 205 } finally { 206 Binder.restoreCallingIdentity(identity); 207 } 208 return null; 209 } 210 notifyOfDownload(Context context)211 private void notifyOfDownload(Context context) { 212 final Intent intent = new Intent(Telephony.Sms.Intents.MMS_DOWNLOADED_ACTION); 213 intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT); 214 215 // Get a list of currently started users. 216 int[] users = null; 217 try { 218 users = ActivityManager.getService().getRunningUserIds(); 219 } catch (RemoteException re) { 220 } 221 if (users == null) { 222 users = new int[] {UserHandle.ALL.getIdentifier()}; 223 } 224 final UserManager userManager = 225 (UserManager) context.getSystemService(Context.USER_SERVICE); 226 227 // Deliver the broadcast only to those running users that are permitted 228 // by user policy. 229 for (int i = users.length - 1; i >= 0; i--) { 230 UserHandle targetUser = new UserHandle(users[i]); 231 if (users[i] != UserHandle.USER_SYSTEM) { 232 // Is the user not allowed to use SMS? 233 if (userManager.hasUserRestriction(UserManager.DISALLOW_SMS, targetUser)) { 234 continue; 235 } 236 // Skip unknown users and managed profiles as well 237 UserInfo info = userManager.getUserInfo(users[i]); 238 if (info == null || info.isManagedProfile()) { 239 continue; 240 } 241 } 242 context.sendOrderedBroadcastAsUser(intent, targetUser, 243 android.Manifest.permission.RECEIVE_MMS, 244 AppOpsManager.OP_RECEIVE_MMS, 245 null, 246 null, Activity.RESULT_OK, null, null); 247 } 248 } 249 250 /** 251 * Transfer the received response to the caller (for download requests write to content uri) 252 * 253 * @param fillIn the intent that will be returned to the caller 254 * @param response the pdu to transfer 255 */ 256 @Override transferResponse(Intent fillIn, final byte[] response)257 protected boolean transferResponse(Intent fillIn, final byte[] response) { 258 return mRequestManager.writePduToContentUri(mContentUri, response); 259 } 260 261 @Override prepareForHttpRequest()262 protected boolean prepareForHttpRequest() { 263 return true; 264 } 265 266 /** 267 * Try downloading via the carrier app. 268 * 269 * @param context The context 270 * @param carrierMessagingServicePackage The carrier messaging service handling the download 271 */ tryDownloadingByCarrierApp(Context context, String carrierMessagingServicePackage)272 public void tryDownloadingByCarrierApp(Context context, String carrierMessagingServicePackage) { 273 final CarrierDownloadManager carrierDownloadManger = new CarrierDownloadManager(); 274 final CarrierDownloadCompleteCallback downloadCallback = 275 new CarrierDownloadCompleteCallback(context, carrierDownloadManger); 276 carrierDownloadManger.downloadMms(context, carrierMessagingServicePackage, 277 downloadCallback); 278 } 279 280 @Override revokeUriPermission(Context context)281 protected void revokeUriPermission(Context context) { 282 context.revokeUriPermission(mContentUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION); 283 } 284 285 /** 286 * Downloads the MMS through through the carrier app. 287 */ 288 private final class CarrierDownloadManager { 289 // Initialized in downloadMms 290 private volatile CarrierDownloadCompleteCallback mCarrierDownloadCallback; 291 private final CarrierMessagingServiceWrapper mCarrierMessagingServiceWrapper = 292 new CarrierMessagingServiceWrapper(); 293 disposeConnection(Context context)294 void disposeConnection(Context context) { 295 mCarrierMessagingServiceWrapper.disconnect(); 296 } 297 downloadMms(Context context, String carrierMessagingServicePackage, CarrierDownloadCompleteCallback carrierDownloadCallback)298 void downloadMms(Context context, String carrierMessagingServicePackage, 299 CarrierDownloadCompleteCallback carrierDownloadCallback) { 300 mCarrierDownloadCallback = carrierDownloadCallback; 301 if (mCarrierMessagingServiceWrapper.bindToCarrierMessagingService( 302 context, carrierMessagingServicePackage, Runnable::run, 303 ()->onServiceReady())) { 304 LogUtil.v("bindService() for carrier messaging service: " 305 + carrierMessagingServicePackage + " succeeded. " 306 + MmsService.formatCrossStackMessageId(mMessageId)); 307 } else { 308 LogUtil.e("bindService() for carrier messaging service: " 309 + carrierMessagingServicePackage + " failed. " 310 + MmsService.formatCrossStackMessageId(mMessageId)); 311 carrierDownloadCallback.onDownloadMmsComplete( 312 CarrierMessagingService.DOWNLOAD_STATUS_RETRY_ON_CARRIER_NETWORK); 313 } 314 } 315 onServiceReady()316 private void onServiceReady() { 317 try { 318 mCarrierMessagingServiceWrapper.downloadMms( 319 mContentUri, mSubId, Uri.parse(mLocationUrl), Runnable::run, 320 mCarrierDownloadCallback); 321 } catch (RuntimeException e) { 322 LogUtil.e("Exception downloading MMS for " 323 + MmsService.formatCrossStackMessageId(mMessageId) 324 + " using the carrier messaging service: " + e, e); 325 mCarrierDownloadCallback.onDownloadMmsComplete( 326 CarrierMessagingService.DOWNLOAD_STATUS_RETRY_ON_CARRIER_NETWORK); 327 } 328 } 329 } 330 331 /** 332 * A callback which notifies carrier messaging app send result. Once the result is ready, the 333 * carrier messaging service connection is disposed. 334 */ 335 private final class CarrierDownloadCompleteCallback extends 336 MmsRequest.CarrierMmsActionCallback { 337 private final Context mContext; 338 private final CarrierDownloadManager mCarrierDownloadManager; 339 CarrierDownloadCompleteCallback(Context context, CarrierDownloadManager carrierDownloadManager)340 public CarrierDownloadCompleteCallback(Context context, 341 CarrierDownloadManager carrierDownloadManager) { 342 mContext = context; 343 mCarrierDownloadManager = carrierDownloadManager; 344 } 345 346 @Override onSendMmsComplete(int result, byte[] sendConfPdu)347 public void onSendMmsComplete(int result, byte[] sendConfPdu) { 348 LogUtil.e("Unexpected onSendMmsComplete call with result: " + result 349 + ", " + MmsService.formatCrossStackMessageId(mMessageId)); 350 } 351 352 @Override onDownloadMmsComplete(int result)353 public void onDownloadMmsComplete(int result) { 354 LogUtil.d("Carrier app result for download: " + result 355 + ", " + MmsService.formatCrossStackMessageId(mMessageId)); 356 mCarrierDownloadManager.disposeConnection(mContext); 357 358 if (!maybeFallbackToRegularDelivery(result)) { 359 processResult( 360 mContext, 361 toSmsManagerResultForInboundMms(result), 362 null /* response */, 363 0 /* httpStatusCode */, 364 /* handledByCarrierApp= */ true); 365 } 366 } 367 } 368 getPayloadSize()369 protected long getPayloadSize() { 370 long wapSize = 0; 371 try { 372 wapSize = SmsManager.getSmsManagerForSubscriptionId(mSubId) 373 .getWapMessageSize(mLocationUrl); 374 } catch (java.util.NoSuchElementException e) { 375 // The download url wasn't found in the wap push cache. Since we're connected to 376 // a satellite and don't know the size of the download, block the download with a 377 // wapSize of 0. 378 } 379 return wapSize; 380 } 381 } 382