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
17package com.android.messaging.datamodel.action;
18
19import android.content.Context;
20import android.os.Bundle;
21import android.os.Parcel;
22import android.os.Parcelable;
23
24import com.android.messaging.Factory;
25import com.android.messaging.datamodel.BugleDatabaseOperations;
26import com.android.messaging.datamodel.BugleNotifications;
27import com.android.messaging.datamodel.DataModel;
28import com.android.messaging.datamodel.DataModelException;
29import com.android.messaging.datamodel.DatabaseWrapper;
30import com.android.messaging.datamodel.MessagingContentProvider;
31import com.android.messaging.datamodel.SyncManager;
32import com.android.messaging.datamodel.data.MessageData;
33import com.android.messaging.datamodel.data.ParticipantData;
34import com.android.messaging.mmslib.pdu.PduHeaders;
35import com.android.messaging.sms.DatabaseMessages;
36import com.android.messaging.sms.MmsUtils;
37import com.android.messaging.util.LogUtil;
38
39import java.util.List;
40
41/**
42 * Action used to "receive" an incoming message
43 */
44public 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     */
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
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
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
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
194    public void writeToParcel(final Parcel parcel, final int flags) {
195        writeActionToParcel(parcel, flags);
196    }
197}
198