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.ICarrierMessagingService;
37 import android.telephony.CarrierMessagingServiceManager;
38 import android.telephony.SmsManager;
39 import android.text.TextUtils;
40 
41 import com.android.mms.service.exception.MmsHttpException;
42 import com.google.android.mms.MmsException;
43 import com.google.android.mms.pdu.GenericPdu;
44 import com.google.android.mms.pdu.PduHeaders;
45 import com.google.android.mms.pdu.PduParser;
46 import com.google.android.mms.pdu.PduPersister;
47 import com.google.android.mms.pdu.RetrieveConf;
48 import com.google.android.mms.util.SqliteWrapper;
49 
50 /**
51  * Request to download an MMS
52  */
53 public 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 
DownloadRequest(RequestManager manager, int subId, String locationUrl, Uri contentUri, PendingIntent downloadedIntent, String creator, Bundle configOverrides, Context context)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
doHttp(Context context, MmsNetworkManager netMgr, ApnSettings apn)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
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");
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 
notifyOfDownload(Context context)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
transferResponse(Intent fillIn, final byte[] response)242     protected boolean transferResponse(Intent fillIn, final byte[] response) {
243         return mRequestManager.writePduToContentUri(mContentUri, response);
244     }
245 
246     @Override
prepareForHttpRequest()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      */
tryDownloadingByCarrierApp(Context context, String carrierMessagingServicePackage)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
revokeUriPermission(Context context)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 
downloadMms(Context context, String carrierMessagingServicePackage, CarrierDownloadCompleteCallback carrierDownloadCallback)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
onServiceReady(ICarrierMessagingService carrierMessagingService)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 
CarrierDownloadCompleteCallback(Context context, CarrierDownloadManager carrierDownloadManager)311         public CarrierDownloadCompleteCallback(Context context,
312                 CarrierDownloadManager carrierDownloadManager) {
313             mContext = context;
314             mCarrierDownloadManager = carrierDownloadManager;
315         }
316 
317         @Override
onSendMmsComplete(int result, byte[] sendConfPdu)318         public void onSendMmsComplete(int result, byte[] sendConfPdu) {
319             LogUtil.e("Unexpected onSendMmsComplete call with result: " + result);
320         }
321 
322         @Override
onDownloadMmsComplete(int result)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