Home | History | Annotate | Download | only in action
      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.datamodel.action;
     18 
     19 import android.content.ContentValues;
     20 import android.content.Context;
     21 import android.net.Uri;
     22 import android.os.Bundle;
     23 import android.os.Parcel;
     24 import android.os.Parcelable;
     25 
     26 import com.android.messaging.Factory;
     27 import com.android.messaging.datamodel.BugleDatabaseOperations;
     28 import com.android.messaging.datamodel.DataModel;
     29 import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
     30 import com.android.messaging.datamodel.DatabaseWrapper;
     31 import com.android.messaging.datamodel.MessagingContentProvider;
     32 import com.android.messaging.datamodel.SyncManager;
     33 import com.android.messaging.datamodel.data.MessageData;
     34 import com.android.messaging.datamodel.data.ParticipantData;
     35 import com.android.messaging.sms.MmsUtils;
     36 import com.android.messaging.util.Assert;
     37 import com.android.messaging.util.Assert.RunsOnMainThread;
     38 import com.android.messaging.util.LogUtil;
     39 
     40 /**
     41  * Downloads an MMS message.
     42  * <p>
     43  * This class is public (not package-private) because the SMS/MMS (e.g. MmsUtils) classes need to
     44  * access the EXTRA_* fields for setting up the 'downloaded' pending intent.
     45  */
     46 public class DownloadMmsAction extends Action implements Parcelable {
     47     private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
     48 
     49     /**
     50      * Interface for DownloadMmsAction listeners
     51      */
     52     public interface DownloadMmsActionListener {
     53         @RunsOnMainThread
     54         abstract void onDownloadMessageStarting(final ActionMonitor monitor,
     55                 final Object data, final MessageData message);
     56         @RunsOnMainThread
     57         abstract void onDownloadMessageSucceeded(final ActionMonitor monitor,
     58                 final Object data, final MessageData message);
     59         @RunsOnMainThread
     60         abstract void onDownloadMessageFailed(final ActionMonitor monitor,
     61                 final Object data, final MessageData message);
     62     }
     63 
     64     /**
     65      * Queue download of an mms notification message (can only be called during execute of action)
     66      */
     67     static boolean queueMmsForDownloadInBackground(final String messageId,
     68             final Action processingAction) {
     69         // When this method is being called, it is always from auto download
     70         final DownloadMmsAction action = new DownloadMmsAction();
     71         // This could queue nothing
     72         return action.queueAction(messageId, processingAction);
     73     }
     74 
     75     private static final String KEY_MESSAGE_ID = "message_id";
     76     private static final String KEY_CONVERSATION_ID = "conversation_id";
     77     private static final String KEY_PARTICIPANT_ID = "participant_id";
     78     private static final String KEY_CONTENT_LOCATION = "content_location";
     79     private static final String KEY_TRANSACTION_ID = "transaction_id";
     80     private static final String KEY_NOTIFICATION_URI = "notification_uri";
     81     private static final String KEY_SUB_ID = "sub_id";
     82     private static final String KEY_SUB_PHONE_NUMBER = "sub_phone_number";
     83     private static final String KEY_AUTO_DOWNLOAD = "auto_download";
     84     private static final String KEY_FAILURE_STATUS = "failure_status";
     85 
     86     // Values we attach to the pending intent that's fired when the message is downloaded.
     87     // Only applicable when downloading via the platform APIs on L+.
     88     public static final String EXTRA_MESSAGE_ID = "message_id";
     89     public static final String EXTRA_CONTENT_URI = "content_uri";
     90     public static final String EXTRA_NOTIFICATION_URI = "notification_uri";
     91     public static final String EXTRA_SUB_ID = "sub_id";
     92     public static final String EXTRA_SUB_PHONE_NUMBER = "sub_phone_number";
     93     public static final String EXTRA_TRANSACTION_ID = "transaction_id";
     94     public static final String EXTRA_CONTENT_LOCATION = "content_location";
     95     public static final String EXTRA_AUTO_DOWNLOAD = "auto_download";
     96     public static final String EXTRA_RECEIVED_TIMESTAMP = "received_timestamp";
     97     public static final String EXTRA_CONVERSATION_ID = "conversation_id";
     98     public static final String EXTRA_PARTICIPANT_ID = "participant_id";
     99     public static final String EXTRA_STATUS_IF_FAILED = "status_if_failed";
    100 
    101     private DownloadMmsAction() {
    102         super();
    103     }
    104 
    105     @Override
    106     protected Object executeAction() {
    107         Assert.fail("DownloadMmsAction must be queued rather than started");
    108         return null;
    109     }
    110 
    111     protected boolean queueAction(final String messageId, final Action processingAction) {
    112         actionParameters.putString(KEY_MESSAGE_ID, messageId);
    113 
    114         final DatabaseWrapper db = DataModel.get().getDatabase();
    115         // Read the message from local db
    116         final MessageData message = BugleDatabaseOperations.readMessage(db, messageId);
    117         if (message != null && message.canDownloadMessage()) {
    118             final Uri notificationUri = message.getSmsMessageUri();
    119             final String conversationId = message.getConversationId();
    120             final int status = message.getStatus();
    121 
    122             final String selfId = message.getSelfId();
    123             final ParticipantData self = BugleDatabaseOperations
    124                     .getExistingParticipant(db, selfId);
    125             final int subId = self.getSubId();
    126             actionParameters.putInt(KEY_SUB_ID, subId);
    127             actionParameters.putString(KEY_CONVERSATION_ID, conversationId);
    128             actionParameters.putString(KEY_PARTICIPANT_ID, message.getParticipantId());
    129             actionParameters.putString(KEY_CONTENT_LOCATION, message.getMmsContentLocation());
    130             actionParameters.putString(KEY_TRANSACTION_ID, message.getMmsTransactionId());
    131             actionParameters.putParcelable(KEY_NOTIFICATION_URI, notificationUri);
    132             actionParameters.putBoolean(KEY_AUTO_DOWNLOAD, isAutoDownload(status));
    133 
    134             final long now = System.currentTimeMillis();
    135             if (message.getInDownloadWindow(now)) {
    136                 // We can still retry
    137                 actionParameters.putString(KEY_SUB_PHONE_NUMBER, self.getNormalizedDestination());
    138 
    139                 final int downloadingStatus = getDownloadingStatus(status);
    140                 // Update message status to indicate downloading.
    141                 updateMessageStatus(notificationUri, messageId, conversationId,
    142                         downloadingStatus, MessageData.RAW_TELEPHONY_STATUS_UNDEFINED);
    143                 // Pre-compute the next status when failed so we don't have to load from db again
    144                 actionParameters.putInt(KEY_FAILURE_STATUS, getFailureStatus(downloadingStatus));
    145 
    146                 // Actual download happens in background
    147                 processingAction.requestBackgroundWork(this);
    148 
    149                 if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
    150                     LogUtil.d(TAG,
    151                             "DownloadMmsAction: Queued download of MMS message " + messageId);
    152                 }
    153                 return true;
    154             } else {
    155                 LogUtil.w(TAG, "DownloadMmsAction: Download of MMS message " + messageId
    156                         + " failed (outside download window)");
    157 
    158                 // Retries depleted and we failed. Update the message status so we won't retry again
    159                 updateMessageStatus(notificationUri, messageId, conversationId,
    160                         MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED,
    161                         MessageData.RAW_TELEPHONY_STATUS_UNDEFINED);
    162                 if (status == MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD) {
    163                     // For auto download failure, we should send a DEFERRED NotifyRespInd
    164                     // to carrier to indicate we will manual download later
    165                     ProcessDownloadedMmsAction.sendDeferredRespStatus(
    166                             messageId, message.getMmsTransactionId(),
    167                             message.getMmsContentLocation(), subId);
    168                     return true;
    169                 }
    170             }
    171         }
    172         return false;
    173     }
    174 
    175     /**
    176      * Find out the auto download state of this message based on its starting status
    177      *
    178      * @param status The starting status of the message.
    179      * @return True if this is a message doing auto downloading, false otherwise
    180      */
    181     private static boolean isAutoDownload(final int status) {
    182         switch (status) {
    183             case MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD:
    184                 return false;
    185             case MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD:
    186                 return true;
    187             default:
    188                 Assert.fail("isAutoDownload: invalid input status " + status);
    189                 return false;
    190         }
    191     }
    192 
    193     /**
    194      * Get the corresponding downloading status based on the starting status of the message
    195      *
    196      * @param status The starting status of the message.
    197      * @return The downloading status
    198      */
    199     private static int getDownloadingStatus(final int status) {
    200         switch (status) {
    201             case MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD:
    202                 return MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING;
    203             case MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD:
    204                 return MessageData.BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING;
    205             default:
    206                 Assert.fail("isAutoDownload: invalid input status " + status);
    207                 return MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING;
    208         }
    209     }
    210 
    211     /**
    212      * Get the corresponding failed status based on the current downloading status
    213      *
    214      * @param status The downloading status
    215      * @return The status the message should have if downloading failed
    216      */
    217     private static int getFailureStatus(final int status) {
    218         switch (status) {
    219             case MessageData.BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING:
    220                 return MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD;
    221             case MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING:
    222                 return MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD;
    223             default:
    224                 Assert.fail("isAutoDownload: invalid input status " + status);
    225                 return MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD;
    226         }
    227     }
    228 
    229     @Override
    230     protected Bundle doBackgroundWork() {
    231         final Context context = Factory.get().getApplicationContext();
    232         final int subId = actionParameters.getInt(KEY_SUB_ID);
    233         final String messageId = actionParameters.getString(KEY_MESSAGE_ID);
    234         final Uri notificationUri = actionParameters.getParcelable(KEY_NOTIFICATION_URI);
    235         final String subPhoneNumber = actionParameters.getString(KEY_SUB_PHONE_NUMBER);
    236         final String transactionId = actionParameters.getString(KEY_TRANSACTION_ID);
    237         final String contentLocation = actionParameters.getString(KEY_CONTENT_LOCATION);
    238         final boolean autoDownload = actionParameters.getBoolean(KEY_AUTO_DOWNLOAD);
    239         final String conversationId = actionParameters.getString(KEY_CONVERSATION_ID);
    240         final String participantId = actionParameters.getString(KEY_PARTICIPANT_ID);
    241         final int statusIfFailed = actionParameters.getInt(KEY_FAILURE_STATUS);
    242 
    243         final long receivedTimestampRoundedToSecond =
    244                 1000 * ((System.currentTimeMillis() + 500) / 1000);
    245 
    246         LogUtil.i(TAG, "DownloadMmsAction: Downloading MMS message " + messageId
    247                 + " (" + (autoDownload ? "auto" : "manual") + ")");
    248 
    249         // Bundle some values we'll need after the message is downloaded (via platform APIs)
    250         final Bundle extras = new Bundle();
    251         extras.putString(EXTRA_MESSAGE_ID, messageId);
    252         extras.putString(EXTRA_CONVERSATION_ID, conversationId);
    253         extras.putString(EXTRA_PARTICIPANT_ID, participantId);
    254         extras.putInt(EXTRA_STATUS_IF_FAILED, statusIfFailed);
    255 
    256         // Start the download
    257         final MmsUtils.StatusPlusUri status = MmsUtils.downloadMmsMessage(context,
    258                 notificationUri, subId, subPhoneNumber, transactionId, contentLocation,
    259                 autoDownload, receivedTimestampRoundedToSecond / 1000L, extras);
    260         if (status == MmsUtils.STATUS_PENDING) {
    261             // Async download; no status yet
    262             if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
    263                 LogUtil.d(TAG, "DownloadMmsAction: Downloading MMS message " + messageId
    264                         + " asynchronously; waiting for pending intent to signal completion");
    265             }
    266         } else {
    267             // Inform sync that message has been added at local received timestamp
    268             final SyncManager syncManager = DataModel.get().getSyncManager();
    269             syncManager.onNewMessageInserted(receivedTimestampRoundedToSecond);
    270             // Handle downloaded message
    271             ProcessDownloadedMmsAction.processMessageDownloadFastFailed(messageId,
    272                     notificationUri, conversationId, participantId, contentLocation, subId,
    273                     subPhoneNumber, statusIfFailed, autoDownload, transactionId,
    274                     status.resultCode);
    275         }
    276         return null;
    277     }
    278 
    279     @Override
    280     protected Object processBackgroundResponse(final Bundle response) {
    281         // Nothing to do here; post-download actions handled by ProcessDownloadedMmsAction
    282         return null;
    283     }
    284 
    285     @Override
    286     protected Object processBackgroundFailure() {
    287         final String messageId = actionParameters.getString(KEY_MESSAGE_ID);
    288         final String transactionId = actionParameters.getString(KEY_TRANSACTION_ID);
    289         final String conversationId = actionParameters.getString(KEY_CONVERSATION_ID);
    290         final String participantId = actionParameters.getString(KEY_PARTICIPANT_ID);
    291         final int statusIfFailed = actionParameters.getInt(KEY_FAILURE_STATUS);
    292         final int subId = actionParameters.getInt(KEY_SUB_ID);
    293 
    294         ProcessDownloadedMmsAction.processDownloadActionFailure(messageId,
    295                 MmsUtils.MMS_REQUEST_MANUAL_RETRY, MessageData.RAW_TELEPHONY_STATUS_UNDEFINED,
    296                 conversationId, participantId, statusIfFailed, subId, transactionId);
    297 
    298         return null;
    299     }
    300 
    301     static void updateMessageStatus(final Uri messageUri, final String messageId,
    302             final String conversationId, final int status, final int rawStatus) {
    303         final Context context = Factory.get().getApplicationContext();
    304         // Downloading status just kept in local DB but need to fix up telephony DB first
    305         if (status == MessageData.BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING ||
    306                 status == MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING) {
    307             MmsUtils.clearMmsStatus(context, messageUri);
    308         }
    309         // Then mark downloading status in our local DB
    310         final ContentValues values = new ContentValues();
    311         values.put(MessageColumns.STATUS, status);
    312         values.put(MessageColumns.RAW_TELEPHONY_STATUS, rawStatus);
    313         final DatabaseWrapper db = DataModel.get().getDatabase();
    314         BugleDatabaseOperations.updateMessageRowIfExists(db, messageId, values);
    315 
    316         MessagingContentProvider.notifyMessagesChanged(conversationId);
    317     }
    318 
    319     private DownloadMmsAction(final Parcel in) {
    320         super(in);
    321     }
    322 
    323     public static final Parcelable.Creator<DownloadMmsAction> CREATOR
    324             = new Parcelable.Creator<DownloadMmsAction>() {
    325         @Override
    326         public DownloadMmsAction createFromParcel(final Parcel in) {
    327             return new DownloadMmsAction(in);
    328         }
    329 
    330         @Override
    331         public DownloadMmsAction[] newArray(final int size) {
    332             return new DownloadMmsAction[size];
    333         }
    334     };
    335 
    336     @Override
    337     public void writeToParcel(final Parcel parcel, final int flags) {
    338         writeActionToParcel(parcel, flags);
    339     }
    340 }
    341