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.ContentValues;
20import android.content.Context;
21import android.net.Uri;
22import android.os.Parcel;
23import android.os.Parcelable;
24import android.provider.Telephony.Sms;
25import android.text.TextUtils;
26
27import com.android.messaging.Factory;
28import com.android.messaging.datamodel.BugleDatabaseOperations;
29import com.android.messaging.datamodel.BugleNotifications;
30import com.android.messaging.datamodel.DataModel;
31import com.android.messaging.datamodel.DatabaseWrapper;
32import com.android.messaging.datamodel.MessagingContentProvider;
33import com.android.messaging.datamodel.SyncManager;
34import com.android.messaging.datamodel.data.MessageData;
35import com.android.messaging.datamodel.data.ParticipantData;
36import com.android.messaging.sms.MmsSmsUtils;
37import com.android.messaging.util.LogUtil;
38import com.android.messaging.util.OsUtil;
39
40/**
41 * Action used to "receive" an incoming message
42 */
43public class ReceiveSmsMessageAction extends Action implements Parcelable {
44    private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
45
46    private static final String KEY_MESSAGE_VALUES = "message_values";
47
48    /**
49     * Create a message received from a particular number in a particular conversation
50     */
51    public ReceiveSmsMessageAction(final ContentValues messageValues) {
52        actionParameters.putParcelable(KEY_MESSAGE_VALUES, messageValues);
53    }
54
55    @Override
56    protected Object executeAction() {
57        final Context context = Factory.get().getApplicationContext();
58        final ContentValues messageValues = actionParameters.getParcelable(KEY_MESSAGE_VALUES);
59        final DatabaseWrapper db = DataModel.get().getDatabase();
60
61        // Get the SIM subscription ID
62        Integer subId = messageValues.getAsInteger(Sms.SUBSCRIPTION_ID);
63        if (subId == null) {
64            subId = ParticipantData.DEFAULT_SELF_SUB_ID;
65        }
66        // Make sure we have a sender address
67        String address = messageValues.getAsString(Sms.ADDRESS);
68        if (TextUtils.isEmpty(address)) {
69            LogUtil.w(TAG, "Received an SMS without an address; using unknown sender.");
70            address = ParticipantData.getUnknownSenderDestination();
71            messageValues.put(Sms.ADDRESS, address);
72        }
73        final ParticipantData rawSender = ParticipantData.getFromRawPhoneBySimLocale(
74                address, subId);
75
76        // TODO: Should use local timestamp for this?
77        final long received = messageValues.getAsLong(Sms.DATE);
78        // Inform sync that message has been added at local received timestamp
79        final SyncManager syncManager = DataModel.get().getSyncManager();
80        syncManager.onNewMessageInserted(received);
81
82        // Make sure we've got a thread id
83        final long threadId = MmsSmsUtils.Threads.getOrCreateThreadId(context, address);
84        messageValues.put(Sms.THREAD_ID, threadId);
85        final boolean blocked = BugleDatabaseOperations.isBlockedDestination(
86                db, rawSender.getNormalizedDestination());
87        final String conversationId = BugleDatabaseOperations.
88                getOrCreateConversationFromRecipient(db, threadId, blocked, rawSender);
89
90        final boolean messageInFocusedConversation =
91                DataModel.get().isFocusedConversation(conversationId);
92        final boolean messageInObservableConversation =
93                DataModel.get().isNewMessageObservable(conversationId);
94
95        MessageData message = null;
96        // Only the primary user gets to insert the message into the telephony db and into bugle's
97        // db. The secondary user goes through this path, but skips doing the actual insert. It
98        // goes through this path because it needs to compute messageInFocusedConversation in order
99        // to calculate whether to skip the notification and play a soft sound if the user is
100        // already in the conversation.
101        if (!OsUtil.isSecondaryUser()) {
102            final boolean read = messageValues.getAsBoolean(Sms.Inbox.READ)
103                    || messageInFocusedConversation;
104            // If you have read it you have seen it
105            final boolean seen = read || messageInObservableConversation || blocked;
106            messageValues.put(Sms.Inbox.READ, read ? Integer.valueOf(1) : Integer.valueOf(0));
107
108            // incoming messages are marked as seen in the telephony db
109            messageValues.put(Sms.Inbox.SEEN, 1);
110
111            // Insert into telephony
112            final Uri messageUri = context.getContentResolver().insert(Sms.Inbox.CONTENT_URI,
113                    messageValues);
114
115            if (messageUri != null) {
116                if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
117                    LogUtil.d(TAG, "ReceiveSmsMessageAction: Inserted SMS message into telephony, "
118                            + "uri = " + messageUri);
119                }
120            } else {
121                LogUtil.e(TAG, "ReceiveSmsMessageAction: Failed to insert SMS into telephony!");
122            }
123
124            final String text = messageValues.getAsString(Sms.BODY);
125            final String subject = messageValues.getAsString(Sms.SUBJECT);
126            final long sent = messageValues.getAsLong(Sms.DATE_SENT);
127            final ParticipantData self = ParticipantData.getSelfParticipant(subId);
128            final Integer pathPresent = messageValues.getAsInteger(Sms.REPLY_PATH_PRESENT);
129            final String smsServiceCenter = messageValues.getAsString(Sms.SERVICE_CENTER);
130            String conversationServiceCenter = null;
131            // Only set service center if message REPLY_PATH_PRESENT = 1
132            if (pathPresent != null && pathPresent == 1 && !TextUtils.isEmpty(smsServiceCenter)) {
133                conversationServiceCenter = smsServiceCenter;
134            }
135            db.beginTransaction();
136            try {
137                final String participantId =
138                        BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, rawSender);
139                final String selfId =
140                        BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, self);
141
142                message = MessageData.createReceivedSmsMessage(messageUri, conversationId,
143                        participantId, selfId, text, subject, sent, received, seen, read);
144
145                BugleDatabaseOperations.insertNewMessageInTransaction(db, message);
146
147                BugleDatabaseOperations.updateConversationMetadataInTransaction(db, conversationId,
148                        message.getMessageId(), message.getReceivedTimeStamp(), blocked,
149                        conversationServiceCenter, true /* shouldAutoSwitchSelfId */);
150
151                final ParticipantData sender = ParticipantData.getFromId(db, participantId);
152                BugleActionToasts.onMessageReceived(conversationId, sender, message);
153                db.setTransactionSuccessful();
154            } finally {
155                db.endTransaction();
156            }
157            LogUtil.i(TAG, "ReceiveSmsMessageAction: Received SMS message " + message.getMessageId()
158                    + " in conversation " + message.getConversationId()
159                    + ", uri = " + messageUri);
160
161            ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(false, this);
162        } else {
163            if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
164                LogUtil.d(TAG, "ReceiveSmsMessageAction: Not inserting received SMS message for "
165                        + "secondary user.");
166            }
167        }
168        // Show a notification to let the user know a new message has arrived
169        BugleNotifications.update(false/*silent*/, conversationId, BugleNotifications.UPDATE_ALL);
170
171        MessagingContentProvider.notifyMessagesChanged(conversationId);
172        MessagingContentProvider.notifyPartsChanged();
173
174        return message;
175    }
176
177    private ReceiveSmsMessageAction(final Parcel in) {
178        super(in);
179    }
180
181    public static final Parcelable.Creator<ReceiveSmsMessageAction> CREATOR
182            = new Parcelable.Creator<ReceiveSmsMessageAction>() {
183        @Override
184        public ReceiveSmsMessageAction createFromParcel(final Parcel in) {
185            return new ReceiveSmsMessageAction(in);
186        }
187
188        @Override
189        public ReceiveSmsMessageAction[] newArray(final int size) {
190            return new ReceiveSmsMessageAction[size];
191        }
192    };
193
194    @Override
195    public void writeToParcel(final Parcel parcel, final int flags) {
196        writeActionToParcel(parcel, flags);
197    }
198}
199