summaryrefslogtreecommitdiffstats
path: root/tests/src/com/android/messaging/datamodel
diff options
context:
space:
mode:
Diffstat (limited to 'tests/src/com/android/messaging/datamodel')
-rw-r--r--tests/src/com/android/messaging/datamodel/BindingTest.java148
-rw-r--r--tests/src/com/android/messaging/datamodel/BitmapPoolTest.java102
-rw-r--r--tests/src/com/android/messaging/datamodel/BugleServiceTestCase.java52
-rw-r--r--tests/src/com/android/messaging/datamodel/ConversationListTest.java28
-rw-r--r--tests/src/com/android/messaging/datamodel/DataModelTest.java71
-rw-r--r--tests/src/com/android/messaging/datamodel/FakeCursor.java161
-rw-r--r--tests/src/com/android/messaging/datamodel/FakeDataModel.java257
-rw-r--r--tests/src/com/android/messaging/datamodel/FrequentContactsCursorBuilderTest.java90
-rw-r--r--tests/src/com/android/messaging/datamodel/MemoryCacheManagerTest.java43
-rw-r--r--tests/src/com/android/messaging/datamodel/ParticipantRefreshTest.java280
-rw-r--r--tests/src/com/android/messaging/datamodel/action/ActionServiceSystemTest.java436
-rw-r--r--tests/src/com/android/messaging/datamodel/action/ActionServiceTest.java275
-rw-r--r--tests/src/com/android/messaging/datamodel/action/ActionTest.java324
-rw-r--r--tests/src/com/android/messaging/datamodel/action/ActionTestHelpers.java191
-rw-r--r--tests/src/com/android/messaging/datamodel/action/GetOrCreateConversationActionTest.java173
-rw-r--r--tests/src/com/android/messaging/datamodel/action/ReadWriteDraftMessageActionTest.java482
-rw-r--r--tests/src/com/android/messaging/datamodel/data/ConversationMessageDataTest.java99
-rw-r--r--tests/src/com/android/messaging/datamodel/data/ConversationParticipantsDataTest.java41
-rw-r--r--tests/src/com/android/messaging/datamodel/data/TestDataFactory.java347
-rw-r--r--tests/src/com/android/messaging/datamodel/media/FakeImageRequest.java68
-rw-r--r--tests/src/com/android/messaging/datamodel/media/FakeImageResource.java55
-rw-r--r--tests/src/com/android/messaging/datamodel/media/FakeMediaCacheManager.java36
-rw-r--r--tests/src/com/android/messaging/datamodel/media/ImageRequestTest.java111
-rw-r--r--tests/src/com/android/messaging/datamodel/media/MediaResourceManagerTest.java174
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();
+ }};
+ }
+}