• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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