/* * 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.datamodel.action; import android.content.ContentValues; import android.content.Context; import android.net.Uri; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import com.android.messaging.Factory; import com.android.messaging.datamodel.BugleDatabaseOperations; import com.android.messaging.datamodel.DataModel; import com.android.messaging.datamodel.DatabaseHelper.MessageColumns; import com.android.messaging.datamodel.DatabaseWrapper; import com.android.messaging.datamodel.MessagingContentProvider; import com.android.messaging.datamodel.SyncManager; import com.android.messaging.datamodel.data.MessageData; import com.android.messaging.datamodel.data.ParticipantData; import com.android.messaging.sms.MmsUtils; import com.android.messaging.util.Assert; import com.android.messaging.util.Assert.RunsOnMainThread; import com.android.messaging.util.LogUtil; /** * Downloads an MMS message. *
* This class is public (not package-private) because the SMS/MMS (e.g. MmsUtils) classes need to
* access the EXTRA_* fields for setting up the 'downloaded' pending intent.
*/
public class DownloadMmsAction extends Action implements Parcelable {
private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
/**
* Interface for DownloadMmsAction listeners
*/
public interface DownloadMmsActionListener {
@RunsOnMainThread
abstract void onDownloadMessageStarting(final ActionMonitor monitor,
final Object data, final MessageData message);
@RunsOnMainThread
abstract void onDownloadMessageSucceeded(final ActionMonitor monitor,
final Object data, final MessageData message);
@RunsOnMainThread
abstract void onDownloadMessageFailed(final ActionMonitor monitor,
final Object data, final MessageData message);
}
/**
* Queue download of an mms notification message (can only be called during execute of action)
*/
static boolean queueMmsForDownloadInBackground(final String messageId,
final Action processingAction) {
// When this method is being called, it is always from auto download
final DownloadMmsAction action = new DownloadMmsAction();
// This could queue nothing
return action.queueAction(messageId, processingAction);
}
private static final String KEY_MESSAGE_ID = "message_id";
private static final String KEY_CONVERSATION_ID = "conversation_id";
private static final String KEY_PARTICIPANT_ID = "participant_id";
private static final String KEY_CONTENT_LOCATION = "content_location";
private static final String KEY_TRANSACTION_ID = "transaction_id";
private static final String KEY_NOTIFICATION_URI = "notification_uri";
private static final String KEY_SUB_ID = "sub_id";
private static final String KEY_SUB_PHONE_NUMBER = "sub_phone_number";
private static final String KEY_AUTO_DOWNLOAD = "auto_download";
private static final String KEY_FAILURE_STATUS = "failure_status";
private static final String KEY_EXPIRY = "expiry";
// Values we attach to the pending intent that's fired when the message is downloaded.
// Only applicable when downloading via the platform APIs on L+.
public static final String EXTRA_MESSAGE_ID = "message_id";
public static final String EXTRA_CONTENT_URI = "content_uri";
public static final String EXTRA_NOTIFICATION_URI = "notification_uri";
public static final String EXTRA_SUB_ID = "sub_id";
public static final String EXTRA_SUB_PHONE_NUMBER = "sub_phone_number";
public static final String EXTRA_TRANSACTION_ID = "transaction_id";
public static final String EXTRA_CONTENT_LOCATION = "content_location";
public static final String EXTRA_AUTO_DOWNLOAD = "auto_download";
public static final String EXTRA_RECEIVED_TIMESTAMP = "received_timestamp";
public static final String EXTRA_CONVERSATION_ID = "conversation_id";
public static final String EXTRA_PARTICIPANT_ID = "participant_id";
public static final String EXTRA_STATUS_IF_FAILED = "status_if_failed";
public static final String EXTRA_EXPIRY = "expiry";
private DownloadMmsAction() {
super();
}
@Override
protected Object executeAction() {
Assert.fail("DownloadMmsAction must be queued rather than started");
return null;
}
protected boolean queueAction(final String messageId, final Action processingAction) {
actionParameters.putString(KEY_MESSAGE_ID, messageId);
final DatabaseWrapper db = DataModel.get().getDatabase();
// Read the message from local db
final MessageData message = BugleDatabaseOperations.readMessage(db, messageId);
if (message != null && message.canDownloadMessage()) {
final Uri notificationUri = message.getSmsMessageUri();
final String conversationId = message.getConversationId();
final int status = message.getStatus();
final String selfId = message.getSelfId();
final ParticipantData self = BugleDatabaseOperations
.getExistingParticipant(db, selfId);
final int subId = self.getSubId();
actionParameters.putInt(KEY_SUB_ID, subId);
actionParameters.putString(KEY_CONVERSATION_ID, conversationId);
actionParameters.putString(KEY_PARTICIPANT_ID, message.getParticipantId());
actionParameters.putString(KEY_CONTENT_LOCATION, message.getMmsContentLocation());
actionParameters.putString(KEY_TRANSACTION_ID, message.getMmsTransactionId());
actionParameters.putParcelable(KEY_NOTIFICATION_URI, notificationUri);
actionParameters.putBoolean(KEY_AUTO_DOWNLOAD, isAutoDownload(status));
actionParameters.putLong(KEY_EXPIRY, message.getMmsExpiry());
final long now = System.currentTimeMillis();
if (message.getInDownloadWindow(now)) {
// We can still retry
actionParameters.putString(KEY_SUB_PHONE_NUMBER, self.getNormalizedDestination());
final int downloadingStatus = getDownloadingStatus(status);
// Update message status to indicate downloading.
updateMessageStatus(notificationUri, messageId, conversationId,
downloadingStatus, MessageData.RAW_TELEPHONY_STATUS_UNDEFINED);
// Pre-compute the next status when failed so we don't have to load from db again
actionParameters.putInt(KEY_FAILURE_STATUS, getFailureStatus(downloadingStatus));
// Actual download happens in background
processingAction.requestBackgroundWork(this);
if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
LogUtil.d(TAG,
"DownloadMmsAction: Queued download of MMS message " + messageId);
}
return true;
} else {
LogUtil.w(TAG, "DownloadMmsAction: Download of MMS message " + messageId
+ " failed (outside download window)");
// Retries depleted and we failed. Update the message status so we won't retry again
updateMessageStatus(notificationUri, messageId, conversationId,
MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED,
MessageData.RAW_TELEPHONY_STATUS_UNDEFINED);
if (status == MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD) {
// For auto download failure, we should send a DEFERRED NotifyRespInd
// to carrier to indicate we will manual download later
ProcessDownloadedMmsAction.sendDeferredRespStatus(
messageId, message.getMmsTransactionId(),
message.getMmsContentLocation(), subId);
return true;
}
}
}
return false;
}
/**
* Find out the auto download state of this message based on its starting status
*
* @param status The starting status of the message.
* @return True if this is a message doing auto downloading, false otherwise
*/
private static boolean isAutoDownload(final int status) {
switch (status) {
case MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD:
return false;
case MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD:
return true;
default:
Assert.fail("isAutoDownload: invalid input status " + status);
return false;
}
}
/**
* Get the corresponding downloading status based on the starting status of the message
*
* @param status The starting status of the message.
* @return The downloading status
*/
private static int getDownloadingStatus(final int status) {
switch (status) {
case MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD:
return MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING;
case MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD:
return MessageData.BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING;
default:
Assert.fail("isAutoDownload: invalid input status " + status);
return MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING;
}
}
/**
* Get the corresponding failed status based on the current downloading status
*
* @param status The downloading status
* @return The status the message should have if downloading failed
*/
private static int getFailureStatus(final int status) {
switch (status) {
case MessageData.BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING:
return MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD;
case MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING:
return MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD;
default:
Assert.fail("isAutoDownload: invalid input status " + status);
return MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD;
}
}
@Override
protected Bundle doBackgroundWork() {
final Context context = Factory.get().getApplicationContext();
final int subId = actionParameters.getInt(KEY_SUB_ID);
final String messageId = actionParameters.getString(KEY_MESSAGE_ID);
final Uri notificationUri = actionParameters.getParcelable(KEY_NOTIFICATION_URI);
final String subPhoneNumber = actionParameters.getString(KEY_SUB_PHONE_NUMBER);
final String transactionId = actionParameters.getString(KEY_TRANSACTION_ID);
final String contentLocation = actionParameters.getString(KEY_CONTENT_LOCATION);
final boolean autoDownload = actionParameters.getBoolean(KEY_AUTO_DOWNLOAD);
final String conversationId = actionParameters.getString(KEY_CONVERSATION_ID);
final String participantId = actionParameters.getString(KEY_PARTICIPANT_ID);
final int statusIfFailed = actionParameters.getInt(KEY_FAILURE_STATUS);
final long expiry = actionParameters.getLong(KEY_EXPIRY);
final long receivedTimestampRoundedToSecond =
1000 * ((System.currentTimeMillis() + 500) / 1000);
LogUtil.i(TAG, "DownloadMmsAction: Downloading MMS message " + messageId
+ " (" + (autoDownload ? "auto" : "manual") + ")");
// Bundle some values we'll need after the message is downloaded (via platform APIs)
final Bundle extras = new Bundle();
extras.putString(EXTRA_MESSAGE_ID, messageId);
extras.putString(EXTRA_CONVERSATION_ID, conversationId);
extras.putString(EXTRA_PARTICIPANT_ID, participantId);
extras.putInt(EXTRA_STATUS_IF_FAILED, statusIfFailed);
// Start the download
final MmsUtils.StatusPlusUri status = MmsUtils.downloadMmsMessage(context,
notificationUri, subId, subPhoneNumber, transactionId, contentLocation,
autoDownload, receivedTimestampRoundedToSecond / 1000L, expiry / 1000L, extras);
if (status == MmsUtils.STATUS_PENDING) {
// Async download; no status yet
if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
LogUtil.d(TAG, "DownloadMmsAction: Downloading MMS message " + messageId
+ " asynchronously; waiting for pending intent to signal completion");
}
} else {
// Inform sync that message has been added at local received timestamp
final SyncManager syncManager = DataModel.get().getSyncManager();
syncManager.onNewMessageInserted(receivedTimestampRoundedToSecond);
// Handle downloaded message
ProcessDownloadedMmsAction.processMessageDownloadFastFailed(messageId,
notificationUri, conversationId, participantId, contentLocation, subId,
subPhoneNumber, statusIfFailed, autoDownload, transactionId,
status.resultCode);
}
return null;
}
@Override
protected Object processBackgroundResponse(final Bundle response) {
// Nothing to do here; post-download actions handled by ProcessDownloadedMmsAction
return null;
}
@Override
protected Object processBackgroundFailure() {
final String messageId = actionParameters.getString(KEY_MESSAGE_ID);
final String transactionId = actionParameters.getString(KEY_TRANSACTION_ID);
final String conversationId = actionParameters.getString(KEY_CONVERSATION_ID);
final String participantId = actionParameters.getString(KEY_PARTICIPANT_ID);
final int statusIfFailed = actionParameters.getInt(KEY_FAILURE_STATUS);
final int subId = actionParameters.getInt(KEY_SUB_ID);
ProcessDownloadedMmsAction.processDownloadActionFailure(messageId,
MmsUtils.MMS_REQUEST_MANUAL_RETRY, MessageData.RAW_TELEPHONY_STATUS_UNDEFINED,
conversationId, participantId, statusIfFailed, subId, transactionId);
return null;
}
static void updateMessageStatus(final Uri messageUri, final String messageId,
final String conversationId, final int status, final int rawStatus) {
final Context context = Factory.get().getApplicationContext();
// Downloading status just kept in local DB but need to fix up telephony DB first
if (status == MessageData.BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING ||
status == MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING) {
MmsUtils.clearMmsStatus(context, messageUri);
}
// Then mark downloading status in our local DB
final ContentValues values = new ContentValues();
values.put(MessageColumns.STATUS, status);
values.put(MessageColumns.RAW_TELEPHONY_STATUS, rawStatus);
final DatabaseWrapper db = DataModel.get().getDatabase();
BugleDatabaseOperations.updateMessageRowIfExists(db, messageId, values);
MessagingContentProvider.notifyMessagesChanged(conversationId);
}
private DownloadMmsAction(final Parcel in) {
super(in);
}
public static final Parcelable.Creator