Home | History | Annotate | Download | only in sms
      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 
     17 package com.android.messaging.sms;
     18 
     19 import android.app.Activity;
     20 import android.app.PendingIntent;
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.net.Uri;
     24 import android.os.Bundle;
     25 import android.support.v7.mms.MmsManager;
     26 import android.telephony.SmsManager;
     27 
     28 import com.android.messaging.datamodel.MmsFileProvider;
     29 import com.android.messaging.datamodel.action.SendMessageAction;
     30 import com.android.messaging.datamodel.data.MessageData;
     31 import com.android.messaging.mmslib.InvalidHeaderValueException;
     32 import com.android.messaging.mmslib.pdu.AcknowledgeInd;
     33 import com.android.messaging.mmslib.pdu.EncodedStringValue;
     34 import com.android.messaging.mmslib.pdu.GenericPdu;
     35 import com.android.messaging.mmslib.pdu.NotifyRespInd;
     36 import com.android.messaging.mmslib.pdu.PduComposer;
     37 import com.android.messaging.mmslib.pdu.PduHeaders;
     38 import com.android.messaging.mmslib.pdu.PduParser;
     39 import com.android.messaging.mmslib.pdu.RetrieveConf;
     40 import com.android.messaging.mmslib.pdu.SendConf;
     41 import com.android.messaging.mmslib.pdu.SendReq;
     42 import com.android.messaging.receiver.SendStatusReceiver;
     43 import com.android.messaging.util.Assert;
     44 import com.android.messaging.util.LogUtil;
     45 import com.android.messaging.util.PhoneUtils;
     46 
     47 import java.io.File;
     48 import java.io.FileOutputStream;
     49 import 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  */
     56 public 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