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