diff options
Diffstat (limited to 'tests/src/com/android/messaging/datamodel')
24 files changed, 4044 insertions, 0 deletions
diff --git a/tests/src/com/android/messaging/datamodel/BindingTest.java b/tests/src/com/android/messaging/datamodel/BindingTest.java new file mode 100644 index 0000000..9205657 --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/BindingTest.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.datamodel; + +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.messaging.BugleTestCase; +import com.android.messaging.datamodel.binding.BindableData; +import com.android.messaging.datamodel.binding.Binding; +import com.android.messaging.datamodel.binding.BindingBase; +import com.android.messaging.datamodel.binding.ImmutableBindingRef; + +/** + * Test binding + */ +@SmallTest +public class BindingTest extends BugleTestCase { + private static final Object TEST_DATA_ID = "myDataId"; + private static final Object YOUR_DATA_ID = "yourDataId"; + + public void testBindingStartsUnbound() { + final Binding<TestBindableData> binding = BindingBase.createBinding(this); + assertNull(binding.getBindingId()); + } + + public void testDataStartsUnbound() { + final TestBindableData data = new TestBindableData(TEST_DATA_ID); + assertFalse(data.isBound()); + } + + public void testBindingUpdatesDataAndBindee() { + final Binding<TestBindableData> binding = BindingBase.createBinding(this); + final TestBindableData data = new TestBindableData(TEST_DATA_ID); + binding.bind(data); + assertTrue(binding.isBound()); + assertEquals(binding.getData(), data); + assertTrue(data.isBound(binding.getBindingId())); + assertFalse(data.isBound("SomeRandomString")); + assertNotNull(binding.getBindingId()); + assertFalse(data.mListenersUnregistered); + } + + public void testRebindingFails() { + final Binding<TestBindableData> binding = BindingBase.createBinding(this); + final TestBindableData yours = new TestBindableData(YOUR_DATA_ID); + binding.bind(yours); + assertEquals(binding.getData(), yours); + assertTrue(yours.isBound(binding.getBindingId())); + final TestBindableData data = new TestBindableData(TEST_DATA_ID); + try { + binding.bind(data); + fail(); + } catch (final IllegalStateException e) { + } + assertTrue(binding.isBound()); + assertEquals(binding.getData(), yours); + assertTrue(yours.isBound(binding.getBindingId())); + } + + public void testUnbindingClearsDataAndBindee() { + final Binding<TestBindableData> binding = BindingBase.createBinding(this); + final TestBindableData data = new TestBindableData(TEST_DATA_ID); + binding.bind(data); + assertTrue(data.isBound(binding.getBindingId())); + assertTrue(binding.isBound()); + binding.unbind(); + try { + final TestBindableData other = binding.getData(); + fail(); + } catch (final IllegalStateException e) { + } + assertFalse(data.isBound()); + assertNull(binding.getBindingId()); + assertTrue(data.mListenersUnregistered); + } + + public void testUnbindingAndRebinding() { + final Binding<TestBindableData> binding = BindingBase.createBinding(this); + final TestBindableData yours = new TestBindableData(YOUR_DATA_ID); + binding.bind(yours); + assertEquals(binding.getData(), yours); + assertTrue(yours.isBound(binding.getBindingId())); + binding.unbind(); + assertFalse(yours.isBound()); + assertNull(binding.getBindingId()); + + final TestBindableData data = new TestBindableData(TEST_DATA_ID); + binding.bind(data); + assertEquals(binding.getData(), data); + assertTrue(data.isBound(binding.getBindingId())); + assertFalse(data.isBound("SomeRandomString")); + assertTrue(binding.isBound()); + assertNotNull(binding.getBindingId()); + } + + public void testBindingReference() { + final Binding<TestBindableData> binding = BindingBase.createBinding(this); + final TestBindableData data = new TestBindableData(TEST_DATA_ID); + binding.bind(data); + assertEquals(binding.getData(), data); + assertTrue(data.isBound(binding.getBindingId())); + + final ImmutableBindingRef<TestBindableData> bindingRef = + BindingBase.createBindingReference(binding); + assertEquals(bindingRef.getData(), data); + assertTrue(data.isBound(bindingRef.getBindingId())); + + binding.unbind(); + assertFalse(binding.isBound()); + assertNull(binding.getBindingId()); + assertFalse(bindingRef.isBound()); + assertNull(bindingRef.getBindingId()); + } + + static class TestBindableData extends BindableData { + private final Object mDataId; + public boolean mListenersUnregistered; + + public TestBindableData(final Object dataId) { + mDataId = dataId; + mListenersUnregistered = false; + } + + @Override + public void unregisterListeners() { + mListenersUnregistered = true; + } + + @Override + public boolean isBound() { + return super.isBound(); + } + } +} diff --git a/tests/src/com/android/messaging/datamodel/BitmapPoolTest.java b/tests/src/com/android/messaging/datamodel/BitmapPoolTest.java new file mode 100644 index 0000000..6d0aaa3 --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/BitmapPoolTest.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.datamodel; + +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.messaging.BugleTestCase; +import com.android.messaging.FakeFactory; +import com.android.messaging.R; + +import org.mockito.Mock; + +import java.util.HashSet; +import java.util.Set; + +@SmallTest +public class BitmapPoolTest extends BugleTestCase { + private static final int POOL_SIZE = 5; + private static final int IMAGE_DIM = 1; + private static final String NAME = "BitmapPoolTest"; + + @Mock private MemoryCacheManager mockMemoryCacheManager; + + @Override + protected void setUp() throws Exception { + super.setUp(); + FakeFactory.register(getTestContext()) + .withMemoryCacheManager(mockMemoryCacheManager); + } + + private Set<Bitmap> fillPoolAndGetPoolContents(final BitmapPool pool, final int width, + final int height) { + final Set<Bitmap> returnedBitmaps = new HashSet<Bitmap>(); + for (int i = 0; i < POOL_SIZE; i++) { + final Bitmap temp = pool.createOrReuseBitmap(width, height); + assertFalse(returnedBitmaps.contains(temp)); + returnedBitmaps.add(temp); + } + for (final Bitmap b : returnedBitmaps) { + pool.reclaimBitmap(b); + } + assertTrue(pool.isFull(width, height)); + return returnedBitmaps; + } + + public void testCreateAndPutBackInPoolTest() { + final BitmapPool pool = new BitmapPool(POOL_SIZE, NAME); + final Bitmap bitmap = pool.createOrReuseBitmap(IMAGE_DIM, IMAGE_DIM); + assertFalse(bitmap.isRecycled()); + assertFalse(pool.isFull(IMAGE_DIM, IMAGE_DIM)); + pool.reclaimBitmap(bitmap); + + // Don't recycle because the pool isn't full yet. + assertFalse(bitmap.isRecycled()); + } + + public void testCreateBeyondFullAndCheckReuseTest() { + final BitmapPool pool = new BitmapPool(POOL_SIZE, NAME); + final Set<Bitmap> returnedBitmaps = + fillPoolAndGetPoolContents(pool, IMAGE_DIM, IMAGE_DIM); + final Bitmap overflowBitmap = pool.createOrReuseBitmap(IMAGE_DIM, IMAGE_DIM); + assertFalse(overflowBitmap.isRecycled()); + assertTrue(returnedBitmaps.contains(overflowBitmap)); + } + + /** + * Make sure that we have the correct options to create mutable for bitmap pool reuse. + */ + public void testAssertBitmapOptionsAreMutable() { + final BitmapFactory.Options options = + BitmapPool.getBitmapOptionsForPool(false, IMAGE_DIM, IMAGE_DIM); + assertTrue(options.inMutable); + } + + public void testDecodeFromResourceBitmap() { + final BitmapPool pool = new BitmapPool(POOL_SIZE, NAME); + final BitmapFactory.Options options = + BitmapPool.getBitmapOptionsForPool(true, IMAGE_DIM, IMAGE_DIM); + final Resources resources = getContext().getResources(); + final Bitmap resourceBitmap = pool.decodeSampledBitmapFromResource( + R.drawable.msg_bubble_incoming, resources, options, IMAGE_DIM, IMAGE_DIM); + assertNotNull(resourceBitmap); + assertTrue(resourceBitmap.getByteCount() > 0); + } +} diff --git a/tests/src/com/android/messaging/datamodel/BugleServiceTestCase.java b/tests/src/com/android/messaging/datamodel/BugleServiceTestCase.java new file mode 100644 index 0000000..42eb647 --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/BugleServiceTestCase.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.datamodel; + +import android.app.Service; +import android.test.ServiceTestCase; + +import com.android.messaging.BugleTestCase; +import com.android.messaging.TestUtil; + + +/* + * Base class for service tests that takes care of housekeeping that is commong amongst our service + * test case. + */ +public abstract class BugleServiceTestCase<T extends Service> extends ServiceTestCase<T> { + + static { + // Set flag during loading of test cases to prevent application initialization starting + BugleTestCase.setTestsRunning(); + } + + public BugleServiceTestCase(final Class<T> serviceClass) { + super(serviceClass); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + TestUtil.testSetup(getContext(), this); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + TestUtil.testTeardown(this); + } +}
\ No newline at end of file diff --git a/tests/src/com/android/messaging/datamodel/ConversationListTest.java b/tests/src/com/android/messaging/datamodel/ConversationListTest.java new file mode 100644 index 0000000..f45696b --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/ConversationListTest.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.datamodel; + +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.messaging.BugleTestCase; + +@SmallTest +public class ConversationListTest extends BugleTestCase { + public void testTesting() { + assertTrue(true); + } +} diff --git a/tests/src/com/android/messaging/datamodel/DataModelTest.java b/tests/src/com/android/messaging/datamodel/DataModelTest.java new file mode 100644 index 0000000..71723a4 --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/DataModelTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.datamodel; + +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.messaging.BugleTestCase; +import com.android.messaging.FakeFactory; +import com.android.messaging.datamodel.data.ConversationData; +import com.android.messaging.datamodel.data.ConversationListData; +import com.android.messaging.datamodel.data.ConversationData.ConversationDataListener; +import com.android.messaging.datamodel.data.ConversationListData.ConversationListDataListener; + +import org.mockito.Mock; + +public class DataModelTest extends BugleTestCase { + + DataModel dataModel; + @Mock protected ConversationDataListener mockConversationDataListener; + @Mock protected ConversationListDataListener mockConversationListDataListener; + + @Override + protected void setUp() throws Exception { + super.setUp(); + dataModel = new DataModelImpl(getTestContext()); + FakeFactory.register(mContext) + .withDataModel(dataModel); + } + + @SmallTest + public void testCreateConversationList() { + final ConversationListData list = dataModel.createConversationListData(getContext(), + mockConversationListDataListener, true); + assertTrue(list instanceof ConversationListData); + final ConversationData conv = dataModel.createConversationData(getContext(), + mockConversationDataListener, "testConversation"); + assertTrue(conv instanceof ConversationData); + } + + private static final String FOCUSED_CONV_ID = "focused_conv_id"; + + @SmallTest + public void testFocusedConversationIsObservable() { + dataModel.setFocusedConversation(FOCUSED_CONV_ID); + assertTrue(dataModel.isNewMessageObservable(FOCUSED_CONV_ID)); + dataModel.setFocusedConversation(null); + assertFalse(dataModel.isNewMessageObservable(FOCUSED_CONV_ID)); + } + + @SmallTest + public void testConversationIsObservableInList() { + dataModel.setConversationListScrolledToNewestConversation(true); + assertTrue(dataModel.isNewMessageObservable(FOCUSED_CONV_ID)); + dataModel.setConversationListScrolledToNewestConversation(false); + assertFalse(dataModel.isNewMessageObservable(FOCUSED_CONV_ID)); + } +} diff --git a/tests/src/com/android/messaging/datamodel/FakeCursor.java b/tests/src/com/android/messaging/datamodel/FakeCursor.java new file mode 100644 index 0000000..59d1b89 --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/FakeCursor.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.datamodel; + +import android.os.Bundle; +import android.test.mock.MockCursor; + +import java.util.ArrayList; + +/** + * A simple in memory fake cursor that can be used for UI tests. + */ +public class FakeCursor extends MockCursor { + private final ArrayList<Integer> mProjection; + private final String[] mColumnNamesOfData; + private final Object[][] mData; + private int mIndex; + + public FakeCursor(final String[] projection, final String[] columnNames, + final Object[][] data) { + mColumnNamesOfData = columnNames; + mData = data; + mIndex = -1; + mProjection = new ArrayList<Integer>(projection.length); + for (final String column : projection) { + mProjection.add(getColumnIndex(column)); + } + } + + public Object getAt(final String columnName, final int row) { + final int dataIdx = getColumnIndex(columnName); + return (dataIdx < 0 || row < 0 || row >= mData.length) ? 0 : mData[row][dataIdx]; + } + + @Override + public int getCount() { + return mData.length; + } + + @Override + public boolean isFirst() { + return mIndex == 0; + } + + @Override + public boolean isLast() { + return mIndex == mData.length - 1; + } + + @Override + public boolean moveToFirst() { + if (mData.length == 0) { + return false; + } + mIndex = 0; + return true; + } + + @Override + public boolean moveToPosition(final int position) { + if (position < 0 || position >= mData.length) { + return false; + } + mIndex = position; + return true; + } + + @Override + public int getPosition() { + return mIndex; + } + + @Override + public boolean moveToPrevious() { + if (mIndex <= 0) { + return false; + } + mIndex--; + return true; + } + + @Override + public boolean moveToNext() { + if (mIndex == mData.length - 1) { + return false; + } + + mIndex++; + return true; + } + + @Override + public int getColumnCount() { + return mColumnNamesOfData.length; + } + + @Override + public int getColumnIndex(final String columnName) { + for (int i = 0 ; i < mColumnNamesOfData.length ; i++) { + if (mColumnNamesOfData[i].equals(columnName)) { + return i; + } + } + return -1; + } + + @Override + public int getColumnIndexOrThrow(final String columnName) { + final int result = getColumnIndex(columnName); + if (result == -1) { + throw new IllegalArgumentException(); + } + + return result; + } + + @Override + public String getString(final int columnIndex) { + final int dataIdx = mProjection.get(columnIndex); + final Object obj = (dataIdx < 0 ? null : mData[mIndex][dataIdx]); + return (obj == null ? null : obj.toString()); + } + + @Override + public int getInt(final int columnIndex) { + final int dataIdx = mProjection.get(columnIndex); + return (dataIdx < 0 ? 0 : (Integer) mData[mIndex][dataIdx]); + } + + @Override + public long getLong(final int columnIndex) { + final int dataIdx = mProjection.get(columnIndex); + return (dataIdx < 0 ? 0 : (Long) mData[mIndex][dataIdx]); + } + + @Override + public void close() { + } + + @Override + public boolean isClosed() { + return false; + } + + @Override + public Bundle getExtras() { return null; } +} diff --git a/tests/src/com/android/messaging/datamodel/FakeDataModel.java b/tests/src/com/android/messaging/datamodel/FakeDataModel.java new file mode 100644 index 0000000..5e80eab --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/FakeDataModel.java @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.datamodel; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; +import android.test.RenamingDelegatingContext; + +import com.android.messaging.datamodel.action.ActionService; +import com.android.messaging.datamodel.action.BackgroundWorker; +import com.android.messaging.datamodel.data.BlockedParticipantsData; +import com.android.messaging.datamodel.data.BlockedParticipantsData.BlockedParticipantsDataListener; +import com.android.messaging.datamodel.data.ContactListItemData; +import com.android.messaging.datamodel.data.ContactPickerData; +import com.android.messaging.datamodel.data.ContactPickerData.ContactPickerDataListener; +import com.android.messaging.datamodel.data.ConversationData; +import com.android.messaging.datamodel.data.ConversationData.ConversationDataListener; +import com.android.messaging.datamodel.data.ConversationListData; +import com.android.messaging.datamodel.data.ConversationListData.ConversationListDataListener; +import com.android.messaging.datamodel.data.DraftMessageData; +import com.android.messaging.datamodel.data.GalleryGridItemData; +import com.android.messaging.datamodel.data.LaunchConversationData; +import com.android.messaging.datamodel.data.LaunchConversationData.LaunchConversationDataListener; +import com.android.messaging.datamodel.data.MediaPickerData; +import com.android.messaging.datamodel.data.MessagePartData; +import com.android.messaging.datamodel.data.ParticipantData; +import com.android.messaging.datamodel.data.ParticipantListItemData; +import com.android.messaging.datamodel.data.PeopleAndOptionsData; +import com.android.messaging.datamodel.data.PeopleAndOptionsData.PeopleAndOptionsDataListener; +import com.android.messaging.datamodel.data.PeopleOptionsItemData; +import com.android.messaging.datamodel.data.SettingsData; +import com.android.messaging.datamodel.data.SettingsData.SettingsDataListener; +import com.android.messaging.datamodel.data.SubscriptionListData; +import com.android.messaging.datamodel.data.TestDataFactory; +import com.android.messaging.datamodel.data.VCardContactItemData; +import com.android.messaging.util.ConnectivityUtil; + +public class FakeDataModel extends DataModel { + private BackgroundWorker mWorker; + private ActionService mActionService; + private final DatabaseHelper mDatabaseHelper; + private ConversationListData mConversationListData; + private ContactPickerData mContactPickerData; + private MediaPickerData mMediaPickerData; + private PeopleAndOptionsData mPeopleAndOptionsData; + private ConnectivityUtil mConnectivityUtil; + private SyncManager mSyncManager; + private SettingsData mSettingsData; + private DraftMessageData mDraftMessageData; + + public FakeDataModel(final Context context) { + super(); + if (context instanceof RenamingDelegatingContext) { + mDatabaseHelper = DatabaseHelper.getNewInstanceForTest(context); + } else { + mDatabaseHelper = null; + } + } + + @Override + public BackgroundWorker getBackgroundWorkerForActionService() { + return mWorker; + } + + public FakeDataModel withBackgroundWorkerForActionService(final BackgroundWorker worker) { + mWorker = worker; + return this; + } + + public FakeDataModel withActionService(final ActionService ActionService) { + mActionService = ActionService; + return this; + } + + public FakeDataModel withConversationListData(final ConversationListData conversationListData) { + mConversationListData = conversationListData; + return this; + } + + public FakeDataModel withContactPickerData(final ContactPickerData contactPickerData) { + mContactPickerData = contactPickerData; + return this; + } + + public FakeDataModel withMediaPickerData(final MediaPickerData mediaPickerData) { + mMediaPickerData = mediaPickerData; + return this; + } + + public FakeDataModel withConnectivityUtil(final ConnectivityUtil connectivityUtil) { + mConnectivityUtil = connectivityUtil; + return this; + } + + public FakeDataModel withSyncManager(final SyncManager syncManager) { + mSyncManager = syncManager; + return this; + } + + public FakeDataModel withPeopleAndOptionsData(final PeopleAndOptionsData peopleAndOptionsData) { + mPeopleAndOptionsData = peopleAndOptionsData; + return this; + } + + public FakeDataModel withSettingsData(final SettingsData settingsData) { + mSettingsData = settingsData; + return this; + } + + public FakeDataModel withDraftMessageData(final DraftMessageData draftMessageData) { + mDraftMessageData = draftMessageData; + return this; + } + + @Override + public ConversationListData createConversationListData(final Context context, + final ConversationListDataListener listener, final boolean archivedMode) { + return mConversationListData; + } + + @Override + public ConversationData createConversationData(final Context context, + final ConversationDataListener listener, final String conversationId) { + throw new IllegalStateException("Add withXXX or mock this method"); + } + + @Override + public ContactListItemData createContactListItemData() { + // This is a lightweight data holder object for each individual list item for which + // we don't perform any data request, so we can directly return a new instance. + return new ContactListItemData(); + } + + @Override + public ContactPickerData createContactPickerData(final Context context, + final ContactPickerDataListener listener) { + return mContactPickerData; + } + + @Override + public MediaPickerData createMediaPickerData(final Context context) { + return mMediaPickerData; + } + + @Override + public GalleryGridItemData createGalleryGridItemData() { + // This is a lightweight data holder object for each individual grid item for which + // we don't perform any data request, so we can directly return a new instance. + return new GalleryGridItemData(); + } + + @Override + public LaunchConversationData createLaunchConversationData( + final LaunchConversationDataListener listener) { + return new LaunchConversationData(listener); + } + + @Override + public PeopleOptionsItemData createPeopleOptionsItemData(final Context context) { + return new PeopleOptionsItemData(context); + } + + @Override + public PeopleAndOptionsData createPeopleAndOptionsData(final String conversationId, + final Context context, final PeopleAndOptionsDataListener listener) { + return mPeopleAndOptionsData; + } + + @Override + public VCardContactItemData createVCardContactItemData(final Context context, + final MessagePartData data) { + return new VCardContactItemData(context, data); + } + + @Override + public VCardContactItemData createVCardContactItemData(final Context context, + final Uri vCardUri) { + return new VCardContactItemData(context, vCardUri); + } + + @Override + public ParticipantListItemData createParticipantListItemData( + final ParticipantData participant) { + return new ParticipantListItemData(participant); + } + + @Override + public SubscriptionListData createSubscriptonListData(Context context) { + return new SubscriptionListData(context); + } + + @Override + public SettingsData createSettingsData(Context context, SettingsDataListener listener) { + return mSettingsData; + } + + @Override + public DraftMessageData createDraftMessageData(String conversationId) { + return mDraftMessageData; + } + + @Override + public ActionService getActionService() { + return mActionService; + } + + @Override + public ConnectivityUtil getConnectivityUtil() { + return mConnectivityUtil; + } + + @Override + public SyncManager getSyncManager() { + return mSyncManager; + } + + @Override + public DatabaseWrapper getDatabase() { + // Note this will crash unless the application context is redirected... + // This is by design so that tests do not inadvertently use the real database + return mDatabaseHelper.getDatabase(); + } + + @Override + void onCreateTables(final SQLiteDatabase db) { + TestDataFactory.createTestData(db); + } + + @Override + public void onActivityResume() { + } + + @Override + public void onApplicationCreated() { + } + + @Override + public BlockedParticipantsData createBlockedParticipantsData(Context context, + BlockedParticipantsDataListener listener) { + return new BlockedParticipantsData(context, listener); + } +} diff --git a/tests/src/com/android/messaging/datamodel/FrequentContactsCursorBuilderTest.java b/tests/src/com/android/messaging/datamodel/FrequentContactsCursorBuilderTest.java new file mode 100644 index 0000000..6b78a07 --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/FrequentContactsCursorBuilderTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.messaging.datamodel; + +import android.database.Cursor; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.messaging.BugleTestCase; +import com.android.messaging.datamodel.data.TestDataFactory; +import com.android.messaging.util.ContactUtil; + +@SmallTest +public class FrequentContactsCursorBuilderTest extends BugleTestCase { + + private void verifyBuiltCursor(final Cursor expected, final Cursor actual) { + final int rowCount = expected.getCount(); + final int columnCount = expected.getColumnCount(); + assertEquals(rowCount, actual.getCount()); + assertEquals(columnCount, actual.getColumnCount()); + for (int i = 0; i < rowCount; i++) { + expected.moveToPosition(i); + actual.moveToPosition(i); + assertEquals(expected.getLong(ContactUtil.INDEX_DATA_ID), + actual.getLong(ContactUtil.INDEX_DATA_ID)); + assertEquals(expected.getLong(ContactUtil.INDEX_CONTACT_ID), + actual.getLong(ContactUtil.INDEX_CONTACT_ID)); + assertEquals(expected.getString(ContactUtil.INDEX_LOOKUP_KEY), + actual.getString(ContactUtil.INDEX_LOOKUP_KEY)); + assertEquals(expected.getString(ContactUtil.INDEX_DISPLAY_NAME), + actual.getString(ContactUtil.INDEX_DISPLAY_NAME)); + assertEquals(expected.getString(ContactUtil.INDEX_PHOTO_URI), + actual.getString(ContactUtil.INDEX_PHOTO_URI)); + assertEquals(expected.getString(ContactUtil.INDEX_PHONE_EMAIL), + actual.getString(ContactUtil.INDEX_PHONE_EMAIL)); + assertEquals(expected.getInt(ContactUtil.INDEX_PHONE_EMAIL_TYPE), + actual.getInt(ContactUtil.INDEX_PHONE_EMAIL_TYPE)); + assertEquals(expected.getString(ContactUtil.INDEX_PHONE_EMAIL_LABEL), + actual.getString(ContactUtil.INDEX_PHONE_EMAIL_LABEL)); + } + } + + public void testIncompleteBuild() { + final FrequentContactsCursorBuilder builder = new FrequentContactsCursorBuilder(); + assertNull(builder.build()); + assertNull(builder.setFrequents(TestDataFactory.getStrequentContactsCursor()).build()); + builder.resetBuilder(); + assertNull(builder.build()); + assertNull(builder.setAllContacts(TestDataFactory.getAllContactListCursor()).build()); + } + + public void testBuildOnce() { + final Cursor cursor = new FrequentContactsCursorBuilder() + .setAllContacts(TestDataFactory.getAllContactListCursor()) + .setFrequents(TestDataFactory.getStrequentContactsCursor()) + .build(); + assertNotNull(cursor); + verifyBuiltCursor(TestDataFactory.getFrequentContactListCursor(), cursor); + } + + public void testBuildTwice() { + final FrequentContactsCursorBuilder builder = new FrequentContactsCursorBuilder(); + final Cursor firstCursor = builder + .setAllContacts(TestDataFactory.getAllContactListCursor()) + .setFrequents(TestDataFactory.getStrequentContactsCursor()) + .build(); + assertNotNull(firstCursor); + builder.resetBuilder(); + assertNull(builder.build()); + + final Cursor secondCursor = builder + .setAllContacts(TestDataFactory.getAllContactListCursor()) + .setFrequents(TestDataFactory.getStrequentContactsCursor()) + .build(); + assertNotNull(firstCursor); + verifyBuiltCursor(TestDataFactory.getFrequentContactListCursor(), secondCursor); + } +} diff --git a/tests/src/com/android/messaging/datamodel/MemoryCacheManagerTest.java b/tests/src/com/android/messaging/datamodel/MemoryCacheManagerTest.java new file mode 100644 index 0000000..5da9e27 --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/MemoryCacheManagerTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.datamodel; + +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.messaging.datamodel.MemoryCacheManager.MemoryCache; + +import org.mockito.Mockito; + +@SmallTest +public class MemoryCacheManagerTest extends AndroidTestCase { + + public void testRegisterCachesGetReclaimed() { + final MemoryCache mockMemoryCache = Mockito.mock(MemoryCache.class); + final MemoryCache otherMockMemoryCache = Mockito.mock(MemoryCache.class); + final MemoryCacheManager memoryCacheManager = new MemoryCacheManager(); + + memoryCacheManager.registerMemoryCache(mockMemoryCache); + memoryCacheManager.registerMemoryCache(otherMockMemoryCache); + memoryCacheManager.reclaimMemory(); + memoryCacheManager.unregisterMemoryCache(otherMockMemoryCache); + memoryCacheManager.reclaimMemory(); + + Mockito.verify(mockMemoryCache, Mockito.times(2)).reclaim(); + Mockito.verify(otherMockMemoryCache, Mockito.times(1)).reclaim(); + } +} diff --git a/tests/src/com/android/messaging/datamodel/ParticipantRefreshTest.java b/tests/src/com/android/messaging/datamodel/ParticipantRefreshTest.java new file mode 100644 index 0000000..cd1d6c7 --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/ParticipantRefreshTest.java @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.datamodel; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; +import android.test.suitebuilder.annotation.SmallTest; +import android.text.TextUtils; + +import com.android.messaging.BugleTestCase; +import com.android.messaging.FakeContentProvider; +import com.android.messaging.FakeContext; +import com.android.messaging.FakeFactory; +import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns; +import com.android.messaging.datamodel.data.ParticipantData; +import com.android.messaging.datamodel.data.ParticipantData.ParticipantsQuery; +import com.android.messaging.util.ContactUtil; + +import org.junit.Assert; + +/** + * Utility class for testing ParticipantRefresh class for different scenarios. + */ +@SmallTest +public class ParticipantRefreshTest extends BugleTestCase { + private FakeContext mContext; + FakeFactory mFakeFactory; + + @Override + public void setUp() throws Exception { + super.setUp(); + + mContext = new FakeContext(getTestContext()); + + final ContentProvider provider = new MessagingContentProvider(); + provider.attachInfo(mContext, null); + mContext.addContentProvider(MessagingContentProvider.AUTHORITY, provider); + + final FakeDataModel fakeDataModel = new FakeDataModel(mContext); + mFakeFactory = FakeFactory.registerWithFakeContext(getTestContext(), mContext) + .withDataModel(fakeDataModel); + } + + /** + * Add some phonelookup result into take PhoneLookup content provider. This will be + * used for doing phone lookup during participant refresh. + */ + private void addPhoneLookup(final String phone, final Object[][] lookupResult) { + final Uri uri = ContactUtil.lookupPhone(mContext, phone).getUri(); + final FakeContentProvider phoneLookup = new FakeContentProvider(mContext, + uri, false); + phoneLookup.addOverrideData(uri, null, null, ContactUtil.PhoneLookupQuery.PROJECTION, + lookupResult); + mFakeFactory.withProvider(uri, phoneLookup); + } + + /** + * Add some participant to test database. + */ + private void addParticipant(final String normalizedDestination, final long contactId, + final String name, final String photoUrl) { + final DatabaseWrapper db = DataModel.get().getDatabase(); + final ContentValues values = new ContentValues(); + + values.put(ParticipantColumns.NORMALIZED_DESTINATION, normalizedDestination); + values.put(ParticipantColumns.CONTACT_ID, contactId); + values.put(ParticipantColumns.FULL_NAME, name); + values.put(ParticipantColumns.PROFILE_PHOTO_URI, photoUrl); + + db.beginTransaction(); + try { + db.insert(DatabaseHelper.PARTICIPANTS_TABLE, null, values); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + + /** + * Verify that participant in the database has expected contacdtId, name and photoUrl fields. + */ + private void verifyParticipant(final String normalizedDestination, final long contactId, + final String name, final String photoUrl) { + final DatabaseWrapper db = DataModel.get().getDatabase(); + db.beginTransaction(); + try { + final String selection = ParticipantColumns.NORMALIZED_DESTINATION + "=?"; + final String[] selectionArgs = new String[] { normalizedDestination }; + + final Cursor cursor = db.query(DatabaseHelper.PARTICIPANTS_TABLE, + ParticipantsQuery.PROJECTION, selection, selectionArgs, null, null, null); + + if (cursor == null || cursor.getCount() != 1) { + Assert.fail("Should have participants for:" + normalizedDestination); + return; + } + + cursor.moveToFirst(); + final int currentContactId = cursor.getInt(ParticipantsQuery.INDEX_CONTACT_ID); + final String currentName = cursor.getString(ParticipantsQuery.INDEX_FULL_NAME); + final String currentPhotoUrl = + cursor.getString(ParticipantsQuery.INDEX_PROFILE_PHOTO_URI); + if (currentContactId != contactId) { + Assert.fail("Contact Id doesn't match. normalizedNumber=" + normalizedDestination + + " expected=" + contactId + " actual=" + currentContactId); + return; + } + + if (!TextUtils.equals(currentName, name)) { + Assert.fail("Name doesn't match. normalizedNumber=" + normalizedDestination + + " expected=" + name + " actual=" + currentName); + return; + } + + if (!TextUtils.equals(currentPhotoUrl, photoUrl)) { + Assert.fail("Contact Id doesn't match. normalizedNumber=" + normalizedDestination + + " expected=" + photoUrl + " actual=" + currentPhotoUrl); + return; + } + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + + /** + * Verify that incremental refresh will resolve previously not resolved participants. + */ + public void testIncrementalRefreshNotResolvedSingleMatch() { + addParticipant("650-123-1233", ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED, + null, null); + addPhoneLookup("650-123-1233", new Object[][] { + { 1L, "John", "content://photo/john", "650-123-1233", null, null, null } + }); + + ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_INCREMENTAL); + verifyParticipant("650-123-1233", 1, "John", "content://photo/john"); + } + + /** + * Verify that incremental refresh will resolve previously not resolved participants. + */ + public void testIncrementalRefreshNotResolvedMultiMatch() { + addParticipant("650-123-1233", ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED, + null, null); + addPhoneLookup("650-123-1233", new Object[][] { + { 1L, "John", "content://photo/john", "650-123-1233", null, null, null }, + { 2L, "Joe", "content://photo/joe", "650-123-1233", null, null, null } + }); + + ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_INCREMENTAL); + verifyParticipant("650-123-1233", 1, "John", "content://photo/john"); + } + + /** + * Verify that incremental refresh will not touch already-resolved participants. + */ + public void testIncrementalRefreshResolvedSingleMatch() { + addParticipant("650-123-1233", 1, "Joh", "content://photo/joh"); + addPhoneLookup("650-123-1233", new Object[][] { + { 1L, "John", "content://photo/john", "650-123-1233", null, null, null } + }); + + ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_INCREMENTAL); + verifyParticipant("650-123-1233", 1, "Joh", "content://photo/joh"); + } + + /** + * Verify that full refresh will correct already-resolved participants if needed + */ + public void testFullRefreshResolvedSingleMatch() { + addParticipant("650-123-1233", 1, "Joh", "content://photo/joh"); + addPhoneLookup("650-123-1233", new Object[][] { + { 1L, "John", "content://photo/john", "650-123-1233", null, null, null } + }); + + ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_FULL); + verifyParticipant("650-123-1233", 1, "John", "content://photo/john"); + } + + /** + * Verify that incremental refresh will not touch participant that is marked as not found. + */ + public void testIncrementalRefreshNotFound() { + addParticipant("650-123-1233", ParticipantData.PARTICIPANT_CONTACT_ID_NOT_FOUND, + null, null); + addPhoneLookup("650-123-1233", new Object[][] { + { 1L, "John", "content://photo/john", "650-123-1233", null, null, null } + }); + + ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_INCREMENTAL); + verifyParticipant("650-123-1233", ParticipantData.PARTICIPANT_CONTACT_ID_NOT_FOUND, + null, null); + } + + /** + * Verify that full refresh will resolve participant that is marked as not found. + */ + public void testFullRefreshNotFound() { + addParticipant("650-123-1233", ParticipantData.PARTICIPANT_CONTACT_ID_NOT_FOUND, + null, null); + addPhoneLookup("650-123-1233", new Object[][] { + { 1L, "John", "content://photo/john", "650-123-1233", null, null, null } + }); + + ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_FULL); + verifyParticipant("650-123-1233", 1, "John", "content://photo/john"); + } + + /** + * Verify that refresh take consideration of current contact_id when having multiple matches. + */ + public void testFullRefreshResolvedMultiMatch1() { + addParticipant("650-123-1233", 1, "Joh", "content://photo/joh"); + addPhoneLookup("650-123-1233", new Object[][] { + { 1L, "John", "content://photo/john", "650-123-1233", null, null, null }, + { 2L, "Joe", "content://photo/joe", "650-123-1233", null, null, null } + }); + + ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_FULL); + verifyParticipant("650-123-1233", 1, "John", "content://photo/john"); + } + + /** + * Verify that refresh take consideration of current contact_id when having multiple matches. + */ + public void testFullRefreshResolvedMultiMatch2() { + addParticipant("650-123-1233", 2, "Joh", "content://photo/joh"); + addPhoneLookup("650-123-1233", new Object[][] { + { 1L, "John", "content://photo/john", "650-123-1233", null, null, null }, + { 2L, "Joe", "content://photo/joe", "650-123-1233", null, null, null } + }); + + ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_FULL); + verifyParticipant("650-123-1233", 2, "Joe", "content://photo/joe"); + } + + /** + * Verify that refresh take first contact in case current contact_id no longer matches. + */ + public void testFullRefreshResolvedMultiMatch3() { + addParticipant("650-123-1233", 3, "Joh", "content://photo/joh"); + addPhoneLookup("650-123-1233", new Object[][] { + { 1L, "John", "content://photo/john", "650-123-1233", null, null, null }, + { 2L, "Joe", "content://photo/joe", "650-123-1233", null, null, null } + }); + + ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_FULL); + verifyParticipant("650-123-1233", 1, "John", "content://photo/john"); + } + + /** + * Verify that refresh take first contact in case current contact_id no longer matches. + */ + public void testFullRefreshResolvedBeforeButNotFoundNow() { + addParticipant("650-123-1233", 3, "Joh", "content://photo/joh"); + addPhoneLookup("650-123-1233", new Object[][] {}); + + ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_FULL); + verifyParticipant("650-123-1233", ParticipantData.PARTICIPANT_CONTACT_ID_NOT_FOUND, + null, null); + } +} diff --git a/tests/src/com/android/messaging/datamodel/action/ActionServiceSystemTest.java b/tests/src/com/android/messaging/datamodel/action/ActionServiceSystemTest.java new file mode 100644 index 0000000..039bec9 --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/action/ActionServiceSystemTest.java @@ -0,0 +1,436 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.datamodel.action; + +import android.content.Intent; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.test.suitebuilder.annotation.MediumTest; +import android.util.Log; + +import com.android.messaging.BugleTestCase; +import com.android.messaging.Factory; +import com.android.messaging.FakeContext; +import com.android.messaging.FakeContext.FakeContextHost; +import com.android.messaging.FakeFactory; +import com.android.messaging.datamodel.BugleServiceTestCase; +import com.android.messaging.datamodel.DataModel; +import com.android.messaging.datamodel.FakeDataModel; +import com.android.messaging.datamodel.action.ActionMonitor.ActionCompletedListener; +import com.android.messaging.datamodel.action.ActionMonitor.ActionExecutedListener; +import com.android.messaging.datamodel.action.ActionTestHelpers.ResultTracker; +import com.android.messaging.datamodel.action.ActionTestHelpers.StubBackgroundWorker; +import com.android.messaging.datamodel.action.ActionTestHelpers.StubConnectivityUtil; +import com.android.messaging.datamodel.action.ActionTestHelpers.StubLoader; + +import java.util.ArrayList; + +@MediumTest +public class ActionServiceSystemTest extends BugleServiceTestCase<ActionServiceImpl> + implements ActionCompletedListener, ActionExecutedListener, FakeContextHost { + private static final String TAG = "ActionServiceSystemTest"; + + static { + // Set flag during loading of test cases to prevent application initialization starting + BugleTestCase.setTestsRunning(); + } + + @Override + public void onActionSucceeded(final ActionMonitor monitor, + final Action action, final Object data, final Object result) { + final TestChatAction test = (TestChatAction) action; + assertEquals("Expect correct action parameter", parameter, test.parameter); + final ResultTracker tracker = (ResultTracker) data; + tracker.completionResult = result; + synchronized(tracker) { + tracker.notifyAll(); + } + } + + @Override + public void onActionFailed(final ActionMonitor monitor, final Action action, + final Object data, final Object result) { + final TestChatAction test = (TestChatAction) action; + assertEquals("Expect correct action parameter", parameter, test.parameter); + final ResultTracker tracker = (ResultTracker) data; + tracker.completionResult = result; + synchronized(tracker) { + tracker.notifyAll(); + } + } + + @Override + public void onActionExecuted(final ActionMonitor monitor, final Action action, + final Object data, final Object result) { + final TestChatAction test = (TestChatAction) action; + assertEquals("Expect correct action parameter", parameter, test.parameter); + final ResultTracker tracker = (ResultTracker) data; + tracker.executionResult = result; + } + + public ActionServiceSystemTest() { + super(ActionServiceImpl.class); + } + + public void testChatActionSucceeds() { + final ResultTracker tracker = new ResultTracker(); + + final ActionService service = DataModel.get().getActionService(); + final TestChatActionMonitor monitor = new TestChatActionMonitor(null, tracker, this, this); + final TestChatAction initial = new TestChatAction(monitor.getActionKey(), parameter); + + assertNull("Expect completion result to start null", tracker.completionResult); + assertNull("Expect execution result to start null", tracker.executionResult); + + final Parcel parcel = Parcel.obtain(); + parcel.writeParcelable(initial, 0); + parcel.setDataPosition(0); + final TestChatAction action = parcel.readParcelable(mContext.getClassLoader()); + + synchronized(mWorker) { + try { + action.start(monitor); + // Wait for callback across threads + mWorker.wait(2000); + } catch (final InterruptedException e) { + assertTrue("Interrupted waiting for execution", false); + } + } + + assertEquals("Expect to see 1 server request queued", 1, + mWorker.getRequestsMade().size()); + final Action request = mWorker.getRequestsMade().get(0); + assertTrue("Expect Test type", request instanceof TestChatAction); + + final Bundle response = new Bundle(); + response.putString(TestChatAction.RESPONSE_TEST, processResponseResult); + synchronized(tracker) { + try { + request.markBackgroundWorkStarting(); + request.markBackgroundWorkQueued(); + + request.markBackgroundWorkStarting(); + request.markBackgroundCompletionQueued(); + service.handleResponseFromBackgroundWorker(request, response); + // Wait for callback across threads + tracker.wait(2000); + } catch (final InterruptedException e) { + assertTrue("Interrupted waiting for response processing", false); + } + } + + // TODO + //assertEquals("Expect execution result set", executeActionResult, tracker.executionResult); + assertEquals("Expect completion result set", processResponseResult, + tracker.completionResult); + } + + public void testChatActionFails() { + final ResultTracker tracker = new ResultTracker(); + + final ActionService service = DataModel.get().getActionService(); + final TestChatActionMonitor monitor = new TestChatActionMonitor(null, tracker, this, this); + final TestChatAction action = new TestChatAction(monitor.getActionKey(), parameter); + + assertNull("Expect completion result to start null", tracker.completionResult); + assertNull("Expect execution result to start null", tracker.executionResult); + + synchronized(mWorker) { + try { + action.start(monitor); + // Wait for callback across threads + mWorker.wait(2000); + } catch (final InterruptedException e) { + assertTrue("Interrupted waiting for requests", false); + } + } + + final ArrayList<Intent> intents = mContext.extractIntents(); + assertNotNull(intents); + assertEquals("Expect to see one intent", intents.size(), 1); + + assertEquals("Expect to see 1 server request queued", 1, + mWorker.getRequestsMade().size()); + final Action request = mWorker.getRequestsMade().get(0); + assertTrue("Expect Test type", request instanceof TestChatAction); + + synchronized(tracker) { + try { + request.markBackgroundWorkStarting(); + request.markBackgroundWorkQueued(); + + request.markBackgroundWorkStarting(); + request.markBackgroundCompletionQueued(); + service.handleFailureFromBackgroundWorker(request, new Exception("It went wrong")); + // Wait for callback across threads + tracker.wait(2000); + } catch (final InterruptedException e) { + assertTrue("Interrupted waiting for response processing", false); + } + } + + // TODO + //assertEquals("Expect execution result set", executeActionResult, tracker.executionResult); + assertEquals("Expect completion result set", processFailureResult, + tracker.completionResult); + } + + public void testChatActionNoMonitor() { + final ActionService service = DataModel.get().getActionService(); + final TestChatAction action = + new TestChatAction(Action.generateUniqueActionKey(null), parameter); + + synchronized(mWorker) { + try { + action.start(); + // Wait for callback across threads + mWorker.wait(2000); + } catch (final InterruptedException e) { + assertTrue("Interrupted waiting for execution", false); + } + } + + assertEquals("Expect to see 1 server request queued", 1, + mWorker.getRequestsMade().size()); + Action request = mWorker.getRequestsMade().get(0); + assertTrue("Expect Test type", request instanceof TestChatAction); + + final Bundle response = new Bundle(); + response.putString(TestChatAction.RESPONSE_TEST, processResponseResult); + synchronized(mWorker) { + try { + service.handleResponseFromBackgroundWorker(request, response); + // Wait for callback across threads + mWorker.wait(2000); + } catch (final InterruptedException e) { + assertTrue("Interrupted waiting for response processing", false); + } + } + + assertEquals("Expect to see second server request queued", + 2, mWorker.getRequestsMade().size()); + request = mWorker.getRequestsMade().get(1); + assertTrue("Expect other type", + request instanceof TestChatActionOther); + } + + public void testChatActionUnregisterListener() { + final ResultTracker tracker = new ResultTracker(); + + final ActionService service = DataModel.get().getActionService(); + final TestChatActionMonitor monitor = new TestChatActionMonitor(null, tracker, this, this); + final TestChatAction action = new TestChatAction(monitor.getActionKey(), parameter); + + assertNull("Expect completion result to start null", tracker.completionResult); + assertNull("Expect execution result to start null", tracker.executionResult); + + synchronized(mWorker) { + try { + action.start(monitor); + // Wait for callback across threads + mWorker.wait(2000); + } catch (final InterruptedException e) { + assertTrue("Interrupted waiting for execution", false); + } + } + + assertEquals("Expect to see 1 server request queued", 1, + mWorker.getRequestsMade().size()); + final Action request = mWorker.getRequestsMade().get(0); + assertTrue("Expect Test type", request instanceof TestChatAction); + + monitor.unregister(); + + final Bundle response = new Bundle(); + synchronized(mWorker) { + try { + request.markBackgroundWorkStarting(); + request.markBackgroundWorkQueued(); + + request.markBackgroundWorkStarting(); + request.markBackgroundCompletionQueued(); + service.handleResponseFromBackgroundWorker(request, response); + // Wait for callback across threads + mWorker.wait(2000); + } catch (final InterruptedException e) { + assertTrue("Interrupted waiting for response processing", false); + } + } + + //assertEquals("Expect execution result set", executeActionResult, tracker.executionResult); + assertEquals("Expect completion never called", null, tracker.completionResult); + } + + StubBackgroundWorker mWorker; + FakeContext mContext; + StubLoader mLoader; + + private static final String parameter = "parameter"; + private static final Object executeActionResult = "executeActionResult"; + private static final String processResponseResult = "processResponseResult"; + private static final Object processFailureResult = "processFailureResult"; + + @Override + public void setUp() throws Exception { + super.setUp(); + Log.d(TAG, "ChatActionTest setUp"); + + mContext = new FakeContext(getContext(), this); + mWorker = new StubBackgroundWorker(); + FakeFactory.registerWithFakeContext(getContext(), mContext) + .withDataModel(new FakeDataModel(mContext) + .withBackgroundWorkerForActionService(mWorker) + .withActionService(new ActionService()) + .withConnectivityUtil(new StubConnectivityUtil(mContext))); + + mLoader = new StubLoader(); + setContext(Factory.get().getApplicationContext()); + } + + @Override + public String getServiceClassName() { + return ActionServiceImpl.class.getName(); + } + + @Override + public void startServiceForStub(final Intent intent) { + this.startService(intent); + } + + @Override + public void onStartCommandForStub(final Intent intent, final int flags, final int startId) { + this.getService().onStartCommand(intent, flags, startId); + } + + public static class TestChatAction extends Action implements Parcelable { + public static String RESPONSE_TEST = "response_test"; + public static String KEY_PARAMETER = "parameter"; + + protected TestChatAction(final String key, final String parameter) { + super(key); + this.actionParameters.putString(KEY_PARAMETER, parameter); + // Cache parameter as a member variable + this.parameter = parameter; + } + + // An example parameter + public final String parameter; + + /** + * Process the action locally - runs on datamodel service thread + */ + @Override + protected Object executeAction() { + requestBackgroundWork(); + return executeActionResult; + } + + /** + * Process the response from the server - runs on datamodel service thread + */ + @Override + protected Object processBackgroundResponse(final Bundle response) { + requestBackgroundWork(new TestChatActionOther(null, parameter)); + return response.get(RESPONSE_TEST); + } + + /** + * Called in case of failures when sending requests - runs on datamodel service thread + */ + @Override + protected Object processBackgroundFailure() { + return processFailureResult; + } + + private TestChatAction(final Parcel in) { + super(in); + // Cache parameter as a member variable + parameter = actionParameters.getString(KEY_PARAMETER); + } + + public static final Parcelable.Creator<TestChatAction> CREATOR + = new Parcelable.Creator<TestChatAction>() { + @Override + public TestChatAction createFromParcel(final Parcel in) { + return new TestChatAction(in); + } + + @Override + public TestChatAction[] newArray(final int size) { + return new TestChatAction[size]; + } + }; + + @Override + public void writeToParcel(final Parcel parcel, final int flags) { + writeActionToParcel(parcel, flags); + } + } + + public static class TestChatActionOther extends Action implements Parcelable { + protected TestChatActionOther(final String key, final String parameter) { + super(generateUniqueActionKey(key)); + this.parameter = parameter; + } + + public final String parameter; + + private TestChatActionOther(final Parcel in) { + super(in); + parameter = in.readString(); + } + + public static final Parcelable.Creator<TestChatActionOther> CREATOR + = new Parcelable.Creator<TestChatActionOther>() { + @Override + public TestChatActionOther createFromParcel(final Parcel in) { + return new TestChatActionOther(in); + } + + @Override + public TestChatActionOther[] newArray(final int size) { + return new TestChatActionOther[size]; + } + }; + + @Override + public void writeToParcel(final Parcel parcel, final int flags) { + writeActionToParcel(parcel, flags); + parcel.writeString(parameter); + } + } + + /** + * An operation that notifies a listener upon completion + */ + public static class TestChatActionMonitor extends ActionMonitor { + /** + * Create action state wrapping an BlockUserAction instance + * @param account - account in which to block the user + * @param baseKey - suggested action key from BlockUserAction + * @param data - optional action specific data that is handed back to listener + * @param listener - action completed listener + */ + public TestChatActionMonitor(final String baseKey, final Object data, + final ActionCompletedListener completed, final ActionExecutedListener executed) { + super(STATE_CREATED, Action.generateUniqueActionKey(baseKey), data); + setCompletedListener(completed); + setExecutedListener(executed); + } + } +} diff --git a/tests/src/com/android/messaging/datamodel/action/ActionServiceTest.java b/tests/src/com/android/messaging/datamodel/action/ActionServiceTest.java new file mode 100644 index 0000000..02cddae --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/action/ActionServiceTest.java @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.datamodel.action; + +import android.content.Intent; +import android.os.Bundle; +import android.os.Looper; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.Process; +import android.test.suitebuilder.annotation.MediumTest; +import android.util.Log; + +import com.android.messaging.Factory; +import com.android.messaging.FakeContext; +import com.android.messaging.FakeContext.FakeContextHost; +import com.android.messaging.FakeFactory; +import com.android.messaging.datamodel.BugleServiceTestCase; +import com.android.messaging.datamodel.FakeDataModel; +import com.android.messaging.datamodel.action.ActionMonitor.ActionCompletedListener; +import com.android.messaging.datamodel.action.ActionMonitor.ActionStateChangedListener; +import com.android.messaging.datamodel.action.ActionTestHelpers.ResultTracker; +import com.android.messaging.datamodel.action.ActionTestHelpers.StubBackgroundWorker; +import com.android.messaging.datamodel.action.ActionTestHelpers.StubConnectivityUtil; +import com.android.messaging.datamodel.action.ActionTestHelpers.StubLoader; +import com.android.messaging.util.WakeLockHelper; + +import java.util.ArrayList; + +@MediumTest +public class ActionServiceTest extends BugleServiceTestCase<ActionServiceImpl> + implements FakeContextHost, ActionStateChangedListener, ActionCompletedListener { + private static final String TAG = "ActionServiceTest"; + + @Override + public void onActionStateChanged(final Action action, final int state) { + mStates.add(state); + } + + @Override + public void onActionSucceeded(final ActionMonitor monitor, + final Action action, final Object data, final Object result) { + final TestChatAction test = (TestChatAction) action; + assertNotSame(test.dontRelyOnMe, dontRelyOnMe); + // This will be true - but only briefly + assertEquals(test.dontRelyOnMe, becauseIChange); + + final ResultTracker tracker = (ResultTracker) data; + tracker.completionResult = result; + synchronized(tracker) { + tracker.notifyAll(); + } + } + + @Override + public void onActionFailed(final ActionMonitor monitor, final Action action, + final Object data, final Object result) { + final TestChatAction test = (TestChatAction) action; + assertNotSame(test.dontRelyOnMe, dontRelyOnMe); + // This will be true - but only briefly + assertEquals(test.dontRelyOnMe, becauseIChange); + + final ResultTracker tracker = (ResultTracker) data; + tracker.completionResult = result; + synchronized(tracker) { + tracker.notifyAll(); + } + } + + /** + * For a dummy action verify that the service intent is constructed and queued correctly and + * that when that intent is processed it actually executes the action. + */ + public void testChatServiceCreatesIntentAndExecutesAction() { + final ResultTracker tracker = new ResultTracker(); + + final TestChatActionMonitor monitor = new TestChatActionMonitor(null, tracker, this, this); + final TestChatAction action = new TestChatAction(monitor.getActionKey(), parameter); + + action.dontRelyOnMe = dontRelyOnMe; + assertFalse("Expect service initially stopped", mServiceStarted); + + action.start(monitor); + + assertTrue("Expect service started", mServiceStarted); + + final ArrayList<Intent> intents = mContext.extractIntents(); + assertNotNull(intents); + assertEquals("Expect to see 1 server request queued", 1, intents.size()); + final Intent intent = intents.get(0); + assertEquals("Check pid", intent.getIntExtra(WakeLockHelper.EXTRA_CALLING_PID, 0), + Process.myPid()); + assertEquals("Check opcode", intent.getIntExtra(ActionServiceImpl.EXTRA_OP_CODE, 0), + ActionServiceImpl.OP_START_ACTION); + assertTrue("Check wakelock held", ActionServiceImpl.sWakeLock.isHeld(intent)); + + synchronized(tracker) { + try { + this.startService(intent); + // Wait for callback across threads + tracker.wait(2000); + } catch (final InterruptedException e) { + assertTrue("Interrupted waiting for response processing", false); + } + } + + assertEquals("Expect three states ", mStates.size(), 3); + assertEquals("State-0 should be STATE_QUEUED", (int)mStates.get(0), + ActionMonitor.STATE_QUEUED); + assertEquals("State-1 should be STATE_EXECUTING", (int)mStates.get(1), + ActionMonitor.STATE_EXECUTING); + assertEquals("State-2 should be STATE_COMPLETE", (int)mStates.get(2), + ActionMonitor.STATE_COMPLETE); + // TODO: Should find a way to reliably wait, this is a bit of a hack + if (ActionServiceImpl.sWakeLock.isHeld(intent)) { + Log.d(TAG, "ActionServiceTest: waiting for wakelock release"); + try { + Thread.sleep(100); + } catch (final InterruptedException e) { + } + } + assertFalse("Check wakelock released", ActionServiceImpl.sWakeLock.isHeld(intent)); + } + + StubBackgroundWorker mWorker; + FakeContext mContext; + StubLoader mLoader; + ActionService mService; + + ArrayList<Integer> mStates; + + private static final String parameter = "parameter"; + private static final Object dontRelyOnMe = "dontRelyOnMe"; + private static final Object becauseIChange = "becauseIChange"; + private static final Object executeActionResult = "executeActionResult"; + private static final Object processResponseResult = "processResponseResult"; + private static final Object processFailureResult = "processFailureResult"; + + public ActionServiceTest() { + super(ActionServiceImpl.class); + } + + @Override + public void setUp() throws Exception { + super.setUp(); + Log.d(TAG, "ChatActionTest setUp"); + + sLooper = Looper.myLooper(); + + mWorker = new StubBackgroundWorker(); + mContext = new FakeContext(getContext(), this); + FakeFactory.registerWithFakeContext(getContext(),mContext) + .withDataModel(new FakeDataModel(mContext) + .withBackgroundWorkerForActionService(mWorker) + .withActionService(new ActionService()) + .withConnectivityUtil(new StubConnectivityUtil(mContext))); + + mStates = new ArrayList<Integer>(); + setContext(Factory.get().getApplicationContext()); + } + + @Override + public String getServiceClassName() { + return ActionServiceImpl.class.getName(); + } + + boolean mServiceStarted = false; + + @Override + public void startServiceForStub(final Intent intent) { + // Do nothing until later + assertFalse(mServiceStarted); + mServiceStarted = true; + } + + @Override + public void onStartCommandForStub(final Intent intent, final int flags, final int startId) { + assertTrue(mServiceStarted); + } + + private static Looper sLooper; + public static void assertRunsOnOtherThread() { + assertTrue (Looper.myLooper() != Looper.getMainLooper()); + assertTrue (Looper.myLooper() != sLooper); + } + + public static class TestChatAction extends Action implements Parcelable { + public static String RESPONSE_TEST = "response_test"; + public static String KEY_PARAMETER = "parameter"; + + protected TestChatAction(final String key, final String parameter) { + super(key); + this.actionParameters.putString(KEY_PARAMETER, parameter); + } + + transient Object dontRelyOnMe; + + /** + * Process the action locally - runs on service thread + */ + @Override + protected Object executeAction() { + this.dontRelyOnMe = becauseIChange; + assertRunsOnOtherThread(); + return executeActionResult; + } + + /** + * Process the response from the server - runs on service thread + */ + @Override + protected Object processBackgroundResponse(final Bundle response) { + assertRunsOnOtherThread(); + return processResponseResult; + } + + /** + * Called in case of failures when sending requests - runs on service thread + */ + @Override + protected Object processBackgroundFailure() { + assertRunsOnOtherThread(); + return processFailureResult; + } + + private TestChatAction(final Parcel in) { + super(in); + } + + public static final Parcelable.Creator<TestChatAction> CREATOR + = new Parcelable.Creator<TestChatAction>() { + @Override + public TestChatAction createFromParcel(final Parcel in) { + return new TestChatAction(in); + } + + @Override + public TestChatAction[] newArray(final int size) { + return new TestChatAction[size]; + } + }; + + @Override + public void writeToParcel(final Parcel parcel, final int flags) { + writeActionToParcel(parcel, flags); + } + } + + /** + * An operation that notifies a listener upon state changes, execution and completion + */ + public static class TestChatActionMonitor extends ActionMonitor { + public TestChatActionMonitor(final String baseKey, final Object data, + final ActionStateChangedListener listener, final ActionCompletedListener executed) { + super(STATE_CREATED, Action.generateUniqueActionKey(baseKey), data); + setStateChangedListener(listener); + setCompletedListener(executed); + assertEquals("Initial state should be STATE_CREATED", mState, STATE_CREATED); + } + } +} diff --git a/tests/src/com/android/messaging/datamodel/action/ActionTest.java b/tests/src/com/android/messaging/datamodel/action/ActionTest.java new file mode 100644 index 0000000..aefa25e --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/action/ActionTest.java @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.datamodel.action; + +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.messaging.BugleTestCase; +import com.android.messaging.FakeFactory; +import com.android.messaging.datamodel.DataModelImpl; +import com.android.messaging.datamodel.action.ActionTestHelpers.StubChatActionMonitor; + +import java.util.ArrayList; + +@MediumTest +public class ActionTest extends BugleTestCase { + private static final String parameter = "parameter"; + private static final Object executeActionResult = "executeActionResult"; + private static final Object processResponseResult = "processResponseResult"; + private static final Object processFailureResult = "processFailureResult"; + + private static final String mActionKey = "TheActionKey"; + private static final Object mData = "PrivateData"; + private StubChatActionMonitor mMonitor; + private TestChatAction mAction; + + private ArrayList<StubChatActionMonitor.StateTransition> mTransitions; + + @Override + public void setUp() throws Exception { + super.setUp(); + FakeFactory.register(getTestContext()) + .withDataModel(new DataModelImpl(getContext())); + + mMonitor = new StubChatActionMonitor(ActionMonitor.STATE_CREATED, mActionKey, + mData); + mAction = new TestChatAction(mActionKey, parameter); + mTransitions = mMonitor.getTransitions(); + } + + private void verifyState(final int count, final int from, final int to) { + assertEquals(to, mMonitor.getState()); + assertEquals(mTransitions.size(), count); + verifyTransition(count-1, from , to); + } + + private void verifyTransition(final int index, final int from, final int to) { + assertTrue(mTransitions.size() > index); + assertEquals(mAction, mTransitions.get(index).action); + assertEquals(from, mTransitions.get(index).from); + assertEquals(to, mTransitions.get(index).to); + } + + @SmallTest + public void testActionStartTransitionsCorrectly() { + mMonitor.setState(ActionMonitor.STATE_CREATED); + + ActionMonitor.registerActionMonitor(mAction.actionKey, mMonitor); + assertTrue(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey)); + assertTrue(ActionMonitor.sActionMonitors.containsValue(mMonitor)); + assertEquals(ActionMonitor.sActionMonitors.get(mAction.actionKey), mMonitor); + + mAction.markStart(); + assertEquals("After start state: STATE_QUEUED", ActionMonitor.STATE_QUEUED, + mMonitor.getState()); + verifyState(1, ActionMonitor.STATE_CREATED, ActionMonitor.STATE_QUEUED); + + ActionMonitor.unregisterActionMonitor(mAction.actionKey, mMonitor); + + assertFalse(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey)); + assertFalse(ActionMonitor.sActionMonitors.containsValue(mMonitor)); + } + + @SmallTest + public void testActionStartAssertsFromIncorrectState() { + mMonitor.setState(ActionMonitor.STATE_UNDEFINED); + + ActionMonitor.registerActionMonitor(mAction.actionKey, mMonitor); + assertTrue(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey)); + assertTrue(ActionMonitor.sActionMonitors.containsValue(mMonitor)); + assertEquals(ActionMonitor.sActionMonitors.get(mAction.actionKey), mMonitor); + + try { + mAction.markStart(); + fail("Expect assertion when starting from STATE_UNDEFINED"); + } catch (final IllegalStateException ex){ + } + ActionMonitor.unregisterActionMonitor(mAction.actionKey, mMonitor); + + assertFalse(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey)); + assertFalse(ActionMonitor.sActionMonitors.containsValue(mMonitor)); + } + + public void testActionTransitionsEndToEndWithRequests() { + assertEquals("Start state: STATE_CREATED", ActionMonitor.STATE_CREATED, + mMonitor.getState()); + + ActionMonitor.registerActionMonitor(mAction.actionKey, mMonitor); + assertTrue(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey)); + assertTrue(ActionMonitor.sActionMonitors.containsValue(mMonitor)); + assertEquals(ActionMonitor.sActionMonitors.get(mAction.actionKey), mMonitor); + + mAction.markStart(); + + verifyState(1, ActionMonitor.STATE_CREATED, ActionMonitor.STATE_QUEUED); + + mAction.markBeginExecute(); + + verifyState(2, ActionMonitor.STATE_QUEUED, ActionMonitor.STATE_EXECUTING); + + final Object result = mAction.executeAction(); + mAction.requestBackgroundWork(); + + assertEquals("Check executeAction result", result, executeActionResult); + + mAction.markEndExecute(result); + + verifyState(3, ActionMonitor.STATE_EXECUTING, + ActionMonitor.STATE_BACKGROUND_ACTIONS_QUEUED); + + mAction.markBackgroundWorkStarting(); + + verifyState(4, ActionMonitor.STATE_BACKGROUND_ACTIONS_QUEUED, + ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION); + + mAction.markBackgroundWorkQueued(); + + verifyState(5, ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION, + ActionMonitor.STATE_BACKGROUND_ACTIONS_QUEUED); + + mAction.markBackgroundWorkStarting(); + + verifyState(6, ActionMonitor.STATE_BACKGROUND_ACTIONS_QUEUED, + ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION); + + final Bundle response = null; + + mAction.markBackgroundCompletionQueued(); + + verifyState(7, ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION, + ActionMonitor.STATE_BACKGROUND_COMPLETION_QUEUED); + + assertTrue(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey)); + assertTrue(ActionMonitor.sActionMonitors.containsValue(mMonitor)); + assertEquals(ActionMonitor.sActionMonitors.get(mAction.actionKey), mMonitor); + + mAction.processBackgroundWorkResponse(response); + + verifyTransition(7, ActionMonitor.STATE_BACKGROUND_COMPLETION_QUEUED, + ActionMonitor.STATE_PROCESSING_BACKGROUND_RESPONSE); + + verifyState(9, ActionMonitor.STATE_PROCESSING_BACKGROUND_RESPONSE, + ActionMonitor.STATE_COMPLETE); + + assertFalse(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey)); + assertFalse(ActionMonitor.sActionMonitors.containsValue(mMonitor)); + } + + public void testActionTransitionsEndToEndFailsRequests() { + assertEquals("Start state: STATE_CREATED", ActionMonitor.STATE_CREATED, + mMonitor.getState()); + + ActionMonitor.registerActionMonitor(mAction.actionKey, mMonitor); + assertTrue(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey)); + assertTrue(ActionMonitor.sActionMonitors.containsValue(mMonitor)); + assertEquals(ActionMonitor.sActionMonitors.get(mAction.actionKey), mMonitor); + + mAction.markStart(); + + verifyState(1, ActionMonitor.STATE_CREATED, ActionMonitor.STATE_QUEUED); + + mAction.markBeginExecute(); + + verifyState(2, ActionMonitor.STATE_QUEUED, ActionMonitor.STATE_EXECUTING); + + final Object result = mAction.executeAction(); + mAction.requestBackgroundWork(); + + assertEquals("Check executeAction result", result, executeActionResult); + + mAction.markEndExecute(result); + + verifyState(3, ActionMonitor.STATE_EXECUTING, + ActionMonitor.STATE_BACKGROUND_ACTIONS_QUEUED); + + mAction.markBackgroundWorkStarting(); + + verifyState(4, ActionMonitor.STATE_BACKGROUND_ACTIONS_QUEUED, + ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION); + + mAction.markBackgroundWorkQueued(); + + verifyState(5, ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION, + ActionMonitor.STATE_BACKGROUND_ACTIONS_QUEUED); + + mAction.markBackgroundWorkStarting(); + + verifyState(6, ActionMonitor.STATE_BACKGROUND_ACTIONS_QUEUED, + ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION); + + assertTrue(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey)); + assertTrue(ActionMonitor.sActionMonitors.containsValue(mMonitor)); + assertEquals(ActionMonitor.sActionMonitors.get(mAction.actionKey), mMonitor); + + mAction.processBackgroundWorkFailure(); + + verifyState(7, ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION, + ActionMonitor.STATE_COMPLETE); + + assertFalse(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey)); + assertFalse(ActionMonitor.sActionMonitors.containsValue(mMonitor)); + } + + public void testActionTransitionsEndToEndNoRequests() { + assertEquals("Start state: STATE_CREATED", ActionMonitor.STATE_CREATED, + mMonitor.getState()); + + ActionMonitor.registerActionMonitor(mAction.actionKey, mMonitor); + assertTrue(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey)); + assertTrue(ActionMonitor.sActionMonitors.containsValue(mMonitor)); + assertEquals(ActionMonitor.sActionMonitors.get(mAction.actionKey), mMonitor); + + mAction.markStart(); + + verifyState(1, ActionMonitor.STATE_CREATED, ActionMonitor.STATE_QUEUED); + + mAction.markBeginExecute(); + + verifyState(2, ActionMonitor.STATE_QUEUED, ActionMonitor.STATE_EXECUTING); + + final Object result = mAction.executeAction(); + + assertEquals("Check executeAction result", result, executeActionResult); + + assertTrue(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey)); + assertTrue(ActionMonitor.sActionMonitors.containsValue(mMonitor)); + assertEquals(ActionMonitor.sActionMonitors.get(mAction.actionKey), mMonitor); + + mAction.markEndExecute(result); + + verifyState(3, ActionMonitor.STATE_EXECUTING, + ActionMonitor.STATE_COMPLETE); + + assertFalse(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey)); + assertFalse(ActionMonitor.sActionMonitors.containsValue(mMonitor)); + } + + public static class TestChatAction extends Action implements Parcelable { + protected TestChatAction(final String key, final String parameter) { + super(key); + this.parameter = parameter; + } + + public final String parameter; + + /** + * Process the action locally - runs on service thread + */ + @Override + protected Object executeAction() { + assertEquals("Check parameter", parameter, ActionTest.parameter); + return executeActionResult; + } + + /** + * Process the response from the server - runs on service thread + */ + @Override + protected Object processBackgroundResponse(final Bundle response) { + assertEquals("Check parameter", parameter, ActionTest.parameter); + return processResponseResult; + } + + /** + * Called in case of failures when sending requests - runs on service thread + */ + @Override + protected Object processBackgroundFailure() { + assertEquals("Check parameter", parameter, ActionTest.parameter); + return processFailureResult; + } + + private TestChatAction(final Parcel in) { + super(in); + parameter = in.readString(); + } + + public static final Parcelable.Creator<TestChatAction> CREATOR + = new Parcelable.Creator<TestChatAction>() { + @Override + public TestChatAction createFromParcel(final Parcel in) { + return new TestChatAction(in); + } + + @Override + public TestChatAction[] newArray(final int size) { + return new TestChatAction[size]; + } + }; + + @Override + public void writeToParcel(final Parcel parcel, final int flags) { + writeActionToParcel(parcel, flags); + parcel.writeString(parameter); + } + } +} diff --git a/tests/src/com/android/messaging/datamodel/action/ActionTestHelpers.java b/tests/src/com/android/messaging/datamodel/action/ActionTestHelpers.java new file mode 100644 index 0000000..d72a0f9 --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/action/ActionTestHelpers.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.datamodel.action; + +import android.content.Context; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Bundle; + +import com.android.messaging.util.ConnectivityUtil; + +import java.util.ArrayList; +import java.util.List; + +public class ActionTestHelpers { + private static final String TAG = "DataModelTestHelpers"; + + static class StubLoader extends ContentObserver { + ArrayList<Uri> mUriList = new ArrayList<Uri>(); + + StubLoader() { + super(null); + } + + public void clear() { + mUriList.clear(); + } + + @Override + public void onChange(final boolean selfChange) { + // Handle change. + mUriList.add(null); + } + + // Implement the onChange(boolean, Uri) method to take advantage of the new Uri argument. + // Only supported on platform 16 and above... + @Override + public void onChange(final boolean selfChange, final Uri uri) { + // Handle change. + mUriList.add(uri); + } + } + + static class StubBackgroundWorker extends BackgroundWorker { + public StubBackgroundWorker() { + super(); + mActions = new ArrayList<Action>(); + } + + ArrayList<Action> mActions; + public ArrayList<Action> getRequestsMade() { + return mActions; + } + + @Override + public void queueBackgroundWork(final List<Action> actions) { + mActions.addAll(actions); + + synchronized(this) { + this.notifyAll(); + } + } + } + + static class ResultTracker { + public Object executionResult; + public Object completionResult; + } + + static class StubChatActionMonitor extends ActionMonitor { + static public class StateTransition { + Action action; + int from; + int to; + public StateTransition(final Action action, final int from, final int to) { + this.action = action; + this.from = from; + this.to = to; + } + } + + private final ArrayList<StateTransition> mTransitions; + public ArrayList<StateTransition> getTransitions() { + return mTransitions; + } + + protected StubChatActionMonitor(final int initialState, final String actionKey, + final Object data) { + super(initialState, actionKey, data); + mTransitions = new ArrayList<StateTransition>(); + } + + @Override + protected void updateState(final Action action, final int expectedState, + final int state) { + mTransitions.add(new StateTransition(action, mState, state)); + super.updateState(action, expectedState, state); + } + + public void setState(final int state) { + mState = state; + } + + public int getState() { + return mState; + } + } + + public static class StubActionService extends ActionService { + public static class StubActionServiceCallLog { + public final Action action; + public final Action request; + public final Bundle response; + public final Exception exception; + public final Action update; + + public StubActionServiceCallLog(final Action action, + final Action request, + final Bundle response, + final Exception exception, + final Action update) { + this.action = action; + this.request = request; + this.response = response; + this.exception = exception; + this.update = update; + } + } + + private final ArrayList<StubActionServiceCallLog> mServiceCalls = + new ArrayList<StubActionServiceCallLog>(); + + public ArrayList<StubActionServiceCallLog> getCalls() { + return mServiceCalls; + } + + @Override + public void startAction(final Action action) { + mServiceCalls.add(new StubActionServiceCallLog(action, null, null, null, null)); + synchronized(this) { + this.notifyAll(); + } + } + + @Override + public void handleResponseFromBackgroundWorker(final Action request, + final Bundle response) { + mServiceCalls.add(new StubActionServiceCallLog(null, request, response, null, null)); + synchronized(this) { + this.notifyAll(); + } + } + + @Override + protected void handleFailureFromBackgroundWorker(final Action request, + final Exception exception) { + mServiceCalls.add(new StubActionServiceCallLog(null, request, null, exception, null)); + synchronized(this) { + this.notifyAll(); + } + } + } + + public static class StubConnectivityUtil extends ConnectivityUtil { + public StubConnectivityUtil(final Context context) { + super(context); + } + + @Override + public void registerForSignalStrength() { + } + + @Override + public void unregisterForSignalStrength() { + } + } +} diff --git a/tests/src/com/android/messaging/datamodel/action/GetOrCreateConversationActionTest.java b/tests/src/com/android/messaging/datamodel/action/GetOrCreateConversationActionTest.java new file mode 100644 index 0000000..b05b022 --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/action/GetOrCreateConversationActionTest.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.datamodel.action; + +import android.content.pm.ProviderInfo; +import android.database.Cursor; +import android.net.Uri; +import android.test.mock.MockContentProvider; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.messaging.BugleTestCase; +import com.android.messaging.FakeContext; +import com.android.messaging.FakeFactory; +import com.android.messaging.datamodel.BugleDatabaseOperations; +import com.android.messaging.datamodel.DataModel; +import com.android.messaging.datamodel.DatabaseWrapper; +import com.android.messaging.datamodel.FakeDataModel; +import com.android.messaging.datamodel.MessagingContentProvider; +import com.android.messaging.datamodel.action.ActionTestHelpers.StubActionService; +import com.android.messaging.datamodel.action.ActionTestHelpers.StubActionService.StubActionServiceCallLog; +import com.android.messaging.datamodel.action.GetOrCreateConversationAction.GetOrCreateConversationActionListener; +import com.android.messaging.datamodel.action.GetOrCreateConversationAction.GetOrCreateConversationActionMonitor; +import com.android.messaging.datamodel.data.ParticipantData; +import com.android.messaging.datamodel.data.TestDataFactory; +import com.android.messaging.sms.MmsUtils; + +import org.mockito.Mock; + +import java.util.ArrayList; + +@SmallTest +public class GetOrCreateConversationActionTest extends BugleTestCase { + + @Mock GetOrCreateConversationActionListener mockListener; + + public void testGetOrCreateConversation() { + final DatabaseWrapper db = DataModel.get().getDatabase(); + + final ArrayList<String> recipients = new ArrayList<String>(); + recipients.add("5551234567"); + recipients.add("5551234568"); + + // Generate a list of partially formed participants + final ArrayList<ParticipantData> participants = new + ArrayList<ParticipantData>(); + + + for (final String recipient : recipients) { + participants.add(ParticipantData.getFromRawPhoneBySystemLocale(recipient)); + } + + // Test that we properly stubbed the SMS provider to return a thread id + final long threadId = MmsUtils.getOrCreateThreadId(mContext, recipients); + assertEquals(TestDataFactory.SMS_MMS_THREAD_ID_CURSOR_VALUE, threadId); + + final String blankId = BugleDatabaseOperations.getExistingConversation(db, threadId, false); + assertNull("Conversation already exists", blankId); + + ArrayList<StubActionServiceCallLog> calls = mService.getCalls(); + + GetOrCreateConversationActionMonitor monitor = + GetOrCreateConversationAction.getOrCreateConversation(participants, null, + mockListener); + + assertEquals("Failed to start service once for action", calls.size(), 1); + assertTrue("Action not GetOrCreateConversationAction", calls.get(0).action instanceof + GetOrCreateConversationAction); + + GetOrCreateConversationAction action = (GetOrCreateConversationAction) + calls.get(0).action; + + Object result = action.executeAction(); + + assertTrue(result instanceof String); + + // Make sure that we created a new conversation + assertEquals(TestDataFactory.NUM_TEST_CONVERSATIONS+1, Integer.parseInt((String)result)); + + // Now get the conversation that we just created again + monitor = GetOrCreateConversationAction.getOrCreateConversation(participants, null, + mockListener); + + calls = mService.getCalls(); + assertEquals("Failed to start service for second action", calls.size(), 2); + assertTrue("Action not GetOrCreateConversationAction", calls.get(1).action instanceof + GetOrCreateConversationAction); + action = (GetOrCreateConversationAction)calls.get(1).action; + result = action.executeAction(); + + assertTrue(result instanceof String); + + final String conversationId = (String) result; + + // Make sure that we found the same conversation id + assertEquals(TestDataFactory.NUM_TEST_CONVERSATIONS+1, Integer.parseInt((String)result)); + + final ArrayList<ParticipantData> conversationParticipants = + BugleDatabaseOperations.getParticipantsForConversation(db, conversationId); + + assertEquals("Participant count mismatch", recipients.size(), + conversationParticipants.size()); + for(final ParticipantData participant : conversationParticipants) { + assertTrue(recipients.contains(participant.getSendDestination())); + } + + final Uri conversationParticipantsUri = + MessagingContentProvider.buildConversationParticipantsUri(conversationId); + final Cursor cursor = mContext.getContentResolver().query(conversationParticipantsUri, + ParticipantData.ParticipantsQuery.PROJECTION, null, null, null); + + int countSelf = 0; + while(cursor.moveToNext()) { + final ParticipantData participant = ParticipantData.getFromCursor(cursor); + if (participant.isSelf()) { + countSelf++; + } else { + assertTrue(recipients.contains(participant.getSendDestination())); + } + } + cursor.close(); + assertEquals("Expect one self participant in conversations", 1, countSelf); + assertEquals("Cursor count mismatch", recipients.size(), cursor.getCount() - countSelf); + + final String realId = BugleDatabaseOperations.getExistingConversation(db, threadId, false); + assertEquals("Conversation already exists", realId, conversationId); + } + + private FakeContext mContext; + private StubActionService mService; + + @Override + public void setUp() throws Exception { + super.setUp(); + + mContext = new FakeContext(getTestContext()); + + final MockContentProvider mockProvider = new MockContentProvider() { + @Override + public Cursor query(final Uri uri, final String[] projection, final String selection, + final String[] selectionArgs, final String sortOrder) { + return TestDataFactory.getSmsMmsThreadIdCursor(); + } + }; + + mContext.addContentProvider("mms-sms", mockProvider); + final MessagingContentProvider provider = new MessagingContentProvider(); + final ProviderInfo providerInfo = new ProviderInfo(); + providerInfo.authority = MessagingContentProvider.AUTHORITY; + provider.attachInfo(mContext, providerInfo); + mContext.addContentProvider(MessagingContentProvider.AUTHORITY, provider); + + mService = new StubActionService(); + final FakeDataModel fakeDataModel = new FakeDataModel(mContext) + .withActionService(mService); + FakeFactory.registerWithFakeContext(getTestContext(), mContext) + .withDataModel(fakeDataModel); + provider.setDatabaseForTest(fakeDataModel.getDatabase()); + } +} diff --git a/tests/src/com/android/messaging/datamodel/action/ReadWriteDraftMessageActionTest.java b/tests/src/com/android/messaging/datamodel/action/ReadWriteDraftMessageActionTest.java new file mode 100644 index 0000000..0405c90 --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/action/ReadWriteDraftMessageActionTest.java @@ -0,0 +1,482 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.datamodel.action; + +import android.content.ContentProvider; +import android.content.pm.ProviderInfo; +import android.database.Cursor; +import android.net.Uri; +import android.test.suitebuilder.annotation.SmallTest; +import android.text.TextUtils; + +import com.android.messaging.BugleTestCase; +import com.android.messaging.FakeContext; +import com.android.messaging.FakeFactory; +import com.android.messaging.datamodel.BugleDatabaseOperations; +import com.android.messaging.datamodel.DataModel; +import com.android.messaging.datamodel.DatabaseHelper; +import com.android.messaging.datamodel.DatabaseHelper.ConversationColumns; +import com.android.messaging.datamodel.DatabaseWrapper; +import com.android.messaging.datamodel.FakeDataModel; +import com.android.messaging.datamodel.MediaScratchFileProvider; +import com.android.messaging.datamodel.MessagingContentProvider; +import com.android.messaging.datamodel.action.ActionTestHelpers.StubActionService; +import com.android.messaging.datamodel.action.ActionTestHelpers.StubActionService.StubActionServiceCallLog; +import com.android.messaging.datamodel.action.ActionTestHelpers.StubConnectivityUtil; +import com.android.messaging.datamodel.action.ReadDraftDataAction.ReadDraftDataActionListener; +import com.android.messaging.datamodel.data.MessageData; +import com.android.messaging.datamodel.data.MessagePartData; +import com.android.messaging.datamodel.data.ParticipantData; +import com.android.messaging.util.ContentType; + +import org.mockito.Mock; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; + +@SmallTest +public class ReadWriteDraftMessageActionTest extends BugleTestCase { + + @Mock ReadDraftDataActionListener mockListener; + + // TODO: Add test cases + // 1. Make sure drafts can include attachments and multiple parts + // 2. Make sure attachments get cleaned up appropriately + // 3. Make sure messageId and partIds not reused (currently each draft is a new message). + public void testWriteDraft() { + final String draftMessage = "draftMessage"; + final long threadId = 1234567; + final boolean senderBlocked = false; + final String participantNumber = "5551234567"; + + final DatabaseWrapper db = DataModel.get().getDatabase(); + + final String conversationId = getOrCreateConversation(db, participantNumber, threadId, + senderBlocked); + final String selfId = getOrCreateSelfId(db); + + // Should clear/stub DB + final ArrayList<StubActionServiceCallLog> calls = mService.getCalls(); + + final MessageData message = MessageData.createDraftSmsMessage(conversationId, selfId, + draftMessage); + + WriteDraftMessageAction.writeDraftMessage(conversationId, message); + + assertEquals("Failed to start service once for action", calls.size(), 1); + assertTrue("Action not SaveDraftMessageAction", + calls.get(0).action instanceof WriteDraftMessageAction); + + final Action save = calls.get(0).action; + + final Object result = save.executeAction(); + + assertTrue("Expect row number string as result", result instanceof String); + final String messageId = (String) result; + + // Should check DB + final MessageData actual = BugleDatabaseOperations.readMessage(db, messageId); + assertNotNull("Database missing draft", actual); + assertEquals("Draft text changed", draftMessage, actual.getMessageText()); + } + + private static String getOrCreateSelfId(final DatabaseWrapper db) { + db.beginTransaction(); + final String selfId = BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, + ParticipantData.getSelfParticipant(ParticipantData.DEFAULT_SELF_SUB_ID)); + db.setTransactionSuccessful(); + db.endTransaction(); + return selfId; + } + + private static String getOrCreateConversation(final DatabaseWrapper db, + final String participantNumber, final long threadId, final boolean senderBlocked) { + final ArrayList<ParticipantData> participants = + new ArrayList<ParticipantData>(); + participants.add(ParticipantData.getFromRawPhoneBySystemLocale(participantNumber)); + + final String conversationId = BugleDatabaseOperations.getOrCreateConversation(db, threadId, + senderBlocked, participants, false, false, null); + assertNotNull("No conversation", conversationId); + return conversationId; + } + + public void testReadDraft() { + final Object data = "data"; + final String draftMessage = "draftMessage"; + final long threadId = 1234567; + final boolean senderBlocked = false; + final String participantNumber = "5552345678"; + + final DatabaseWrapper db = DataModel.get().getDatabase(); + + final String conversationId = getOrCreateConversation(db, participantNumber, threadId, + senderBlocked); + final String selfId = getOrCreateSelfId(db); + + // Should clear/stub DB + final ArrayList<StubActionServiceCallLog> calls = mService.getCalls(); + + final MessageData message = MessageData.createDraftSmsMessage(conversationId, selfId, + draftMessage); + + BugleDatabaseOperations.updateDraftMessageData(db, conversationId, message, + BugleDatabaseOperations.UPDATE_MODE_ADD_DRAFT); + + final ActionMonitor monitor = + ReadDraftDataAction.readDraftData(conversationId, null, data, mockListener); + + assertEquals("Unexpected number of calls to service", 1, calls.size()); + assertTrue("Action not of type ReadDraftMessageAction", + calls.get(0).action instanceof ReadDraftDataAction); + + final Action read = calls.get(0).action; + + final Object result = read.executeAction(); + + assertTrue(result instanceof ReadDraftDataAction.DraftData); + final ReadDraftDataAction.DraftData draft = (ReadDraftDataAction.DraftData) result; + + assertEquals("Draft message text differs", draftMessage, draft.message.getMessageText()); + assertEquals("Draft self differs", selfId, draft.message.getSelfId()); + assertEquals("Draft conversation differs", conversationId, + draft.conversation.getConversationId()); + } + + public void testReadDraftForNewConversation() { + final Object data = "data"; + long threadId = 1234567; + final boolean senderBlocked = false; + long phoneNumber = 5557654567L; + final String notConversationId = "ThisIsNotValidConversationId"; + + final DatabaseWrapper db = DataModel.get().getDatabase(); + final String selfId = getOrCreateSelfId(db); + + // Unless set a new conversation should have a null draft message + final MessageData blank = BugleDatabaseOperations.readDraftMessageData(db, + notConversationId, selfId); + assertNull(blank); + + String conversationId = null; + do { + conversationId = BugleDatabaseOperations.getExistingConversation(db, + threadId, senderBlocked); + threadId++; + phoneNumber++; + } + while(!TextUtils.isEmpty(conversationId)); + + final ArrayList<ParticipantData> participants = + new ArrayList<ParticipantData>(); + participants.add(ParticipantData.getFromRawPhoneBySystemLocale(Long.toString(phoneNumber))); + + conversationId = BugleDatabaseOperations.getOrCreateConversation(db, threadId, + senderBlocked, participants, false, false, null); + assertNotNull("No conversation", conversationId); + + final MessageData actual = BugleDatabaseOperations.readDraftMessageData(db, conversationId, + selfId); + assertNull(actual); + + // Should clear/stub DB + final ArrayList<StubActionServiceCallLog> calls = mService.getCalls(); + + final ActionMonitor monitor = + ReadDraftDataAction.readDraftData(conversationId, null, data, mockListener); + + assertEquals("Unexpected number of calls to service", 1, calls.size()); + assertTrue("Action not of type ReadDraftMessageAction", + calls.get(0).action instanceof ReadDraftDataAction); + + final Action read = calls.get(0).action; + + final Object result = read.executeAction(); + + assertTrue(result instanceof ReadDraftDataAction.DraftData); + final ReadDraftDataAction.DraftData draft = (ReadDraftDataAction.DraftData) result; + + assertEquals("Draft message text differs", "", draft.message.getMessageText()); + assertEquals("Draft self differs", selfId, draft.message.getSelfId()); + assertEquals("Draft conversation differs", conversationId, + draft.conversation.getConversationId()); + } + + public void testWriteAndReadDraft() { + final Object data = "data"; + final String draftMessage = "draftMessage"; + + final DatabaseWrapper db = DataModel.get().getDatabase(); + final Cursor conversations = db.query(DatabaseHelper.CONVERSATIONS_TABLE, + new String[] { ConversationColumns._ID, ConversationColumns.CURRENT_SELF_ID }, null, + null, null /* groupBy */, null /* having */, null /* orderBy */); + + if (conversations.moveToFirst()) { + final String conversationId = conversations.getString(0); + final String selfId = getOrCreateSelfId(db); + + // Should clear/stub DB + final ArrayList<StubActionServiceCallLog> calls = mService.getCalls(); + + final MessageData message = MessageData.createDraftSmsMessage(conversationId, selfId, + draftMessage); + + WriteDraftMessageAction.writeDraftMessage(conversationId, message); + + assertEquals("Failed to start service once for action", calls.size(), 1); + assertTrue("Action not SaveDraftMessageAction", + calls.get(0).action instanceof WriteDraftMessageAction); + + final Action save = calls.get(0).action; + + Object result = save.executeAction(); + + assertTrue("Expect row number string as result", result instanceof String); + + // Should check DB + + final ActionMonitor monitor = + ReadDraftDataAction.readDraftData(conversationId, null, data, + mockListener); + + assertEquals("Expect two calls queued", 2, calls.size()); + assertTrue("Expect action", calls.get(1).action instanceof ReadDraftDataAction); + + final Action read = calls.get(1).action; + + result = read.executeAction(); + + assertTrue(result instanceof ReadDraftDataAction.DraftData); + final ReadDraftDataAction.DraftData draft = (ReadDraftDataAction.DraftData) result; + + assertEquals("Draft message text differs", draftMessage, draft.message.getMessageText()); + // The conversation's self id is used as the draft's self id. + assertEquals("Draft self differs", conversations.getString(1), + draft.message.getSelfId()); + assertEquals("Draft conversation differs", conversationId, + draft.conversation.getConversationId()); + } else { + fail("No conversations in database"); + } + } + + public void testUpdateDraft() { + final String initialMessage = "initialMessage"; + final String draftMessage = "draftMessage"; + final long threadId = 1234567; + final boolean senderBlocked = false; + final String participantNumber = "5553456789"; + + final DatabaseWrapper db = DataModel.get().getDatabase(); + + final String conversationId = getOrCreateConversation(db, participantNumber, threadId, + senderBlocked); + final String selfId = getOrCreateSelfId(db); + + final ArrayList<StubActionServiceCallLog> calls = mService.getCalls(); + + // Insert initial message + MessageData initial = MessageData.createDraftSmsMessage(conversationId, selfId, + initialMessage); + + BugleDatabaseOperations.updateDraftMessageData(db, conversationId, initial, + BugleDatabaseOperations.UPDATE_MODE_ADD_DRAFT); + + initial = BugleDatabaseOperations.readDraftMessageData(db, + conversationId, selfId); + assertEquals("Initial text mismatch", initialMessage, initial.getMessageText()); + + // Now update the draft + final MessageData message = MessageData.createDraftSmsMessage(conversationId, selfId, + draftMessage); + WriteDraftMessageAction.writeDraftMessage(conversationId, message); + + assertEquals("Failed to start service once for action", calls.size(), 1); + assertTrue("Action not SaveDraftMessageAction", + calls.get(0).action instanceof WriteDraftMessageAction); + + final Action save = calls.get(0).action; + + final Object result = save.executeAction(); + + assertTrue("Expect row number string as result", result instanceof String); + + // Check DB + final MessageData actual = BugleDatabaseOperations.readDraftMessageData(db, + conversationId, selfId); + assertNotNull("Database missing draft", actual); + assertEquals("Draft text mismatch", draftMessage, actual.getMessageText()); + assertNull("Draft messageId should be null", actual.getMessageId()); + } + + public void testBugleDatabaseDraftOperations() { + final String initialMessage = "initialMessage"; + final String draftMessage = "draftMessage"; + final long threadId = 1234599; + final boolean senderBlocked = false; + final String participantNumber = "5553456798"; + final String subject = "subject here"; + + final DatabaseWrapper db = DataModel.get().getDatabase(); + + final String conversationId = getOrCreateConversation(db, participantNumber, threadId, + senderBlocked); + final String selfId = getOrCreateSelfId(db); + + final String text = "This is some text"; + final Uri mOutputUri = MediaScratchFileProvider.buildMediaScratchSpaceUri("txt"); + OutputStream outputStream = null; + try { + outputStream = mContext.getContentResolver().openOutputStream(mOutputUri); + outputStream.write(text.getBytes()); + } catch (final FileNotFoundException e) { + fail("Cannot open output file"); + } catch (final IOException e) { + fail("Cannot write output file"); + } + + final MessageData initial = + MessageData.createDraftMmsMessage(conversationId, selfId, initialMessage, subject); + initial.addPart(MessagePartData.createMediaMessagePart(ContentType.MULTIPART_MIXED, + mOutputUri, 0, 0)); + + final String initialMessageId = BugleDatabaseOperations.updateDraftMessageData(db, + conversationId, initial, BugleDatabaseOperations.UPDATE_MODE_ADD_DRAFT); + assertNotNull(initialMessageId); + + final MessageData initialDraft = BugleDatabaseOperations.readMessage(db, initialMessageId); + assertNotNull(initialDraft); + int cnt = 0; + for(final MessagePartData part : initialDraft.getParts()) { + if (part.isAttachment()) { + assertEquals(part.getContentUri(), mOutputUri); + } else { + assertEquals(part.getText(), initialMessage); + } + cnt++; + } + assertEquals("Wrong number of parts", 2, cnt); + + InputStream inputStream = null; + try { + inputStream = mContext.getContentResolver().openInputStream(mOutputUri); + final byte[] buffer = new byte[256]; + final int read = inputStream.read(buffer); + assertEquals(read, text.getBytes().length); + } catch (final FileNotFoundException e) { + fail("Cannot open input file"); + } catch (final IOException e) { + fail("Cannot read input file"); + } + + final String moreText = "This is some more text"; + final Uri mAnotherUri = MediaScratchFileProvider.buildMediaScratchSpaceUri("txt"); + outputStream = null; + try { + outputStream = mContext.getContentResolver().openOutputStream(mAnotherUri); + outputStream.write(moreText.getBytes()); + } catch (final FileNotFoundException e) { + fail("Cannot open output file"); + } catch (final IOException e) { + fail("Cannot write output file"); + } + + final MessageData another = + MessageData.createDraftMmsMessage(conversationId, selfId, draftMessage, subject); + another.addPart(MessagePartData.createMediaMessagePart(ContentType.MMS_MULTIPART_MIXED, + mAnotherUri, 0, 0)); + + final String anotherMessageId = BugleDatabaseOperations.updateDraftMessageData(db, + conversationId, another, BugleDatabaseOperations.UPDATE_MODE_ADD_DRAFT); + assertNotNull(anotherMessageId); + + final MessageData anotherDraft = BugleDatabaseOperations.readMessage(db, anotherMessageId); + assertNotNull(anotherDraft); + cnt = 0; + for(final MessagePartData part : anotherDraft.getParts()) { + if (part.isAttachment()) { + assertEquals(part.getContentUri(), mAnotherUri); + } else { + assertEquals(part.getText(), draftMessage); + } + cnt++; + } + assertEquals("Wrong number of parts", 2, cnt); + + inputStream = null; + try { + inputStream = mContext.getContentResolver().openInputStream(mOutputUri); + assertNull("Original draft content should have been deleted", inputStream); + } catch (final FileNotFoundException e) { + } + inputStream = null; + try { + inputStream = mContext.getContentResolver().openInputStream(mAnotherUri); + final byte[] buffer = new byte[256]; + final int read = inputStream.read(buffer); + assertEquals(read, moreText.getBytes().length); + } catch (final FileNotFoundException e) { + fail("Cannot open input file"); + } catch (final IOException e) { + fail("Cannot read input file"); + } + + final MessageData last = null; + final String lastPartId = BugleDatabaseOperations.updateDraftMessageData(db, + conversationId, last, BugleDatabaseOperations.UPDATE_MODE_ADD_DRAFT); + assertNull(lastPartId); + + inputStream = null; + try { + inputStream = mContext.getContentResolver().openInputStream(mAnotherUri); + assertNull("Original draft content should have been deleted", inputStream); + } catch (final FileNotFoundException e) { + } + + } + + private StubActionService mService; + + @Override + public void setUp() throws Exception { + super.setUp(); + + final FakeContext context = new FakeContext(getTestContext()); + + final ContentProvider bugleProvider = new MessagingContentProvider(); + final ProviderInfo bugleProviderInfo = new ProviderInfo(); + bugleProviderInfo.authority = MessagingContentProvider.AUTHORITY; + bugleProvider.attachInfo(mContext, bugleProviderInfo); + context.addContentProvider(MessagingContentProvider.AUTHORITY, bugleProvider); + final ContentProvider mediaProvider = new MediaScratchFileProvider(); + final ProviderInfo mediaProviderInfo = new ProviderInfo(); + mediaProviderInfo.authority = MediaScratchFileProvider.AUTHORITY; + mediaProvider.attachInfo(mContext, mediaProviderInfo); + context.addContentProvider(MediaScratchFileProvider.AUTHORITY, mediaProvider); + + mService = new StubActionService(); + final FakeDataModel fakeDataModel = new FakeDataModel(context) + .withActionService(mService) + .withConnectivityUtil(new StubConnectivityUtil(context)); + FakeFactory.registerWithFakeContext(getTestContext(), context) + .withDataModel(fakeDataModel); + + } +} diff --git a/tests/src/com/android/messaging/datamodel/data/ConversationMessageDataTest.java b/tests/src/com/android/messaging/datamodel/data/ConversationMessageDataTest.java new file mode 100644 index 0000000..c6b9b20 --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/data/ConversationMessageDataTest.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.messaging.datamodel.data; + +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.messaging.BugleTestCase; +import com.android.messaging.FakeFactory; +import com.android.messaging.datamodel.FakeCursor; +import com.android.messaging.datamodel.data.ConversationMessageData; +import com.android.messaging.datamodel.data.MessageData; +import com.android.messaging.datamodel.data.ConversationMessageData.ConversationMessageViewColumns; + +@SmallTest +public class ConversationMessageDataTest extends BugleTestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + FakeFactory.register(getTestContext()); + } + + public void testBindFirstMessage() { + final FakeCursor testCursor = TestDataFactory.getConversationMessageCursor(); + final ConversationMessageData data = new ConversationMessageData(); + testCursor.moveToFirst(); + data.bind(testCursor); + // TODO: Add before checking in all bound fields... + assertEquals(testCursor.getAt(ConversationMessageViewColumns.STATUS, 0).equals( + MessageData.BUGLE_STATUS_INCOMING_COMPLETE), data.getIsIncoming()); + assertEquals(testCursor.getAt(ConversationMessageViewColumns.SENDER_PROFILE_PHOTO_URI, + 0), data.getSenderProfilePhotoUri()); + assertEquals(testCursor.getAt(ConversationMessageViewColumns.SENDER_FULL_NAME, 0), + data.getSenderFullName()); + } + + public void testBindTwice() { + final FakeCursor testCursor = TestDataFactory.getConversationMessageCursor(); + final ConversationMessageData data = new ConversationMessageData(); + testCursor.moveToPosition(1); + data.bind(testCursor); + assertEquals(TestDataFactory.getMessageText(testCursor, 1), data.getText()); + assertEquals(testCursor.getAt(ConversationMessageViewColumns.RECEIVED_TIMESTAMP, 1), + data.getReceivedTimeStamp()); + assertEquals(testCursor.getAt(ConversationMessageViewColumns.STATUS, 1).equals( + MessageData.BUGLE_STATUS_INCOMING_COMPLETE), data.getIsIncoming()); + assertEquals(testCursor.getAt(ConversationMessageViewColumns.SENDER_PROFILE_PHOTO_URI, + 1), data.getSenderProfilePhotoUri()); + assertEquals(testCursor.getAt(ConversationMessageViewColumns.SENDER_FULL_NAME, 1), + data.getSenderFullName()); + testCursor.moveToPosition(2); + data.bind(testCursor); + assertEquals(TestDataFactory.getMessageText(testCursor, 2), data.getText()); + assertEquals(testCursor.getAt(ConversationMessageViewColumns.RECEIVED_TIMESTAMP, 2), + data.getReceivedTimeStamp()); + assertEquals(testCursor.getAt(ConversationMessageViewColumns.STATUS, 2).equals( + MessageData.BUGLE_STATUS_INCOMING_COMPLETE), data.getIsIncoming()); + assertEquals(testCursor.getAt(ConversationMessageViewColumns.SENDER_PROFILE_PHOTO_URI, + 2), data.getSenderProfilePhotoUri()); + assertEquals(testCursor.getAt(ConversationMessageViewColumns.SENDER_FULL_NAME, 2), + data.getSenderFullName()); + } + + public void testMessageClustering() { + final FakeCursor testCursor = TestDataFactory.getConversationMessageCursor(); + final ConversationMessageData data = new ConversationMessageData(); + testCursor.moveToPosition(0); + data.bind(testCursor); + assertFalse(data.getCanClusterWithPreviousMessage()); + assertFalse(data.getCanClusterWithNextMessage()); + + testCursor.moveToPosition(1); + data.bind(testCursor); + assertFalse(data.getCanClusterWithPreviousMessage()); + assertFalse(data.getCanClusterWithNextMessage()); + + testCursor.moveToPosition(2); + data.bind(testCursor); + assertFalse(data.getCanClusterWithPreviousMessage()); + assertTrue(data.getCanClusterWithNextMessage()); // 2 and 3 can be clustered + testCursor.moveToPosition(3); + + data.bind(testCursor); + assertTrue(data.getCanClusterWithPreviousMessage()); // 2 and 3 can be clustered + assertFalse(data.getCanClusterWithNextMessage()); + } +} diff --git a/tests/src/com/android/messaging/datamodel/data/ConversationParticipantsDataTest.java b/tests/src/com/android/messaging/datamodel/data/ConversationParticipantsDataTest.java new file mode 100644 index 0000000..527a600 --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/data/ConversationParticipantsDataTest.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.messaging.datamodel.data; + +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.messaging.BugleTestCase; +import com.android.messaging.datamodel.FakeCursor; +import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns; +import com.android.messaging.datamodel.data.ConversationParticipantsData; +import com.android.messaging.datamodel.data.ParticipantData; + +@SmallTest +public class ConversationParticipantsDataTest extends BugleTestCase { + public void testBindParticipants() { + final FakeCursor testCursor = TestDataFactory.getConversationParticipantsCursor(); + final ConversationParticipantsData data = new ConversationParticipantsData(); + data.bind(testCursor); + + assertEquals(data.getParticipantListExcludingSelf().size(), testCursor.getCount()); + final ParticipantData participant2 = data.getParticipantById("2"); + assertNotNull(participant2); + assertEquals(participant2.getFirstName(), testCursor.getAt( + ParticipantColumns.FIRST_NAME, 1) ); + assertEquals(participant2.getSendDestination(), testCursor.getAt( + ParticipantColumns.SEND_DESTINATION, 1)); + } +} diff --git a/tests/src/com/android/messaging/datamodel/data/TestDataFactory.java b/tests/src/com/android/messaging/datamodel/data/TestDataFactory.java new file mode 100644 index 0000000..8527e2b --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/data/TestDataFactory.java @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.datamodel.data; + +import android.content.ContentValues; +import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; +import android.provider.BaseColumns; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.provider.ContactsContract.Contacts; +import android.provider.MediaStore.Images.Media; + +import com.android.messaging.datamodel.BugleDatabaseOperations; +import com.android.messaging.datamodel.DatabaseHelper; +import com.android.messaging.datamodel.DatabaseHelper.ConversationColumns; +import com.android.messaging.datamodel.DatabaseHelper.MessageColumns; +import com.android.messaging.datamodel.DatabaseHelper.PartColumns; +import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns; +import com.android.messaging.datamodel.FakeCursor; +import com.android.messaging.datamodel.data.ConversationListItemData.ConversationListViewColumns; +import com.android.messaging.datamodel.data.ConversationMessageData.ConversationMessageViewColumns; +import com.android.messaging.util.Assert; +import com.android.messaging.util.ContactUtil; +import com.android.messaging.util.ContentType; + +import java.util.Arrays; +import java.util.List; + +/** + * A factory for fake objects that can be useful for multiple tests. + */ +public class TestDataFactory { + private final static String[] sConversationListCursorColumns = new String[] { + ConversationListViewColumns._ID, + ConversationListViewColumns.NAME, + ConversationListViewColumns.ICON, + ConversationListViewColumns.SNIPPET_TEXT, + ConversationListViewColumns.PREVIEW_URI, + ConversationListViewColumns.SORT_TIMESTAMP, + ConversationListViewColumns.READ, + ConversationListViewColumns.PREVIEW_CONTENT_TYPE, + ConversationListViewColumns.MESSAGE_STATUS, + }; + + private final static String[] sContactCursorColumns = new String[] { + Phone.CONTACT_ID, + Phone.DISPLAY_NAME_PRIMARY, + Phone.PHOTO_THUMBNAIL_URI, + Phone.NUMBER, + Phone.TYPE, + Phone.LABEL, + Phone.LOOKUP_KEY, + Phone._ID, + Phone.SORT_KEY_PRIMARY, + }; + + private final static String[] sFrequentContactCursorColumns = new String[] { + Contacts._ID, + Contacts.DISPLAY_NAME, + Contacts.PHOTO_URI, + Phone.LOOKUP_KEY, + }; + + private final static String[] sConversationMessageCursorColumns = new String[] { + ConversationMessageViewColumns._ID, + ConversationMessageViewColumns.CONVERSATION_ID, + ConversationMessageViewColumns.PARTICIPANT_ID, + ConversationMessageViewColumns.SENT_TIMESTAMP, + ConversationMessageViewColumns.RECEIVED_TIMESTAMP, + ConversationMessageViewColumns.STATUS, + ConversationMessageViewColumns.SENDER_FULL_NAME, + ConversationMessageViewColumns.SENDER_PROFILE_PHOTO_URI, + ConversationMessageViewColumns.PARTS_IDS, + ConversationMessageViewColumns.PARTS_CONTENT_TYPES, + ConversationMessageViewColumns.PARTS_CONTENT_URIS, + ConversationMessageViewColumns.PARTS_WIDTHS, + ConversationMessageViewColumns.PARTS_HEIGHTS, + ConversationMessageViewColumns.PARTS_TEXTS, + ConversationMessageViewColumns.PARTS_COUNT + }; + + private final static String[] sGalleryCursorColumns = new String[] { + Media._ID, + Media.DATA, + Media.WIDTH, + Media.HEIGHT, + Media.MIME_TYPE + }; + + public static FakeCursor getConversationListCursor() { + final Object[][] cursorData = new Object[][] { + new Object[] { Long.valueOf(1), "name1", "content://icon1", + "snippetText1", "content://snippetUri1", Long.valueOf(10), 1, + ContentType.IMAGE_JPEG, MessageData.BUGLE_STATUS_INCOMING_COMPLETE}, + new Object[] { Long.valueOf(2), "name2", "content://icon2", + "snippetText2", "content://snippetUri2", Long.valueOf(20) + 24*60*60*1000, + 0, ContentType.IMAGE_JPEG, MessageData.BUGLE_STATUS_INCOMING_COMPLETE}, + new Object[] { Long.valueOf(3), "name3", "content://icon3", + "snippetText3", "content://snippetUri3", Long.valueOf(30) + 2*24*60*60*1000, + 0, ContentType.IMAGE_JPEG, MessageData.BUGLE_STATUS_OUTGOING_COMPLETE} + }; + return new FakeCursor(ConversationListItemData.PROJECTION, sConversationListCursorColumns, + cursorData); + } + public static final int CONVERSATION_LIST_CURSOR_READ_MESSAGE_INDEX = 0; + public static final int CONVERSATION_LIST_CURSOR_UNREAD_MESSAGE_INDEX = 1; + + public static FakeCursor getEmptyConversationListCursor() { + return new FakeCursor(ConversationListItemData.PROJECTION, sConversationListCursorColumns, + new Object[][] {}); + } + + public static FakeCursor getConversationMessageCursor() { + final Object[][] cursorData = new Object[][] { + new Object[] { Long.valueOf(0), Long.valueOf(1), Long.valueOf(1), + Long.valueOf(10), Long.valueOf(10), + MessageData.BUGLE_STATUS_INCOMING_COMPLETE, "Alice", null, + "0", "text/plain", "''", -1, -1, "msg0", 1}, + new Object[] { Long.valueOf(1), Long.valueOf(1), Long.valueOf(2), + Long.valueOf(20), Long.valueOf(20), + MessageData.BUGLE_STATUS_OUTGOING_COMPLETE, "Bob", null, + "1", "text/plain", "''", -1, -1, "msg1", 1}, + new Object[] { Long.valueOf(2), Long.valueOf(1), Long.valueOf(1), + Long.valueOf(30), Long.valueOf(30), + MessageData.BUGLE_STATUS_OUTGOING_COMPLETE, "Alice", null, + "2", "contentType3", "'content://fakeUri3'", "0", "0", "msg1", 1}, + new Object[] { Long.valueOf(3), Long.valueOf(1), Long.valueOf(1), + Long.valueOf(40), Long.valueOf(40), + MessageData.BUGLE_STATUS_OUTGOING_COMPLETE, "Alice", null, + "3|4", "'contentType4'|'text/plain'", "'content://fakeUri4'|''", "0|-1", "0|-1", "''|'msg3'", 2}, + }; + return new FakeCursor( + ConversationMessageData.getProjection(), + sConversationMessageCursorColumns, + cursorData); + } + + public static String getMessageText(final FakeCursor messageCursor, final int row) { + final String allPartsText = messageCursor.getAt(ConversationMessageViewColumns.PARTS_TEXTS, row) + .toString(); + final int partsCount = (Integer) messageCursor.getAt( + ConversationMessageViewColumns.PARTS_COUNT, row); + final String messageId = messageCursor.getAt( + ConversationMessageViewColumns._ID, row).toString(); + final List<MessagePartData> parts = ConversationMessageData.makeParts( + messageCursor.getAt(ConversationMessageViewColumns.PARTS_IDS, row).toString(), + messageCursor.getAt(ConversationMessageViewColumns.PARTS_CONTENT_TYPES, row).toString(), + messageCursor.getAt(ConversationMessageViewColumns.PARTS_CONTENT_URIS, row).toString(), + messageCursor.getAt(ConversationMessageViewColumns.PARTS_WIDTHS, row).toString(), + messageCursor.getAt(ConversationMessageViewColumns.PARTS_HEIGHTS, row).toString(), + messageCursor.getAt(ConversationMessageViewColumns.PARTS_TEXTS, row).toString(), + partsCount, + messageId); + + for (final MessagePartData part : parts) { + if (part.isText()) { + return part.getText(); + } + } + return null; + } + + // Indexes where to find consecutive and non consecutive messages from same participant + // (respect to index - 1). + public static final int MESSAGE_WITH_SAME_PARTICIPANT_AS_PREVIOUS = 3; + public static final int MESSAGE_WITH_DIFFERENT_PARTICIPANT_AS_PREVIOUS = 2; + + public static FakeCursor getConversationParticipantsCursor() { + final String[] sConversationParticipantsCursorColumns = new String[] { + ParticipantColumns._ID, + ParticipantColumns.SUB_ID, + ParticipantColumns.NORMALIZED_DESTINATION, + ParticipantColumns.SEND_DESTINATION, + ParticipantColumns.FULL_NAME, + ParticipantColumns.FIRST_NAME, + ParticipantColumns.PROFILE_PHOTO_URI, + }; + + final Object[][] cursorData = new Object[][] { + new Object[] { 1, ParticipantData.OTHER_THAN_SELF_SUB_ID, "+15554567890", + "(555)456-7890", "alice in wonderland", "alice", "alice.png" }, + new Object[] { 2, ParticipantData.OTHER_THAN_SELF_SUB_ID, "+15551011121", + "(555)101-1121", "bob the baker", "bob", "bob.png"}, + new Object[] { 3, ParticipantData.OTHER_THAN_SELF_SUB_ID, "+15551314152", + "(555)131-4152", "charles in charge", "charles", "charles.png" }, + }; + + return new FakeCursor(ParticipantData.ParticipantsQuery.PROJECTION, + sConversationParticipantsCursorColumns, cursorData); + } + + public static final int CONTACT_LIST_CURSOR_FIRST_LEVEL_CONTACT_INDEX = 0; + public static final int CONTACT_LIST_CURSOR_SECOND_LEVEL_CONTACT_INDEX = 2; + + /** + * Returns a cursor for the all contacts list consumable by ContactPickerFragment. + */ + public static FakeCursor getAllContactListCursor() { + final Object[][] cursorData = new Object[][] { + new Object[] { Long.valueOf(0), "John Smith", "content://uri1", + "425-555-1234", Phone.TYPE_HOME, "", "0", Long.valueOf(0), 0 }, + new Object[] { Long.valueOf(1), "Sun Woo Kong", "content://uri2", + "425-555-1235", Phone.TYPE_MOBILE, "", "1", Long.valueOf(1), 1 }, + new Object[] { Long.valueOf(1), "Sun Woo Kong", "content://uri2", + "425-555-1238", Phone.TYPE_HOME, "", "1", Long.valueOf(2), 2 }, + new Object[] { Long.valueOf(2), "Anna Kinney", "content://uri3", + "425-555-1236", Phone.TYPE_MAIN, "", "3", Long.valueOf(3), 3 }, + new Object[] { Long.valueOf(3), "Mike Jones", "content://uri3", + "425-555-1236", Phone.TYPE_MAIN, "", "5", Long.valueOf(4), 4 }, + }; + return new FakeCursor(ContactUtil.PhoneQuery.PROJECTION, sContactCursorColumns, + cursorData); + } + + /** + * Returns a cursor for the frequent contacts list consumable by ContactPickerFragment. + * Note: make it so that this cursor is the generated result of getStrequentContactsCursor() + * and getAllContactListCursor(), i.e., expand the entries in getStrequentContactsCursor() + * with the details from getAllContactListCursor() + */ + public static FakeCursor getFrequentContactListCursor() { + final Object[][] cursorData = new Object[][] { + new Object[] { Long.valueOf(2), "Anna Kinney", "content://uri3", + "425-555-1236", Phone.TYPE_MAIN, "", "3", Long.valueOf(3), 0 }, + new Object[] { Long.valueOf(1), "Sun Woo Kong", "content://uri2", + "425-555-1235", Phone.TYPE_MOBILE, "", "1", Long.valueOf(1), 1}, + new Object[] { Long.valueOf(1), "Sun Woo Kong", "content://uri2", + "425-555-1238", Phone.TYPE_HOME, "", "1", Long.valueOf(2), 2 }, + new Object[] { Long.valueOf(0), "John Smith", "content://uri1", + "425-555-1234", Phone.TYPE_HOME, "", "0", Long.valueOf(0), 3 }, + }; + return new FakeCursor(ContactUtil.PhoneQuery.PROJECTION, sContactCursorColumns, + cursorData); + } + + /** + * Returns a strequent (starred + frequent) cursor (like the one produced by android contact + * provider's CONTENT_STREQUENT_URI query) that's consumable by FrequentContactsCursorBuilder. + */ + public static FakeCursor getStrequentContactsCursor() { + final Object[][] cursorData = new Object[][] { + new Object[] { Long.valueOf(0), "Anna Kinney", "content://uri1", "3" }, + new Object[] { Long.valueOf(1), "Sun Woo Kong", "content://uri2", "1" }, + new Object[] { Long.valueOf(2), "John Smith", "content://uri3", "0" }, + // Email-only entry that shouldn't be included in the result. + new Object[] { Long.valueOf(3), "Email Contact", "content://uri4", "100" }, + }; + return new FakeCursor(ContactUtil.FrequentContactQuery.PROJECTION, + sFrequentContactCursorColumns, cursorData); + } + + public static final int SMS_MMS_THREAD_ID_CURSOR_VALUE = 123456789; + + public static FakeCursor getSmsMmsThreadIdCursor() { + final String[] ID_PROJECTION = { BaseColumns._ID }; + final Object[][] cursorData = new Object[][] { + new Object[] { Long.valueOf(SMS_MMS_THREAD_ID_CURSOR_VALUE) }, + }; + return new FakeCursor(ID_PROJECTION, ID_PROJECTION, cursorData); + } + + public static FakeCursor getGalleryGridCursor() { + final Object[][] cursorData = new Object[][] { + new Object[] { Long.valueOf(0), "/sdcard/image1", 100, 100, "image/jpeg" }, + new Object[] { Long.valueOf(1), "/sdcard/image2", 200, 200, "image/png" }, + new Object[] { Long.valueOf(2), "/sdcard/image3", 300, 300, "image/jpeg" }, + }; + return new FakeCursor(GalleryGridItemData.IMAGE_PROJECTION, sGalleryCursorColumns, + cursorData); + } + + public static final int NUM_TEST_CONVERSATIONS = 10; + + /** + * Create test data in our db. + * + * Ideally this will create more realistic data with more variety. + */ + public static void createTestData(final SQLiteDatabase db) { + BugleDatabaseOperations.clearParticipantIdCache(); + + // Timestamp for 1 day ago + final long yesterday = System.currentTimeMillis() - (24 * 60 * 60 * 1000); + + final ContentValues conversationValues = new ContentValues(); + for (int i = 1; i <= NUM_TEST_CONVERSATIONS; i++) { + conversationValues.put(ConversationColumns.NAME, "Conversation " + i); + final long conversationId = db.insert(DatabaseHelper.CONVERSATIONS_TABLE, null, + conversationValues); + + final ContentValues messageValues = new ContentValues(); + for (int m = 1; m <= 25; m++) { + // Move forward ten minutes per conversation, 1 minute per message. + final long messageTime = yesterday + (i * 10 * 60 * 1000) + (m * 60 * 1000); + messageValues.put(MessageColumns.RECEIVED_TIMESTAMP, messageTime); + messageValues.put(MessageColumns.CONVERSATION_ID, conversationId); + messageValues.put(MessageColumns.SENDER_PARTICIPANT_ID, + Math.abs(("" + messageTime).hashCode()) % 2); + final long messageId = db.insert(DatabaseHelper.MESSAGES_TABLE, null, messageValues); + + // Create a text part for this message + final ContentValues partValues = new ContentValues(); + partValues.put(PartColumns.MESSAGE_ID, messageId); + partValues.put(PartColumns.CONVERSATION_ID, conversationId); + partValues.put(PartColumns.TEXT, "Conversation: " + conversationId + + " Message: " + m); + db.insert(DatabaseHelper.PARTS_TABLE, null, partValues); + + // Update the snippet for this conversation to the latest message inserted + conversationValues.clear(); + conversationValues.put(ConversationColumns.LATEST_MESSAGE_ID, messageId); + final int updatedCount = db.update(DatabaseHelper.CONVERSATIONS_TABLE, + conversationValues, + "_id=?", new String[]{String.valueOf(conversationId)}); + Assert.isTrue(updatedCount == 1); + } + } + } + + public static List<MessagePartData> getTestDraftAttachments() { + final MessagePartData[] retParts = new MessagePartData[] { + new MessagePartData(ContentType.IMAGE_JPEG, Uri.parse("content://image"), + 100, 100), + new MessagePartData(ContentType.VIDEO_3GPP, Uri.parse("content://video"), + 100, 100), + new MessagePartData(ContentType.TEXT_VCARD, Uri.parse("content://vcard"), + 0, 0), + new MessagePartData(ContentType.AUDIO_3GPP, Uri.parse("content://audio"), + 0, 0) + }; + return Arrays.asList(retParts); + } +} diff --git a/tests/src/com/android/messaging/datamodel/media/FakeImageRequest.java b/tests/src/com/android/messaging/datamodel/media/FakeImageRequest.java new file mode 100644 index 0000000..7f6ac3f --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/media/FakeImageRequest.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.messaging.datamodel.media; + +import android.text.TextUtils; + +import java.util.List; + +public class FakeImageRequest implements MediaRequest<FakeImageResource> { + public static final String INVALID_KEY = "invalid"; + private final String mKey; + private final int mSize; + + public FakeImageRequest(final String key, final int size) { + mKey = key; + mSize = size; + } + + @Override + public String getKey() { + return mKey; + } + + @Override + public FakeImageResource loadMediaBlocking(List<MediaRequest<FakeImageResource>> chainedTask) + throws Exception { + if (TextUtils.equals(mKey, INVALID_KEY)) { + throw new Exception(); + } else { + return new FakeImageResource(mSize, mKey); + } + } + + @Override + public int getCacheId() { + return FakeMediaCacheManager.FAKE_IMAGE_CACHE; + } + + @SuppressWarnings("unchecked") + @Override + public MediaCache<FakeImageResource> getMediaCache() { + return (MediaCache<FakeImageResource>) MediaCacheManager.get().getOrCreateMediaCacheById( + getCacheId()); + } + + @Override + public int getRequestType() { + return MediaRequest.REQUEST_LOAD_MEDIA; + } + + @Override + public MediaRequestDescriptor<FakeImageResource> getDescriptor() { + return null; + } +} diff --git a/tests/src/com/android/messaging/datamodel/media/FakeImageResource.java b/tests/src/com/android/messaging/datamodel/media/FakeImageResource.java new file mode 100644 index 0000000..969854f --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/media/FakeImageResource.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.messaging.datamodel.media; + +public class FakeImageResource extends RefCountedMediaResource { + private boolean mClosed = false; + private boolean mCached = false; + private final int mSize; + private final String mImageId; + + public FakeImageResource(final int size, final String imageId) { + super(null); + mSize = size; + mImageId = imageId; + } + + public boolean isClosed() { + return mClosed; + } + + public String getImageId() { + return mImageId; + } + + public void setCached(final boolean cached) { + mCached = cached; + } + + public boolean getCached() { + return mCached; + } + + @Override + public int getMediaSize() { + return mSize; + } + + @Override + protected void close() { + mClosed = true; + } +} diff --git a/tests/src/com/android/messaging/datamodel/media/FakeMediaCacheManager.java b/tests/src/com/android/messaging/datamodel/media/FakeMediaCacheManager.java new file mode 100644 index 0000000..34b3a55 --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/media/FakeMediaCacheManager.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.messaging.datamodel.media; + +public class FakeMediaCacheManager extends MediaCacheManager { + // List of available fake cache ids. + public static final int FAKE_IMAGE_CACHE = 1; + public static final int FAKE_BATCH_IMAGE_CACHE = 2; + + @Override + public MediaCache<?> createMediaCacheById(final int id) { + switch (id) { + case FAKE_IMAGE_CACHE: + // Make a cache of only 3 KB of data. + return new MediaCache<FakeImageResource>(3, FAKE_IMAGE_CACHE, "FakeImageCache"); + + case FAKE_BATCH_IMAGE_CACHE: + return new MediaCache<FakeImageResource>(10, FAKE_BATCH_IMAGE_CACHE, + "FakeBatchImageCache"); + } + return null; + } +} diff --git a/tests/src/com/android/messaging/datamodel/media/ImageRequestTest.java b/tests/src/com/android/messaging/datamodel/media/ImageRequestTest.java new file mode 100644 index 0000000..2cfec7d --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/media/ImageRequestTest.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.messaging.datamodel.media; + +import android.content.ContentResolver; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.messaging.BugleTestCase; +import com.android.messaging.FakeFactory; +import com.android.messaging.R; +import com.android.messaging.datamodel.MemoryCacheManager; +import com.android.messaging.util.ImageUtils; + +import org.mockito.ArgumentCaptor; +import org.mockito.Matchers; +import org.mockito.Mockito; +import org.mockito.Spy; + +import java.io.IOException; + +@SmallTest +public class ImageRequestTest extends BugleTestCase { + private static final int DOWNSAMPLE_IMAGE_SIZE = 2; + + @Spy protected ImageUtils spyImageUtils; + + @Override + protected void setUp() throws Exception { + super.setUp(); + FakeFactory.register(getTestContext()) + .withMemoryCacheManager(new MemoryCacheManager()) + .withMediaCacheManager(new BugleMediaCacheManager()); + spyImageUtils = Mockito.spy(new ImageUtils()); + ImageUtils.set(spyImageUtils); + } + + public void testLoadImageUnspecifiedSize() { + final String uriString = ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + + getContext().getPackageName() + "/" + R.drawable.ic_audio_light; + final Uri uri = Uri.parse(uriString); + final UriImageRequest imageRequest = new UriImageRequest(getContext(), + new UriImageRequestDescriptor(uri)); + try { + final ImageResource imageResource = imageRequest.loadMediaBlocking(null); + final ArgumentCaptor<BitmapFactory.Options> options = + ArgumentCaptor.forClass(BitmapFactory.Options.class); + Mockito.verify(spyImageUtils).calculateInSampleSize( + options.capture(), + Matchers.eq(ImageRequest.UNSPECIFIED_SIZE), + Matchers.eq(ImageRequest.UNSPECIFIED_SIZE)); + assertEquals(1, options.getValue().inSampleSize); + assertNotNull(imageResource); + assertNotNull(imageResource.getBitmap()); + + // Make sure there's no scaling on the bitmap. + final int bitmapWidth = imageResource.getBitmap().getWidth(); + final int bitmapHeight = imageResource.getBitmap().getHeight(); + assertEquals(options.getValue().outWidth, bitmapWidth); + assertEquals(options.getValue().outHeight, bitmapHeight); + } catch (final IOException e) { + fail("IO exception while trying to load image resource"); + } + } + + public void testLoadImageWithDownsampling() { + final String uriString = ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + + getContext().getPackageName() + "/" + R.drawable.ic_audio_light; + final Uri uri = Uri.parse(uriString); + final UriImageRequest imageRequest = new UriImageRequest(getContext(), + new UriImageRequestDescriptor(uri, DOWNSAMPLE_IMAGE_SIZE, DOWNSAMPLE_IMAGE_SIZE, + false, true /* isStatic */, false /* cropToCircle */, + ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */, + ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */)); + try { + final ImageResource imageResource = imageRequest.loadMediaBlocking(null); + final ArgumentCaptor<BitmapFactory.Options> options = + ArgumentCaptor.forClass(BitmapFactory.Options.class); + Mockito.verify(spyImageUtils).calculateInSampleSize( + options.capture(), + Matchers.eq(DOWNSAMPLE_IMAGE_SIZE), Matchers.eq(DOWNSAMPLE_IMAGE_SIZE)); + assertNotSame(1, options.getValue().inSampleSize); + assertNotNull(imageResource); + assertNotNull(imageResource.getBitmap()); + + // Make sure there's down sampling on the bitmap. + final int bitmapWidth = imageResource.getBitmap().getWidth(); + final int bitmapHeight = imageResource.getBitmap().getHeight(); + assertTrue(bitmapWidth >= DOWNSAMPLE_IMAGE_SIZE && + bitmapHeight >= DOWNSAMPLE_IMAGE_SIZE && + (bitmapWidth <= DOWNSAMPLE_IMAGE_SIZE * 4 || + bitmapHeight <= DOWNSAMPLE_IMAGE_SIZE * 4)); + } catch (final IOException e) { + fail("IO exception while trying to load image resource"); + } + } +} diff --git a/tests/src/com/android/messaging/datamodel/media/MediaResourceManagerTest.java b/tests/src/com/android/messaging/datamodel/media/MediaResourceManagerTest.java new file mode 100644 index 0000000..d214067 --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/media/MediaResourceManagerTest.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.messaging.datamodel.media; + +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.messaging.BugleTestCase; +import com.android.messaging.FakeFactory; +import com.android.messaging.datamodel.MemoryCacheManager; +import com.android.messaging.datamodel.media.MediaResourceManager.MediaResourceLoadListener; + +import java.util.concurrent.CountDownLatch; + +@SmallTest +public class MediaResourceManagerTest extends BugleTestCase { + private static final int KB = 1024; + + // Loaded image resource from the MediaResourceManager callback. + private FakeImageResource mImageResource; + private BindableMediaRequest<FakeImageResource> mImageRequest; + + @Override + protected void setUp() throws Exception { + super.setUp(); + FakeFactory.register(getTestContext()) + .withMemoryCacheManager(new MemoryCacheManager()) + .withMediaCacheManager(new FakeMediaCacheManager()); + } + + public void testLoadFromCache() { + final MediaResourceManager mediaResourceManager = + new MediaResourceManager(); + MediaCacheManager.get().reclaim(); + assertNotNull(mediaResourceManager); + + // Load one image of 1KB + loadImage(mediaResourceManager, "image1", 1 * KB, false /* shouldBeCached */, false); + assertEquals("image1", mImageResource.getImageId()); + final FakeImageResource loadedResource = mImageResource; + + // Load the same image. + loadImage(mediaResourceManager, "image1", 1 * KB, true /* shouldBeCached */, false); + assertEquals(loadedResource, mImageResource); + } + + public void testCacheEviction() { + final MediaResourceManager mediaResourceManager = + new MediaResourceManager(); + MediaCacheManager.get().reclaim(); + assertNotNull(mediaResourceManager); + + // Load one image of 1KB + loadImage(mediaResourceManager, "image1", 1 * KB, false /* shouldBeCached */, false); + assertEquals("image1", mImageResource.getImageId()); + + // Load another image + loadImage(mediaResourceManager, "image2", 2 * KB, false /* shouldBeCached */, false); + assertEquals("image2", mImageResource.getImageId()); + + // Load another image. This should fill the cache and cause eviction of image1. + loadImage(mediaResourceManager, "image3", 2 * KB, false /* shouldBeCached */, false); + assertEquals("image3", mImageResource.getImageId()); + + // Load image1. It shouldn't be cached any more. + loadImage(mediaResourceManager, "image1", 1 * KB, false /* shouldBeCached */, false); + assertEquals("image1", mImageResource.getImageId()); + } + + public void testReclaimMemoryFromMediaCache() { + final MediaResourceManager mediaResourceManager = + new MediaResourceManager(); + MediaCacheManager.get().reclaim(); + assertNotNull(mediaResourceManager); + + // Load one image of 1KB + loadImage(mediaResourceManager, "image1", 1 * KB, false /* shouldBeCached */, false); + assertEquals("image1", mImageResource.getImageId()); + + // Purge everything from the cache, now the image should no longer be cached. + MediaCacheManager.get().reclaim(); + + // The image resource should have no ref left. + assertEquals(0, mImageResource.getRefCount()); + assertTrue(mImageResource.isClosed()); + loadImage(mediaResourceManager, "image1", 1 * KB, false /* shouldBeCached */, false); + assertEquals("image1", mImageResource.getImageId()); + } + + public void testLoadInvalidImage() { + final MediaResourceManager mediaResourceManager = + new MediaResourceManager(); + MediaCacheManager.get().reclaim(); + assertNotNull(mediaResourceManager); + + // Test the failure case with invalid resource. + loadImage(mediaResourceManager, FakeImageRequest.INVALID_KEY, 1 * KB, false, + true /* shouldFail */); + } + + public void testLoadImageSynchronously() { + final MediaResourceManager mediaResourceManager = + new MediaResourceManager(); + MediaCacheManager.get().reclaim(); + assertNotNull(mediaResourceManager); + + // Test a normal sync load. + final FakeImageRequest request = new FakeImageRequest("image1", 1 * KB); + final FakeImageResource resource = mediaResourceManager.requestMediaResourceSync(request); + assertNotNull(resource); + assertFalse(resource.isClosed()); + assertNotSame(0, resource.getRefCount()); + resource.release(); + + // Test a failed sync load. + final FakeImageRequest invalidRequest = + new FakeImageRequest(FakeImageRequest.INVALID_KEY, 1 * KB); + assertNull(mediaResourceManager.requestMediaResourceSync(invalidRequest)); + } + + private void loadImage(final MediaResourceManager manager, final String key, + final int size, final boolean shouldBeCached, final boolean shouldFail) { + try { + final CountDownLatch signal = new CountDownLatch(1); + mImageRequest = AsyncMediaRequestWrapper.createWith(new FakeImageRequest(key, size), + createAssertListener(shouldBeCached, shouldFail, signal)); + mImageRequest.bind("1"); + manager.requestMediaResourceAsync(mImageRequest); + + // Wait for the asynchronous callback before proceeding. + signal.await(); + } catch (final InterruptedException e) { + fail("Something interrupted the signal await."); + } + } + + private MediaResourceLoadListener<FakeImageResource> createAssertListener( + final boolean shouldBeCached, final boolean shouldFail, final CountDownLatch signal) { + return new MediaResourceLoadListener<FakeImageResource>() { + @Override + public void onMediaResourceLoaded(final MediaRequest<FakeImageResource> request, + final FakeImageResource resource, final boolean isCached) { + assertEquals(mImageRequest, request); + assertNotNull(resource); + assertFalse(resource.isClosed()); + assertNotSame(0, resource.getRefCount()); + assertFalse(shouldFail); + assertEquals(shouldBeCached, resource.getCached()); + resource.setCached(true); + mImageResource = resource; + signal.countDown(); + } + + @Override + public void onMediaResourceLoadError( + final MediaRequest<FakeImageResource> request, final Exception exception) { + assertTrue(shouldFail); + mImageResource = null; + signal.countDown(); + }}; + } +} |