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! messageId: " + mMessageId);
76             throw new MmsHttpException(0/*statusCode*/, "MMS network is not ready. messageId: "
77                     + mMessageId);
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
getPendingIntent()92     protected PendingIntent getPendingIntent() {
93         return mDownloadedIntent;
94     }
95 
96     @Override
getQueueType()97     protected int getQueueType() {
98         return MmsService.QUEUE_INDEX_DOWNLOAD;
99     }
100 
101     @Override
persistIfRequired(Context context, int result, byte[] response)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. messageId: " + mMessageId);
111         if (response == null || response.length < 1) {
112             LogUtil.e(requestId, "persistIfRequired: empty response. messageId: " + mMessageId);
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. messageId: "
122                         + mMessageId);
123                 return null;
124             }
125             final RetrieveConf retrieveConf = (RetrieveConf) pdu;
126             final int status = retrieveConf.getRetrieveStatus();
127             if (status != PduHeaders.RETRIEVE_STATUS_OK) {
128                 LogUtil.e(requestId, "persistIfRequired: retrieve failed " + status
129                         + ", messageId: " + mMessageId);
130                 // Update the retrieve status of the NotificationInd
131                 final ContentValues values = new ContentValues(1);
132                 values.put(Telephony.Mms.RETRIEVE_STATUS, status);
133                 SqliteWrapper.update(
134                         context,
135                         context.getContentResolver(),
136                         Telephony.Mms.CONTENT_URI,
137                         values,
138                         LOCATION_SELECTION,
139                         new String[] {
140                                 Integer.toString(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND),
141                                 mLocationUrl
142                         });
143                 return null;
144             }
145             // Store the downloaded message
146             final PduPersister persister = PduPersister.getPduPersister(context);
147             final Uri messageUri = persister.persist(
148                     pdu,
149                     Telephony.Mms.Inbox.CONTENT_URI,
150                     true/*createThreadId*/,
151                     true/*groupMmsEnabled*/,
152                     null/*preOpenedFiles*/);
153             if (messageUri == null) {
154                 LogUtil.e(requestId, "persistIfRequired: can not persist message. messageId: "
155                         + mMessageId);
156                 return null;
157             }
158             // Update some of the properties of the message
159             final ContentValues values = new ContentValues();
160             values.put(Telephony.Mms.DATE, System.currentTimeMillis() / 1000L);
161             values.put(Telephony.Mms.READ, 0);
162             values.put(Telephony.Mms.SEEN, 0);
163             if (!TextUtils.isEmpty(mCreator)) {
164                 values.put(Telephony.Mms.CREATOR, mCreator);
165             }
166             values.put(Telephony.Mms.SUBSCRIPTION_ID, mSubId);
167             if (SqliteWrapper.update(
168                     context,
169                     context.getContentResolver(),
170                     messageUri,
171                     values,
172                     null/*where*/,
173                     null/*selectionArg*/) != 1) {
174                 LogUtil.e(requestId, "persistIfRequired: can not update message. messageId: "
175                         + mMessageId);
176             }
177             // Delete the corresponding NotificationInd
178             SqliteWrapper.delete(context,
179                     context.getContentResolver(),
180                     Telephony.Mms.CONTENT_URI,
181                     LOCATION_SELECTION,
182                     new String[]{
183                             Integer.toString(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND),
184                             mLocationUrl
185                     });
186 
187             return messageUri;
188         } catch (MmsException e) {
189             LogUtil.e(requestId, "persistIfRequired: can not persist message. messageId: "
190                     + mMessageId, e);
191         } catch (SQLiteException e) {
192             LogUtil.e(requestId, "persistIfRequired: can not update message. messageId: "
193                     + mMessageId, e);
194         } catch (RuntimeException e) {
195             LogUtil.e(requestId, "persistIfRequired: can not parse response. messageId: "
196                     + mMessageId, e);
197         } finally {
198             Binder.restoreCallingIdentity(identity);
199         }
200         return null;
201     }
202 
notifyOfDownload(Context context)203     private void notifyOfDownload(Context context) {
204         final Intent intent = new Intent(Telephony.Sms.Intents.MMS_DOWNLOADED_ACTION);
205         intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
206 
207         // Get a list of currently started users.
208         int[] users = null;
209         try {
210             users = ActivityManager.getService().getRunningUserIds();
211         } catch (RemoteException re) {
212         }
213         if (users == null) {
214             users = new int[] {UserHandle.ALL.getIdentifier()};
215         }
216         final UserManager userManager =
217                 (UserManager) context.getSystemService(Context.USER_SERVICE);
218 
219         // Deliver the broadcast only to those running users that are permitted
220         // by user policy.
221         for (int i = users.length - 1; i >= 0; i--) {
222             UserHandle targetUser = new UserHandle(users[i]);
223             if (users[i] != UserHandle.USER_SYSTEM) {
224                 // Is the user not allowed to use SMS?
225                 if (userManager.hasUserRestriction(UserManager.DISALLOW_SMS, targetUser)) {
226                     continue;
227                 }
228                 // Skip unknown users and managed profiles as well
229                 UserInfo info = userManager.getUserInfo(users[i]);
230                 if (info == null || info.isManagedProfile()) {
231                     continue;
232                 }
233             }
234             context.sendOrderedBroadcastAsUser(intent, targetUser,
235                     android.Manifest.permission.RECEIVE_MMS,
236                     AppOpsManager.OP_RECEIVE_MMS,
237                     null,
238                     null, Activity.RESULT_OK, null, null);
239         }
240     }
241 
242     /**
243      * Transfer the received response to the caller (for download requests write to content uri)
244      *
245      * @param fillIn the intent that will be returned to the caller
246      * @param response the pdu to transfer
247      */
248     @Override
transferResponse(Intent fillIn, final byte[] response)249     protected boolean transferResponse(Intent fillIn, final byte[] response) {
250         return mRequestManager.writePduToContentUri(mContentUri, response);
251     }
252 
253     @Override
prepareForHttpRequest()254     protected boolean prepareForHttpRequest() {
255         return true;
256     }
257 
258     /**
259      * Try downloading via the carrier app.
260      *
261      * @param context The context
262      * @param carrierMessagingServicePackage The carrier messaging service handling the download
263      */
tryDownloadingByCarrierApp(Context context, String carrierMessagingServicePackage)264     public void tryDownloadingByCarrierApp(Context context, String carrierMessagingServicePackage) {
265         final CarrierDownloadManager carrierDownloadManger = new CarrierDownloadManager();
266         final CarrierDownloadCompleteCallback downloadCallback =
267                 new CarrierDownloadCompleteCallback(context, carrierDownloadManger);
268         carrierDownloadManger.downloadMms(context, carrierMessagingServicePackage,
269                 downloadCallback);
270     }
271 
272     @Override
revokeUriPermission(Context context)273     protected void revokeUriPermission(Context context) {
274         context.revokeUriPermission(mContentUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
275     }
276 
277     /**
278      * Downloads the MMS through through the carrier app.
279      */
280     private final class CarrierDownloadManager extends CarrierMessagingServiceWrapper {
281         // Initialized in downloadMms
282         private volatile CarrierDownloadCompleteCallback mCarrierDownloadCallback;
283 
downloadMms(Context context, String carrierMessagingServicePackage, CarrierDownloadCompleteCallback carrierDownloadCallback)284         void downloadMms(Context context, String carrierMessagingServicePackage,
285                 CarrierDownloadCompleteCallback carrierDownloadCallback) {
286             mCarrierDownloadCallback = carrierDownloadCallback;
287             if (bindToCarrierMessagingService(context, carrierMessagingServicePackage)) {
288                 LogUtil.v("bindService() for carrier messaging service succeeded. messageId: "
289                         + mMessageId);
290             } else {
291                 LogUtil.e("bindService() for carrier messaging service failed. messageId: "
292                         + mMessageId);
293                 carrierDownloadCallback.onDownloadMmsComplete(
294                         CarrierMessagingService.DOWNLOAD_STATUS_RETRY_ON_CARRIER_NETWORK);
295             }
296         }
297 
298         @Override
onServiceReady()299         public void onServiceReady() {
300             try {
301                 downloadMms(mContentUri, mSubId, Uri.parse(mLocationUrl),
302                         mCarrierDownloadCallback);
303             } catch (RuntimeException e) {
304                 LogUtil.e("Exception downloading MMS for messageId " + mMessageId
305                         + " using the carrier messaging service: " + e, e);
306                 mCarrierDownloadCallback.onDownloadMmsComplete(
307                         CarrierMessagingService.DOWNLOAD_STATUS_RETRY_ON_CARRIER_NETWORK);
308             }
309         }
310     }
311 
312     /**
313      * A callback which notifies carrier messaging app send result. Once the result is ready, the
314      * carrier messaging service connection is disposed.
315      */
316     private final class CarrierDownloadCompleteCallback extends
317             MmsRequest.CarrierMmsActionCallback {
318         private final Context mContext;
319         private final CarrierDownloadManager mCarrierDownloadManager;
320 
CarrierDownloadCompleteCallback(Context context, CarrierDownloadManager carrierDownloadManager)321         public CarrierDownloadCompleteCallback(Context context,
322                 CarrierDownloadManager carrierDownloadManager) {
323             mContext = context;
324             mCarrierDownloadManager = carrierDownloadManager;
325         }
326 
327         @Override
onSendMmsComplete(int result, byte[] sendConfPdu)328         public void onSendMmsComplete(int result, byte[] sendConfPdu) {
329             LogUtil.e("Unexpected onSendMmsComplete call with result: " + result
330                     + ", messageId: " + mMessageId);
331         }
332 
333         @Override
onDownloadMmsComplete(int result)334         public void onDownloadMmsComplete(int result) {
335             LogUtil.d("Carrier app result for download: " + result
336                     + ", messageId: " + mMessageId);
337             mCarrierDownloadManager.disposeConnection(mContext);
338 
339             if (!maybeFallbackToRegularDelivery(result)) {
340                 processResult(mContext, toSmsManagerResult(result), null/* response */,
341                         0/* httpStatusCode */);
342             }
343         }
344     }
345 }
346