Initial checkin of AOSP Messaging app.

b/23110861

Change-Id: I9aa980d7569247d6b2ca78f5dcb4502e1eaadb8a
This commit is contained in:
Mike Dodd
2015-08-12 08:58:28 -07:00
parent 8b3e2b9c1b
commit 461a34b466
1645 changed files with 186271 additions and 0 deletions
@@ -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();
}
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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));
}
}
@@ -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; }
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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();
}
}
@@ -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);
}
}
@@ -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);
}
}
}
@@ -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);
}
}
}
@@ -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);
}
}
}
@@ -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() {
}
}
}
@@ -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());
}
}
@@ -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);
}
}
@@ -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());
}
}
@@ -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));
}
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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");
}
}
}
@@ -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();
}};
}
}