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