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.AppOpsManager;
21 import android.app.PendingIntent;
22 import android.content.ContentValues;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.net.Uri;
26 import android.os.Binder;
27 import android.os.Bundle;
28 import android.os.RemoteException;
29 import android.os.UserHandle;
30 import android.provider.Telephony;
31 import android.service.carrier.CarrierMessagingService;
32 import android.service.carrier.ICarrierMessagingService;
33 import android.telephony.CarrierMessagingServiceManager;
34 import android.telephony.SmsManager;
35 import android.telephony.TelephonyManager;
36 import android.text.TextUtils;
37 import android.util.Log;
38 
39 import com.android.internal.telephony.SmsApplication;
40 import com.android.mms.service.exception.MmsHttpException;
41 
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.SendConf;
48 import com.google.android.mms.pdu.SendReq;
49 import com.google.android.mms.util.SqliteWrapper;
50 
51 import java.util.List;
52 
53 /**
54  * Request to send an MMS
55  */
56 public class SendRequest extends MmsRequest {
57     private final Uri mPduUri;
58     private byte[] mPduData;
59     private final String mLocationUrl;
60     private final PendingIntent mSentIntent;
61 
SendRequest(RequestManager manager, int subId, Uri contentUri, String locationUrl, PendingIntent sentIntent, String creator, Bundle configOverrides)62     public SendRequest(RequestManager manager, int subId, Uri contentUri, String locationUrl,
63             PendingIntent sentIntent, String creator, Bundle configOverrides) {
64         super(manager, subId, creator, configOverrides);
65         mPduUri = contentUri;
66         mPduData = null;
67         mLocationUrl = locationUrl;
68         mSentIntent = sentIntent;
69     }
70 
71     @Override
doHttp(Context context, MmsNetworkManager netMgr, ApnSettings apn)72     protected byte[] doHttp(Context context, MmsNetworkManager netMgr, ApnSettings apn)
73             throws MmsHttpException {
74         final MmsHttpClient mmsHttpClient = netMgr.getOrCreateHttpClient();
75         if (mmsHttpClient == null) {
76             Log.e(MmsService.TAG, "MMS network is not ready!");
77             throw new MmsHttpException(0/*statusCode*/, "MMS network is not ready");
78         }
79         return mmsHttpClient.execute(
80                 mLocationUrl != null ? mLocationUrl : apn.getMmscUrl(),
81                 mPduData,
82                 MmsHttpClient.METHOD_POST,
83                 apn.isProxySet(),
84                 apn.getProxyAddress(),
85                 apn.getProxyPort(),
86                 mMmsConfig);
87     }
88 
89     @Override
getPendingIntent()90     protected PendingIntent getPendingIntent() {
91         return mSentIntent;
92     }
93 
94     @Override
getQueueType()95     protected int getQueueType() {
96         return MmsService.QUEUE_INDEX_SEND;
97     }
98 
99     @Override
persistIfRequired(Context context, int result, byte[] response)100     protected Uri persistIfRequired(Context context, int result, byte[] response) {
101         if (!SmsApplication.shouldWriteMessageForPackage(mCreator, context)) {
102             // Not required to persist
103             return null;
104         }
105         Log.d(MmsService.TAG, "SendRequest.persistIfRequired");
106         if (mPduData == null) {
107             Log.e(MmsService.TAG, "SendRequest.persistIfRequired: empty PDU");
108             return null;
109         }
110         final long identity = Binder.clearCallingIdentity();
111         try {
112             final boolean supportContentDisposition = mMmsConfig.getSupportMmsContentDisposition();
113             // Persist the request PDU first
114             GenericPdu pdu = (new PduParser(mPduData, supportContentDisposition)).parse();
115             if (pdu == null) {
116                 Log.e(MmsService.TAG, "SendRequest.persistIfRequired: can't parse input PDU");
117                 return null;
118             }
119             if (!(pdu instanceof SendReq)) {
120                 Log.d(MmsService.TAG, "SendRequest.persistIfRequired: not SendReq");
121                 return null;
122             }
123             final PduPersister persister = PduPersister.getPduPersister(context);
124             final Uri messageUri = persister.persist(
125                     pdu,
126                     Telephony.Mms.Sent.CONTENT_URI,
127                     true/*createThreadId*/,
128                     true/*groupMmsEnabled*/,
129                     null/*preOpenedFiles*/);
130             if (messageUri == null) {
131                 Log.e(MmsService.TAG, "SendRequest.persistIfRequired: can not persist message");
132                 return null;
133             }
134             // Update the additional columns based on the send result
135             final ContentValues values = new ContentValues();
136             SendConf sendConf = null;
137             if (response != null && response.length > 0) {
138                 pdu = (new PduParser(response, supportContentDisposition)).parse();
139                 if (pdu != null && pdu instanceof SendConf) {
140                     sendConf = (SendConf) pdu;
141                 }
142             }
143             if (result != Activity.RESULT_OK
144                     || sendConf == null
145                     || sendConf.getResponseStatus() != PduHeaders.RESPONSE_STATUS_OK) {
146                 // Since we can't persist a message directly into FAILED box,
147                 // we have to update the column after we persist it into SENT box.
148                 // The gap between the state change is tiny so I would not expect
149                 // it to cause any serious problem
150                 // TODO: we should add a "failed" URI for this in MmsProvider?
151                 values.put(Telephony.Mms.MESSAGE_BOX, Telephony.Mms.MESSAGE_BOX_FAILED);
152             }
153             if (sendConf != null) {
154                 values.put(Telephony.Mms.RESPONSE_STATUS, sendConf.getResponseStatus());
155                 values.put(Telephony.Mms.MESSAGE_ID,
156                         PduPersister.toIsoString(sendConf.getMessageId()));
157             }
158             values.put(Telephony.Mms.DATE, System.currentTimeMillis() / 1000L);
159             values.put(Telephony.Mms.READ, 1);
160             values.put(Telephony.Mms.SEEN, 1);
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(context, context.getContentResolver(), messageUri, values,
166                     null/*where*/, null/*selectionArg*/) != 1) {
167                 Log.e(MmsService.TAG, "SendRequest.persistIfRequired: failed to update message");
168             }
169             return messageUri;
170         } catch (MmsException e) {
171             Log.e(MmsService.TAG, "SendRequest.persistIfRequired: can not persist message", e);
172         } catch (RuntimeException e) {
173             Log.e(MmsService.TAG, "SendRequest.persistIfRequired: unexpected parsing failure", e);
174         } finally {
175             Binder.restoreCallingIdentity(identity);
176         }
177         return null;
178     }
179 
180     /**
181      * Read the pdu from the file descriptor and cache pdu bytes in request
182      * @return true if pdu read successfully
183      */
readPduFromContentUri()184     private boolean readPduFromContentUri() {
185         if (mPduData != null) {
186             return true;
187         }
188         final int bytesTobeRead = mMmsConfig.getMaxMessageSize();
189         mPduData = mRequestManager.readPduFromContentUri(mPduUri, bytesTobeRead);
190         return (mPduData != null);
191     }
192 
193     /**
194      * Transfer the received response to the caller (for send requests the pdu is small and can
195      *  just include bytes as extra in the "returned" intent).
196      *
197      * @param fillIn the intent that will be returned to the caller
198      * @param response the pdu to transfer
199      */
200     @Override
transferResponse(Intent fillIn, byte[] response)201     protected boolean transferResponse(Intent fillIn, byte[] response) {
202         // SendConf pdus are always small and can be included in the intent
203         if (response != null) {
204             fillIn.putExtra(SmsManager.EXTRA_MMS_DATA, response);
205         }
206         return true;
207     }
208 
209     /**
210      * Read the data from the file descriptor if not yet done
211      * @return whether data successfully read
212      */
213     @Override
prepareForHttpRequest()214     protected boolean prepareForHttpRequest() {
215         return readPduFromContentUri();
216     }
217 
218     /**
219      * Try sending via the carrier app
220      *
221      * @param context the context
222      * @param carrierMessagingServicePackage the carrier messaging service sending the MMS
223      */
trySendingByCarrierApp(Context context, String carrierMessagingServicePackage)224     public void trySendingByCarrierApp(Context context, String carrierMessagingServicePackage) {
225         final CarrierSendManager carrierSendManger = new CarrierSendManager();
226         final CarrierSendCompleteCallback sendCallback = new CarrierSendCompleteCallback(
227                 context, carrierSendManger);
228         carrierSendManger.sendMms(context, carrierMessagingServicePackage, sendCallback);
229     }
230 
231     @Override
revokeUriPermission(Context context)232     protected void revokeUriPermission(Context context) {
233         context.revokeUriPermission(mPduUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
234     }
235 
236     /**
237      * Sends the MMS through through the carrier app.
238      */
239     private final class CarrierSendManager extends CarrierMessagingServiceManager {
240         // Initialized in sendMms
241         private volatile CarrierSendCompleteCallback mCarrierSendCompleteCallback;
242 
sendMms(Context context, String carrierMessagingServicePackage, CarrierSendCompleteCallback carrierSendCompleteCallback)243         void sendMms(Context context, String carrierMessagingServicePackage,
244                 CarrierSendCompleteCallback carrierSendCompleteCallback) {
245             mCarrierSendCompleteCallback = carrierSendCompleteCallback;
246             if (bindToCarrierMessagingService(context, carrierMessagingServicePackage)) {
247                 Log.v(MmsService.TAG, "bindService() for carrier messaging service succeeded");
248             } else {
249                 Log.e(MmsService.TAG, "bindService() for carrier messaging service failed");
250                 carrierSendCompleteCallback.onSendMmsComplete(
251                         CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK,
252                         null /* no sendConfPdu */);
253             }
254         }
255 
256         @Override
onServiceReady(ICarrierMessagingService carrierMessagingService)257         protected void onServiceReady(ICarrierMessagingService carrierMessagingService) {
258             try {
259                 Uri locationUri = null;
260                 if (mLocationUrl != null) {
261                     locationUri = Uri.parse(mLocationUrl);
262                 }
263                 carrierMessagingService.sendMms(mPduUri, mSubId, locationUri,
264                         mCarrierSendCompleteCallback);
265             } catch (RemoteException e) {
266                 Log.e(MmsService.TAG,
267                         "Exception sending MMS using the carrier messaging service: " + e);
268                 mCarrierSendCompleteCallback.onSendMmsComplete(
269                         CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK,
270                         null /* no sendConfPdu */);
271             }
272         }
273     }
274 
275     /**
276      * A callback which notifies carrier messaging app send result. Once the result is ready, the
277      * carrier messaging service connection is disposed.
278      */
279     private final class CarrierSendCompleteCallback extends
280             MmsRequest.CarrierMmsActionCallback {
281         private final Context mContext;
282         private final CarrierSendManager mCarrierSendManager;
283 
CarrierSendCompleteCallback(Context context, CarrierSendManager carrierSendManager)284         public CarrierSendCompleteCallback(Context context, CarrierSendManager carrierSendManager) {
285             mContext = context;
286             mCarrierSendManager = carrierSendManager;
287         }
288 
289         @Override
onSendMmsComplete(int result, byte[] sendConfPdu)290         public void onSendMmsComplete(int result, byte[] sendConfPdu) {
291             Log.d(MmsService.TAG, "Carrier app result for send: " + result);
292             mCarrierSendManager.disposeConnection(mContext);
293 
294             if (!maybeFallbackToRegularDelivery(result)) {
295                 processResult(mContext, toSmsManagerResult(result), sendConfPdu,
296                         0/* httpStatusCode */);
297             }
298         }
299 
300         @Override
onDownloadMmsComplete(int result)301         public void onDownloadMmsComplete(int result) {
302             Log.e(MmsService.TAG, "Unexpected onDownloadMmsComplete call with result: " + result);
303         }
304     }
305 }
306