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.ContentProvider;
20 import android.content.pm.ProviderInfo;
21 import android.database.Cursor;
22 import android.net.Uri;
23 import android.test.suitebuilder.annotation.SmallTest;
24 import android.text.TextUtils;
25 
26 import com.android.messaging.BugleTestCase;
27 import com.android.messaging.FakeContext;
28 import com.android.messaging.FakeFactory;
29 import com.android.messaging.datamodel.BugleDatabaseOperations;
30 import com.android.messaging.datamodel.DataModel;
31 import com.android.messaging.datamodel.DatabaseHelper;
32 import com.android.messaging.datamodel.DatabaseHelper.ConversationColumns;
33 import com.android.messaging.datamodel.DatabaseWrapper;
34 import com.android.messaging.datamodel.FakeDataModel;
35 import com.android.messaging.datamodel.MediaScratchFileProvider;
36 import com.android.messaging.datamodel.MessagingContentProvider;
37 import com.android.messaging.datamodel.action.ActionTestHelpers.StubActionService;
38 import com.android.messaging.datamodel.action.ActionTestHelpers.StubActionService.StubActionServiceCallLog;
39 import com.android.messaging.datamodel.action.ReadDraftDataAction.ReadDraftDataActionListener;
40 import com.android.messaging.datamodel.data.MessageData;
41 import com.android.messaging.datamodel.data.MessagePartData;
42 import com.android.messaging.datamodel.data.ParticipantData;
43 import com.android.messaging.util.ContentType;
44 
45 import org.mockito.Mock;
46 
47 import java.io.FileNotFoundException;
48 import java.io.IOException;
49 import java.io.InputStream;
50 import java.io.OutputStream;
51 import java.util.ArrayList;
52 
53 @SmallTest
54 public class ReadWriteDraftMessageActionTest extends BugleTestCase {
55 
56     @Mock ReadDraftDataActionListener mockListener;
57 
58     // TODO: Add test cases
59     //  1. Make sure drafts can include attachments and multiple parts
60     //  2. Make sure attachments get cleaned up appropriately
61     //  3. Make sure messageId and partIds not reused (currently each draft is a new message).
testWriteDraft()62     public void testWriteDraft() {
63         final String draftMessage = "draftMessage";
64         final long threadId = 1234567;
65         final boolean senderBlocked = false;
66         final String participantNumber = "5551234567";
67 
68         final DatabaseWrapper db = DataModel.get().getDatabase();
69 
70         final String conversationId = getOrCreateConversation(db, participantNumber, threadId,
71                 senderBlocked);
72         final String selfId = getOrCreateSelfId(db);
73 
74         // Should clear/stub DB
75         final ArrayList<StubActionServiceCallLog> calls = mService.getCalls();
76 
77         final MessageData message = MessageData.createDraftSmsMessage(conversationId, selfId,
78                 draftMessage);
79 
80         WriteDraftMessageAction.writeDraftMessage(conversationId, message);
81 
82         assertEquals("Failed to start service once for action", calls.size(), 1);
83         assertTrue("Action not SaveDraftMessageAction",
84                 calls.get(0).action instanceof WriteDraftMessageAction);
85 
86         final Action save = calls.get(0).action;
87 
88         final Object result = save.executeAction();
89 
90         assertTrue("Expect row number string as result", result instanceof String);
91         final String messageId = (String) result;
92 
93         // Should check DB
94         final MessageData actual = BugleDatabaseOperations.readMessage(db, messageId);
95         assertNotNull("Database missing draft", actual);
96         assertEquals("Draft text changed", draftMessage, actual.getMessageText());
97     }
98 
getOrCreateSelfId(final DatabaseWrapper db)99     private static String getOrCreateSelfId(final DatabaseWrapper db) {
100         db.beginTransaction();
101         final String selfId = BugleDatabaseOperations.getOrCreateParticipantInTransaction(db,
102                 ParticipantData.getSelfParticipant(ParticipantData.DEFAULT_SELF_SUB_ID));
103         db.setTransactionSuccessful();
104         db.endTransaction();
105         return selfId;
106     }
107 
getOrCreateConversation(final DatabaseWrapper db, final String participantNumber, final long threadId, final boolean senderBlocked)108     private static String getOrCreateConversation(final DatabaseWrapper db,
109             final String participantNumber, final long threadId, final boolean senderBlocked) {
110         final ArrayList<ParticipantData> participants =
111                 new ArrayList<ParticipantData>();
112         participants.add(ParticipantData.getFromRawPhoneBySystemLocale(participantNumber));
113 
114         final String conversationId = BugleDatabaseOperations.getOrCreateConversation(db, threadId,
115                 senderBlocked, participants, false, false, null);
116         assertNotNull("No conversation", conversationId);
117         return conversationId;
118     }
119 
testReadDraft()120     public void testReadDraft() {
121         final Object data = "data";
122         final String draftMessage = "draftMessage";
123         final long threadId = 1234567;
124         final boolean senderBlocked = false;
125         final String participantNumber = "5552345678";
126 
127         final DatabaseWrapper db = DataModel.get().getDatabase();
128 
129         final String conversationId = getOrCreateConversation(db, participantNumber, threadId,
130                 senderBlocked);
131         final String selfId = getOrCreateSelfId(db);
132 
133         // Should clear/stub DB
134         final ArrayList<StubActionServiceCallLog> calls = mService.getCalls();
135 
136         final MessageData message = MessageData.createDraftSmsMessage(conversationId, selfId,
137                 draftMessage);
138 
139         BugleDatabaseOperations.updateDraftMessageData(db, conversationId, message,
140                 BugleDatabaseOperations.UPDATE_MODE_ADD_DRAFT);
141 
142         final ActionMonitor monitor =
143                 ReadDraftDataAction.readDraftData(conversationId, null, data, mockListener);
144 
145         assertEquals("Unexpected number of calls to service", 1, calls.size());
146         assertTrue("Action not of type ReadDraftMessageAction",
147                 calls.get(0).action instanceof ReadDraftDataAction);
148 
149         final Action read = calls.get(0).action;
150 
151         final Object result = read.executeAction();
152 
153         assertTrue(result instanceof ReadDraftDataAction.DraftData);
154         final ReadDraftDataAction.DraftData draft = (ReadDraftDataAction.DraftData) result;
155 
156         assertEquals("Draft message text differs", draftMessage, draft.message.getMessageText());
157         assertEquals("Draft self differs", selfId, draft.message.getSelfId());
158         assertEquals("Draft conversation differs", conversationId,
159                 draft.conversation.getConversationId());
160     }
161 
testReadDraftForNewConversation()162     public void testReadDraftForNewConversation() {
163         final Object data = "data";
164         long threadId = 1234567;
165         final boolean senderBlocked = false;
166         long phoneNumber = 5557654567L;
167         final String notConversationId = "ThisIsNotValidConversationId";
168 
169         final DatabaseWrapper db = DataModel.get().getDatabase();
170         final String selfId = getOrCreateSelfId(db);
171 
172         // Unless set a new conversation should have a null draft message
173         final MessageData blank = BugleDatabaseOperations.readDraftMessageData(db,
174                 notConversationId, selfId);
175         assertNull(blank);
176 
177         String conversationId = null;
178         do {
179             conversationId = BugleDatabaseOperations.getExistingConversation(db,
180                     threadId, senderBlocked);
181             threadId++;
182             phoneNumber++;
183         }
184         while(!TextUtils.isEmpty(conversationId));
185 
186         final ArrayList<ParticipantData> participants =
187                 new ArrayList<ParticipantData>();
188         participants.add(ParticipantData.getFromRawPhoneBySystemLocale(Long.toString(phoneNumber)));
189 
190         conversationId = BugleDatabaseOperations.getOrCreateConversation(db, threadId,
191                 senderBlocked, participants, false, false, null);
192         assertNotNull("No conversation", conversationId);
193 
194         final MessageData actual = BugleDatabaseOperations.readDraftMessageData(db, conversationId,
195                 selfId);
196         assertNull(actual);
197 
198         // Should clear/stub DB
199         final ArrayList<StubActionServiceCallLog> calls = mService.getCalls();
200 
201         final ActionMonitor monitor =
202                 ReadDraftDataAction.readDraftData(conversationId, null, data, mockListener);
203 
204         assertEquals("Unexpected number of calls to service", 1, calls.size());
205         assertTrue("Action not of type ReadDraftMessageAction",
206                 calls.get(0).action instanceof ReadDraftDataAction);
207 
208         final Action read = calls.get(0).action;
209 
210         final Object result = read.executeAction();
211 
212         assertTrue(result instanceof ReadDraftDataAction.DraftData);
213         final ReadDraftDataAction.DraftData draft = (ReadDraftDataAction.DraftData) result;
214 
215         assertEquals("Draft message text differs", "", draft.message.getMessageText());
216         assertEquals("Draft self differs", selfId, draft.message.getSelfId());
217         assertEquals("Draft conversation differs", conversationId,
218                 draft.conversation.getConversationId());
219     }
220 
testWriteAndReadDraft()221     public void testWriteAndReadDraft() {
222         final Object data = "data";
223         final String draftMessage = "draftMessage";
224 
225         final DatabaseWrapper db = DataModel.get().getDatabase();
226         final Cursor conversations = db.query(DatabaseHelper.CONVERSATIONS_TABLE,
227                 new String[] { ConversationColumns._ID, ConversationColumns.CURRENT_SELF_ID }, null,
228                 null, null /* groupBy */, null /* having */, null /* orderBy */);
229 
230         if (conversations.moveToFirst()) {
231             final String conversationId = conversations.getString(0);
232             final String selfId = getOrCreateSelfId(db);
233 
234             // Should clear/stub DB
235             final ArrayList<StubActionServiceCallLog> calls = mService.getCalls();
236 
237             final MessageData message = MessageData.createDraftSmsMessage(conversationId, selfId,
238                     draftMessage);
239 
240             WriteDraftMessageAction.writeDraftMessage(conversationId, message);
241 
242             assertEquals("Failed to start service once for action", calls.size(), 1);
243             assertTrue("Action not SaveDraftMessageAction",
244                     calls.get(0).action instanceof WriteDraftMessageAction);
245 
246             final Action save = calls.get(0).action;
247 
248             Object result = save.executeAction();
249 
250             assertTrue("Expect row number string as result", result instanceof String);
251 
252             // Should check DB
253 
254             final ActionMonitor monitor =
255                     ReadDraftDataAction.readDraftData(conversationId, null, data,
256                             mockListener);
257 
258             assertEquals("Expect two calls queued", 2, calls.size());
259             assertTrue("Expect action", calls.get(1).action instanceof ReadDraftDataAction);
260 
261             final Action read = calls.get(1).action;
262 
263             result = read.executeAction();
264 
265             assertTrue(result instanceof ReadDraftDataAction.DraftData);
266             final ReadDraftDataAction.DraftData draft = (ReadDraftDataAction.DraftData) result;
267 
268             assertEquals("Draft message text differs", draftMessage, draft.message.getMessageText());
269             // The conversation's self id is used as the draft's self id.
270             assertEquals("Draft self differs", conversations.getString(1),
271                     draft.message.getSelfId());
272             assertEquals("Draft conversation differs", conversationId,
273                     draft.conversation.getConversationId());
274         } else {
275             fail("No conversations in database");
276         }
277     }
278 
testUpdateDraft()279     public void testUpdateDraft() {
280         final String initialMessage = "initialMessage";
281         final String draftMessage = "draftMessage";
282         final long threadId = 1234567;
283         final boolean senderBlocked = false;
284         final String participantNumber = "5553456789";
285 
286         final DatabaseWrapper db = DataModel.get().getDatabase();
287 
288         final String conversationId = getOrCreateConversation(db, participantNumber, threadId,
289                 senderBlocked);
290         final String selfId = getOrCreateSelfId(db);
291 
292         final ArrayList<StubActionServiceCallLog> calls = mService.getCalls();
293 
294         // Insert initial message
295         MessageData initial = MessageData.createDraftSmsMessage(conversationId, selfId,
296                 initialMessage);
297 
298         BugleDatabaseOperations.updateDraftMessageData(db, conversationId, initial,
299                 BugleDatabaseOperations.UPDATE_MODE_ADD_DRAFT);
300 
301         initial = BugleDatabaseOperations.readDraftMessageData(db,
302                 conversationId, selfId);
303         assertEquals("Initial text mismatch", initialMessage, initial.getMessageText());
304 
305         // Now update the draft
306         final MessageData message = MessageData.createDraftSmsMessage(conversationId, selfId,
307                 draftMessage);
308         WriteDraftMessageAction.writeDraftMessage(conversationId, message);
309 
310         assertEquals("Failed to start service once for action", calls.size(), 1);
311         assertTrue("Action not SaveDraftMessageAction",
312                 calls.get(0).action instanceof WriteDraftMessageAction);
313 
314         final Action save = calls.get(0).action;
315 
316         final Object result = save.executeAction();
317 
318         assertTrue("Expect row number string as result", result instanceof String);
319 
320         // Check DB
321         final MessageData actual =  BugleDatabaseOperations.readDraftMessageData(db,
322                 conversationId, selfId);
323         assertNotNull("Database missing draft", actual);
324         assertEquals("Draft text mismatch", draftMessage, actual.getMessageText());
325         assertNull("Draft messageId should be null", actual.getMessageId());
326     }
327 
testBugleDatabaseDraftOperations()328     public void testBugleDatabaseDraftOperations() {
329         final String initialMessage = "initialMessage";
330         final String draftMessage = "draftMessage";
331         final long threadId = 1234599;
332         final boolean senderBlocked = false;
333         final String participantNumber = "5553456798";
334         final String subject = "subject here";
335 
336         final DatabaseWrapper db = DataModel.get().getDatabase();
337 
338         final String conversationId = getOrCreateConversation(db, participantNumber, threadId,
339                 senderBlocked);
340         final String selfId = getOrCreateSelfId(db);
341 
342         final String text = "This is some text";
343         final Uri mOutputUri = MediaScratchFileProvider.buildMediaScratchSpaceUri("txt");
344         OutputStream outputStream = null;
345         try {
346             outputStream = mContext.getContentResolver().openOutputStream(mOutputUri);
347             outputStream.write(text.getBytes());
348         } catch (final FileNotFoundException e) {
349             fail("Cannot open output file");
350         } catch (final IOException e) {
351             fail("Cannot write output file");
352         }
353 
354         final MessageData initial =
355                 MessageData.createDraftMmsMessage(conversationId, selfId, initialMessage, subject);
356         initial.addPart(MessagePartData.createMediaMessagePart(ContentType.MULTIPART_MIXED,
357                 mOutputUri, 0, 0));
358 
359         final String initialMessageId = BugleDatabaseOperations.updateDraftMessageData(db,
360                 conversationId, initial, BugleDatabaseOperations.UPDATE_MODE_ADD_DRAFT);
361         assertNotNull(initialMessageId);
362 
363         final MessageData initialDraft = BugleDatabaseOperations.readMessage(db, initialMessageId);
364         assertNotNull(initialDraft);
365         int cnt = 0;
366         for(final MessagePartData part : initialDraft.getParts()) {
367             if (part.isAttachment()) {
368                 assertEquals(part.getContentUri(), mOutputUri);
369             } else {
370                 assertEquals(part.getText(), initialMessage);
371             }
372             cnt++;
373         }
374         assertEquals("Wrong number of parts", 2, cnt);
375 
376         InputStream inputStream = null;
377         try {
378             inputStream = mContext.getContentResolver().openInputStream(mOutputUri);
379             final byte[] buffer = new byte[256];
380             final int read = inputStream.read(buffer);
381             assertEquals(read, text.getBytes().length);
382         } catch (final FileNotFoundException e) {
383             fail("Cannot open input file");
384         } catch (final IOException e) {
385             fail("Cannot read input file");
386         }
387 
388         final String moreText = "This is some more text";
389         final Uri mAnotherUri = MediaScratchFileProvider.buildMediaScratchSpaceUri("txt");
390         outputStream = null;
391         try {
392             outputStream = mContext.getContentResolver().openOutputStream(mAnotherUri);
393             outputStream.write(moreText.getBytes());
394         } catch (final FileNotFoundException e) {
395             fail("Cannot open output file");
396         } catch (final IOException e) {
397             fail("Cannot write output file");
398         }
399 
400         final MessageData another =
401                 MessageData.createDraftMmsMessage(conversationId, selfId, draftMessage, subject);
402         another.addPart(MessagePartData.createMediaMessagePart(ContentType.MMS_MULTIPART_MIXED,
403                 mAnotherUri, 0, 0));
404 
405         final String anotherMessageId = BugleDatabaseOperations.updateDraftMessageData(db,
406                 conversationId, another, BugleDatabaseOperations.UPDATE_MODE_ADD_DRAFT);
407         assertNotNull(anotherMessageId);
408 
409         final MessageData anotherDraft = BugleDatabaseOperations.readMessage(db, anotherMessageId);
410         assertNotNull(anotherDraft);
411         cnt = 0;
412         for(final MessagePartData part : anotherDraft.getParts()) {
413             if (part.isAttachment()) {
414                 assertEquals(part.getContentUri(), mAnotherUri);
415             } else {
416                 assertEquals(part.getText(), draftMessage);
417             }
418             cnt++;
419         }
420         assertEquals("Wrong number of parts", 2, cnt);
421 
422         inputStream = null;
423         try {
424             inputStream = mContext.getContentResolver().openInputStream(mOutputUri);
425             assertNull("Original draft content should have been deleted", inputStream);
426         } catch (final FileNotFoundException e) {
427         }
428         inputStream = null;
429         try {
430             inputStream = mContext.getContentResolver().openInputStream(mAnotherUri);
431             final byte[] buffer = new byte[256];
432             final int read = inputStream.read(buffer);
433             assertEquals(read, moreText.getBytes().length);
434         } catch (final FileNotFoundException e) {
435             fail("Cannot open input file");
436         } catch (final IOException e) {
437             fail("Cannot read input file");
438         }
439 
440         final MessageData last = null;
441         final String lastPartId = BugleDatabaseOperations.updateDraftMessageData(db,
442                 conversationId, last, BugleDatabaseOperations.UPDATE_MODE_ADD_DRAFT);
443         assertNull(lastPartId);
444 
445         inputStream = null;
446         try {
447             inputStream = mContext.getContentResolver().openInputStream(mAnotherUri);
448             assertNull("Original draft content should have been deleted", inputStream);
449         } catch (final FileNotFoundException e) {
450         }
451 
452     }
453 
454     private StubActionService mService;
455 
456     @Override
setUp()457     public void setUp() throws Exception {
458         super.setUp();
459 
460         final FakeContext context = new FakeContext(getTestContext());
461 
462         final ContentProvider bugleProvider = new MessagingContentProvider();
463         final ProviderInfo bugleProviderInfo = new ProviderInfo();
464         bugleProviderInfo.authority = MessagingContentProvider.AUTHORITY;
465         bugleProvider.attachInfo(mContext, bugleProviderInfo);
466         context.addContentProvider(MessagingContentProvider.AUTHORITY, bugleProvider);
467         final ContentProvider mediaProvider = new MediaScratchFileProvider();
468         final ProviderInfo mediaProviderInfo = new ProviderInfo();
469         mediaProviderInfo.authority = MediaScratchFileProvider.AUTHORITY;
470         mediaProvider.attachInfo(mContext, mediaProviderInfo);
471         context.addContentProvider(MediaScratchFileProvider.AUTHORITY, mediaProvider);
472 
473         mService = new StubActionService();
474         final FakeDataModel fakeDataModel = new FakeDataModel(context)
475                 .withActionService(mService);
476         FakeFactory.registerWithFakeContext(getTestContext(), context)
477                 .withDataModel(fakeDataModel);
478 
479     }
480 }
481