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