1/*
2 * Copyright (C) 2015 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
17package com.android.messaging.sms;
18
19import android.app.Activity;
20import android.app.PendingIntent;
21import android.content.Context;
22import android.content.Intent;
23import android.net.Uri;
24import android.os.Bundle;
25import android.support.v7.mms.MmsManager;
26import android.telephony.SmsManager;
27
28import com.android.messaging.datamodel.MmsFileProvider;
29import com.android.messaging.datamodel.action.SendMessageAction;
30import com.android.messaging.datamodel.data.MessageData;
31import com.android.messaging.mmslib.InvalidHeaderValueException;
32import com.android.messaging.mmslib.pdu.AcknowledgeInd;
33import com.android.messaging.mmslib.pdu.EncodedStringValue;
34import com.android.messaging.mmslib.pdu.GenericPdu;
35import com.android.messaging.mmslib.pdu.NotifyRespInd;
36import com.android.messaging.mmslib.pdu.PduComposer;
37import com.android.messaging.mmslib.pdu.PduHeaders;
38import com.android.messaging.mmslib.pdu.PduParser;
39import com.android.messaging.mmslib.pdu.RetrieveConf;
40import com.android.messaging.mmslib.pdu.SendConf;
41import com.android.messaging.mmslib.pdu.SendReq;
42import com.android.messaging.receiver.SendStatusReceiver;
43import com.android.messaging.util.Assert;
44import com.android.messaging.util.LogUtil;
45import com.android.messaging.util.PhoneUtils;
46
47import java.io.File;
48import java.io.FileOutputStream;
49import java.io.IOException;
50
51/**
52 * Class that sends chat message via MMS.
53 *
54 * The interface emulates a blocking send similar to making an HTTP request.
55 */
56public class MmsSender {
57    private static final String TAG = LogUtil.BUGLE_TAG;
58
59    /**
60     * Send an MMS message.
61     *
62     * @param context Context
63     * @param messageUri The unique URI of the message for identifying it during sending
64     * @param sendReq The SendReq PDU of the message
65     * @throws MmsFailureException
66     */
67    public static void sendMms(final Context context, final int subId, final Uri messageUri,
68            final SendReq sendReq, final Bundle sentIntentExras) throws MmsFailureException {
69        sendMms(context,
70                subId,
71                messageUri,
72                null /* locationUrl */,
73                sendReq,
74                true /* responseImportant */,
75                sentIntentExras);
76    }
77
78    /**
79     * Send NotifyRespInd (response to mms auto download).
80     *
81     * @param context Context
82     * @param subId subscription to use to send the response
83     * @param transactionId The transaction id of the MMS message
84     * @param contentLocation The url of the MMS message
85     * @param status The status to send with the NotifyRespInd
86     * @throws MmsFailureException
87     * @throws InvalidHeaderValueException
88     */
89    public static void sendNotifyResponseForMmsDownload(final Context context, final int subId,
90            final byte[] transactionId, final String contentLocation, final int status)
91            throws MmsFailureException, InvalidHeaderValueException {
92        // Create the M-NotifyResp.ind
93        final NotifyRespInd notifyRespInd = new NotifyRespInd(
94                PduHeaders.CURRENT_MMS_VERSION, transactionId, status);
95        final Uri messageUri = Uri.parse(contentLocation);
96        // Pack M-NotifyResp.ind and send it
97        sendMms(context,
98                subId,
99                messageUri,
100                MmsConfig.get(subId).getNotifyWapMMSC() ? contentLocation : null,
101                notifyRespInd,
102                false /* responseImportant */,
103                null /* sentIntentExtras */);
104    }
105
106    /**
107     * Send AcknowledgeInd (response to mms manual download). Ignore failures.
108     *
109     * @param context Context
110     * @param subId The SIM's subId we are currently using
111     * @param transactionId The transaction id of the MMS message
112     * @param contentLocation The url of the MMS message
113     * @throws MmsFailureException
114     * @throws InvalidHeaderValueException
115     */
116    public static void sendAcknowledgeForMmsDownload(final Context context, final int subId,
117            final byte[] transactionId, final String contentLocation)
118            throws MmsFailureException, InvalidHeaderValueException {
119        final String selfNumber = PhoneUtils.get(subId).getCanonicalForSelf(true/*allowOverride*/);
120        // Create the M-Acknowledge.ind
121        final AcknowledgeInd acknowledgeInd = new AcknowledgeInd(PduHeaders.CURRENT_MMS_VERSION,
122                transactionId);
123        acknowledgeInd.setFrom(new EncodedStringValue(selfNumber));
124        final Uri messageUri = Uri.parse(contentLocation);
125        // Sending
126        sendMms(context,
127                subId,
128                messageUri,
129                MmsConfig.get(subId).getNotifyWapMMSC() ? contentLocation : null,
130                acknowledgeInd,
131                false /*responseImportant*/,
132                null /* sentIntentExtras */);
133    }
134
135    /**
136     * Send a generic PDU.
137     *
138     * @param context Context
139     * @param messageUri The unique URI of the message for identifying it during sending
140     * @param locationUrl The optional URL to send to
141     * @param pdu The PDU to send
142     * @param responseImportant If the sending response is important. Responses to the
143     * Sending of AcknowledgeInd and NotifyRespInd are not important.
144     * @throws MmsFailureException
145     */
146    private static void sendMms(final Context context, final int subId, final Uri messageUri,
147            final String locationUrl, final GenericPdu pdu, final boolean responseImportant,
148            final Bundle sentIntentExtras) throws MmsFailureException {
149        // Write PDU to temporary file to send to platform
150        final Uri contentUri = writePduToTempFile(context, pdu, subId);
151
152        // Construct PendingIntent that will notify us when message sending is complete
153        final Intent sentIntent = new Intent(SendStatusReceiver.MMS_SENT_ACTION,
154                messageUri,
155                context,
156                SendStatusReceiver.class);
157        sentIntent.putExtra(SendMessageAction.EXTRA_CONTENT_URI, contentUri);
158        sentIntent.putExtra(SendMessageAction.EXTRA_RESPONSE_IMPORTANT, responseImportant);
159        if (sentIntentExtras != null) {
160            sentIntent.putExtras(sentIntentExtras);
161        }
162        final PendingIntent sentPendingIntent = PendingIntent.getBroadcast(
163                context,
164                0 /*request code*/,
165                sentIntent,
166                PendingIntent.FLAG_UPDATE_CURRENT);
167
168        // Send the message
169        MmsManager.sendMultimediaMessage(subId, context, contentUri, locationUrl,
170                sentPendingIntent);
171    }
172
173    private static Uri writePduToTempFile(final Context context, final GenericPdu pdu, int subId)
174            throws MmsFailureException {
175        final Uri contentUri = MmsFileProvider.buildRawMmsUri();
176        final File tempFile = MmsFileProvider.getFile(contentUri);
177        FileOutputStream writer = null;
178        try {
179            // Ensure rawmms directory exists
180            tempFile.getParentFile().mkdirs();
181            writer = new FileOutputStream(tempFile);
182            final byte[] pduBytes = new PduComposer(context, pdu).make();
183            if (pduBytes == null) {
184                throw new MmsFailureException(
185                        MmsUtils.MMS_REQUEST_NO_RETRY, "Failed to compose PDU");
186            }
187            if (pduBytes.length > MmsConfig.get(subId).getMaxMessageSize()) {
188                throw new MmsFailureException(
189                        MmsUtils.MMS_REQUEST_NO_RETRY,
190                        MessageData.RAW_TELEPHONY_STATUS_MESSAGE_TOO_BIG);
191            }
192            writer.write(pduBytes);
193        } catch (final IOException e) {
194            if (tempFile != null) {
195                tempFile.delete();
196            }
197            LogUtil.e(TAG, "Cannot create temporary file " + tempFile.getAbsolutePath(), e);
198            throw new MmsFailureException(
199                    MmsUtils.MMS_REQUEST_AUTO_RETRY, "Cannot create raw mms file");
200        } catch (final OutOfMemoryError e) {
201            if (tempFile != null) {
202                tempFile.delete();
203            }
204            LogUtil.e(TAG, "Out of memory in composing PDU", e);
205            throw new MmsFailureException(
206                    MmsUtils.MMS_REQUEST_MANUAL_RETRY,
207                    MessageData.RAW_TELEPHONY_STATUS_MESSAGE_TOO_BIG);
208        } finally {
209            if (writer != null) {
210                try {
211                    writer.close();
212                } catch (final IOException e) {
213                    // no action we can take here
214                }
215            }
216        }
217        return contentUri;
218    }
219
220    public static SendConf parseSendConf(byte[] response, int subId) {
221        if (response != null) {
222            final GenericPdu respPdu = new PduParser(
223                    response, MmsConfig.get(subId).getSupportMmsContentDisposition()).parse();
224            if (respPdu != null) {
225                if (respPdu instanceof SendConf) {
226                    return (SendConf) respPdu;
227                } else {
228                    LogUtil.e(TAG, "MmsSender: send response not SendConf");
229                }
230            } else {
231                // Invalid PDU
232                LogUtil.e(TAG, "MmsSender: send invalid response");
233            }
234        }
235        // Empty or invalid response
236        return null;
237    }
238
239    /**
240     * Download an MMS message.
241     *
242     * @param context Context
243     * @param contentLocation The url of the MMS message
244     * @throws MmsFailureException
245     * @throws InvalidHeaderValueException
246     */
247    public static void downloadMms(final Context context, final int subId,
248            final String contentLocation, Bundle extras) throws MmsFailureException,
249            InvalidHeaderValueException {
250        final Uri requestUri = Uri.parse(contentLocation);
251        final Uri contentUri = MmsFileProvider.buildRawMmsUri();
252
253        final Intent downloadedIntent = new Intent(SendStatusReceiver.MMS_DOWNLOADED_ACTION,
254                requestUri,
255                context,
256                SendStatusReceiver.class);
257        downloadedIntent.putExtra(SendMessageAction.EXTRA_CONTENT_URI, contentUri);
258        if (extras != null) {
259            downloadedIntent.putExtras(extras);
260        }
261        final PendingIntent downloadedPendingIntent = PendingIntent.getBroadcast(
262                context,
263                0 /*request code*/,
264                downloadedIntent,
265                PendingIntent.FLAG_UPDATE_CURRENT);
266
267        MmsManager.downloadMultimediaMessage(subId, context, contentLocation, contentUri,
268                downloadedPendingIntent);
269    }
270
271    public static RetrieveConf parseRetrieveConf(byte[] data, int subId) {
272        if (data != null) {
273            final GenericPdu pdu = new PduParser(
274                    data, MmsConfig.get(subId).getSupportMmsContentDisposition()).parse();
275            if (pdu != null) {
276                if (pdu instanceof RetrieveConf) {
277                    return (RetrieveConf) pdu;
278                } else {
279                    LogUtil.e(TAG, "MmsSender: downloaded pdu not RetrieveConf: "
280                            + pdu.getClass().getName());
281                }
282            } else {
283                LogUtil.e(TAG, "MmsSender: downloaded pdu could not be parsed (invalid)");
284            }
285        }
286        LogUtil.e(TAG, "MmsSender: downloaded pdu is empty");
287        return null;
288    }
289
290    // Process different result code from platform MMS service
291    public static int getErrorResultStatus(int resultCode, int httpStatusCode) {
292        Assert.isFalse(resultCode == Activity.RESULT_OK);
293        switch (resultCode) {
294            case SmsManager.MMS_ERROR_UNABLE_CONNECT_MMS:
295            case SmsManager.MMS_ERROR_IO_ERROR:
296                return MmsUtils.MMS_REQUEST_AUTO_RETRY;
297            case SmsManager.MMS_ERROR_INVALID_APN:
298            case SmsManager.MMS_ERROR_CONFIGURATION_ERROR:
299            case SmsManager.MMS_ERROR_NO_DATA_NETWORK:
300            case SmsManager.MMS_ERROR_UNSPECIFIED:
301                return MmsUtils.MMS_REQUEST_MANUAL_RETRY;
302            case SmsManager.MMS_ERROR_HTTP_FAILURE:
303                if (httpStatusCode == 404) {
304                    return MmsUtils.MMS_REQUEST_NO_RETRY;
305                } else {
306                    return MmsUtils.MMS_REQUEST_AUTO_RETRY;
307                }
308            default:
309                return MmsUtils.MMS_REQUEST_MANUAL_RETRY;
310        }
311    }
312}
313