| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.messaging.sms; |
| |
| import android.app.Activity; |
| import android.app.PendingIntent; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import androidx.appcompat.mms.MmsManager; |
| import android.telephony.SmsManager; |
| |
| import com.android.messaging.datamodel.MmsFileProvider; |
| import com.android.messaging.datamodel.action.SendMessageAction; |
| import com.android.messaging.datamodel.data.MessageData; |
| import com.android.messaging.mmslib.InvalidHeaderValueException; |
| import com.android.messaging.mmslib.pdu.AcknowledgeInd; |
| import com.android.messaging.mmslib.pdu.EncodedStringValue; |
| import com.android.messaging.mmslib.pdu.GenericPdu; |
| import com.android.messaging.mmslib.pdu.NotifyRespInd; |
| import com.android.messaging.mmslib.pdu.PduComposer; |
| import com.android.messaging.mmslib.pdu.PduHeaders; |
| import com.android.messaging.mmslib.pdu.PduParser; |
| import com.android.messaging.mmslib.pdu.RetrieveConf; |
| import com.android.messaging.mmslib.pdu.SendConf; |
| import com.android.messaging.mmslib.pdu.SendReq; |
| import com.android.messaging.receiver.SendStatusReceiver; |
| import com.android.messaging.util.Assert; |
| import com.android.messaging.util.LogUtil; |
| import com.android.messaging.util.PhoneUtils; |
| |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| |
| /** |
| * Class that sends chat message via MMS. |
| * |
| * The interface emulates a blocking send similar to making an HTTP request. |
| */ |
| public class MmsSender { |
| private static final String TAG = LogUtil.BUGLE_TAG; |
| |
| /** |
| * Send an MMS message. |
| * |
| * @param context Context |
| * @param messageUri The unique URI of the message for identifying it during sending |
| * @param sendReq The SendReq PDU of the message |
| * @throws MmsFailureException |
| */ |
| public static void sendMms(final Context context, final int subId, final Uri messageUri, |
| final SendReq sendReq, final Bundle sentIntentExras) throws MmsFailureException { |
| sendMms(context, |
| subId, |
| messageUri, |
| null /* locationUrl */, |
| sendReq, |
| true /* responseImportant */, |
| sentIntentExras); |
| } |
| |
| /** |
| * Send NotifyRespInd (response to mms auto download). |
| * |
| * @param context Context |
| * @param subId subscription to use to send the response |
| * @param transactionId The transaction id of the MMS message |
| * @param contentLocation The url of the MMS message |
| * @param status The status to send with the NotifyRespInd |
| * @throws MmsFailureException |
| * @throws InvalidHeaderValueException |
| */ |
| public static void sendNotifyResponseForMmsDownload(final Context context, final int subId, |
| final byte[] transactionId, final String contentLocation, final int status) |
| throws MmsFailureException, InvalidHeaderValueException { |
| // Create the M-NotifyResp.ind |
| final NotifyRespInd notifyRespInd = new NotifyRespInd( |
| PduHeaders.CURRENT_MMS_VERSION, transactionId, status); |
| final Uri messageUri = Uri.parse(contentLocation); |
| // Pack M-NotifyResp.ind and send it |
| sendMms(context, |
| subId, |
| messageUri, |
| MmsConfig.get(subId).getNotifyWapMMSC() ? contentLocation : null, |
| notifyRespInd, |
| false /* responseImportant */, |
| null /* sentIntentExtras */); |
| } |
| |
| /** |
| * Send AcknowledgeInd (response to mms manual download). Ignore failures. |
| * |
| * @param context Context |
| * @param subId The SIM's subId we are currently using |
| * @param transactionId The transaction id of the MMS message |
| * @param contentLocation The url of the MMS message |
| * @throws MmsFailureException |
| * @throws InvalidHeaderValueException |
| */ |
| public static void sendAcknowledgeForMmsDownload(final Context context, final int subId, |
| final byte[] transactionId, final String contentLocation) |
| throws MmsFailureException, InvalidHeaderValueException { |
| final String selfNumber = PhoneUtils.get(subId).getCanonicalForSelf(true/*allowOverride*/); |
| // Create the M-Acknowledge.ind |
| final AcknowledgeInd acknowledgeInd = new AcknowledgeInd(PduHeaders.CURRENT_MMS_VERSION, |
| transactionId); |
| acknowledgeInd.setFrom(new EncodedStringValue(selfNumber)); |
| final Uri messageUri = Uri.parse(contentLocation); |
| // Sending |
| sendMms(context, |
| subId, |
| messageUri, |
| MmsConfig.get(subId).getNotifyWapMMSC() ? contentLocation : null, |
| acknowledgeInd, |
| false /*responseImportant*/, |
| null /* sentIntentExtras */); |
| } |
| |
| /** |
| * Send a generic PDU. |
| * |
| * @param context Context |
| * @param messageUri The unique URI of the message for identifying it during sending |
| * @param locationUrl The optional URL to send to |
| * @param pdu The PDU to send |
| * @param responseImportant If the sending response is important. Responses to the |
| * Sending of AcknowledgeInd and NotifyRespInd are not important. |
| * @throws MmsFailureException |
| */ |
| private static void sendMms(final Context context, final int subId, final Uri messageUri, |
| final String locationUrl, final GenericPdu pdu, final boolean responseImportant, |
| final Bundle sentIntentExtras) throws MmsFailureException { |
| // Write PDU to temporary file to send to platform |
| final Uri contentUri = writePduToTempFile(context, pdu, subId); |
| |
| // Construct PendingIntent that will notify us when message sending is complete |
| final Intent sentIntent = new Intent(SendStatusReceiver.MMS_SENT_ACTION, |
| messageUri, |
| context, |
| SendStatusReceiver.class); |
| sentIntent.putExtra(SendMessageAction.EXTRA_CONTENT_URI, contentUri); |
| sentIntent.putExtra(SendMessageAction.EXTRA_RESPONSE_IMPORTANT, responseImportant); |
| if (sentIntentExtras != null) { |
| sentIntent.putExtras(sentIntentExtras); |
| } |
| final PendingIntent sentPendingIntent = PendingIntent.getBroadcast( |
| context, |
| 0 /*request code*/, |
| sentIntent, |
| PendingIntent.FLAG_UPDATE_CURRENT); |
| |
| // Send the message |
| MmsManager.sendMultimediaMessage(subId, context, contentUri, locationUrl, |
| sentPendingIntent); |
| } |
| |
| private static Uri writePduToTempFile(final Context context, final GenericPdu pdu, int subId) |
| throws MmsFailureException { |
| final Uri contentUri = MmsFileProvider.buildRawMmsUri(); |
| final File tempFile = MmsFileProvider.getFile(contentUri); |
| FileOutputStream writer = null; |
| try { |
| // Ensure rawmms directory exists |
| tempFile.getParentFile().mkdirs(); |
| writer = new FileOutputStream(tempFile); |
| final byte[] pduBytes = new PduComposer(context, pdu).make(); |
| if (pduBytes == null) { |
| throw new MmsFailureException( |
| MmsUtils.MMS_REQUEST_NO_RETRY, "Failed to compose PDU"); |
| } |
| if (pduBytes.length > MmsConfig.get(subId).getMaxMessageSize()) { |
| throw new MmsFailureException( |
| MmsUtils.MMS_REQUEST_NO_RETRY, |
| MessageData.RAW_TELEPHONY_STATUS_MESSAGE_TOO_BIG); |
| } |
| writer.write(pduBytes); |
| } catch (final IOException e) { |
| if (tempFile != null) { |
| tempFile.delete(); |
| } |
| LogUtil.e(TAG, "Cannot create temporary file " + tempFile.getAbsolutePath(), e); |
| throw new MmsFailureException( |
| MmsUtils.MMS_REQUEST_AUTO_RETRY, "Cannot create raw mms file"); |
| } catch (final OutOfMemoryError e) { |
| if (tempFile != null) { |
| tempFile.delete(); |
| } |
| LogUtil.e(TAG, "Out of memory in composing PDU", e); |
| throw new MmsFailureException( |
| MmsUtils.MMS_REQUEST_MANUAL_RETRY, |
| MessageData.RAW_TELEPHONY_STATUS_MESSAGE_TOO_BIG); |
| } finally { |
| if (writer != null) { |
| try { |
| writer.close(); |
| } catch (final IOException e) { |
| // no action we can take here |
| } |
| } |
| } |
| return contentUri; |
| } |
| |
| public static SendConf parseSendConf(byte[] response, int subId) { |
| if (response != null) { |
| final GenericPdu respPdu = new PduParser( |
| response, MmsConfig.get(subId).getSupportMmsContentDisposition()).parse(); |
| if (respPdu != null) { |
| if (respPdu instanceof SendConf) { |
| return (SendConf) respPdu; |
| } else { |
| LogUtil.e(TAG, "MmsSender: send response not SendConf"); |
| } |
| } else { |
| // Invalid PDU |
| LogUtil.e(TAG, "MmsSender: send invalid response"); |
| } |
| } |
| // Empty or invalid response |
| return null; |
| } |
| |
| /** |
| * Download an MMS message. |
| * |
| * @param context Context |
| * @param contentLocation The url of the MMS message |
| * @throws MmsFailureException |
| * @throws InvalidHeaderValueException |
| */ |
| public static void downloadMms(final Context context, final int subId, |
| final String contentLocation, Bundle extras) throws MmsFailureException, |
| InvalidHeaderValueException { |
| final Uri requestUri = Uri.parse(contentLocation); |
| final Uri contentUri = MmsFileProvider.buildRawMmsUri(); |
| |
| final Intent downloadedIntent = new Intent(SendStatusReceiver.MMS_DOWNLOADED_ACTION, |
| requestUri, |
| context, |
| SendStatusReceiver.class); |
| downloadedIntent.putExtra(SendMessageAction.EXTRA_CONTENT_URI, contentUri); |
| if (extras != null) { |
| downloadedIntent.putExtras(extras); |
| } |
| final PendingIntent downloadedPendingIntent = PendingIntent.getBroadcast( |
| context, |
| 0 /*request code*/, |
| downloadedIntent, |
| PendingIntent.FLAG_UPDATE_CURRENT); |
| |
| MmsManager.downloadMultimediaMessage(subId, context, contentLocation, contentUri, |
| downloadedPendingIntent); |
| } |
| |
| public static RetrieveConf parseRetrieveConf(byte[] data, int subId) { |
| if (data != null) { |
| final GenericPdu pdu = new PduParser( |
| data, MmsConfig.get(subId).getSupportMmsContentDisposition()).parse(); |
| if (pdu != null) { |
| if (pdu instanceof RetrieveConf) { |
| return (RetrieveConf) pdu; |
| } else { |
| LogUtil.e(TAG, "MmsSender: downloaded pdu not RetrieveConf: " |
| + pdu.getClass().getName()); |
| } |
| } else { |
| LogUtil.e(TAG, "MmsSender: downloaded pdu could not be parsed (invalid)"); |
| } |
| } |
| LogUtil.e(TAG, "MmsSender: downloaded pdu is empty"); |
| return null; |
| } |
| |
| // Process different result code from platform MMS service |
| public static int getErrorResultStatus(int resultCode, int httpStatusCode) { |
| Assert.isFalse(resultCode == Activity.RESULT_OK); |
| switch (resultCode) { |
| case SmsManager.MMS_ERROR_UNABLE_CONNECT_MMS: |
| case SmsManager.MMS_ERROR_IO_ERROR: |
| return MmsUtils.MMS_REQUEST_AUTO_RETRY; |
| case SmsManager.MMS_ERROR_INVALID_APN: |
| case SmsManager.MMS_ERROR_CONFIGURATION_ERROR: |
| case SmsManager.MMS_ERROR_NO_DATA_NETWORK: |
| case SmsManager.MMS_ERROR_UNSPECIFIED: |
| return MmsUtils.MMS_REQUEST_MANUAL_RETRY; |
| case SmsManager.MMS_ERROR_HTTP_FAILURE: |
| if (httpStatusCode == 404) { |
| return MmsUtils.MMS_REQUEST_NO_RETRY; |
| } else { |
| return MmsUtils.MMS_REQUEST_AUTO_RETRY; |
| } |
| default: |
| return MmsUtils.MMS_REQUEST_MANUAL_RETRY; |
| } |
| } |
| } |