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