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.Context;
20 import android.os.Bundle;
21 import android.os.Parcel;
22 import android.os.Parcelable;
23 
24 import com.android.messaging.Factory;
25 import com.android.messaging.datamodel.BugleDatabaseOperations;
26 import com.android.messaging.datamodel.BugleNotifications;
27 import com.android.messaging.datamodel.DataModel;
28 import com.android.messaging.datamodel.DataModelException;
29 import com.android.messaging.datamodel.DatabaseWrapper;
30 import com.android.messaging.datamodel.MessagingContentProvider;
31 import com.android.messaging.datamodel.SyncManager;
32 import com.android.messaging.datamodel.data.MessageData;
33 import com.android.messaging.datamodel.data.ParticipantData;
34 import com.android.messaging.mmslib.pdu.PduHeaders;
35 import com.android.messaging.sms.DatabaseMessages;
36 import com.android.messaging.sms.MmsUtils;
37 import com.android.messaging.util.LogUtil;
38 
39 import java.util.List;
40 
41 /**
42  * Action used to "receive" an incoming message
43  */
44 public class ReceiveMmsMessageAction extends Action implements Parcelable {
45     private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
46 
47     private static final String KEY_SUB_ID = "sub_id";
48     private static final String KEY_PUSH_DATA = "push_data";
49     private static final String KEY_TRANSACTION_ID = "transaction_id";
50     private static final String KEY_CONTENT_LOCATION = "content_location";
51 
52     /**
53      * Create a message received from a particular number in a particular conversation
54      */
ReceiveMmsMessageAction(final int subId, final byte[] pushData)55     public ReceiveMmsMessageAction(final int subId, final byte[] pushData) {
56         actionParameters.putInt(KEY_SUB_ID, subId);
57         actionParameters.putByteArray(KEY_PUSH_DATA, pushData);
58     }
59 
60     @Override
executeAction()61     protected Object executeAction() {
62         final Context context = Factory.get().getApplicationContext();
63         final int subId = actionParameters.getInt(KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID);
64         final byte[] pushData = actionParameters.getByteArray(KEY_PUSH_DATA);
65         final DatabaseWrapper db = DataModel.get().getDatabase();
66 
67         // Write received message to telephony DB
68         MessageData message = null;
69         final ParticipantData self = BugleDatabaseOperations.getOrCreateSelf(db, subId);
70 
71         final long received = System.currentTimeMillis();
72         // Inform sync that message has been added at local received timestamp
73         final SyncManager syncManager = DataModel.get().getSyncManager();
74         syncManager.onNewMessageInserted(received);
75 
76         // TODO: Should use local time to set received time in MMS message
77         final DatabaseMessages.MmsMessage mms = MmsUtils.processReceivedPdu(
78                 context, pushData, self.getSubId(), self.getNormalizedDestination());
79 
80         if (mms != null) {
81             final List<String> recipients = MmsUtils.getRecipientsByThread(mms.mThreadId);
82             String from = MmsUtils.getMmsSender(recipients, mms.getUri());
83             if (from == null) {
84                 LogUtil.w(TAG, "Received an MMS without sender address; using unknown sender.");
85                 from = ParticipantData.getUnknownSenderDestination();
86             }
87             final ParticipantData rawSender = ParticipantData.getFromRawPhoneBySimLocale(
88                     from, subId);
89             final boolean blocked = BugleDatabaseOperations.isBlockedDestination(
90                     db, rawSender.getNormalizedDestination());
91             final boolean autoDownload = (!blocked && MmsUtils.allowMmsAutoRetrieve(subId));
92             final String conversationId =
93                     BugleDatabaseOperations.getOrCreateConversationFromThreadId(db, mms.mThreadId,
94                             blocked, subId);
95 
96             final boolean messageInFocusedConversation =
97                     DataModel.get().isFocusedConversation(conversationId);
98             final boolean messageInObservableConversation =
99                     DataModel.get().isNewMessageObservable(conversationId);
100 
101             // TODO: Also write these values to the telephony provider
102             mms.mRead = messageInFocusedConversation;
103             mms.mSeen = messageInObservableConversation || blocked;
104 
105             // Write received placeholder message to our DB
106             db.beginTransaction();
107             try {
108                 final String participantId =
109                         BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, rawSender);
110                 final String selfId =
111                         BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, self);
112 
113                 message = MmsUtils.createMmsMessage(mms, conversationId, participantId, selfId,
114                         (autoDownload ? MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD :
115                             MessageData.BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD));
116                 // Write the message
117                 BugleDatabaseOperations.insertNewMessageInTransaction(db, message);
118 
119                 if (!autoDownload) {
120                     BugleDatabaseOperations.updateConversationMetadataInTransaction(db,
121                             conversationId, message.getMessageId(), message.getReceivedTimeStamp(),
122                             blocked, true /* shouldAutoSwitchSelfId */);
123                     final ParticipantData sender = ParticipantData .getFromId(
124                             db, participantId);
125                     BugleActionToasts.onMessageReceived(conversationId, sender, message);
126                 }
127                 // else update the conversation once we have downloaded final message (or failed)
128                 db.setTransactionSuccessful();
129             } finally {
130                 db.endTransaction();
131             }
132 
133             // Update conversation if not immediately initiating a download
134             if (!autoDownload) {
135                 MessagingContentProvider.notifyMessagesChanged(message.getConversationId());
136                 MessagingContentProvider.notifyPartsChanged();
137 
138                 // Show a notification to let the user know a new message has arrived
139                 BugleNotifications.update(false/*silent*/, conversationId,
140                         BugleNotifications.UPDATE_ALL);
141 
142                 // Send the NotifyRespInd with DEFERRED status since no auto download
143                 actionParameters.putString(KEY_TRANSACTION_ID, mms.mTransactionId);
144                 actionParameters.putString(KEY_CONTENT_LOCATION, mms.mContentLocation);
145                 requestBackgroundWork();
146             }
147 
148             LogUtil.i(TAG, "ReceiveMmsMessageAction: Received MMS message " + message.getMessageId()
149                     + " in conversation " + message.getConversationId()
150                     + ", uri = " + message.getSmsMessageUri());
151         } else {
152             LogUtil.e(TAG, "ReceiveMmsMessageAction: Skipping processing of incoming PDU");
153         }
154 
155         ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(false, this);
156 
157         return message;
158     }
159 
160     @Override
doBackgroundWork()161     protected Bundle doBackgroundWork() throws DataModelException {
162         final Context context = Factory.get().getApplicationContext();
163         final int subId = actionParameters.getInt(KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID);
164         final String transactionId = actionParameters.getString(KEY_TRANSACTION_ID);
165         final String contentLocation = actionParameters.getString(KEY_CONTENT_LOCATION);
166         MmsUtils.sendNotifyResponseForMmsDownload(
167                 context,
168                 subId,
169                 MmsUtils.stringToBytes(transactionId, "UTF-8"),
170                 contentLocation,
171                 PduHeaders.STATUS_DEFERRED);
172         // We don't need to return anything.
173         return null;
174     }
175 
ReceiveMmsMessageAction(final Parcel in)176     private ReceiveMmsMessageAction(final Parcel in) {
177         super(in);
178     }
179 
180     public static final Parcelable.Creator<ReceiveMmsMessageAction> CREATOR
181             = new Parcelable.Creator<ReceiveMmsMessageAction>() {
182         @Override
183         public ReceiveMmsMessageAction createFromParcel(final Parcel in) {
184             return new ReceiveMmsMessageAction(in);
185         }
186 
187         @Override
188         public ReceiveMmsMessageAction[] newArray(final int size) {
189             return new ReceiveMmsMessageAction[size];
190         }
191     };
192 
193     @Override
writeToParcel(final Parcel parcel, final int flags)194     public void writeToParcel(final Parcel parcel, final int flags) {
195         writeActionToParcel(parcel, flags);
196     }
197 }
198