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