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.Parcel;
23 import android.os.Parcelable;
24 import android.provider.Telephony.Sms;
25 import android.text.TextUtils;
26 
27 import com.android.messaging.Factory;
28 import com.android.messaging.datamodel.BugleDatabaseOperations;
29 import com.android.messaging.datamodel.BugleNotifications;
30 import com.android.messaging.datamodel.DataModel;
31 import com.android.messaging.datamodel.DatabaseWrapper;
32 import com.android.messaging.datamodel.MessagingContentProvider;
33 import com.android.messaging.datamodel.SyncManager;
34 import com.android.messaging.datamodel.data.MessageData;
35 import com.android.messaging.datamodel.data.ParticipantData;
36 import com.android.messaging.sms.MmsSmsUtils;
37 import com.android.messaging.util.LogUtil;
38 import com.android.messaging.util.OsUtil;
39 
40 /**
41  * Action used to "receive" an incoming message
42  */
43 public 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      */
ReceiveSmsMessageAction(final ContentValues messageValues)51     public ReceiveSmsMessageAction(final ContentValues messageValues) {
52         actionParameters.putParcelable(KEY_MESSAGE_VALUES, messageValues);
53     }
54 
55     @Override
executeAction()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 
ReceiveSmsMessageAction(final Parcel in)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
writeToParcel(final Parcel parcel, final int flags)195     public void writeToParcel(final Parcel parcel, final int flags) {
196         writeActionToParcel(parcel, flags);
197     }
198 }
199