From 461a34b466cb4b13dbbc2ec6330b31e217b2ac4e Mon Sep 17 00:00:00 2001 From: Mike Dodd Date: Tue, 11 Aug 2015 11:16:59 -0700 Subject: Initial checkin of AOSP Messaging app. b/23110861 Change-Id: I9aa980d7569247d6b2ca78f5dcb4502e1eaadb8a --- tests/Android.mk | 39 ++ tests/AndroidManifest.xml | 31 ++ tests/src/com/android/messaging/BugleTestCase.java | 59 +++ .../com/android/messaging/FakeContentProvider.java | 177 ++++++++ tests/src/com/android/messaging/FakeContext.java | 101 +++++ tests/src/com/android/messaging/FakeFactory.java | 288 ++++++++++++ tests/src/com/android/messaging/TestUtil.java | 84 ++++ .../android/messaging/datamodel/BindingTest.java | 148 +++++++ .../messaging/datamodel/BitmapPoolTest.java | 102 +++++ .../messaging/datamodel/BugleServiceTestCase.java | 52 +++ .../messaging/datamodel/ConversationListTest.java | 28 ++ .../android/messaging/datamodel/DataModelTest.java | 71 +++ .../android/messaging/datamodel/FakeCursor.java | 161 +++++++ .../android/messaging/datamodel/FakeDataModel.java | 257 +++++++++++ .../FrequentContactsCursorBuilderTest.java | 90 ++++ .../datamodel/MemoryCacheManagerTest.java | 43 ++ .../datamodel/ParticipantRefreshTest.java | 280 ++++++++++++ .../datamodel/action/ActionServiceSystemTest.java | 436 +++++++++++++++++++ .../datamodel/action/ActionServiceTest.java | 275 ++++++++++++ .../messaging/datamodel/action/ActionTest.java | 324 ++++++++++++++ .../datamodel/action/ActionTestHelpers.java | 191 ++++++++ .../action/GetOrCreateConversationActionTest.java | 173 ++++++++ .../action/ReadWriteDraftMessageActionTest.java | 482 +++++++++++++++++++++ .../data/ConversationMessageDataTest.java | 99 +++++ .../data/ConversationParticipantsDataTest.java | 41 ++ .../messaging/datamodel/data/TestDataFactory.java | 347 +++++++++++++++ .../datamodel/media/FakeImageRequest.java | 68 +++ .../datamodel/media/FakeImageResource.java | 55 +++ .../datamodel/media/FakeMediaCacheManager.java | 36 ++ .../datamodel/media/ImageRequestTest.java | 111 +++++ .../datamodel/media/MediaResourceManagerTest.java | 174 ++++++++ .../ui/ActivityInstrumentationTestCaseIntent.java | 37 ++ .../ui/BugleActivityInstrumentationTestCase.java | 52 +++ .../android/messaging/ui/BugleActivityTest.java | 52 +++ .../messaging/ui/BugleActivityUnitTestCase.java | 55 +++ .../messaging/ui/CustomHeaderViewPagerTest.java | 61 +++ .../android/messaging/ui/FakeListViewHolder.java | 60 +++ .../com/android/messaging/ui/FragmentTestCase.java | 120 +++++ .../messaging/ui/MultiAttachmentLayoutTest.java | 122 ++++++ tests/src/com/android/messaging/ui/ViewTest.java | 66 +++ .../AttachmentChooserFragmentTest.java | 172 ++++++++ .../ui/contact/ContactListItemViewTest.java | 127 ++++++ .../ui/contact/ContactPickerFragmentTest.java | 221 ++++++++++ .../ui/conversation/ComposeMessageViewTest.java | 184 ++++++++ .../ConversationActivityUiStateTest.java | 121 ++++++ .../ui/conversation/ConversationFragmentTest.java | 160 +++++++ .../conversation/ConversationInputManagerTest.java | 190 ++++++++ .../conversation/ConversationMessageViewTest.java | 110 +++++ .../ConversationListFragmentTest.java | 130 ++++++ .../ConversationListItemViewTest.java | 181 ++++++++ .../ui/mediapicker/AudioRecordViewTest.java | 89 ++++ .../ui/mediapicker/CameraManagerTest.java | 154 +++++++ .../ui/mediapicker/GalleryGridItemViewTest.java | 145 +++++++ .../messaging/ui/mediapicker/MediaPickerTest.java | 129 ++++++ .../ui/mediapicker/MockCameraFactory.java | 73 ++++ .../android/messaging/util/BugleGservicesTest.java | 33 ++ .../android/messaging/util/ContactUtilTest.java | 306 +++++++++++++ .../android/messaging/util/FakeBugleGservices.java | 55 +++ .../com/android/messaging/util/FakeBuglePrefs.java | 84 ++++ .../com/android/messaging/util/FakeMediaUtil.java | 33 ++ .../android/messaging/util/YouTubeUtilTest.java | 62 +++ 61 files changed, 8207 insertions(+) create mode 100644 tests/Android.mk create mode 100644 tests/AndroidManifest.xml create mode 100644 tests/src/com/android/messaging/BugleTestCase.java create mode 100644 tests/src/com/android/messaging/FakeContentProvider.java create mode 100644 tests/src/com/android/messaging/FakeContext.java create mode 100644 tests/src/com/android/messaging/FakeFactory.java create mode 100644 tests/src/com/android/messaging/TestUtil.java create mode 100644 tests/src/com/android/messaging/datamodel/BindingTest.java create mode 100644 tests/src/com/android/messaging/datamodel/BitmapPoolTest.java create mode 100644 tests/src/com/android/messaging/datamodel/BugleServiceTestCase.java create mode 100644 tests/src/com/android/messaging/datamodel/ConversationListTest.java create mode 100644 tests/src/com/android/messaging/datamodel/DataModelTest.java create mode 100644 tests/src/com/android/messaging/datamodel/FakeCursor.java create mode 100644 tests/src/com/android/messaging/datamodel/FakeDataModel.java create mode 100644 tests/src/com/android/messaging/datamodel/FrequentContactsCursorBuilderTest.java create mode 100644 tests/src/com/android/messaging/datamodel/MemoryCacheManagerTest.java create mode 100644 tests/src/com/android/messaging/datamodel/ParticipantRefreshTest.java create mode 100644 tests/src/com/android/messaging/datamodel/action/ActionServiceSystemTest.java create mode 100644 tests/src/com/android/messaging/datamodel/action/ActionServiceTest.java create mode 100644 tests/src/com/android/messaging/datamodel/action/ActionTest.java create mode 100644 tests/src/com/android/messaging/datamodel/action/ActionTestHelpers.java create mode 100644 tests/src/com/android/messaging/datamodel/action/GetOrCreateConversationActionTest.java create mode 100644 tests/src/com/android/messaging/datamodel/action/ReadWriteDraftMessageActionTest.java create mode 100644 tests/src/com/android/messaging/datamodel/data/ConversationMessageDataTest.java create mode 100644 tests/src/com/android/messaging/datamodel/data/ConversationParticipantsDataTest.java create mode 100644 tests/src/com/android/messaging/datamodel/data/TestDataFactory.java create mode 100644 tests/src/com/android/messaging/datamodel/media/FakeImageRequest.java create mode 100644 tests/src/com/android/messaging/datamodel/media/FakeImageResource.java create mode 100644 tests/src/com/android/messaging/datamodel/media/FakeMediaCacheManager.java create mode 100644 tests/src/com/android/messaging/datamodel/media/ImageRequestTest.java create mode 100644 tests/src/com/android/messaging/datamodel/media/MediaResourceManagerTest.java create mode 100644 tests/src/com/android/messaging/ui/ActivityInstrumentationTestCaseIntent.java create mode 100644 tests/src/com/android/messaging/ui/BugleActivityInstrumentationTestCase.java create mode 100644 tests/src/com/android/messaging/ui/BugleActivityTest.java create mode 100644 tests/src/com/android/messaging/ui/BugleActivityUnitTestCase.java create mode 100644 tests/src/com/android/messaging/ui/CustomHeaderViewPagerTest.java create mode 100644 tests/src/com/android/messaging/ui/FakeListViewHolder.java create mode 100644 tests/src/com/android/messaging/ui/FragmentTestCase.java create mode 100644 tests/src/com/android/messaging/ui/MultiAttachmentLayoutTest.java create mode 100644 tests/src/com/android/messaging/ui/ViewTest.java create mode 100644 tests/src/com/android/messaging/ui/attachmentchooser/AttachmentChooserFragmentTest.java create mode 100644 tests/src/com/android/messaging/ui/contact/ContactListItemViewTest.java create mode 100644 tests/src/com/android/messaging/ui/contact/ContactPickerFragmentTest.java create mode 100644 tests/src/com/android/messaging/ui/conversation/ComposeMessageViewTest.java create mode 100644 tests/src/com/android/messaging/ui/conversation/ConversationActivityUiStateTest.java create mode 100644 tests/src/com/android/messaging/ui/conversation/ConversationFragmentTest.java create mode 100644 tests/src/com/android/messaging/ui/conversation/ConversationInputManagerTest.java create mode 100644 tests/src/com/android/messaging/ui/conversation/ConversationMessageViewTest.java create mode 100644 tests/src/com/android/messaging/ui/conversationlist/ConversationListFragmentTest.java create mode 100644 tests/src/com/android/messaging/ui/conversationlist/ConversationListItemViewTest.java create mode 100644 tests/src/com/android/messaging/ui/mediapicker/AudioRecordViewTest.java create mode 100644 tests/src/com/android/messaging/ui/mediapicker/CameraManagerTest.java create mode 100644 tests/src/com/android/messaging/ui/mediapicker/GalleryGridItemViewTest.java create mode 100644 tests/src/com/android/messaging/ui/mediapicker/MediaPickerTest.java create mode 100644 tests/src/com/android/messaging/ui/mediapicker/MockCameraFactory.java create mode 100644 tests/src/com/android/messaging/util/BugleGservicesTest.java create mode 100644 tests/src/com/android/messaging/util/ContactUtilTest.java create mode 100644 tests/src/com/android/messaging/util/FakeBugleGservices.java create mode 100644 tests/src/com/android/messaging/util/FakeBuglePrefs.java create mode 100644 tests/src/com/android/messaging/util/FakeMediaUtil.java create mode 100644 tests/src/com/android/messaging/util/YouTubeUtilTest.java (limited to 'tests') diff --git a/tests/Android.mk b/tests/Android.mk new file mode 100644 index 0000000..f3f4752 --- /dev/null +++ b/tests/Android.mk @@ -0,0 +1,39 @@ +# 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. + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests + +# Only compile source java files in this apk. +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_PACKAGE_NAME := messagingtests + +LOCAL_INSTRUMENTATION_FOR := messaging + +LOCAL_JACK_ENABLED := disabled + +# Matching ../Android.mk +LOCAL_SDK_VERSION := current + +LOCAL_CERTIFICATE := platform + +LOCAL_STATIC_JAVA_LIBRARIES := \ + mockito-target + +include $(BUILD_PACKAGE) + +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml new file mode 100644 index 0000000..867d8e1 --- /dev/null +++ b/tests/AndroidManifest.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + diff --git a/tests/src/com/android/messaging/BugleTestCase.java b/tests/src/com/android/messaging/BugleTestCase.java new file mode 100644 index 0000000..64c6c86 --- /dev/null +++ b/tests/src/com/android/messaging/BugleTestCase.java @@ -0,0 +1,59 @@ +/* + * 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; + +import android.content.Context; +import android.test.AndroidTestCase; + +/* + * Base class for service tests that takes care of housekeeping that is common amongst basic test + * cases. + */ +public abstract class BugleTestCase extends AndroidTestCase { + + static { + // Set flag during loading of test cases to prevent application initialization starting + setTestsRunning(); + } + + public static void setTestsRunning() { + BugleApplication.setTestsRunning(); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + TestUtil.testSetup(super.getContext(), this); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + TestUtil.testTeardown(this); + } + + @Override + public Context getContext() { + // This doesn't really get the "application context" - just the fake context + // that the factory has been initialized with for each test case. + return Factory.get().getApplicationContext(); + } + + public Context getTestContext() { + return super.getContext(); + } +} \ No newline at end of file diff --git a/tests/src/com/android/messaging/FakeContentProvider.java b/tests/src/com/android/messaging/FakeContentProvider.java new file mode 100644 index 0000000..53c29ba --- /dev/null +++ b/tests/src/com/android/messaging/FakeContentProvider.java @@ -0,0 +1,177 @@ +/* + * 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; + +import android.content.ContentProvider; +import android.content.ContentProviderClient; +import android.content.ContentValues; +import android.content.Context; +import android.content.pm.ProviderInfo; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.RemoteException; +import android.support.v4.util.SimpleArrayMap; +import android.text.TextUtils; + +import com.android.messaging.datamodel.FakeCursor; +import com.android.messaging.util.LogUtil; + +import java.util.ArrayList; + +public class FakeContentProvider extends ContentProvider { + + private static class ContentOverride { + private final String uri; + private final String where; + private final String args; + private final String[] columns; + private final Object[][] data; + + ContentOverride(final String uri, final String where, final String args, + final String[] columns, final Object[][] data) { + this.uri = uri; + this.where = where; + this.args = args; + this.columns = columns; + this.data = data; + } + + boolean match(final String uri, final String where, final String[] args) { + if (!this.uri.equals(uri) || !TextUtils.equals(this.where, where)) { + return false; + } + + if (this.args == null || args == null) { + return this.args == null && args == null; + } + + return this.args.equals(TextUtils.join(";", args)); + } + } + + private final Context mGlobalContext; + private final ArrayList mOverrides = new ArrayList(); + private final SimpleArrayMap mTypes = new SimpleArrayMap(); + private final ContentProviderClient mProvider; + private final Uri mUri; + + public FakeContentProvider(final Context context, final Uri uri, final boolean canDelegate) { + mGlobalContext = context; + mUri = uri; + if (canDelegate) { + mProvider = mGlobalContext.getContentResolver().acquireContentProviderClient(mUri); + } else { + mProvider = null; + } + + ProviderInfo providerInfo = new ProviderInfo(); + providerInfo.authority = uri.getAuthority(); + + this.attachInfo(mGlobalContext, providerInfo); + } + + public void addOverrideData(final Uri uri, final String where, final String args, + final String[] columns, final Object[][] data) { + mOverrides.add(new ContentOverride(uri.toString(), where, args, columns, data)); + } + + public void addOverrideType(final Uri uri, final String type) { + mTypes.put(uri.toString(), type); + } + + @Override + public boolean onCreate() { + return false; + } + + @Override + public void shutdown() { + if (mProvider != null) { + mProvider.release(); + } + } + + @Override + public Cursor query(final Uri uri, final String[] projection, final String selection, + final String[] selectionArgs, final String sortOrder) { + LogUtil.w(LogUtil.BUGLE_TAG, "FakeContentProvider: query " + uri.toString() + + " for " + (projection == null ? null : TextUtils.join(",", projection)) + + " where " + selection + + " with " + (selectionArgs == null ? null : TextUtils.join(";", selectionArgs))); + + for(final ContentOverride content : mOverrides) { + if (content.match(uri.toString(), selection, selectionArgs)) { + return new FakeCursor(projection, content.columns, content.data); + } + } + if (mProvider != null) { + try { + LogUtil.w(LogUtil.BUGLE_TAG, "FakeContentProvider: delgating"); + + final Cursor cursor = mProvider.query(uri, projection, selection, selectionArgs, + sortOrder); + + LogUtil.w(LogUtil.BUGLE_TAG, "FakeContentProvider: response size " + + cursor.getCount() + " contains " + TextUtils.join(",", + cursor.getColumnNames()) + " type(0) " + cursor.getType(0)); + + return cursor; + } catch (final RemoteException e) { + e.printStackTrace(); + } + } + return null; + } + + @Override + public String getType(final Uri uri) { + String type = mTypes.get(uri.toString()); + if (type == null) { + try { + type = mProvider.getType(uri); + } catch (final RemoteException e) { + e.printStackTrace(); + } + } + return type; + } + + @Override + public Uri insert(final Uri uri, final ContentValues values) { + // TODO: Add code to track insert operations and return correct status + throw new UnsupportedOperationException(); + } + + @Override + public int delete(final Uri uri, final String selection, final String[] selectionArgs) { + // TODO: Add code to track delete operations and return correct status + throw new UnsupportedOperationException(); + } + + @Override + public int update(final Uri uri, final ContentValues values, final String selection, + final String[] selectionArgs) { + // TODO: Add code to track update operations and return correct status + throw new UnsupportedOperationException(); + } + + public Bundle call(final String callingPkg, final String method, final String arg, + final Bundle extras) { + return null; + } +} diff --git a/tests/src/com/android/messaging/FakeContext.java b/tests/src/com/android/messaging/FakeContext.java new file mode 100644 index 0000000..671e0f3 --- /dev/null +++ b/tests/src/com/android/messaging/FakeContext.java @@ -0,0 +1,101 @@ +/* + * 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; + +import android.content.ComponentName; +import android.content.ContentProvider; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.test.RenamingDelegatingContext; +import android.test.mock.MockContentResolver; +import android.util.Log; + +import java.util.ArrayList; + +public class FakeContext extends RenamingDelegatingContext { + private static final String TAG = "FakeContext"; + + public interface FakeContextHost { + public String getServiceClassName(); + public void startServiceForStub(Intent intent); + public void onStartCommandForStub(Intent intent, int flags, int startid); + } + + ArrayList mStartedIntents; + boolean mServiceStarted = false; + private final FakeContextHost mService; + private final MockContentResolver mContentResolver; + + + public FakeContext(final Context context, final FakeContextHost service) { + super(context, "test_"); + mService = service; + mStartedIntents = new ArrayList(); + mContentResolver = new MockContentResolver(); + } + + public FakeContext(final Context context) { + this(context, null); + } + + public ArrayList extractIntents() { + final ArrayList intents = mStartedIntents; + mStartedIntents = new ArrayList(); + return intents; + } + + @Override + public ComponentName startService(final Intent intent) { + // Record that a startService occurred with the intent that was passed. + Log.d(TAG, "MockContext receiving startService. intent=" + intent.toString()); + mStartedIntents.add(intent); + if (mService == null) { + return super.startService(intent); + } else if (intent.getComponent() != null && + intent.getComponent().getClassName().equals(mService.getServiceClassName())) { + if (!mServiceStarted) { + Log.d(TAG, "MockContext first start service."); + mService.startServiceForStub(intent); + } else { + Log.d(TAG, "MockContext not first start service. Calling onStartCommand."); + mService.onStartCommandForStub(intent, 0, 0); + } + mServiceStarted = true; + return new ComponentName(this, intent.getComponent().getClassName()); + } + return null; + } + + @Override + public ContentResolver getContentResolver() { + // If you want to use a content provider in your test, then you need to add it + // explicitly. + return mContentResolver; + } + + public void addContentProvider(final String name, final ContentProvider provider) { + mContentResolver.addProvider(name, provider); + } + + public void addDefaultProvider(final Context context, final Uri uri) { + final FakeContentProvider provider = new FakeContentProvider(context, uri, true); + mContentResolver.addProvider(uri.getAuthority(), provider); + } + +} diff --git a/tests/src/com/android/messaging/FakeFactory.java b/tests/src/com/android/messaging/FakeFactory.java new file mode 100644 index 0000000..41ede77 --- /dev/null +++ b/tests/src/com/android/messaging/FakeFactory.java @@ -0,0 +1,288 @@ +/* + * 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; + +import android.content.ContentProvider; +import android.content.Context; +import android.net.Uri; +import android.provider.Settings; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; + +import com.android.messaging.datamodel.DataModel; +import com.android.messaging.datamodel.MemoryCacheManager; +import com.android.messaging.datamodel.ParticipantRefresh.ContactContentObserver; +import com.android.messaging.datamodel.media.MediaCacheManager; +import com.android.messaging.datamodel.media.MediaResourceManager; +import com.android.messaging.sms.ApnDatabase; +import com.android.messaging.sms.BugleCarrierConfigValuesLoader; +import com.android.messaging.ui.UIIntents; +import com.android.messaging.util.Assert; +import com.android.messaging.util.BugleGservices; +import com.android.messaging.util.BuglePrefs; +import com.android.messaging.util.FakeBugleGservices; +import com.android.messaging.util.FakeBuglePrefs; +import com.android.messaging.util.MediaUtil; +import com.android.messaging.util.OsUtil; +import com.android.messaging.util.PhoneUtils; + +import org.mockito.Matchers; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.util.List; + +public class FakeFactory extends Factory { + private Context mContext; + private FakeContext mFakeContext; + private BugleGservices mBugleGservices; + private BuglePrefs mBuglePrefs; + private DataModel mDataModel; + private UIIntents mUIIntents; + private MemoryCacheManager mMemoryCacheManager; + private MediaResourceManager mMediaResourceManager; + private MediaCacheManager mMediaCacheManager; + @Mock protected PhoneUtils mPhoneUtils; + private MediaUtil mMediaUtil; + private BugleCarrierConfigValuesLoader mCarrierConfigValuesLoader; + + private FakeFactory() { + } + + public static FakeFactory registerWithFakeContext(final Context context, + final FakeContext fake) { + // In tests we currently NEVER run the application/factory initialization + Assert.isTrue(!sRegistered); + Assert.isTrue(!sInitialized); + + final FakeFactory factory = new FakeFactory(); + Factory.setInstance(factory); + + // At this point Factory is published. Services can now get initialized and depend on + // Factory.get(). + factory.mContext = context; + factory.mFakeContext = fake; + factory.mMediaResourceManager = Mockito.mock(MediaResourceManager.class); + factory.mBugleGservices = new FakeBugleGservices(); + factory.mBuglePrefs = new FakeBuglePrefs(); + factory.mPhoneUtils = Mockito.mock(PhoneUtils.class); + + ApnDatabase.initializeAppContext(context); + + Mockito.when(factory.mPhoneUtils.getCanonicalBySystemLocale(Matchers.anyString())) + .thenAnswer(new Answer() { + @Override + public String answer(final InvocationOnMock invocation) throws Throwable { + final Object[] args = invocation.getArguments(); + return (String) args[0]; + } + } + ); + Mockito.when(factory.mPhoneUtils.getCanonicalBySimLocale(Matchers.anyString())).thenAnswer( + new Answer() { + @Override + public String answer(final InvocationOnMock invocation) throws Throwable { + final Object[] args = invocation.getArguments(); + return (String) args[0]; + } + } + ); + Mockito.when(factory.mPhoneUtils.formatForDisplay(Matchers.anyString())).thenAnswer( + new Answer() { + @Override + public String answer(final InvocationOnMock invocation) throws Throwable { + return (String) invocation.getArguments()[0]; + } + } + ); + if (OsUtil.isAtLeastL_MR1()) { + Mockito.when(factory.mPhoneUtils.toLMr1()).thenReturn( + new PhoneUtils.LMr1() { + @Override + public SubscriptionInfo getActiveSubscriptionInfo() { + return null; + } + + @Override + public List getActiveSubscriptionInfoList() { + return null; + } + + @Override + public void registerOnSubscriptionsChangedListener( + final SubscriptionManager.OnSubscriptionsChangedListener listener) { + } + } + ); + } + // By default only allow reading of system settings (that we provide) - can delegate + // to real provider if required. + final FakeContentProvider settings = new FakeContentProvider(context, + Settings.System.CONTENT_URI, false); + settings.addOverrideData(Settings.System.CONTENT_URI, "name=?", "time_12_24", + new String[] { "value" }, new Object[][] { { "12" } }); + settings.addOverrideData(Settings.System.CONTENT_URI, "name=?", "sound_effects_enabled", + new String[] { "value" }, new Object[][] { { 1 } }); + + factory.withProvider(Settings.System.CONTENT_URI, settings); + + return factory; + } + + public static FakeFactory register(final Context applicationContext) { + final FakeContext context = new FakeContext(applicationContext); + return registerWithFakeContext(applicationContext, context); + } + + public static FakeFactory registerWithoutFakeContext(final Context applicationContext) { + return registerWithFakeContext(applicationContext, null); + } + + @Override + public void onRequiredPermissionsAcquired() { + } + + @Override + public Context getApplicationContext() { + return ((mFakeContext != null) ? mFakeContext : mContext ); + } + + @Override + public DataModel getDataModel() { + return mDataModel; + } + + @Override + public BugleGservices getBugleGservices() { + return mBugleGservices; + } + + @Override + public BuglePrefs getApplicationPrefs() { + return mBuglePrefs; + } + + @Override + public BuglePrefs getWidgetPrefs() { + return mBuglePrefs; + } + + @Override + public BuglePrefs getSubscriptionPrefs(final int subId) { + return mBuglePrefs; + } + + @Override + public UIIntents getUIIntents() { + return mUIIntents; + } + + @Override + public MemoryCacheManager getMemoryCacheManager() { + return mMemoryCacheManager; + } + + @Override + public MediaResourceManager getMediaResourceManager() { + return mMediaResourceManager; + } + + @Override + public MediaCacheManager getMediaCacheManager() { + return mMediaCacheManager; + } + + @Override + public PhoneUtils getPhoneUtils(final int subId) { + return mPhoneUtils; + } + + @Override + public MediaUtil getMediaUtil() { + return mMediaUtil; + } + + @Override + public BugleCarrierConfigValuesLoader getCarrierConfigValuesLoader() { + return mCarrierConfigValuesLoader; + } + + @Override + public ContactContentObserver getContactContentObserver() { + return null; + } + + @Override + public void reclaimMemory() { + } + + @Override + public void onActivityResume() { + } + + public FakeFactory withDataModel(final DataModel dataModel) { + this.mDataModel = dataModel; + return this; + } + + public FakeFactory withUIIntents(final UIIntents uiIntents) { + this.mUIIntents = uiIntents; + return this; + } + + public FakeFactory withMemoryCacheManager(final MemoryCacheManager memoryCacheManager) { + this.mMemoryCacheManager = memoryCacheManager; + return this; + } + + public FakeFactory withBugleGservices(final BugleGservices bugleGservices) { + this.mBugleGservices = bugleGservices; + return this; + } + + public FakeFactory withMediaCacheManager(final MediaCacheManager mediaCacheManager) { + this.mMediaCacheManager = mediaCacheManager; + return this; + } + + public FakeFactory withProvider(final Uri uri, final ContentProvider provider) { + if (mFakeContext != null) { + mFakeContext.addContentProvider(uri.getAuthority(), provider); + } + return this; + } + + public FakeFactory withDefaultProvider(final Uri uri) { + if (mFakeContext != null) { + mFakeContext.addDefaultProvider(this.mContext, uri); + } + return this; + } + + public FakeFactory withMediaUtil(final MediaUtil mediaUtil) { + this.mMediaUtil = mediaUtil; + return this; + } + + public FakeFactory withCarrierConfigValuesLoader( + final BugleCarrierConfigValuesLoader carrierConfigValuesLoader) { + this.mCarrierConfigValuesLoader = carrierConfigValuesLoader; + return this; + } +} diff --git a/tests/src/com/android/messaging/TestUtil.java b/tests/src/com/android/messaging/TestUtil.java new file mode 100644 index 0000000..77a8450 --- /dev/null +++ b/tests/src/com/android/messaging/TestUtil.java @@ -0,0 +1,84 @@ +/* + * 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; + +import android.appwidget.AppWidgetManager; +import android.content.ComponentName; +import android.content.Context; +import android.os.PowerManager; +import android.test.InstrumentationTestCase; + +import com.android.messaging.util.LogUtil; +import com.android.messaging.widget.BugleWidgetProvider; +import com.android.messaging.widget.WidgetConversationProvider; + +import junit.framework.TestCase; + +import org.mockito.MockitoAnnotations; + +/** + * Helpers that can be called from all test base classes to 'reset' state and prevent as much as + * possible having side effects leak from one test to another. + */ +public class TestUtil { + public static void testSetup(final Context context, final TestCase testCase) { + haltIfTestsAreNotAbleToRun(context); + + // Workaround to get mockito to work. + // See https://code.google.com/p/dexmaker/issues/detail?id=2. TODO: Apparently + // solvable by using a different runner. + System.setProperty("dexmaker.dexcache", + context.getCacheDir().getPath()); + + // Initialize @Mock objects. + MockitoAnnotations.initMocks(testCase); + + // Tests have to explicitly override this + Factory.setInstance(null); + } + + public static void testTeardown(final TestCase testCase) { + if (testCase instanceof InstrumentationTestCase) { + // Make sure the test case is finished running or we'll get NPEs when accessing + // Fragment.get() + ((InstrumentationTestCase) testCase).getInstrumentation().waitForIdleSync(); + } + Factory.setInstance(null); + } + + private static void haltIfTestsAreNotAbleToRun(final Context context) { + final PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + if (!pm.isScreenOn()) { + // Ideally we could turn it on for you using the WindowManager, but we currently run + // the tests independently of the activity life cycle. + LogUtil.wtf(LogUtil.BUGLE_TAG, "You need to turn on your screen to run tests!"); + } + + final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); + int [] conversationWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, + WidgetConversationProvider.class)); + int [] conversationListWidgetIds = appWidgetManager.getAppWidgetIds(new + ComponentName(context, BugleWidgetProvider.class)); + + if ((conversationWidgetIds.length > 0) || (conversationListWidgetIds.length > 0)) { + // Currently widgets asynchronously access our content providers and singletons which + // interacts badly with our test setup and tear down. + LogUtil.wtf(LogUtil.BUGLE_TAG, "You currently can't reliably run unit tests" + + " with a Messaging widget on your desktop!"); + } + } +} diff --git a/tests/src/com/android/messaging/datamodel/BindingTest.java b/tests/src/com/android/messaging/datamodel/BindingTest.java new file mode 100644 index 0000000..9205657 --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/BindingTest.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.datamodel; + +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.messaging.BugleTestCase; +import com.android.messaging.datamodel.binding.BindableData; +import com.android.messaging.datamodel.binding.Binding; +import com.android.messaging.datamodel.binding.BindingBase; +import com.android.messaging.datamodel.binding.ImmutableBindingRef; + +/** + * Test binding + */ +@SmallTest +public class BindingTest extends BugleTestCase { + private static final Object TEST_DATA_ID = "myDataId"; + private static final Object YOUR_DATA_ID = "yourDataId"; + + public void testBindingStartsUnbound() { + final Binding 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 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 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 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 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 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 bindingRef = + BindingBase.createBindingReference(binding); + assertEquals(bindingRef.getData(), data); + assertTrue(data.isBound(bindingRef.getBindingId())); + + binding.unbind(); + assertFalse(binding.isBound()); + assertNull(binding.getBindingId()); + assertFalse(bindingRef.isBound()); + assertNull(bindingRef.getBindingId()); + } + + static class TestBindableData extends BindableData { + private final Object mDataId; + public boolean mListenersUnregistered; + + public TestBindableData(final Object dataId) { + mDataId = dataId; + mListenersUnregistered = false; + } + + @Override + public void unregisterListeners() { + mListenersUnregistered = true; + } + + @Override + public boolean isBound() { + return super.isBound(); + } + } +} diff --git a/tests/src/com/android/messaging/datamodel/BitmapPoolTest.java b/tests/src/com/android/messaging/datamodel/BitmapPoolTest.java new file mode 100644 index 0000000..6d0aaa3 --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/BitmapPoolTest.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.datamodel; + +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.messaging.BugleTestCase; +import com.android.messaging.FakeFactory; +import com.android.messaging.R; + +import org.mockito.Mock; + +import java.util.HashSet; +import java.util.Set; + +@SmallTest +public class BitmapPoolTest extends BugleTestCase { + private static final int POOL_SIZE = 5; + private static final int IMAGE_DIM = 1; + private static final String NAME = "BitmapPoolTest"; + + @Mock private MemoryCacheManager mockMemoryCacheManager; + + @Override + protected void setUp() throws Exception { + super.setUp(); + FakeFactory.register(getTestContext()) + .withMemoryCacheManager(mockMemoryCacheManager); + } + + private Set fillPoolAndGetPoolContents(final BitmapPool pool, final int width, + final int height) { + final Set returnedBitmaps = new HashSet(); + 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 returnedBitmaps = + fillPoolAndGetPoolContents(pool, IMAGE_DIM, IMAGE_DIM); + final Bitmap overflowBitmap = pool.createOrReuseBitmap(IMAGE_DIM, IMAGE_DIM); + assertFalse(overflowBitmap.isRecycled()); + assertTrue(returnedBitmaps.contains(overflowBitmap)); + } + + /** + * Make sure that we have the correct options to create mutable for bitmap pool reuse. + */ + public void testAssertBitmapOptionsAreMutable() { + final BitmapFactory.Options options = + BitmapPool.getBitmapOptionsForPool(false, IMAGE_DIM, IMAGE_DIM); + assertTrue(options.inMutable); + } + + public void testDecodeFromResourceBitmap() { + final BitmapPool pool = new BitmapPool(POOL_SIZE, NAME); + final BitmapFactory.Options options = + BitmapPool.getBitmapOptionsForPool(true, IMAGE_DIM, IMAGE_DIM); + final Resources resources = getContext().getResources(); + final Bitmap resourceBitmap = pool.decodeSampledBitmapFromResource( + R.drawable.msg_bubble_incoming, resources, options, IMAGE_DIM, IMAGE_DIM); + assertNotNull(resourceBitmap); + assertTrue(resourceBitmap.getByteCount() > 0); + } +} diff --git a/tests/src/com/android/messaging/datamodel/BugleServiceTestCase.java b/tests/src/com/android/messaging/datamodel/BugleServiceTestCase.java new file mode 100644 index 0000000..42eb647 --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/BugleServiceTestCase.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.datamodel; + +import android.app.Service; +import android.test.ServiceTestCase; + +import com.android.messaging.BugleTestCase; +import com.android.messaging.TestUtil; + + +/* + * Base class for service tests that takes care of housekeeping that is commong amongst our service + * test case. + */ +public abstract class BugleServiceTestCase extends ServiceTestCase { + + static { + // Set flag during loading of test cases to prevent application initialization starting + BugleTestCase.setTestsRunning(); + } + + public BugleServiceTestCase(final Class serviceClass) { + super(serviceClass); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + TestUtil.testSetup(getContext(), this); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + TestUtil.testTeardown(this); + } +} \ No newline at end of file diff --git a/tests/src/com/android/messaging/datamodel/ConversationListTest.java b/tests/src/com/android/messaging/datamodel/ConversationListTest.java new file mode 100644 index 0000000..f45696b --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/ConversationListTest.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.datamodel; + +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.messaging.BugleTestCase; + +@SmallTest +public class ConversationListTest extends BugleTestCase { + public void testTesting() { + assertTrue(true); + } +} diff --git a/tests/src/com/android/messaging/datamodel/DataModelTest.java b/tests/src/com/android/messaging/datamodel/DataModelTest.java new file mode 100644 index 0000000..71723a4 --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/DataModelTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.datamodel; + +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.messaging.BugleTestCase; +import com.android.messaging.FakeFactory; +import com.android.messaging.datamodel.data.ConversationData; +import com.android.messaging.datamodel.data.ConversationListData; +import com.android.messaging.datamodel.data.ConversationData.ConversationDataListener; +import com.android.messaging.datamodel.data.ConversationListData.ConversationListDataListener; + +import org.mockito.Mock; + +public class DataModelTest extends BugleTestCase { + + DataModel dataModel; + @Mock protected ConversationDataListener mockConversationDataListener; + @Mock protected ConversationListDataListener mockConversationListDataListener; + + @Override + protected void setUp() throws Exception { + super.setUp(); + dataModel = new DataModelImpl(getTestContext()); + FakeFactory.register(mContext) + .withDataModel(dataModel); + } + + @SmallTest + public void testCreateConversationList() { + final ConversationListData list = dataModel.createConversationListData(getContext(), + mockConversationListDataListener, true); + assertTrue(list instanceof ConversationListData); + final ConversationData conv = dataModel.createConversationData(getContext(), + mockConversationDataListener, "testConversation"); + assertTrue(conv instanceof ConversationData); + } + + private static final String FOCUSED_CONV_ID = "focused_conv_id"; + + @SmallTest + public void testFocusedConversationIsObservable() { + dataModel.setFocusedConversation(FOCUSED_CONV_ID); + assertTrue(dataModel.isNewMessageObservable(FOCUSED_CONV_ID)); + dataModel.setFocusedConversation(null); + assertFalse(dataModel.isNewMessageObservable(FOCUSED_CONV_ID)); + } + + @SmallTest + public void testConversationIsObservableInList() { + dataModel.setConversationListScrolledToNewestConversation(true); + assertTrue(dataModel.isNewMessageObservable(FOCUSED_CONV_ID)); + dataModel.setConversationListScrolledToNewestConversation(false); + assertFalse(dataModel.isNewMessageObservable(FOCUSED_CONV_ID)); + } +} diff --git a/tests/src/com/android/messaging/datamodel/FakeCursor.java b/tests/src/com/android/messaging/datamodel/FakeCursor.java new file mode 100644 index 0000000..59d1b89 --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/FakeCursor.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.datamodel; + +import android.os.Bundle; +import android.test.mock.MockCursor; + +import java.util.ArrayList; + +/** + * A simple in memory fake cursor that can be used for UI tests. + */ +public class FakeCursor extends MockCursor { + private final ArrayList 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(projection.length); + for (final String column : projection) { + mProjection.add(getColumnIndex(column)); + } + } + + public Object getAt(final String columnName, final int row) { + final int dataIdx = getColumnIndex(columnName); + return (dataIdx < 0 || row < 0 || row >= mData.length) ? 0 : mData[row][dataIdx]; + } + + @Override + public int getCount() { + return mData.length; + } + + @Override + public boolean isFirst() { + return mIndex == 0; + } + + @Override + public boolean isLast() { + return mIndex == mData.length - 1; + } + + @Override + public boolean moveToFirst() { + if (mData.length == 0) { + return false; + } + mIndex = 0; + return true; + } + + @Override + public boolean moveToPosition(final int position) { + if (position < 0 || position >= mData.length) { + return false; + } + mIndex = position; + return true; + } + + @Override + public int getPosition() { + return mIndex; + } + + @Override + public boolean moveToPrevious() { + if (mIndex <= 0) { + return false; + } + mIndex--; + return true; + } + + @Override + public boolean moveToNext() { + if (mIndex == mData.length - 1) { + return false; + } + + mIndex++; + return true; + } + + @Override + public int getColumnCount() { + return mColumnNamesOfData.length; + } + + @Override + public int getColumnIndex(final String columnName) { + for (int i = 0 ; i < mColumnNamesOfData.length ; i++) { + if (mColumnNamesOfData[i].equals(columnName)) { + return i; + } + } + return -1; + } + + @Override + public int getColumnIndexOrThrow(final String columnName) { + final int result = getColumnIndex(columnName); + if (result == -1) { + throw new IllegalArgumentException(); + } + + return result; + } + + @Override + public String getString(final int columnIndex) { + final int dataIdx = mProjection.get(columnIndex); + final Object obj = (dataIdx < 0 ? null : mData[mIndex][dataIdx]); + return (obj == null ? null : obj.toString()); + } + + @Override + public int getInt(final int columnIndex) { + final int dataIdx = mProjection.get(columnIndex); + return (dataIdx < 0 ? 0 : (Integer) mData[mIndex][dataIdx]); + } + + @Override + public long getLong(final int columnIndex) { + final int dataIdx = mProjection.get(columnIndex); + return (dataIdx < 0 ? 0 : (Long) mData[mIndex][dataIdx]); + } + + @Override + public void close() { + } + + @Override + public boolean isClosed() { + return false; + } + + @Override + public Bundle getExtras() { return null; } +} diff --git a/tests/src/com/android/messaging/datamodel/FakeDataModel.java b/tests/src/com/android/messaging/datamodel/FakeDataModel.java new file mode 100644 index 0000000..5e80eab --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/FakeDataModel.java @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.datamodel; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; +import android.test.RenamingDelegatingContext; + +import com.android.messaging.datamodel.action.ActionService; +import com.android.messaging.datamodel.action.BackgroundWorker; +import com.android.messaging.datamodel.data.BlockedParticipantsData; +import com.android.messaging.datamodel.data.BlockedParticipantsData.BlockedParticipantsDataListener; +import com.android.messaging.datamodel.data.ContactListItemData; +import com.android.messaging.datamodel.data.ContactPickerData; +import com.android.messaging.datamodel.data.ContactPickerData.ContactPickerDataListener; +import com.android.messaging.datamodel.data.ConversationData; +import com.android.messaging.datamodel.data.ConversationData.ConversationDataListener; +import com.android.messaging.datamodel.data.ConversationListData; +import com.android.messaging.datamodel.data.ConversationListData.ConversationListDataListener; +import com.android.messaging.datamodel.data.DraftMessageData; +import com.android.messaging.datamodel.data.GalleryGridItemData; +import com.android.messaging.datamodel.data.LaunchConversationData; +import com.android.messaging.datamodel.data.LaunchConversationData.LaunchConversationDataListener; +import com.android.messaging.datamodel.data.MediaPickerData; +import com.android.messaging.datamodel.data.MessagePartData; +import com.android.messaging.datamodel.data.ParticipantData; +import com.android.messaging.datamodel.data.ParticipantListItemData; +import com.android.messaging.datamodel.data.PeopleAndOptionsData; +import com.android.messaging.datamodel.data.PeopleAndOptionsData.PeopleAndOptionsDataListener; +import com.android.messaging.datamodel.data.PeopleOptionsItemData; +import com.android.messaging.datamodel.data.SettingsData; +import com.android.messaging.datamodel.data.SettingsData.SettingsDataListener; +import com.android.messaging.datamodel.data.SubscriptionListData; +import com.android.messaging.datamodel.data.TestDataFactory; +import com.android.messaging.datamodel.data.VCardContactItemData; +import com.android.messaging.util.ConnectivityUtil; + +public class FakeDataModel extends DataModel { + private BackgroundWorker mWorker; + private ActionService mActionService; + private final DatabaseHelper mDatabaseHelper; + private ConversationListData mConversationListData; + private ContactPickerData mContactPickerData; + private MediaPickerData mMediaPickerData; + private PeopleAndOptionsData mPeopleAndOptionsData; + private ConnectivityUtil mConnectivityUtil; + private SyncManager mSyncManager; + private SettingsData mSettingsData; + private DraftMessageData mDraftMessageData; + + public FakeDataModel(final Context context) { + super(); + if (context instanceof RenamingDelegatingContext) { + mDatabaseHelper = DatabaseHelper.getNewInstanceForTest(context); + } else { + mDatabaseHelper = null; + } + } + + @Override + public BackgroundWorker getBackgroundWorkerForActionService() { + return mWorker; + } + + public FakeDataModel withBackgroundWorkerForActionService(final BackgroundWorker worker) { + mWorker = worker; + return this; + } + + public FakeDataModel withActionService(final ActionService ActionService) { + mActionService = ActionService; + return this; + } + + public FakeDataModel withConversationListData(final ConversationListData conversationListData) { + mConversationListData = conversationListData; + return this; + } + + public FakeDataModel withContactPickerData(final ContactPickerData contactPickerData) { + mContactPickerData = contactPickerData; + return this; + } + + public FakeDataModel withMediaPickerData(final MediaPickerData mediaPickerData) { + mMediaPickerData = mediaPickerData; + return this; + } + + public FakeDataModel withConnectivityUtil(final ConnectivityUtil connectivityUtil) { + mConnectivityUtil = connectivityUtil; + return this; + } + + public FakeDataModel withSyncManager(final SyncManager syncManager) { + mSyncManager = syncManager; + return this; + } + + public FakeDataModel withPeopleAndOptionsData(final PeopleAndOptionsData peopleAndOptionsData) { + mPeopleAndOptionsData = peopleAndOptionsData; + return this; + } + + public FakeDataModel withSettingsData(final SettingsData settingsData) { + mSettingsData = settingsData; + return this; + } + + public FakeDataModel withDraftMessageData(final DraftMessageData draftMessageData) { + mDraftMessageData = draftMessageData; + return this; + } + + @Override + public ConversationListData createConversationListData(final Context context, + final ConversationListDataListener listener, final boolean archivedMode) { + return mConversationListData; + } + + @Override + public ConversationData createConversationData(final Context context, + final ConversationDataListener listener, final String conversationId) { + throw new IllegalStateException("Add withXXX or mock this method"); + } + + @Override + public ContactListItemData createContactListItemData() { + // This is a lightweight data holder object for each individual list item for which + // we don't perform any data request, so we can directly return a new instance. + return new ContactListItemData(); + } + + @Override + public ContactPickerData createContactPickerData(final Context context, + final ContactPickerDataListener listener) { + return mContactPickerData; + } + + @Override + public MediaPickerData createMediaPickerData(final Context context) { + return mMediaPickerData; + } + + @Override + public GalleryGridItemData createGalleryGridItemData() { + // This is a lightweight data holder object for each individual grid item for which + // we don't perform any data request, so we can directly return a new instance. + return new GalleryGridItemData(); + } + + @Override + public LaunchConversationData createLaunchConversationData( + final LaunchConversationDataListener listener) { + return new LaunchConversationData(listener); + } + + @Override + public PeopleOptionsItemData createPeopleOptionsItemData(final Context context) { + return new PeopleOptionsItemData(context); + } + + @Override + public PeopleAndOptionsData createPeopleAndOptionsData(final String conversationId, + final Context context, final PeopleAndOptionsDataListener listener) { + return mPeopleAndOptionsData; + } + + @Override + public VCardContactItemData createVCardContactItemData(final Context context, + final MessagePartData data) { + return new VCardContactItemData(context, data); + } + + @Override + public VCardContactItemData createVCardContactItemData(final Context context, + final Uri vCardUri) { + return new VCardContactItemData(context, vCardUri); + } + + @Override + public ParticipantListItemData createParticipantListItemData( + final ParticipantData participant) { + return new ParticipantListItemData(participant); + } + + @Override + public SubscriptionListData createSubscriptonListData(Context context) { + return new SubscriptionListData(context); + } + + @Override + public SettingsData createSettingsData(Context context, SettingsDataListener listener) { + return mSettingsData; + } + + @Override + public DraftMessageData createDraftMessageData(String conversationId) { + return mDraftMessageData; + } + + @Override + public ActionService getActionService() { + return mActionService; + } + + @Override + public ConnectivityUtil getConnectivityUtil() { + return mConnectivityUtil; + } + + @Override + public SyncManager getSyncManager() { + return mSyncManager; + } + + @Override + public DatabaseWrapper getDatabase() { + // Note this will crash unless the application context is redirected... + // This is by design so that tests do not inadvertently use the real database + return mDatabaseHelper.getDatabase(); + } + + @Override + void onCreateTables(final SQLiteDatabase db) { + TestDataFactory.createTestData(db); + } + + @Override + public void onActivityResume() { + } + + @Override + public void onApplicationCreated() { + } + + @Override + public BlockedParticipantsData createBlockedParticipantsData(Context context, + BlockedParticipantsDataListener listener) { + return new BlockedParticipantsData(context, listener); + } +} diff --git a/tests/src/com/android/messaging/datamodel/FrequentContactsCursorBuilderTest.java b/tests/src/com/android/messaging/datamodel/FrequentContactsCursorBuilderTest.java new file mode 100644 index 0000000..6b78a07 --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/FrequentContactsCursorBuilderTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.messaging.datamodel; + +import android.database.Cursor; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.messaging.BugleTestCase; +import com.android.messaging.datamodel.data.TestDataFactory; +import com.android.messaging.util.ContactUtil; + +@SmallTest +public class FrequentContactsCursorBuilderTest extends BugleTestCase { + + private void verifyBuiltCursor(final Cursor expected, final Cursor actual) { + final int rowCount = expected.getCount(); + final int columnCount = expected.getColumnCount(); + assertEquals(rowCount, actual.getCount()); + assertEquals(columnCount, actual.getColumnCount()); + for (int i = 0; i < rowCount; i++) { + expected.moveToPosition(i); + actual.moveToPosition(i); + assertEquals(expected.getLong(ContactUtil.INDEX_DATA_ID), + actual.getLong(ContactUtil.INDEX_DATA_ID)); + assertEquals(expected.getLong(ContactUtil.INDEX_CONTACT_ID), + actual.getLong(ContactUtil.INDEX_CONTACT_ID)); + assertEquals(expected.getString(ContactUtil.INDEX_LOOKUP_KEY), + actual.getString(ContactUtil.INDEX_LOOKUP_KEY)); + assertEquals(expected.getString(ContactUtil.INDEX_DISPLAY_NAME), + actual.getString(ContactUtil.INDEX_DISPLAY_NAME)); + assertEquals(expected.getString(ContactUtil.INDEX_PHOTO_URI), + actual.getString(ContactUtil.INDEX_PHOTO_URI)); + assertEquals(expected.getString(ContactUtil.INDEX_PHONE_EMAIL), + actual.getString(ContactUtil.INDEX_PHONE_EMAIL)); + assertEquals(expected.getInt(ContactUtil.INDEX_PHONE_EMAIL_TYPE), + actual.getInt(ContactUtil.INDEX_PHONE_EMAIL_TYPE)); + assertEquals(expected.getString(ContactUtil.INDEX_PHONE_EMAIL_LABEL), + actual.getString(ContactUtil.INDEX_PHONE_EMAIL_LABEL)); + } + } + + public void testIncompleteBuild() { + final FrequentContactsCursorBuilder builder = new FrequentContactsCursorBuilder(); + assertNull(builder.build()); + assertNull(builder.setFrequents(TestDataFactory.getStrequentContactsCursor()).build()); + builder.resetBuilder(); + assertNull(builder.build()); + assertNull(builder.setAllContacts(TestDataFactory.getAllContactListCursor()).build()); + } + + public void testBuildOnce() { + final Cursor cursor = new FrequentContactsCursorBuilder() + .setAllContacts(TestDataFactory.getAllContactListCursor()) + .setFrequents(TestDataFactory.getStrequentContactsCursor()) + .build(); + assertNotNull(cursor); + verifyBuiltCursor(TestDataFactory.getFrequentContactListCursor(), cursor); + } + + public void testBuildTwice() { + final FrequentContactsCursorBuilder builder = new FrequentContactsCursorBuilder(); + final Cursor firstCursor = builder + .setAllContacts(TestDataFactory.getAllContactListCursor()) + .setFrequents(TestDataFactory.getStrequentContactsCursor()) + .build(); + assertNotNull(firstCursor); + builder.resetBuilder(); + assertNull(builder.build()); + + final Cursor secondCursor = builder + .setAllContacts(TestDataFactory.getAllContactListCursor()) + .setFrequents(TestDataFactory.getStrequentContactsCursor()) + .build(); + assertNotNull(firstCursor); + verifyBuiltCursor(TestDataFactory.getFrequentContactListCursor(), secondCursor); + } +} diff --git a/tests/src/com/android/messaging/datamodel/MemoryCacheManagerTest.java b/tests/src/com/android/messaging/datamodel/MemoryCacheManagerTest.java new file mode 100644 index 0000000..5da9e27 --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/MemoryCacheManagerTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.datamodel; + +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.messaging.datamodel.MemoryCacheManager.MemoryCache; + +import org.mockito.Mockito; + +@SmallTest +public class MemoryCacheManagerTest extends AndroidTestCase { + + public void testRegisterCachesGetReclaimed() { + final MemoryCache mockMemoryCache = Mockito.mock(MemoryCache.class); + final MemoryCache otherMockMemoryCache = Mockito.mock(MemoryCache.class); + final MemoryCacheManager memoryCacheManager = new MemoryCacheManager(); + + memoryCacheManager.registerMemoryCache(mockMemoryCache); + memoryCacheManager.registerMemoryCache(otherMockMemoryCache); + memoryCacheManager.reclaimMemory(); + memoryCacheManager.unregisterMemoryCache(otherMockMemoryCache); + memoryCacheManager.reclaimMemory(); + + Mockito.verify(mockMemoryCache, Mockito.times(2)).reclaim(); + Mockito.verify(otherMockMemoryCache, Mockito.times(1)).reclaim(); + } +} diff --git a/tests/src/com/android/messaging/datamodel/ParticipantRefreshTest.java b/tests/src/com/android/messaging/datamodel/ParticipantRefreshTest.java new file mode 100644 index 0000000..cd1d6c7 --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/ParticipantRefreshTest.java @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.datamodel; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; +import android.test.suitebuilder.annotation.SmallTest; +import android.text.TextUtils; + +import com.android.messaging.BugleTestCase; +import com.android.messaging.FakeContentProvider; +import com.android.messaging.FakeContext; +import com.android.messaging.FakeFactory; +import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns; +import com.android.messaging.datamodel.data.ParticipantData; +import com.android.messaging.datamodel.data.ParticipantData.ParticipantsQuery; +import com.android.messaging.util.ContactUtil; + +import org.junit.Assert; + +/** + * Utility class for testing ParticipantRefresh class for different scenarios. + */ +@SmallTest +public class ParticipantRefreshTest extends BugleTestCase { + private FakeContext mContext; + FakeFactory mFakeFactory; + + @Override + public void setUp() throws Exception { + super.setUp(); + + mContext = new FakeContext(getTestContext()); + + final ContentProvider provider = new MessagingContentProvider(); + provider.attachInfo(mContext, null); + mContext.addContentProvider(MessagingContentProvider.AUTHORITY, provider); + + final FakeDataModel fakeDataModel = new FakeDataModel(mContext); + mFakeFactory = FakeFactory.registerWithFakeContext(getTestContext(), mContext) + .withDataModel(fakeDataModel); + } + + /** + * Add some phonelookup result into take PhoneLookup content provider. This will be + * used for doing phone lookup during participant refresh. + */ + private void addPhoneLookup(final String phone, final Object[][] lookupResult) { + final Uri uri = ContactUtil.lookupPhone(mContext, phone).getUri(); + final FakeContentProvider phoneLookup = new FakeContentProvider(mContext, + uri, false); + phoneLookup.addOverrideData(uri, null, null, ContactUtil.PhoneLookupQuery.PROJECTION, + lookupResult); + mFakeFactory.withProvider(uri, phoneLookup); + } + + /** + * Add some participant to test database. + */ + private void addParticipant(final String normalizedDestination, final long contactId, + final String name, final String photoUrl) { + final DatabaseWrapper db = DataModel.get().getDatabase(); + final ContentValues values = new ContentValues(); + + values.put(ParticipantColumns.NORMALIZED_DESTINATION, normalizedDestination); + values.put(ParticipantColumns.CONTACT_ID, contactId); + values.put(ParticipantColumns.FULL_NAME, name); + values.put(ParticipantColumns.PROFILE_PHOTO_URI, photoUrl); + + db.beginTransaction(); + try { + db.insert(DatabaseHelper.PARTICIPANTS_TABLE, null, values); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + + /** + * Verify that participant in the database has expected contacdtId, name and photoUrl fields. + */ + private void verifyParticipant(final String normalizedDestination, final long contactId, + final String name, final String photoUrl) { + final DatabaseWrapper db = DataModel.get().getDatabase(); + db.beginTransaction(); + try { + final String selection = ParticipantColumns.NORMALIZED_DESTINATION + "=?"; + final String[] selectionArgs = new String[] { normalizedDestination }; + + final Cursor cursor = db.query(DatabaseHelper.PARTICIPANTS_TABLE, + ParticipantsQuery.PROJECTION, selection, selectionArgs, null, null, null); + + if (cursor == null || cursor.getCount() != 1) { + Assert.fail("Should have participants for:" + normalizedDestination); + return; + } + + cursor.moveToFirst(); + final int currentContactId = cursor.getInt(ParticipantsQuery.INDEX_CONTACT_ID); + final String currentName = cursor.getString(ParticipantsQuery.INDEX_FULL_NAME); + final String currentPhotoUrl = + cursor.getString(ParticipantsQuery.INDEX_PROFILE_PHOTO_URI); + if (currentContactId != contactId) { + Assert.fail("Contact Id doesn't match. normalizedNumber=" + normalizedDestination + + " expected=" + contactId + " actual=" + currentContactId); + return; + } + + if (!TextUtils.equals(currentName, name)) { + Assert.fail("Name doesn't match. normalizedNumber=" + normalizedDestination + + " expected=" + name + " actual=" + currentName); + return; + } + + if (!TextUtils.equals(currentPhotoUrl, photoUrl)) { + Assert.fail("Contact Id doesn't match. normalizedNumber=" + normalizedDestination + + " expected=" + photoUrl + " actual=" + currentPhotoUrl); + return; + } + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + + /** + * Verify that incremental refresh will resolve previously not resolved participants. + */ + public void testIncrementalRefreshNotResolvedSingleMatch() { + addParticipant("650-123-1233", ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED, + null, null); + addPhoneLookup("650-123-1233", new Object[][] { + { 1L, "John", "content://photo/john", "650-123-1233", null, null, null } + }); + + ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_INCREMENTAL); + verifyParticipant("650-123-1233", 1, "John", "content://photo/john"); + } + + /** + * Verify that incremental refresh will resolve previously not resolved participants. + */ + public void testIncrementalRefreshNotResolvedMultiMatch() { + addParticipant("650-123-1233", ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED, + null, null); + addPhoneLookup("650-123-1233", new Object[][] { + { 1L, "John", "content://photo/john", "650-123-1233", null, null, null }, + { 2L, "Joe", "content://photo/joe", "650-123-1233", null, null, null } + }); + + ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_INCREMENTAL); + verifyParticipant("650-123-1233", 1, "John", "content://photo/john"); + } + + /** + * Verify that incremental refresh will not touch already-resolved participants. + */ + public void testIncrementalRefreshResolvedSingleMatch() { + addParticipant("650-123-1233", 1, "Joh", "content://photo/joh"); + addPhoneLookup("650-123-1233", new Object[][] { + { 1L, "John", "content://photo/john", "650-123-1233", null, null, null } + }); + + ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_INCREMENTAL); + verifyParticipant("650-123-1233", 1, "Joh", "content://photo/joh"); + } + + /** + * Verify that full refresh will correct already-resolved participants if needed + */ + public void testFullRefreshResolvedSingleMatch() { + addParticipant("650-123-1233", 1, "Joh", "content://photo/joh"); + addPhoneLookup("650-123-1233", new Object[][] { + { 1L, "John", "content://photo/john", "650-123-1233", null, null, null } + }); + + ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_FULL); + verifyParticipant("650-123-1233", 1, "John", "content://photo/john"); + } + + /** + * Verify that incremental refresh will not touch participant that is marked as not found. + */ + public void testIncrementalRefreshNotFound() { + addParticipant("650-123-1233", ParticipantData.PARTICIPANT_CONTACT_ID_NOT_FOUND, + null, null); + addPhoneLookup("650-123-1233", new Object[][] { + { 1L, "John", "content://photo/john", "650-123-1233", null, null, null } + }); + + ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_INCREMENTAL); + verifyParticipant("650-123-1233", ParticipantData.PARTICIPANT_CONTACT_ID_NOT_FOUND, + null, null); + } + + /** + * Verify that full refresh will resolve participant that is marked as not found. + */ + public void testFullRefreshNotFound() { + addParticipant("650-123-1233", ParticipantData.PARTICIPANT_CONTACT_ID_NOT_FOUND, + null, null); + addPhoneLookup("650-123-1233", new Object[][] { + { 1L, "John", "content://photo/john", "650-123-1233", null, null, null } + }); + + ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_FULL); + verifyParticipant("650-123-1233", 1, "John", "content://photo/john"); + } + + /** + * Verify that refresh take consideration of current contact_id when having multiple matches. + */ + public void testFullRefreshResolvedMultiMatch1() { + addParticipant("650-123-1233", 1, "Joh", "content://photo/joh"); + addPhoneLookup("650-123-1233", new Object[][] { + { 1L, "John", "content://photo/john", "650-123-1233", null, null, null }, + { 2L, "Joe", "content://photo/joe", "650-123-1233", null, null, null } + }); + + ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_FULL); + verifyParticipant("650-123-1233", 1, "John", "content://photo/john"); + } + + /** + * Verify that refresh take consideration of current contact_id when having multiple matches. + */ + public void testFullRefreshResolvedMultiMatch2() { + addParticipant("650-123-1233", 2, "Joh", "content://photo/joh"); + addPhoneLookup("650-123-1233", new Object[][] { + { 1L, "John", "content://photo/john", "650-123-1233", null, null, null }, + { 2L, "Joe", "content://photo/joe", "650-123-1233", null, null, null } + }); + + ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_FULL); + verifyParticipant("650-123-1233", 2, "Joe", "content://photo/joe"); + } + + /** + * Verify that refresh take first contact in case current contact_id no longer matches. + */ + public void testFullRefreshResolvedMultiMatch3() { + addParticipant("650-123-1233", 3, "Joh", "content://photo/joh"); + addPhoneLookup("650-123-1233", new Object[][] { + { 1L, "John", "content://photo/john", "650-123-1233", null, null, null }, + { 2L, "Joe", "content://photo/joe", "650-123-1233", null, null, null } + }); + + ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_FULL); + verifyParticipant("650-123-1233", 1, "John", "content://photo/john"); + } + + /** + * Verify that refresh take first contact in case current contact_id no longer matches. + */ + public void testFullRefreshResolvedBeforeButNotFoundNow() { + addParticipant("650-123-1233", 3, "Joh", "content://photo/joh"); + addPhoneLookup("650-123-1233", new Object[][] {}); + + ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_FULL); + verifyParticipant("650-123-1233", ParticipantData.PARTICIPANT_CONTACT_ID_NOT_FOUND, + null, null); + } +} diff --git a/tests/src/com/android/messaging/datamodel/action/ActionServiceSystemTest.java b/tests/src/com/android/messaging/datamodel/action/ActionServiceSystemTest.java new file mode 100644 index 0000000..039bec9 --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/action/ActionServiceSystemTest.java @@ -0,0 +1,436 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.datamodel.action; + +import android.content.Intent; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.test.suitebuilder.annotation.MediumTest; +import android.util.Log; + +import com.android.messaging.BugleTestCase; +import com.android.messaging.Factory; +import com.android.messaging.FakeContext; +import com.android.messaging.FakeContext.FakeContextHost; +import com.android.messaging.FakeFactory; +import com.android.messaging.datamodel.BugleServiceTestCase; +import com.android.messaging.datamodel.DataModel; +import com.android.messaging.datamodel.FakeDataModel; +import com.android.messaging.datamodel.action.ActionMonitor.ActionCompletedListener; +import com.android.messaging.datamodel.action.ActionMonitor.ActionExecutedListener; +import com.android.messaging.datamodel.action.ActionTestHelpers.ResultTracker; +import com.android.messaging.datamodel.action.ActionTestHelpers.StubBackgroundWorker; +import com.android.messaging.datamodel.action.ActionTestHelpers.StubConnectivityUtil; +import com.android.messaging.datamodel.action.ActionTestHelpers.StubLoader; + +import java.util.ArrayList; + +@MediumTest +public class ActionServiceSystemTest extends BugleServiceTestCase + 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 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 CREATOR + = new Parcelable.Creator() { + @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 CREATOR + = new Parcelable.Creator() { + @Override + public TestChatActionOther createFromParcel(final Parcel in) { + return new TestChatActionOther(in); + } + + @Override + public TestChatActionOther[] newArray(final int size) { + return new TestChatActionOther[size]; + } + }; + + @Override + public void writeToParcel(final Parcel parcel, final int flags) { + writeActionToParcel(parcel, flags); + parcel.writeString(parameter); + } + } + + /** + * An operation that notifies a listener upon completion + */ + public static class TestChatActionMonitor extends ActionMonitor { + /** + * Create action state wrapping an BlockUserAction instance + * @param account - account in which to block the user + * @param baseKey - suggested action key from BlockUserAction + * @param data - optional action specific data that is handed back to listener + * @param listener - action completed listener + */ + public TestChatActionMonitor(final String baseKey, final Object data, + final ActionCompletedListener completed, final ActionExecutedListener executed) { + super(STATE_CREATED, Action.generateUniqueActionKey(baseKey), data); + setCompletedListener(completed); + setExecutedListener(executed); + } + } +} diff --git a/tests/src/com/android/messaging/datamodel/action/ActionServiceTest.java b/tests/src/com/android/messaging/datamodel/action/ActionServiceTest.java new file mode 100644 index 0000000..02cddae --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/action/ActionServiceTest.java @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.datamodel.action; + +import android.content.Intent; +import android.os.Bundle; +import android.os.Looper; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.Process; +import android.test.suitebuilder.annotation.MediumTest; +import android.util.Log; + +import com.android.messaging.Factory; +import com.android.messaging.FakeContext; +import com.android.messaging.FakeContext.FakeContextHost; +import com.android.messaging.FakeFactory; +import com.android.messaging.datamodel.BugleServiceTestCase; +import com.android.messaging.datamodel.FakeDataModel; +import com.android.messaging.datamodel.action.ActionMonitor.ActionCompletedListener; +import com.android.messaging.datamodel.action.ActionMonitor.ActionStateChangedListener; +import com.android.messaging.datamodel.action.ActionTestHelpers.ResultTracker; +import com.android.messaging.datamodel.action.ActionTestHelpers.StubBackgroundWorker; +import com.android.messaging.datamodel.action.ActionTestHelpers.StubConnectivityUtil; +import com.android.messaging.datamodel.action.ActionTestHelpers.StubLoader; +import com.android.messaging.util.WakeLockHelper; + +import java.util.ArrayList; + +@MediumTest +public class ActionServiceTest extends BugleServiceTestCase + 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 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 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(); + 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 CREATOR + = new Parcelable.Creator() { + @Override + public TestChatAction createFromParcel(final Parcel in) { + return new TestChatAction(in); + } + + @Override + public TestChatAction[] newArray(final int size) { + return new TestChatAction[size]; + } + }; + + @Override + public void writeToParcel(final Parcel parcel, final int flags) { + writeActionToParcel(parcel, flags); + } + } + + /** + * An operation that notifies a listener upon state changes, execution and completion + */ + public static class TestChatActionMonitor extends ActionMonitor { + public TestChatActionMonitor(final String baseKey, final Object data, + final ActionStateChangedListener listener, final ActionCompletedListener executed) { + super(STATE_CREATED, Action.generateUniqueActionKey(baseKey), data); + setStateChangedListener(listener); + setCompletedListener(executed); + assertEquals("Initial state should be STATE_CREATED", mState, STATE_CREATED); + } + } +} diff --git a/tests/src/com/android/messaging/datamodel/action/ActionTest.java b/tests/src/com/android/messaging/datamodel/action/ActionTest.java new file mode 100644 index 0000000..aefa25e --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/action/ActionTest.java @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.datamodel.action; + +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.messaging.BugleTestCase; +import com.android.messaging.FakeFactory; +import com.android.messaging.datamodel.DataModelImpl; +import com.android.messaging.datamodel.action.ActionTestHelpers.StubChatActionMonitor; + +import java.util.ArrayList; + +@MediumTest +public class ActionTest extends BugleTestCase { + private static final String parameter = "parameter"; + private static final Object executeActionResult = "executeActionResult"; + private static final Object processResponseResult = "processResponseResult"; + private static final Object processFailureResult = "processFailureResult"; + + private static final String mActionKey = "TheActionKey"; + private static final Object mData = "PrivateData"; + private StubChatActionMonitor mMonitor; + private TestChatAction mAction; + + private ArrayList 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 CREATOR + = new Parcelable.Creator() { + @Override + public TestChatAction createFromParcel(final Parcel in) { + return new TestChatAction(in); + } + + @Override + public TestChatAction[] newArray(final int size) { + return new TestChatAction[size]; + } + }; + + @Override + public void writeToParcel(final Parcel parcel, final int flags) { + writeActionToParcel(parcel, flags); + parcel.writeString(parameter); + } + } +} diff --git a/tests/src/com/android/messaging/datamodel/action/ActionTestHelpers.java b/tests/src/com/android/messaging/datamodel/action/ActionTestHelpers.java new file mode 100644 index 0000000..d72a0f9 --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/action/ActionTestHelpers.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.datamodel.action; + +import android.content.Context; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Bundle; + +import com.android.messaging.util.ConnectivityUtil; + +import java.util.ArrayList; +import java.util.List; + +public class ActionTestHelpers { + private static final String TAG = "DataModelTestHelpers"; + + static class StubLoader extends ContentObserver { + ArrayList mUriList = new ArrayList(); + + 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(); + } + + ArrayList mActions; + public ArrayList getRequestsMade() { + return mActions; + } + + @Override + public void queueBackgroundWork(final List 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 mTransitions; + public ArrayList getTransitions() { + return mTransitions; + } + + protected StubChatActionMonitor(final int initialState, final String actionKey, + final Object data) { + super(initialState, actionKey, data); + mTransitions = new ArrayList(); + } + + @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 mServiceCalls = + new ArrayList(); + + public ArrayList getCalls() { + return mServiceCalls; + } + + @Override + public void startAction(final Action action) { + mServiceCalls.add(new StubActionServiceCallLog(action, null, null, null, null)); + synchronized(this) { + this.notifyAll(); + } + } + + @Override + public void handleResponseFromBackgroundWorker(final Action request, + final Bundle response) { + mServiceCalls.add(new StubActionServiceCallLog(null, request, response, null, null)); + synchronized(this) { + this.notifyAll(); + } + } + + @Override + protected void handleFailureFromBackgroundWorker(final Action request, + final Exception exception) { + mServiceCalls.add(new StubActionServiceCallLog(null, request, null, exception, null)); + synchronized(this) { + this.notifyAll(); + } + } + } + + public static class StubConnectivityUtil extends ConnectivityUtil { + public StubConnectivityUtil(final Context context) { + super(context); + } + + @Override + public void registerForSignalStrength() { + } + + @Override + public void unregisterForSignalStrength() { + } + } +} diff --git a/tests/src/com/android/messaging/datamodel/action/GetOrCreateConversationActionTest.java b/tests/src/com/android/messaging/datamodel/action/GetOrCreateConversationActionTest.java new file mode 100644 index 0000000..b05b022 --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/action/GetOrCreateConversationActionTest.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.datamodel.action; + +import android.content.pm.ProviderInfo; +import android.database.Cursor; +import android.net.Uri; +import android.test.mock.MockContentProvider; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.messaging.BugleTestCase; +import com.android.messaging.FakeContext; +import com.android.messaging.FakeFactory; +import com.android.messaging.datamodel.BugleDatabaseOperations; +import com.android.messaging.datamodel.DataModel; +import com.android.messaging.datamodel.DatabaseWrapper; +import com.android.messaging.datamodel.FakeDataModel; +import com.android.messaging.datamodel.MessagingContentProvider; +import com.android.messaging.datamodel.action.ActionTestHelpers.StubActionService; +import com.android.messaging.datamodel.action.ActionTestHelpers.StubActionService.StubActionServiceCallLog; +import com.android.messaging.datamodel.action.GetOrCreateConversationAction.GetOrCreateConversationActionListener; +import com.android.messaging.datamodel.action.GetOrCreateConversationAction.GetOrCreateConversationActionMonitor; +import com.android.messaging.datamodel.data.ParticipantData; +import com.android.messaging.datamodel.data.TestDataFactory; +import com.android.messaging.sms.MmsUtils; + +import org.mockito.Mock; + +import java.util.ArrayList; + +@SmallTest +public class GetOrCreateConversationActionTest extends BugleTestCase { + + @Mock GetOrCreateConversationActionListener mockListener; + + public void testGetOrCreateConversation() { + final DatabaseWrapper db = DataModel.get().getDatabase(); + + final ArrayList recipients = new ArrayList(); + recipients.add("5551234567"); + recipients.add("5551234568"); + + // Generate a list of partially formed participants + final ArrayList participants = new + ArrayList(); + + + 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 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 conversationParticipants = + BugleDatabaseOperations.getParticipantsForConversation(db, conversationId); + + assertEquals("Participant count mismatch", recipients.size(), + conversationParticipants.size()); + for(final ParticipantData participant : conversationParticipants) { + assertTrue(recipients.contains(participant.getSendDestination())); + } + + final Uri conversationParticipantsUri = + MessagingContentProvider.buildConversationParticipantsUri(conversationId); + final Cursor cursor = mContext.getContentResolver().query(conversationParticipantsUri, + ParticipantData.ParticipantsQuery.PROJECTION, null, null, null); + + int countSelf = 0; + while(cursor.moveToNext()) { + final ParticipantData participant = ParticipantData.getFromCursor(cursor); + if (participant.isSelf()) { + countSelf++; + } else { + assertTrue(recipients.contains(participant.getSendDestination())); + } + } + cursor.close(); + assertEquals("Expect one self participant in conversations", 1, countSelf); + assertEquals("Cursor count mismatch", recipients.size(), cursor.getCount() - countSelf); + + final String realId = BugleDatabaseOperations.getExistingConversation(db, threadId, false); + assertEquals("Conversation already exists", realId, conversationId); + } + + private FakeContext mContext; + private StubActionService mService; + + @Override + public void setUp() throws Exception { + super.setUp(); + + mContext = new FakeContext(getTestContext()); + + final MockContentProvider mockProvider = new MockContentProvider() { + @Override + public Cursor query(final Uri uri, final String[] projection, final String selection, + final String[] selectionArgs, final String sortOrder) { + return TestDataFactory.getSmsMmsThreadIdCursor(); + } + }; + + mContext.addContentProvider("mms-sms", mockProvider); + final MessagingContentProvider provider = new MessagingContentProvider(); + final ProviderInfo providerInfo = new ProviderInfo(); + providerInfo.authority = MessagingContentProvider.AUTHORITY; + provider.attachInfo(mContext, providerInfo); + mContext.addContentProvider(MessagingContentProvider.AUTHORITY, provider); + + mService = new StubActionService(); + final FakeDataModel fakeDataModel = new FakeDataModel(mContext) + .withActionService(mService); + FakeFactory.registerWithFakeContext(getTestContext(), mContext) + .withDataModel(fakeDataModel); + provider.setDatabaseForTest(fakeDataModel.getDatabase()); + } +} diff --git a/tests/src/com/android/messaging/datamodel/action/ReadWriteDraftMessageActionTest.java b/tests/src/com/android/messaging/datamodel/action/ReadWriteDraftMessageActionTest.java new file mode 100644 index 0000000..0405c90 --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/action/ReadWriteDraftMessageActionTest.java @@ -0,0 +1,482 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.datamodel.action; + +import android.content.ContentProvider; +import android.content.pm.ProviderInfo; +import android.database.Cursor; +import android.net.Uri; +import android.test.suitebuilder.annotation.SmallTest; +import android.text.TextUtils; + +import com.android.messaging.BugleTestCase; +import com.android.messaging.FakeContext; +import com.android.messaging.FakeFactory; +import com.android.messaging.datamodel.BugleDatabaseOperations; +import com.android.messaging.datamodel.DataModel; +import com.android.messaging.datamodel.DatabaseHelper; +import com.android.messaging.datamodel.DatabaseHelper.ConversationColumns; +import com.android.messaging.datamodel.DatabaseWrapper; +import com.android.messaging.datamodel.FakeDataModel; +import com.android.messaging.datamodel.MediaScratchFileProvider; +import com.android.messaging.datamodel.MessagingContentProvider; +import com.android.messaging.datamodel.action.ActionTestHelpers.StubActionService; +import com.android.messaging.datamodel.action.ActionTestHelpers.StubActionService.StubActionServiceCallLog; +import com.android.messaging.datamodel.action.ActionTestHelpers.StubConnectivityUtil; +import com.android.messaging.datamodel.action.ReadDraftDataAction.ReadDraftDataActionListener; +import com.android.messaging.datamodel.data.MessageData; +import com.android.messaging.datamodel.data.MessagePartData; +import com.android.messaging.datamodel.data.ParticipantData; +import com.android.messaging.util.ContentType; + +import org.mockito.Mock; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; + +@SmallTest +public class ReadWriteDraftMessageActionTest extends BugleTestCase { + + @Mock ReadDraftDataActionListener mockListener; + + // TODO: Add test cases + // 1. Make sure drafts can include attachments and multiple parts + // 2. Make sure attachments get cleaned up appropriately + // 3. Make sure messageId and partIds not reused (currently each draft is a new message). + public void testWriteDraft() { + final String draftMessage = "draftMessage"; + final long threadId = 1234567; + final boolean senderBlocked = false; + final String participantNumber = "5551234567"; + + final DatabaseWrapper db = DataModel.get().getDatabase(); + + final String conversationId = getOrCreateConversation(db, participantNumber, threadId, + senderBlocked); + final String selfId = getOrCreateSelfId(db); + + // Should clear/stub DB + final ArrayList 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 participants = + new ArrayList(); + 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 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 participants = + new ArrayList(); + 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 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 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 calls = mService.getCalls(); + + // Insert initial message + MessageData initial = MessageData.createDraftSmsMessage(conversationId, selfId, + initialMessage); + + BugleDatabaseOperations.updateDraftMessageData(db, conversationId, initial, + BugleDatabaseOperations.UPDATE_MODE_ADD_DRAFT); + + initial = BugleDatabaseOperations.readDraftMessageData(db, + conversationId, selfId); + assertEquals("Initial text mismatch", initialMessage, initial.getMessageText()); + + // Now update the draft + final MessageData message = MessageData.createDraftSmsMessage(conversationId, selfId, + draftMessage); + WriteDraftMessageAction.writeDraftMessage(conversationId, message); + + assertEquals("Failed to start service once for action", calls.size(), 1); + assertTrue("Action not SaveDraftMessageAction", + calls.get(0).action instanceof WriteDraftMessageAction); + + final Action save = calls.get(0).action; + + final Object result = save.executeAction(); + + assertTrue("Expect row number string as result", result instanceof String); + + // Check DB + final MessageData actual = BugleDatabaseOperations.readDraftMessageData(db, + conversationId, selfId); + assertNotNull("Database missing draft", actual); + assertEquals("Draft text mismatch", draftMessage, actual.getMessageText()); + assertNull("Draft messageId should be null", actual.getMessageId()); + } + + public void testBugleDatabaseDraftOperations() { + final String initialMessage = "initialMessage"; + final String draftMessage = "draftMessage"; + final long threadId = 1234599; + final boolean senderBlocked = false; + final String participantNumber = "5553456798"; + final String subject = "subject here"; + + final DatabaseWrapper db = DataModel.get().getDatabase(); + + final String conversationId = getOrCreateConversation(db, participantNumber, threadId, + senderBlocked); + final String selfId = getOrCreateSelfId(db); + + final String text = "This is some text"; + final Uri mOutputUri = MediaScratchFileProvider.buildMediaScratchSpaceUri("txt"); + OutputStream outputStream = null; + try { + outputStream = mContext.getContentResolver().openOutputStream(mOutputUri); + outputStream.write(text.getBytes()); + } catch (final FileNotFoundException e) { + fail("Cannot open output file"); + } catch (final IOException e) { + fail("Cannot write output file"); + } + + final MessageData initial = + MessageData.createDraftMmsMessage(conversationId, selfId, initialMessage, subject); + initial.addPart(MessagePartData.createMediaMessagePart(ContentType.MULTIPART_MIXED, + mOutputUri, 0, 0)); + + final String initialMessageId = BugleDatabaseOperations.updateDraftMessageData(db, + conversationId, initial, BugleDatabaseOperations.UPDATE_MODE_ADD_DRAFT); + assertNotNull(initialMessageId); + + final MessageData initialDraft = BugleDatabaseOperations.readMessage(db, initialMessageId); + assertNotNull(initialDraft); + int cnt = 0; + for(final MessagePartData part : initialDraft.getParts()) { + if (part.isAttachment()) { + assertEquals(part.getContentUri(), mOutputUri); + } else { + assertEquals(part.getText(), initialMessage); + } + cnt++; + } + assertEquals("Wrong number of parts", 2, cnt); + + InputStream inputStream = null; + try { + inputStream = mContext.getContentResolver().openInputStream(mOutputUri); + final byte[] buffer = new byte[256]; + final int read = inputStream.read(buffer); + assertEquals(read, text.getBytes().length); + } catch (final FileNotFoundException e) { + fail("Cannot open input file"); + } catch (final IOException e) { + fail("Cannot read input file"); + } + + final String moreText = "This is some more text"; + final Uri mAnotherUri = MediaScratchFileProvider.buildMediaScratchSpaceUri("txt"); + outputStream = null; + try { + outputStream = mContext.getContentResolver().openOutputStream(mAnotherUri); + outputStream.write(moreText.getBytes()); + } catch (final FileNotFoundException e) { + fail("Cannot open output file"); + } catch (final IOException e) { + fail("Cannot write output file"); + } + + final MessageData another = + MessageData.createDraftMmsMessage(conversationId, selfId, draftMessage, subject); + another.addPart(MessagePartData.createMediaMessagePart(ContentType.MMS_MULTIPART_MIXED, + mAnotherUri, 0, 0)); + + final String anotherMessageId = BugleDatabaseOperations.updateDraftMessageData(db, + conversationId, another, BugleDatabaseOperations.UPDATE_MODE_ADD_DRAFT); + assertNotNull(anotherMessageId); + + final MessageData anotherDraft = BugleDatabaseOperations.readMessage(db, anotherMessageId); + assertNotNull(anotherDraft); + cnt = 0; + for(final MessagePartData part : anotherDraft.getParts()) { + if (part.isAttachment()) { + assertEquals(part.getContentUri(), mAnotherUri); + } else { + assertEquals(part.getText(), draftMessage); + } + cnt++; + } + assertEquals("Wrong number of parts", 2, cnt); + + inputStream = null; + try { + inputStream = mContext.getContentResolver().openInputStream(mOutputUri); + assertNull("Original draft content should have been deleted", inputStream); + } catch (final FileNotFoundException e) { + } + inputStream = null; + try { + inputStream = mContext.getContentResolver().openInputStream(mAnotherUri); + final byte[] buffer = new byte[256]; + final int read = inputStream.read(buffer); + assertEquals(read, moreText.getBytes().length); + } catch (final FileNotFoundException e) { + fail("Cannot open input file"); + } catch (final IOException e) { + fail("Cannot read input file"); + } + + final MessageData last = null; + final String lastPartId = BugleDatabaseOperations.updateDraftMessageData(db, + conversationId, last, BugleDatabaseOperations.UPDATE_MODE_ADD_DRAFT); + assertNull(lastPartId); + + inputStream = null; + try { + inputStream = mContext.getContentResolver().openInputStream(mAnotherUri); + assertNull("Original draft content should have been deleted", inputStream); + } catch (final FileNotFoundException e) { + } + + } + + private StubActionService mService; + + @Override + public void setUp() throws Exception { + super.setUp(); + + final FakeContext context = new FakeContext(getTestContext()); + + final ContentProvider bugleProvider = new MessagingContentProvider(); + final ProviderInfo bugleProviderInfo = new ProviderInfo(); + bugleProviderInfo.authority = MessagingContentProvider.AUTHORITY; + bugleProvider.attachInfo(mContext, bugleProviderInfo); + context.addContentProvider(MessagingContentProvider.AUTHORITY, bugleProvider); + final ContentProvider mediaProvider = new MediaScratchFileProvider(); + final ProviderInfo mediaProviderInfo = new ProviderInfo(); + mediaProviderInfo.authority = MediaScratchFileProvider.AUTHORITY; + mediaProvider.attachInfo(mContext, mediaProviderInfo); + context.addContentProvider(MediaScratchFileProvider.AUTHORITY, mediaProvider); + + mService = new StubActionService(); + final FakeDataModel fakeDataModel = new FakeDataModel(context) + .withActionService(mService) + .withConnectivityUtil(new StubConnectivityUtil(context)); + FakeFactory.registerWithFakeContext(getTestContext(), context) + .withDataModel(fakeDataModel); + + } +} diff --git a/tests/src/com/android/messaging/datamodel/data/ConversationMessageDataTest.java b/tests/src/com/android/messaging/datamodel/data/ConversationMessageDataTest.java new file mode 100644 index 0000000..c6b9b20 --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/data/ConversationMessageDataTest.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.messaging.datamodel.data; + +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.messaging.BugleTestCase; +import com.android.messaging.FakeFactory; +import com.android.messaging.datamodel.FakeCursor; +import com.android.messaging.datamodel.data.ConversationMessageData; +import com.android.messaging.datamodel.data.MessageData; +import com.android.messaging.datamodel.data.ConversationMessageData.ConversationMessageViewColumns; + +@SmallTest +public class ConversationMessageDataTest extends BugleTestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + FakeFactory.register(getTestContext()); + } + + public void testBindFirstMessage() { + final FakeCursor testCursor = TestDataFactory.getConversationMessageCursor(); + final ConversationMessageData data = new ConversationMessageData(); + testCursor.moveToFirst(); + data.bind(testCursor); + // TODO: Add before checking in all bound fields... + assertEquals(testCursor.getAt(ConversationMessageViewColumns.STATUS, 0).equals( + MessageData.BUGLE_STATUS_INCOMING_COMPLETE), data.getIsIncoming()); + assertEquals(testCursor.getAt(ConversationMessageViewColumns.SENDER_PROFILE_PHOTO_URI, + 0), data.getSenderProfilePhotoUri()); + assertEquals(testCursor.getAt(ConversationMessageViewColumns.SENDER_FULL_NAME, 0), + data.getSenderFullName()); + } + + public void testBindTwice() { + final FakeCursor testCursor = TestDataFactory.getConversationMessageCursor(); + final ConversationMessageData data = new ConversationMessageData(); + testCursor.moveToPosition(1); + data.bind(testCursor); + assertEquals(TestDataFactory.getMessageText(testCursor, 1), data.getText()); + assertEquals(testCursor.getAt(ConversationMessageViewColumns.RECEIVED_TIMESTAMP, 1), + data.getReceivedTimeStamp()); + assertEquals(testCursor.getAt(ConversationMessageViewColumns.STATUS, 1).equals( + MessageData.BUGLE_STATUS_INCOMING_COMPLETE), data.getIsIncoming()); + assertEquals(testCursor.getAt(ConversationMessageViewColumns.SENDER_PROFILE_PHOTO_URI, + 1), data.getSenderProfilePhotoUri()); + assertEquals(testCursor.getAt(ConversationMessageViewColumns.SENDER_FULL_NAME, 1), + data.getSenderFullName()); + testCursor.moveToPosition(2); + data.bind(testCursor); + assertEquals(TestDataFactory.getMessageText(testCursor, 2), data.getText()); + assertEquals(testCursor.getAt(ConversationMessageViewColumns.RECEIVED_TIMESTAMP, 2), + data.getReceivedTimeStamp()); + assertEquals(testCursor.getAt(ConversationMessageViewColumns.STATUS, 2).equals( + MessageData.BUGLE_STATUS_INCOMING_COMPLETE), data.getIsIncoming()); + assertEquals(testCursor.getAt(ConversationMessageViewColumns.SENDER_PROFILE_PHOTO_URI, + 2), data.getSenderProfilePhotoUri()); + assertEquals(testCursor.getAt(ConversationMessageViewColumns.SENDER_FULL_NAME, 2), + data.getSenderFullName()); + } + + public void testMessageClustering() { + final FakeCursor testCursor = TestDataFactory.getConversationMessageCursor(); + final ConversationMessageData data = new ConversationMessageData(); + testCursor.moveToPosition(0); + data.bind(testCursor); + assertFalse(data.getCanClusterWithPreviousMessage()); + assertFalse(data.getCanClusterWithNextMessage()); + + testCursor.moveToPosition(1); + data.bind(testCursor); + assertFalse(data.getCanClusterWithPreviousMessage()); + assertFalse(data.getCanClusterWithNextMessage()); + + testCursor.moveToPosition(2); + data.bind(testCursor); + assertFalse(data.getCanClusterWithPreviousMessage()); + assertTrue(data.getCanClusterWithNextMessage()); // 2 and 3 can be clustered + testCursor.moveToPosition(3); + + data.bind(testCursor); + assertTrue(data.getCanClusterWithPreviousMessage()); // 2 and 3 can be clustered + assertFalse(data.getCanClusterWithNextMessage()); + } +} diff --git a/tests/src/com/android/messaging/datamodel/data/ConversationParticipantsDataTest.java b/tests/src/com/android/messaging/datamodel/data/ConversationParticipantsDataTest.java new file mode 100644 index 0000000..527a600 --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/data/ConversationParticipantsDataTest.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.messaging.datamodel.data; + +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.messaging.BugleTestCase; +import com.android.messaging.datamodel.FakeCursor; +import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns; +import com.android.messaging.datamodel.data.ConversationParticipantsData; +import com.android.messaging.datamodel.data.ParticipantData; + +@SmallTest +public class ConversationParticipantsDataTest extends BugleTestCase { + public void testBindParticipants() { + final FakeCursor testCursor = TestDataFactory.getConversationParticipantsCursor(); + final ConversationParticipantsData data = new ConversationParticipantsData(); + data.bind(testCursor); + + assertEquals(data.getParticipantListExcludingSelf().size(), testCursor.getCount()); + final ParticipantData participant2 = data.getParticipantById("2"); + assertNotNull(participant2); + assertEquals(participant2.getFirstName(), testCursor.getAt( + ParticipantColumns.FIRST_NAME, 1) ); + assertEquals(participant2.getSendDestination(), testCursor.getAt( + ParticipantColumns.SEND_DESTINATION, 1)); + } +} diff --git a/tests/src/com/android/messaging/datamodel/data/TestDataFactory.java b/tests/src/com/android/messaging/datamodel/data/TestDataFactory.java new file mode 100644 index 0000000..8527e2b --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/data/TestDataFactory.java @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.datamodel.data; + +import android.content.ContentValues; +import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; +import android.provider.BaseColumns; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.provider.ContactsContract.Contacts; +import android.provider.MediaStore.Images.Media; + +import com.android.messaging.datamodel.BugleDatabaseOperations; +import com.android.messaging.datamodel.DatabaseHelper; +import com.android.messaging.datamodel.DatabaseHelper.ConversationColumns; +import com.android.messaging.datamodel.DatabaseHelper.MessageColumns; +import com.android.messaging.datamodel.DatabaseHelper.PartColumns; +import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns; +import com.android.messaging.datamodel.FakeCursor; +import com.android.messaging.datamodel.data.ConversationListItemData.ConversationListViewColumns; +import com.android.messaging.datamodel.data.ConversationMessageData.ConversationMessageViewColumns; +import com.android.messaging.util.Assert; +import com.android.messaging.util.ContactUtil; +import com.android.messaging.util.ContentType; + +import java.util.Arrays; +import java.util.List; + +/** + * A factory for fake objects that can be useful for multiple tests. + */ +public class TestDataFactory { + private final static String[] sConversationListCursorColumns = new String[] { + ConversationListViewColumns._ID, + ConversationListViewColumns.NAME, + ConversationListViewColumns.ICON, + ConversationListViewColumns.SNIPPET_TEXT, + ConversationListViewColumns.PREVIEW_URI, + ConversationListViewColumns.SORT_TIMESTAMP, + ConversationListViewColumns.READ, + ConversationListViewColumns.PREVIEW_CONTENT_TYPE, + ConversationListViewColumns.MESSAGE_STATUS, + }; + + private final static String[] sContactCursorColumns = new String[] { + Phone.CONTACT_ID, + Phone.DISPLAY_NAME_PRIMARY, + Phone.PHOTO_THUMBNAIL_URI, + Phone.NUMBER, + Phone.TYPE, + Phone.LABEL, + Phone.LOOKUP_KEY, + Phone._ID, + Phone.SORT_KEY_PRIMARY, + }; + + private final static String[] sFrequentContactCursorColumns = new String[] { + Contacts._ID, + Contacts.DISPLAY_NAME, + Contacts.PHOTO_URI, + Phone.LOOKUP_KEY, + }; + + private final static String[] sConversationMessageCursorColumns = new String[] { + ConversationMessageViewColumns._ID, + ConversationMessageViewColumns.CONVERSATION_ID, + ConversationMessageViewColumns.PARTICIPANT_ID, + ConversationMessageViewColumns.SENT_TIMESTAMP, + ConversationMessageViewColumns.RECEIVED_TIMESTAMP, + ConversationMessageViewColumns.STATUS, + ConversationMessageViewColumns.SENDER_FULL_NAME, + ConversationMessageViewColumns.SENDER_PROFILE_PHOTO_URI, + ConversationMessageViewColumns.PARTS_IDS, + ConversationMessageViewColumns.PARTS_CONTENT_TYPES, + ConversationMessageViewColumns.PARTS_CONTENT_URIS, + ConversationMessageViewColumns.PARTS_WIDTHS, + ConversationMessageViewColumns.PARTS_HEIGHTS, + ConversationMessageViewColumns.PARTS_TEXTS, + ConversationMessageViewColumns.PARTS_COUNT + }; + + private final static String[] sGalleryCursorColumns = new String[] { + Media._ID, + Media.DATA, + Media.WIDTH, + Media.HEIGHT, + Media.MIME_TYPE + }; + + public static FakeCursor getConversationListCursor() { + final Object[][] cursorData = new Object[][] { + new Object[] { Long.valueOf(1), "name1", "content://icon1", + "snippetText1", "content://snippetUri1", Long.valueOf(10), 1, + ContentType.IMAGE_JPEG, MessageData.BUGLE_STATUS_INCOMING_COMPLETE}, + new Object[] { Long.valueOf(2), "name2", "content://icon2", + "snippetText2", "content://snippetUri2", Long.valueOf(20) + 24*60*60*1000, + 0, ContentType.IMAGE_JPEG, MessageData.BUGLE_STATUS_INCOMING_COMPLETE}, + new Object[] { Long.valueOf(3), "name3", "content://icon3", + "snippetText3", "content://snippetUri3", Long.valueOf(30) + 2*24*60*60*1000, + 0, ContentType.IMAGE_JPEG, MessageData.BUGLE_STATUS_OUTGOING_COMPLETE} + }; + return new FakeCursor(ConversationListItemData.PROJECTION, sConversationListCursorColumns, + cursorData); + } + public static final int CONVERSATION_LIST_CURSOR_READ_MESSAGE_INDEX = 0; + public static final int CONVERSATION_LIST_CURSOR_UNREAD_MESSAGE_INDEX = 1; + + public static FakeCursor getEmptyConversationListCursor() { + return new FakeCursor(ConversationListItemData.PROJECTION, sConversationListCursorColumns, + new Object[][] {}); + } + + public static FakeCursor getConversationMessageCursor() { + final Object[][] cursorData = new Object[][] { + new Object[] { Long.valueOf(0), Long.valueOf(1), Long.valueOf(1), + Long.valueOf(10), Long.valueOf(10), + MessageData.BUGLE_STATUS_INCOMING_COMPLETE, "Alice", null, + "0", "text/plain", "''", -1, -1, "msg0", 1}, + new Object[] { Long.valueOf(1), Long.valueOf(1), Long.valueOf(2), + Long.valueOf(20), Long.valueOf(20), + MessageData.BUGLE_STATUS_OUTGOING_COMPLETE, "Bob", null, + "1", "text/plain", "''", -1, -1, "msg1", 1}, + new Object[] { Long.valueOf(2), Long.valueOf(1), Long.valueOf(1), + Long.valueOf(30), Long.valueOf(30), + MessageData.BUGLE_STATUS_OUTGOING_COMPLETE, "Alice", null, + "2", "contentType3", "'content://fakeUri3'", "0", "0", "msg1", 1}, + new Object[] { Long.valueOf(3), Long.valueOf(1), Long.valueOf(1), + Long.valueOf(40), Long.valueOf(40), + MessageData.BUGLE_STATUS_OUTGOING_COMPLETE, "Alice", null, + "3|4", "'contentType4'|'text/plain'", "'content://fakeUri4'|''", "0|-1", "0|-1", "''|'msg3'", 2}, + }; + return new FakeCursor( + ConversationMessageData.getProjection(), + sConversationMessageCursorColumns, + cursorData); + } + + public static String getMessageText(final FakeCursor messageCursor, final int row) { + final String allPartsText = messageCursor.getAt(ConversationMessageViewColumns.PARTS_TEXTS, row) + .toString(); + final int partsCount = (Integer) messageCursor.getAt( + ConversationMessageViewColumns.PARTS_COUNT, row); + final String messageId = messageCursor.getAt( + ConversationMessageViewColumns._ID, row).toString(); + final List 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 getTestDraftAttachments() { + final MessagePartData[] retParts = new MessagePartData[] { + new MessagePartData(ContentType.IMAGE_JPEG, Uri.parse("content://image"), + 100, 100), + new MessagePartData(ContentType.VIDEO_3GPP, Uri.parse("content://video"), + 100, 100), + new MessagePartData(ContentType.TEXT_VCARD, Uri.parse("content://vcard"), + 0, 0), + new MessagePartData(ContentType.AUDIO_3GPP, Uri.parse("content://audio"), + 0, 0) + }; + return Arrays.asList(retParts); + } +} diff --git a/tests/src/com/android/messaging/datamodel/media/FakeImageRequest.java b/tests/src/com/android/messaging/datamodel/media/FakeImageRequest.java new file mode 100644 index 0000000..7f6ac3f --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/media/FakeImageRequest.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.messaging.datamodel.media; + +import android.text.TextUtils; + +import java.util.List; + +public class FakeImageRequest implements MediaRequest { + 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> 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 getMediaCache() { + return (MediaCache) MediaCacheManager.get().getOrCreateMediaCacheById( + getCacheId()); + } + + @Override + public int getRequestType() { + return MediaRequest.REQUEST_LOAD_MEDIA; + } + + @Override + public MediaRequestDescriptor getDescriptor() { + return null; + } +} diff --git a/tests/src/com/android/messaging/datamodel/media/FakeImageResource.java b/tests/src/com/android/messaging/datamodel/media/FakeImageResource.java new file mode 100644 index 0000000..969854f --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/media/FakeImageResource.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.messaging.datamodel.media; + +public class FakeImageResource extends RefCountedMediaResource { + private boolean mClosed = false; + private boolean mCached = false; + private final int mSize; + private final String mImageId; + + public FakeImageResource(final int size, final String imageId) { + super(null); + mSize = size; + mImageId = imageId; + } + + public boolean isClosed() { + return mClosed; + } + + public String getImageId() { + return mImageId; + } + + public void setCached(final boolean cached) { + mCached = cached; + } + + public boolean getCached() { + return mCached; + } + + @Override + public int getMediaSize() { + return mSize; + } + + @Override + protected void close() { + mClosed = true; + } +} diff --git a/tests/src/com/android/messaging/datamodel/media/FakeMediaCacheManager.java b/tests/src/com/android/messaging/datamodel/media/FakeMediaCacheManager.java new file mode 100644 index 0000000..34b3a55 --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/media/FakeMediaCacheManager.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.messaging.datamodel.media; + +public class FakeMediaCacheManager extends MediaCacheManager { + // List of available fake cache ids. + public static final int FAKE_IMAGE_CACHE = 1; + public static final int FAKE_BATCH_IMAGE_CACHE = 2; + + @Override + public MediaCache createMediaCacheById(final int id) { + switch (id) { + case FAKE_IMAGE_CACHE: + // Make a cache of only 3 KB of data. + return new MediaCache(3, FAKE_IMAGE_CACHE, "FakeImageCache"); + + case FAKE_BATCH_IMAGE_CACHE: + return new MediaCache(10, FAKE_BATCH_IMAGE_CACHE, + "FakeBatchImageCache"); + } + return null; + } +} diff --git a/tests/src/com/android/messaging/datamodel/media/ImageRequestTest.java b/tests/src/com/android/messaging/datamodel/media/ImageRequestTest.java new file mode 100644 index 0000000..2cfec7d --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/media/ImageRequestTest.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.messaging.datamodel.media; + +import android.content.ContentResolver; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.messaging.BugleTestCase; +import com.android.messaging.FakeFactory; +import com.android.messaging.R; +import com.android.messaging.datamodel.MemoryCacheManager; +import com.android.messaging.util.ImageUtils; + +import org.mockito.ArgumentCaptor; +import org.mockito.Matchers; +import org.mockito.Mockito; +import org.mockito.Spy; + +import java.io.IOException; + +@SmallTest +public class ImageRequestTest extends BugleTestCase { + private static final int DOWNSAMPLE_IMAGE_SIZE = 2; + + @Spy protected ImageUtils spyImageUtils; + + @Override + protected void setUp() throws Exception { + super.setUp(); + FakeFactory.register(getTestContext()) + .withMemoryCacheManager(new MemoryCacheManager()) + .withMediaCacheManager(new BugleMediaCacheManager()); + spyImageUtils = Mockito.spy(new ImageUtils()); + ImageUtils.set(spyImageUtils); + } + + public void testLoadImageUnspecifiedSize() { + final String uriString = ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + + getContext().getPackageName() + "/" + R.drawable.ic_audio_light; + final Uri uri = Uri.parse(uriString); + final UriImageRequest imageRequest = new UriImageRequest(getContext(), + new UriImageRequestDescriptor(uri)); + try { + final ImageResource imageResource = imageRequest.loadMediaBlocking(null); + final ArgumentCaptor 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 options = + ArgumentCaptor.forClass(BitmapFactory.Options.class); + Mockito.verify(spyImageUtils).calculateInSampleSize( + options.capture(), + Matchers.eq(DOWNSAMPLE_IMAGE_SIZE), Matchers.eq(DOWNSAMPLE_IMAGE_SIZE)); + assertNotSame(1, options.getValue().inSampleSize); + assertNotNull(imageResource); + assertNotNull(imageResource.getBitmap()); + + // Make sure there's down sampling on the bitmap. + final int bitmapWidth = imageResource.getBitmap().getWidth(); + final int bitmapHeight = imageResource.getBitmap().getHeight(); + assertTrue(bitmapWidth >= DOWNSAMPLE_IMAGE_SIZE && + bitmapHeight >= DOWNSAMPLE_IMAGE_SIZE && + (bitmapWidth <= DOWNSAMPLE_IMAGE_SIZE * 4 || + bitmapHeight <= DOWNSAMPLE_IMAGE_SIZE * 4)); + } catch (final IOException e) { + fail("IO exception while trying to load image resource"); + } + } +} diff --git a/tests/src/com/android/messaging/datamodel/media/MediaResourceManagerTest.java b/tests/src/com/android/messaging/datamodel/media/MediaResourceManagerTest.java new file mode 100644 index 0000000..d214067 --- /dev/null +++ b/tests/src/com/android/messaging/datamodel/media/MediaResourceManagerTest.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.messaging.datamodel.media; + +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.messaging.BugleTestCase; +import com.android.messaging.FakeFactory; +import com.android.messaging.datamodel.MemoryCacheManager; +import com.android.messaging.datamodel.media.MediaResourceManager.MediaResourceLoadListener; + +import java.util.concurrent.CountDownLatch; + +@SmallTest +public class MediaResourceManagerTest extends BugleTestCase { + private static final int KB = 1024; + + // Loaded image resource from the MediaResourceManager callback. + private FakeImageResource mImageResource; + private BindableMediaRequest 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 createAssertListener( + final boolean shouldBeCached, final boolean shouldFail, final CountDownLatch signal) { + return new MediaResourceLoadListener() { + @Override + public void onMediaResourceLoaded(final MediaRequest 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 request, final Exception exception) { + assertTrue(shouldFail); + mImageResource = null; + signal.countDown(); + }}; + } +} diff --git a/tests/src/com/android/messaging/ui/ActivityInstrumentationTestCaseIntent.java b/tests/src/com/android/messaging/ui/ActivityInstrumentationTestCaseIntent.java new file mode 100644 index 0000000..5ea6aa7 --- /dev/null +++ b/tests/src/com/android/messaging/ui/ActivityInstrumentationTestCaseIntent.java @@ -0,0 +1,37 @@ +/* + * 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.ui; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; + + +/** + * Purpose of this class is providing a workaround for https://b/14561718 + */ +public class ActivityInstrumentationTestCaseIntent extends Intent { + public ActivityInstrumentationTestCaseIntent(Context packageContext, Class cls) { + super(packageContext, cls); + } + @Override + public Intent setComponent(ComponentName component) { + // Ignore the ComponentName set, as the one ActivityUnitTest does is wrong (and actually + // unnecessary). + return this; + } +} \ No newline at end of file diff --git a/tests/src/com/android/messaging/ui/BugleActivityInstrumentationTestCase.java b/tests/src/com/android/messaging/ui/BugleActivityInstrumentationTestCase.java new file mode 100644 index 0000000..60cddff --- /dev/null +++ b/tests/src/com/android/messaging/ui/BugleActivityInstrumentationTestCase.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.ui; + +import android.app.Activity; + +import com.android.messaging.BugleTestCase; +import com.android.messaging.TestUtil; + +/** + * Helper class that extends ActivityInstrumentationTestCase2 to provide some extra common + * initialization (eg. Mockito boilerplate). + */ +public class BugleActivityInstrumentationTestCase + extends android.test.ActivityInstrumentationTestCase2 { + + static { + // Set flag during loading of test cases to prevent application initialization starting + BugleTestCase.setTestsRunning(); + } + + public BugleActivityInstrumentationTestCase(final Class activityClass) { + super(activityClass); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + setActivityInitialTouchMode(false); + TestUtil.testSetup(getInstrumentation().getTargetContext(), this); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + TestUtil.testTeardown(this); + } +} \ No newline at end of file diff --git a/tests/src/com/android/messaging/ui/BugleActivityTest.java b/tests/src/com/android/messaging/ui/BugleActivityTest.java new file mode 100644 index 0000000..05e32fa --- /dev/null +++ b/tests/src/com/android/messaging/ui/BugleActivityTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.ui; + +import com.android.messaging.FakeFactory; +import com.android.messaging.datamodel.DataModel; + +import org.mockito.Mock; +import org.mockito.Mockito; + +public abstract class BugleActivityTest extends BugleActivityUnitTestCase { + @Mock protected DataModel mDataModel; + + public BugleActivityTest() { + super(BugleActionBarActivity.class); + } + + @SuppressWarnings("unchecked") + @Override + protected void setUp() throws Exception { + super.setUp(); + + // Create activity + final ActivityInstrumentationTestCaseIntent intent = + new ActivityInstrumentationTestCaseIntent(getInstrumentation().getTargetContext(), + TestActivity.class); + startActivity(intent, null, null); + + FakeFactory.register(getInstrumentation().getTargetContext()) + .withDataModel(mDataModel); + } + + public void testOnResumeDataModelCallback() { + getInstrumentation().callActivityOnStart(getActivity()); + getInstrumentation().callActivityOnResume(getActivity()); + Mockito.verify(mDataModel).onActivityResume(); + } +} diff --git a/tests/src/com/android/messaging/ui/BugleActivityUnitTestCase.java b/tests/src/com/android/messaging/ui/BugleActivityUnitTestCase.java new file mode 100644 index 0000000..dcbd785 --- /dev/null +++ b/tests/src/com/android/messaging/ui/BugleActivityUnitTestCase.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.messaging.ui; + +import android.app.Activity; +import android.view.ContextThemeWrapper; + +import com.android.messaging.BugleTestCase; +import com.android.messaging.R; +import com.android.messaging.TestUtil; + +/** + * Base class for activity unit test cases, provides boilerplate setup/teardown. + */ +public abstract class BugleActivityUnitTestCase extends + android.test.ActivityUnitTestCase { + + static { + // Set flag during loading of test cases to prevent application initialization starting + BugleTestCase.setTestsRunning(); + } + + public BugleActivityUnitTestCase(final Class activityClass) { + super(activityClass); + } + + @SuppressWarnings("unchecked") + @Override + protected void setUp() throws Exception { + super.setUp(); + TestUtil.testSetup(getInstrumentation().getTargetContext(), this); + + setActivityContext(new ContextThemeWrapper(getInstrumentation().getTargetContext(), + R.style.Theme_AppCompat_Light_DarkActionBar)); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + TestUtil.testTeardown(this); + } +} diff --git a/tests/src/com/android/messaging/ui/CustomHeaderViewPagerTest.java b/tests/src/com/android/messaging/ui/CustomHeaderViewPagerTest.java new file mode 100644 index 0000000..011cb82 --- /dev/null +++ b/tests/src/com/android/messaging/ui/CustomHeaderViewPagerTest.java @@ -0,0 +1,61 @@ +/* + * 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.ui; + +import android.support.v4.view.ViewPager; +import android.view.View; +import android.view.ViewGroup; +import android.widget.SimpleCursorAdapter; + +import com.android.messaging.FakeFactory; +import com.android.messaging.R; + +public class CustomHeaderViewPagerTest extends ViewTest { + + @Override + protected void setUp() throws Exception { + super.setUp(); + FakeFactory.register(getInstrumentation().getTargetContext()); + } + + public void testBindFirstLevel() { + final CustomHeaderViewPager view = new CustomHeaderViewPager(getActivity(), null); + final SimpleCursorAdapter adapter = + new SimpleCursorAdapter(getActivity(), 0, null, null, null, 0); + final CustomHeaderPagerViewHolder[] viewHolders = { + new FakeListViewHolder(getActivity(), adapter), + new FakeListViewHolder(getActivity(), adapter) + }; + + view.setViewHolders(viewHolders); + final ViewPager pager = (ViewPager) view.findViewById(R.id.pager); + final ViewGroup tabStrip = (ViewGroup) view.findViewById(R.id.tab_strip); + final ViewPagerTabStrip realTab = (ViewPagerTabStrip) tabStrip.getChildAt(0); + + assertEquals(2, realTab.getChildCount()); + View headerTitleButton = realTab.getChildAt(1); + // Click on the first page. Now the view pager should switch to that page accordingly. + clickButton(headerTitleButton); + assertEquals(1, pager.getCurrentItem()); + } + + @Override + protected int getLayoutIdForView() { + // All set up should be done by creating a CustomHeaderViewPager which handles inflating + // the layout + return 0; + } +} diff --git a/tests/src/com/android/messaging/ui/FakeListViewHolder.java b/tests/src/com/android/messaging/ui/FakeListViewHolder.java new file mode 100644 index 0000000..d4de885 --- /dev/null +++ b/tests/src/com/android/messaging/ui/FakeListViewHolder.java @@ -0,0 +1,60 @@ +/* + * 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.ui; + +import android.content.Context; +import android.widget.CursorAdapter; + +import com.android.messaging.R; + +/** + * A fake {@link CustomHeaderPagerListViewHolder} for CustomHeaderViewPager tests only. + */ +public class FakeListViewHolder extends CustomHeaderPagerListViewHolder { + public FakeListViewHolder(final Context context, final CursorAdapter adapter) { + super(context, adapter); + } + + @Override + protected int getLayoutResId() { + return 0; + } + + @Override + protected int getPageTitleResId() { + return android.R.string.untitled; + } + + @Override + protected int getEmptyViewResId() { + return R.id.empty_view; + } + + @Override + protected int getListViewResId() { + return android.R.id.list; + } + + @Override + protected int getEmptyViewTitleResId() { + return R.string.contact_list_empty_text; + } + + @Override + protected int getEmptyViewImageResId() { + return R.drawable.ic_oobe_freq_list; + } +} \ No newline at end of file diff --git a/tests/src/com/android/messaging/ui/FragmentTestCase.java b/tests/src/com/android/messaging/ui/FragmentTestCase.java new file mode 100644 index 0000000..eb65dc6 --- /dev/null +++ b/tests/src/com/android/messaging/ui/FragmentTestCase.java @@ -0,0 +1,120 @@ +/* + * 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.ui; + +import android.app.Fragment; +import android.app.FragmentManager; +import android.content.res.Configuration; +import android.view.View; + +/** + * Helper class that extends Bugle.ui.ActivityInstrumentationTestCase to provide common behavior + * across fragment tests. + */ +public abstract class FragmentTestCase + extends BugleActivityInstrumentationTestCase { + + protected T mFragment; + protected Class mFragmentClass; + + public FragmentTestCase(final Class fragmentClass) { + super(TestActivity.class); + mFragmentClass = fragmentClass; + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + protected T getFragment() { + // Fragment creation deferred (typically until test time) so that factory/appcontext is + // ready. + if (mFragment == null) { + try { + mFragment = mFragmentClass.newInstance(); + } catch (final InstantiationException e) { + throw new IllegalStateException("Failed to instantiate fragment"); + } catch (final IllegalAccessException e) { + throw new IllegalStateException("Failed to instantiate fragment"); + } + } + + return mFragment; + } + + protected void attachFragment() { + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + final FragmentManager fragmentManager = getActivity().getFragmentManager(); + fragmentManager.beginTransaction().add(mFragment, null /* tag */).commit(); + } + }); + + getInstrumentation().waitForIdleSync(); + } + + @Override + protected void tearDown() throws Exception { + // In landscape mode, sleep for a second first. + // The reason is: our UI tests don't wait for the UI thread to finish settling down + // before exiting (because they can't know when the UI thread is done). In portrait mode, + // things generally work fine here -- the UI thread is done by the time the test is done. + // In landscape mode, though, since the launcher is in portrait mode, there is a lot of + // extra work that happens in our UI when the app launches into landscape mode, and the + // UI is often not done by the time the test finishes running. So then our teardown + // nulls out the Factory, and then the UI keeps running and derefs the null factory, + // and things blow up. + // So ... as a cheap hack, sleep for one second before finishing the teardown of UI + // tests, but only do it in landscape mode (so that developers running it in portrait + // mode can still run the tests faster). + if (this.getInstrumentation().getTargetContext().getResources(). + getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + super.tearDown(); + } + + protected void clickButton(final View view) { + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + view.performClick(); + } + }); + getInstrumentation().waitForIdleSync(); + } + + protected void setFocus(final View view, final boolean focused) { + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + if (focused) { + view.requestFocus(); + } else { + view.clearFocus(); + } + } + }); + getInstrumentation().waitForIdleSync(); + } +} \ No newline at end of file diff --git a/tests/src/com/android/messaging/ui/MultiAttachmentLayoutTest.java b/tests/src/com/android/messaging/ui/MultiAttachmentLayoutTest.java new file mode 100644 index 0000000..cf9a647 --- /dev/null +++ b/tests/src/com/android/messaging/ui/MultiAttachmentLayoutTest.java @@ -0,0 +1,122 @@ +/* + * 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.ui; + + +import android.content.Context; +import android.net.Uri; +import android.test.suitebuilder.annotation.MediumTest; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.android.messaging.FakeFactory; +import com.android.messaging.datamodel.data.MessagePartData; + +import java.util.Arrays; +import java.util.Collections; + +@MediumTest +public class MultiAttachmentLayoutTest extends ViewTest { + @Override + protected void setUp() throws Exception { + super.setUp(); + final Context context = getInstrumentation().getTargetContext(); + FakeFactory.register(context); + } + + @Override + protected MultiAttachmentLayout getView() { + if (mView == null) { + // View creation deferred (typically until test time) so that factory/appcontext is + // ready. + mView = new MultiAttachmentLayout(getActivity(), null); + mView.setLayoutParams(new ViewGroup.LayoutParams(100, 100)); + } + return mView; + } + + protected void verifyContent( + final MultiAttachmentLayout view, + final int imageCount, + final int plusCount) { + final int count = view.getChildCount(); + int actualImageCount = 0; + final boolean needPlusText = plusCount > 0; + boolean hasPlusText = false; + for (int i = 0; i < count; i++) { + final View child = view.getChildAt(i); + if (child instanceof AsyncImageView) { + actualImageCount++; + } else if (child instanceof TextView) { + assertTrue(plusCount > 0); + assertTrue(((TextView) child).getText().toString().contains("" + plusCount)); + hasPlusText = true; + } else { + // Nothing other than image and overflow text view should appear in this layout. + fail("unexpected view in layout. view = " + child); + } + } + assertEquals(imageCount, actualImageCount); + assertEquals(needPlusText, hasPlusText); + } + + public void testBindTwoAttachments() { + final MultiAttachmentLayout view = getView(); + final MessagePartData testAttachment1 = MessagePartData.createMediaMessagePart( + "image/jpeg", Uri.parse("content://uri1"), 100, 100); + final MessagePartData testAttachment2 = MessagePartData.createMediaMessagePart( + "image/jpeg", Uri.parse("content://uri2"), 100, 100); + + view.bindAttachments(createAttachmentList(testAttachment1, testAttachment2), + null /* transitionRect */, 2); + verifyContent(view, 2, 0); + } + + public void testBindFiveAttachments() { + final MultiAttachmentLayout view = getView(); + final MessagePartData testAttachment1 = MessagePartData.createMediaMessagePart( + "image/jpeg", Uri.parse("content://uri1"), 100, 100); + final MessagePartData testAttachment2 = MessagePartData.createMediaMessagePart( + "image/jpeg", Uri.parse("content://uri2"), 100, 100); + final MessagePartData testAttachment3 = MessagePartData.createMediaMessagePart( + "image/jpeg", Uri.parse("content://uri3"), 100, 100); + final MessagePartData testAttachment4 = MessagePartData.createMediaMessagePart( + "image/jpeg", Uri.parse("content://uri4"), 100, 100); + final MessagePartData testAttachment5 = MessagePartData.createMediaMessagePart( + "image/jpeg", Uri.parse("content://uri5"), 100, 100); + + view.bindAttachments(createAttachmentList(testAttachment1, testAttachment2, testAttachment3, + testAttachment4, testAttachment5), null /* transitionRect */, 5); + verifyContent(view, 4, 1); + } + + public void testBindTwice() { + // Put the above two tests together so we can simulate binding twice. + testBindTwoAttachments(); + testBindFiveAttachments(); + } + + private Iterable createAttachmentList(final MessagePartData... attachments) { + return Collections.unmodifiableList(Arrays.asList(attachments)); + } + + @Override + protected int getLayoutIdForView() { + return 0; // We construct the view with getView(). + } +} \ No newline at end of file diff --git a/tests/src/com/android/messaging/ui/ViewTest.java b/tests/src/com/android/messaging/ui/ViewTest.java new file mode 100644 index 0000000..c4e8431 --- /dev/null +++ b/tests/src/com/android/messaging/ui/ViewTest.java @@ -0,0 +1,66 @@ +/* + * 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.ui; + +import android.view.View; + + +/** + * Base class for view tests. Derived class just has to provide a layout id. Tests can then just + * call getView() to get a created view and test its behavior. + */ +public abstract class ViewTest extends BugleActivityUnitTestCase { + public ViewTest() { + super(TestActivity.class); + } + + protected T mView; + + @SuppressWarnings("unchecked") + @Override + protected void setUp() throws Exception { + super.setUp(); + + // Create activity + final ActivityInstrumentationTestCaseIntent intent = + new ActivityInstrumentationTestCaseIntent(getInstrumentation().getTargetContext(), + TestActivity.class); + startActivity(intent, null, null); + } + + @SuppressWarnings("unchecked") + protected T getView() { + if (mView == null) { + // View creation deferred (typically until test time) so that factory/appcontext is + // ready. + mView = (T) getActivity().getLayoutInflater().inflate(getLayoutIdForView(), null); + } + return mView; + } + + protected void clickButton(final View view) { + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + view.performClick(); + } + }); + getInstrumentation().waitForIdleSync(); + } + + protected abstract int getLayoutIdForView(); +} diff --git a/tests/src/com/android/messaging/ui/attachmentchooser/AttachmentChooserFragmentTest.java b/tests/src/com/android/messaging/ui/attachmentchooser/AttachmentChooserFragmentTest.java new file mode 100644 index 0000000..30c711b --- /dev/null +++ b/tests/src/com/android/messaging/ui/attachmentchooser/AttachmentChooserFragmentTest.java @@ -0,0 +1,172 @@ +/* + * 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.ui.attachmentchooser; + +import android.app.Fragment; +import android.test.suitebuilder.annotation.LargeTest; +import android.widget.CheckBox; + +import com.android.messaging.FakeFactory; +import com.android.messaging.R; +import com.android.messaging.datamodel.DataModel; +import com.android.messaging.datamodel.data.DraftMessageData; +import com.android.messaging.datamodel.data.MessageData; +import com.android.messaging.datamodel.data.MessagePartData; +import com.android.messaging.datamodel.data.TestDataFactory; +import com.android.messaging.ui.FragmentTestCase; +import com.android.messaging.ui.TestActivity; +import com.android.messaging.ui.TestActivity.FragmentEventListener; +import com.android.messaging.ui.attachmentchooser.AttachmentChooserFragment; +import com.android.messaging.ui.attachmentchooser.AttachmentGridItemView; +import com.android.messaging.ui.attachmentchooser.AttachmentGridView; +import com.android.messaging.ui.attachmentchooser.AttachmentChooserFragment.AttachmentChooserFragmentHost; +import com.android.messaging.ui.conversationlist.ConversationListFragment; + +import org.mockito.ArgumentMatcher; +import org.mockito.Matchers; +import org.mockito.Mock; +import org.mockito.Mockito; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + + +/** + * Unit tests for {@link ConversationListFragment}. + */ +@LargeTest +public class AttachmentChooserFragmentTest extends FragmentTestCase { + + @Mock protected DataModel mockDataModel; + @Mock protected DraftMessageData mockDraftMessageData; + @Mock protected AttachmentChooserFragmentHost mockHost; + + private static final String CONVERSATION_ID = "cid"; + + /** A custom argument matcher that checks whether the set argument passed in is a set + * with identical attachment data as the given set. + */ + private class IsSetOfGivenAttachments extends ArgumentMatcher> { + private final Set mGivenParts; + public IsSetOfGivenAttachments(final Set givenParts) { + mGivenParts = givenParts; + } + + @Override + public boolean matches(final Object set) { + @SuppressWarnings("unchecked") + final Set actualSet = (Set) set; + if (actualSet.size() != mGivenParts.size()) { + return false; + } + return mGivenParts.containsAll(actualSet) && actualSet.containsAll(mGivenParts); + } + } + + public AttachmentChooserFragmentTest() { + super(AttachmentChooserFragment.class); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + FakeFactory.register(this.getInstrumentation().getTargetContext()) + .withDataModel(mockDataModel); + } + + private void loadWith(final List attachments) { + Mockito.when(mockDraftMessageData.isBound(Matchers.anyString())) + .thenReturn(true); + Mockito.doReturn(mockDraftMessageData) + .when(mockDataModel) + .createDraftMessageData(Mockito.anyString()); + Mockito.doReturn(attachments) + .when(mockDraftMessageData) + .getReadOnlyAttachments(); + Mockito.when(mockDataModel.createDraftMessageData( + Matchers.anyString())) + .thenReturn(mockDraftMessageData); + + // Create fragment synchronously to avoid need for volatile, synchronization etc. + final AttachmentChooserFragment fragment = getFragment(); + // Binding to model happens when attaching fragment to activity, so hook into test + // activity to do so. + getActivity().setFragmentEventListener(new FragmentEventListener() { + @Override + public void onAttachFragment(final Fragment attachedFragment) { + if (fragment == attachedFragment) { + fragment.setConversationId(CONVERSATION_ID); + } + } + }); + + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + fragment.setHost(mockHost); + getActivity().setFragment(fragment); + Mockito.verify(mockDataModel).createDraftMessageData( + Mockito.matches(CONVERSATION_ID)); + Mockito.verify(mockDraftMessageData).loadFromStorage( + Matchers.eq(fragment.mBinding), Matchers.eq((MessageData) null), + Matchers.eq(false)); + } + }); + // Now load the cursor + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + fragment.onDraftChanged(mockDraftMessageData, DraftMessageData.ALL_CHANGED); + } + }); + getInstrumentation().waitForIdleSync(); + } + + public void testUnselect() { + final List attachments = TestDataFactory.getTestDraftAttachments(); + loadWith(attachments); + final AttachmentGridView attachmentGridView = (AttachmentGridView) + getFragment().getView().findViewById(R.id.grid); + assertEquals("bad view count", attachments.size(), + attachmentGridView.getAdapter().getCount()); + + final AttachmentGridItemView itemView = (AttachmentGridItemView) + attachmentGridView.getChildAt(0); + assertEquals(attachmentGridView, itemView.testGetHostInterface()); + final CheckBox checkBox = (CheckBox) itemView.findViewById(R.id.checkbox); + assertEquals(true, checkBox.isChecked()); + assertEquals(true, attachmentGridView.isItemSelected(itemView.mAttachmentData)); + clickButton(checkBox); + assertEquals(false, checkBox.isChecked()); + assertEquals(false, attachmentGridView.isItemSelected(itemView.mAttachmentData)); + + final AttachmentGridItemView itemView2 = (AttachmentGridItemView) + attachmentGridView.getChildAt(1); + final CheckBox checkBox2 = (CheckBox) itemView2.findViewById(R.id.checkbox); + clickButton(checkBox2); + + getFragment().confirmSelection(); + final MessagePartData[] attachmentsToRemove = new MessagePartData[] { + itemView.mAttachmentData, itemView2.mAttachmentData }; + Mockito.verify(mockDraftMessageData).removeExistingAttachments(Matchers.argThat( + new IsSetOfGivenAttachments(new HashSet<>(Arrays.asList(attachmentsToRemove))))); + Mockito.verify(mockDraftMessageData).saveToStorage(Matchers.eq(getFragment().mBinding)); + Mockito.verify(mockHost).onConfirmSelection(); + } +} diff --git a/tests/src/com/android/messaging/ui/contact/ContactListItemViewTest.java b/tests/src/com/android/messaging/ui/contact/ContactListItemViewTest.java new file mode 100644 index 0000000..de4c583 --- /dev/null +++ b/tests/src/com/android/messaging/ui/contact/ContactListItemViewTest.java @@ -0,0 +1,127 @@ +/* + * 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.ui.contact; + +import android.content.Context; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.provider.ContactsContract.Contacts; +import android.view.View; +import android.widget.TextView; + +import com.android.messaging.FakeFactory; +import com.android.messaging.R; +import com.android.messaging.datamodel.FakeCursor; +import com.android.messaging.datamodel.FakeDataModel; +import com.android.messaging.datamodel.data.ContactListItemData; +import com.android.messaging.datamodel.data.TestDataFactory; +import com.android.messaging.ui.ContactIconView; +import com.android.messaging.ui.ViewTest; + +import org.mockito.Mock; +import org.mockito.Mockito; + +public class ContactListItemViewTest extends ViewTest { + + @Mock ContactListItemView.HostInterface mockHost; + + @Override + protected void setUp() throws Exception { + super.setUp(); + final Context context = getInstrumentation().getTargetContext(); + FakeFactory.register(context) + .withDataModel(new FakeDataModel(context)); + } + + protected void verifyAddedContactForData(final ContactListItemData data, + final ContactListItemView view) { + Mockito.verify(mockHost).onContactListItemClicked(data, view); + } + + protected void verifyContent( + final ContactListItemView view, + final String contactName, + final String contactDetail, + final String avatarUrl, + final boolean showAvatar) { + final TextView contactNameView = (TextView) view.findViewById(R.id.contact_name); + final TextView contactDetailView = (TextView) view.findViewById(R.id.contact_details); + final ContactIconView avatarView = (ContactIconView) view.findViewById(R.id.contact_icon); + + assertNotNull(contactNameView); + assertEquals(contactName, contactNameView.getText()); + assertNotNull(contactDetail); + assertEquals(contactDetail, contactDetailView.getText()); + assertNotNull(avatarView); + if (showAvatar) { + assertTrue(avatarView.mImageRequestBinding.isBound()); + assertEquals(View.VISIBLE, avatarView.getVisibility()); + } else { + assertFalse(avatarView.mImageRequestBinding.isBound()); + assertEquals(View.INVISIBLE, avatarView.getVisibility()); + } + } + + public void testBindFirstLevel() { + final ContactListItemView view = getView(); + final FakeCursor cursor = TestDataFactory.getAllContactListCursor(); + final int row = TestDataFactory.CONTACT_LIST_CURSOR_FIRST_LEVEL_CONTACT_INDEX; + cursor.moveToPosition(row); + view.bind(cursor, mockHost, false, null); + verifyContent(view, (String) cursor.getAt(Contacts.DISPLAY_NAME, row), + (String) cursor.getAt(Phone.NUMBER, row), + (String) cursor.getAt(Contacts.PHOTO_THUMBNAIL_URI, row), true); + } + + public void testBindSecondLevel() { + final ContactListItemView view = getView(); + final FakeCursor cursor = TestDataFactory.getAllContactListCursor(); + final int row = TestDataFactory.CONTACT_LIST_CURSOR_SECOND_LEVEL_CONTACT_INDEX; + cursor.moveToPosition(row); + view.bind(cursor, mockHost, false, null); + verifyContent(view, (String) cursor.getAt(Contacts.DISPLAY_NAME, row), + (String) cursor.getAt(Phone.NUMBER, row), + (String) cursor.getAt(Contacts.PHOTO_THUMBNAIL_URI, row), false); + } + + public void testClickAddedContact() { + final ContactListItemView view = getView(); + final FakeCursor cursor = TestDataFactory.getAllContactListCursor(); + cursor.moveToFirst(); + + view.bind(cursor, mockHost, false, null); + view.performClick(); + verifyAddedContactForData(view.mData, view); + } + + public void testBindTwice() { + final ContactListItemView view = getView(); + final FakeCursor cursor = TestDataFactory.getAllContactListCursor(); + + cursor.moveToFirst(); + view.bind(cursor, mockHost, false, null); + + cursor.moveToNext(); + view.bind(cursor, mockHost, false, null); + verifyContent(view, (String) cursor.getAt(Contacts.DISPLAY_NAME, 1), + (String) cursor.getAt(Phone.NUMBER, 1), + (String) cursor.getAt(Contacts.PHOTO_THUMBNAIL_URI, 1), true); + } + + @Override + protected int getLayoutIdForView() { + return R.layout.contact_list_item_view; + } +} diff --git a/tests/src/com/android/messaging/ui/contact/ContactPickerFragmentTest.java b/tests/src/com/android/messaging/ui/contact/ContactPickerFragmentTest.java new file mode 100644 index 0000000..5b1503b --- /dev/null +++ b/tests/src/com/android/messaging/ui/contact/ContactPickerFragmentTest.java @@ -0,0 +1,221 @@ +/* + * 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.ui.contact; + +import android.content.Context; +import android.database.Cursor; +import android.support.v4.view.ViewPager; +import android.test.suitebuilder.annotation.LargeTest; +import android.view.View; +import android.widget.ListView; + +import com.android.messaging.FakeFactory; +import com.android.messaging.R; +import com.android.messaging.datamodel.FakeDataModel; +import com.android.messaging.datamodel.action.ActionTestHelpers; +import com.android.messaging.datamodel.action.ActionTestHelpers.StubActionService; +import com.android.messaging.datamodel.action.ActionTestHelpers.StubActionService.StubActionServiceCallLog; +import com.android.messaging.datamodel.action.GetOrCreateConversationAction; +import com.android.messaging.datamodel.data.ContactPickerData; +import com.android.messaging.datamodel.data.ParticipantData; +import com.android.messaging.datamodel.data.TestDataFactory; +import com.android.messaging.ui.CustomHeaderViewPagerAdapter; +import com.android.messaging.ui.FragmentTestCase; +import com.android.messaging.ui.UIIntents; +import com.android.messaging.ui.contact.ContactPickerFragment.ContactPickerFragmentHost; + +import org.mockito.Matchers; +import org.mockito.Mock; +import org.mockito.Mockito; + +import java.util.ArrayList; +import java.util.List; + + +/** + * Unit tests for {@link ContactPickerFragment}. + */ +@LargeTest +public class ContactPickerFragmentTest + extends FragmentTestCase { + + @Mock protected ContactPickerData mMockContactPickerData; + @Mock protected UIIntents mMockUIIntents; + @Mock protected ContactPickerFragmentHost mockHost; + protected FakeDataModel mFakeDataModel; + private ActionTestHelpers.StubActionService mService; + + public ContactPickerFragmentTest() { + super(ContactPickerFragment.class); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + final Context context = getInstrumentation().getTargetContext(); + mService = new StubActionService(); + mFakeDataModel = new FakeDataModel(context) + .withContactPickerData(mMockContactPickerData) + .withActionService(mService); + FakeFactory.register(context) + .withDataModel(mFakeDataModel) + .withUIIntents(mMockUIIntents); + } + + /** + * Helper method to initialize the ContactPickerFragment and its data. + */ + private ContactPickerFragmentTest initFragment(final int initialMode) { + Mockito.when(mMockContactPickerData.isBound(Matchers.anyString())) + .thenReturn(true); + + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + final ContactPickerFragment fragment = getFragment(); + fragment.setHost(mockHost); + fragment.setContactPickingMode(initialMode, false); + + getActivity().setFragment(fragment); + Mockito.verify(mMockContactPickerData).init(fragment.getLoaderManager(), + fragment.mBinding); + } + }); + getInstrumentation().waitForIdleSync(); + return this; + } + + /** + * Bind the datamodel with all contacts cursor to populate the all contacts list in the + * fragment. + */ + private ContactPickerFragmentTest loadWithAllContactsCursor(final Cursor cursor) { + Mockito.when(mMockContactPickerData.isBound(Matchers.anyString())) + .thenReturn(true); + + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + getFragment().onAllContactsCursorUpdated(cursor); + } + }); + getInstrumentation().waitForIdleSync(); + return this; + } + + /** + * Bind the datamodel with frequent contacts cursor to populate the contacts list in the + * fragment. + */ + private ContactPickerFragmentTest loadWithFrequentContactsCursor(final Cursor cursor) { + Mockito.when(mMockContactPickerData.isBound(Matchers.anyString())) + .thenReturn(true); + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + getFragment().onFrequentContactsCursorUpdated(cursor); + } + }); + getInstrumentation().waitForIdleSync(); + return this; + } + + /** + * Test the initial state of the fragment before loading data. + */ + public void testInitialState() { + initFragment(ContactPickerFragment.MODE_PICK_INITIAL_CONTACT); + + // Make sure that the frequent contacts view is shown by default. + final ViewPager pager = (ViewPager) getFragment().getView().findViewById(R.id.pager); + final View currentPagedView = pager.getChildAt(pager.getCurrentItem()); + final View frequentContactsView = ((CustomHeaderViewPagerAdapter) pager.getAdapter()) + .getViewHolder(0).getView(null); + assertEquals(frequentContactsView, currentPagedView); + } + + /** + * Verifies that list view gets correctly populated given a cursor. + */ + public void testLoadAllContactsList() { + final Cursor cursor = TestDataFactory.getAllContactListCursor(); + initFragment(ContactPickerFragment.MODE_PICK_INITIAL_CONTACT) + .loadWithAllContactsCursor(cursor); + final ListView listView = (ListView) getFragment().getView() + .findViewById(R.id.all_contacts_list); + assertEquals(cursor.getCount(), listView.getCount()); + } + + /** + * Verifies that list view gets correctly populated given a cursor. + */ + public void testLoadFrequentContactsList() { + final Cursor cursor = TestDataFactory.getFrequentContactListCursor(); + initFragment(ContactPickerFragment.MODE_PICK_INITIAL_CONTACT) + .loadWithFrequentContactsCursor(cursor); + final ListView listView = (ListView) getFragment().getView() + .findViewById(R.id.frequent_contacts_list); + assertEquals(cursor.getCount(), listView.getCount()); + } + + public void testPickInitialContact() { + final Cursor cursor = TestDataFactory.getFrequentContactListCursor(); + initFragment(ContactPickerFragment.MODE_PICK_INITIAL_CONTACT) + .loadWithFrequentContactsCursor(cursor); + final ListView listView = (ListView) getFragment().getView() + .findViewById(R.id.frequent_contacts_list); + // Click on the first contact to add it. + final ContactListItemView cliv = (ContactListItemView) listView.getChildAt(0); + clickButton(cliv); + final ContactRecipientAutoCompleteView chipsView = (ContactRecipientAutoCompleteView) + getFragment().getView() + .findViewById(R.id.recipient_text_view); + // Verify the contact is added to the chips view. + final List participants = + chipsView.getRecipientParticipantDataForConversationCreation(); + assertEquals(1, participants.size()); + assertEquals(cliv.mData.getDestination(), participants.get(0).getSendDestination()); + assertTrue(mService.getCalls().get(0).action instanceof GetOrCreateConversationAction); + } + + public void testLeaveChipsMode() { + final Cursor cursor = TestDataFactory.getFrequentContactListCursor(); + initFragment(ContactPickerFragment.MODE_CHIPS_ONLY) + .loadWithFrequentContactsCursor(cursor); + // Click on the add more participants button + // TODO: Figure out a way to click on the add more participants button now that + // it's part of the menu. + // final ImageButton AddMoreParticipantsButton = (ImageButton) getFragment().getView() + // .findViewById(R.id.add_more_participants_button); + // clickButton(AddMoreParticipantsButton); + // Mockito.verify(mockHost).onInitiateAddMoreParticipants(); + } + + public void testPickMoreContacts() { + final Cursor cursor = TestDataFactory.getFrequentContactListCursor(); + initFragment(ContactPickerFragment.MODE_PICK_MORE_CONTACTS) + .loadWithFrequentContactsCursor(cursor); + final ListView listView = (ListView) getFragment().getView() + .findViewById(R.id.frequent_contacts_list); + // Click on the first contact to add it. + final ContactListItemView cliv = (ContactListItemView) listView.getChildAt(0); + clickButton(cliv); + // Verify that we don't attempt to create a conversation right away. + assertEquals(0, mService.getCalls().size()); + } +} diff --git a/tests/src/com/android/messaging/ui/conversation/ComposeMessageViewTest.java b/tests/src/com/android/messaging/ui/conversation/ComposeMessageViewTest.java new file mode 100644 index 0000000..2dd2a89 --- /dev/null +++ b/tests/src/com/android/messaging/ui/conversation/ComposeMessageViewTest.java @@ -0,0 +1,184 @@ +/* + * 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.ui.conversation; + +import android.content.Context; +import android.media.MediaPlayer; +import android.test.suitebuilder.annotation.MediumTest; +import android.view.View; +import android.widget.EditText; + +import com.android.messaging.FakeFactory; +import com.android.messaging.R; +import com.android.messaging.datamodel.DataModel; +import com.android.messaging.datamodel.binding.Binding; +import com.android.messaging.datamodel.binding.BindingBase; +import com.android.messaging.datamodel.data.ConversationData; +import com.android.messaging.datamodel.data.DraftMessageData; +import com.android.messaging.datamodel.data.DraftMessageData.CheckDraftForSendTask; +import com.android.messaging.datamodel.data.DraftMessageData.CheckDraftTaskCallback; +import com.android.messaging.datamodel.data.MessageData; +import com.android.messaging.datamodel.data.MessagePartData; +import com.android.messaging.ui.ViewTest; +import com.android.messaging.ui.conversation.ComposeMessageView.IComposeMessageViewHost; +import com.android.messaging.util.BugleGservices; +import com.android.messaging.util.FakeMediaUtil; +import com.android.messaging.util.ImeUtil; + +import org.mockito.ArgumentMatcher; +import org.mockito.Matchers; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.util.ArrayList; +import java.util.Collections; + +@MediumTest +public class ComposeMessageViewTest extends ViewTest { + private Context mContext; + + @Mock protected DataModel mockDataModel; + @Mock protected DraftMessageData mockDraftMessageData; + @Mock protected BugleGservices mockBugleGservices; + @Mock protected ImeUtil mockImeUtil; + @Mock protected IComposeMessageViewHost mockIComposeMessageViewHost; + @Mock protected MediaPlayer mockMediaPlayer; + @Mock protected ConversationInputManager mockInputManager; + @Mock protected ConversationData mockConversationData; + + Binding mConversationBinding; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mContext = getInstrumentation().getTargetContext(); + FakeFactory.register(mContext) + .withDataModel(mockDataModel) + .withBugleGservices(mockBugleGservices) + .withMediaUtil(new FakeMediaUtil(mockMediaPlayer)); + + Mockito.doReturn(true).when(mockConversationData).isBound(Mockito.anyString()); + mConversationBinding = BindingBase.createBinding(this); + mConversationBinding.bind(mockConversationData); + } + + @Override + protected ComposeMessageView getView() { + final ComposeMessageView view = super.getView(); + view.setInputManager(mockInputManager); + view.setConversationDataModel(BindingBase.createBindingReference(mConversationBinding)); + return view; + } + + @Override + protected int getLayoutIdForView() { + return R.layout.compose_message_view; + } + + public void testSend() { + Mockito.when(mockDraftMessageData.getReadOnlyAttachments()) + .thenReturn(Collections.unmodifiableList(new ArrayList())); + Mockito.when(mockDraftMessageData.getIsDefaultSmsApp()).thenReturn(true); + Mockito.when(mockIComposeMessageViewHost.isReadyForAction()).thenReturn(true); + final ComposeMessageView view = getView(); + + final MessageData message = MessageData.createDraftSmsMessage("fake_id", "just_a_self_id", + "Sample Message"); + + Mockito.when(mockDraftMessageData.isBound(Matchers.anyString())) + .thenReturn(true); + Mockito.when(mockDraftMessageData.getMessageText()).thenReturn(message.getMessageText()); + Mockito.when(mockDraftMessageData.prepareMessageForSending( + Matchers.>any())) + .thenReturn(message); + Mockito.when(mockDraftMessageData.hasPendingAttachments()).thenReturn(false); + Mockito.doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + // Synchronously pass the draft check and callback. + ((CheckDraftTaskCallback)invocation.getArguments()[2]).onDraftChecked( + mockDraftMessageData, CheckDraftForSendTask.RESULT_PASSED); + return null; + } + }).when(mockDraftMessageData).checkDraftForAction(Mockito.anyBoolean(), Mockito.anyInt(), + Mockito.any(), + Mockito.>any()); + + view.bind(mockDraftMessageData, mockIComposeMessageViewHost); + + final EditText composeEditText = (EditText) view.findViewById(R.id.compose_message_text); + final View sendButton = view.findViewById(R.id.send_message_button); + + view.requestDraftMessage(false); + + Mockito.verify(mockDraftMessageData).loadFromStorage(Matchers.any(BindingBase.class), + Matchers.any(MessageData.class), Mockito.eq(false)); + + view.onDraftChanged(mockDraftMessageData, DraftMessageData.ALL_CHANGED); + + assertEquals(message.getMessageText(), composeEditText.getText().toString()); + + sendButton.performClick(); + Mockito.verify(mockIComposeMessageViewHost).sendMessage( + Mockito.argThat(new ArgumentMatcher() { + @Override + public boolean matches(final Object o) { + assertEquals(message.getMessageText(), ((MessageData) o).getMessageText()); + return true; + } + })); + } + + public void testNotDefaultSms() { + Mockito.when(mockDraftMessageData.getReadOnlyAttachments()) + .thenReturn(Collections.unmodifiableList(new ArrayList())); + Mockito.when(mockDraftMessageData.getIsDefaultSmsApp()).thenReturn(false); + Mockito.when(mockIComposeMessageViewHost.isReadyForAction()).thenReturn(false); + final ComposeMessageView view = getView(); + + final MessageData message = MessageData.createDraftSmsMessage("fake_id", "just_a_self_id", + "Sample Message"); + + Mockito.when(mockDraftMessageData.isBound(Matchers.anyString())) + .thenReturn(true); + Mockito.when(mockDraftMessageData.getMessageText()).thenReturn(message.getMessageText()); + Mockito.when(mockDraftMessageData.prepareMessageForSending( + Matchers.>any())) + .thenReturn(message); + Mockito.when(mockDraftMessageData.hasPendingAttachments()).thenReturn(false); + + view.bind(mockDraftMessageData, mockIComposeMessageViewHost); + + final EditText composeEditText = (EditText) view.findViewById(R.id.compose_message_text); + final View sendButton = view.findViewById(R.id.send_message_button); + + view.requestDraftMessage(false); + + Mockito.verify(mockDraftMessageData).loadFromStorage(Matchers.any(BindingBase.class), + Matchers.any(MessageData.class), Mockito.eq(false)); + + view.onDraftChanged(mockDraftMessageData, DraftMessageData.ALL_CHANGED); + + assertEquals(message.getMessageText(), composeEditText.getText().toString()); + + sendButton.performClick(); + Mockito.verify(mockIComposeMessageViewHost).warnOfMissingActionConditions( + Matchers.any(Boolean.class), Matchers.any(Runnable.class)); + } +} diff --git a/tests/src/com/android/messaging/ui/conversation/ConversationActivityUiStateTest.java b/tests/src/com/android/messaging/ui/conversation/ConversationActivityUiStateTest.java new file mode 100644 index 0000000..7c6903d --- /dev/null +++ b/tests/src/com/android/messaging/ui/conversation/ConversationActivityUiStateTest.java @@ -0,0 +1,121 @@ +/* + * 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.ui.conversation; + +import android.test.suitebuilder.annotation.SmallTest; +import android.text.TextUtils; + +import com.android.messaging.BugleTestCase; +import com.android.messaging.ui.contact.ContactPickerFragment; +import com.android.messaging.ui.conversation.ConversationActivityUiState; +import com.android.messaging.ui.conversation.ConversationActivityUiState.ConversationActivityUiStateHost; + +import org.mockito.Mock; +import org.mockito.Mockito; + +@SmallTest +public class ConversationActivityUiStateTest extends BugleTestCase { + @Mock protected ConversationActivityUiStateHost mockListener; + + /** + * Test the Ui state where we start off with the contact picker to pick the first contact. + */ + public void testPickInitialContact() { + final ConversationActivityUiState uiState = new ConversationActivityUiState(null); + uiState.setHost(mockListener); + assertTrue(uiState.shouldShowContactPickerFragment()); + assertFalse(uiState.shouldShowConversationFragment()); + assertEquals(ContactPickerFragment.MODE_PICK_INITIAL_CONTACT, + uiState.getDesiredContactPickingMode()); + uiState.onGetOrCreateConversation("conversation1"); + Mockito.verify(mockListener, Mockito.times(1)).onConversationContactPickerUiStateChanged( + Mockito.eq(ConversationActivityUiState.STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT), + Mockito.eq( + ConversationActivityUiState.STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW), + Mockito.anyBoolean()); + assertTrue(uiState.shouldShowContactPickerFragment()); + assertTrue(uiState.shouldShowConversationFragment()); + assertTrue(TextUtils.equals("conversation1", uiState.getConversationId())); + assertEquals(ContactPickerFragment.MODE_CHIPS_ONLY, + uiState.getDesiredContactPickingMode()); + } + + /** + * Test the Ui state where we have both the chips view and the conversation view and we + * start message compose. + */ + public void testHybridUiStateStartCompose() { + final ConversationActivityUiState uiState = new ConversationActivityUiState("conv1"); + uiState.testSetUiState( + ConversationActivityUiState.STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW); + uiState.setHost(mockListener); + + // Start message compose. + uiState.onStartMessageCompose(); + Mockito.verify(mockListener, Mockito.times(1)).onConversationContactPickerUiStateChanged( + Mockito.eq( + ConversationActivityUiState.STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW), + Mockito.eq(ConversationActivityUiState.STATE_CONVERSATION_ONLY), + Mockito.anyBoolean()); + assertFalse(uiState.shouldShowContactPickerFragment()); + assertTrue(uiState.shouldShowConversationFragment()); + } + + /** + * Test the Ui state where we have both the chips view and the conversation view and we + * try to add a participant. + */ + public void testHybridUiStateAddParticipant() { + final ConversationActivityUiState uiState = new ConversationActivityUiState("conv1"); + uiState.testSetUiState( + ConversationActivityUiState.STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW); + uiState.setHost(mockListener); + + uiState.onAddMoreParticipants(); + Mockito.verify(mockListener, Mockito.times(1)).onConversationContactPickerUiStateChanged( + Mockito.eq( + ConversationActivityUiState.STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW), + Mockito.eq( + ConversationActivityUiState.STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS), + Mockito.anyBoolean()); + assertTrue(uiState.shouldShowContactPickerFragment()); + assertFalse(uiState.shouldShowConversationFragment()); + assertEquals(ContactPickerFragment.MODE_PICK_MORE_CONTACTS, + uiState.getDesiredContactPickingMode()); + } + + /** + * Test the Ui state where we are trying to add more participants and commit. + */ + public void testCommitAddParticipant() { + final ConversationActivityUiState uiState = new ConversationActivityUiState(null); + uiState.testSetUiState( + ConversationActivityUiState.STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS); + uiState.setHost(mockListener); + + uiState.onGetOrCreateConversation("conversation1"); + + // After adding more contacts, the terminal state is always conversation only (i.e. we + // don't go back to hybrid mode). + Mockito.verify(mockListener, Mockito.times(1)).onConversationContactPickerUiStateChanged( + Mockito.eq(ConversationActivityUiState.STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS), + Mockito.eq(ConversationActivityUiState.STATE_CONVERSATION_ONLY), + Mockito.anyBoolean()); + assertFalse(uiState.shouldShowContactPickerFragment()); + assertTrue(uiState.shouldShowConversationFragment()); + } +} diff --git a/tests/src/com/android/messaging/ui/conversation/ConversationFragmentTest.java b/tests/src/com/android/messaging/ui/conversation/ConversationFragmentTest.java new file mode 100644 index 0000000..c92fbf6 --- /dev/null +++ b/tests/src/com/android/messaging/ui/conversation/ConversationFragmentTest.java @@ -0,0 +1,160 @@ +/* + * 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.ui.conversation; + +import android.app.Activity; +import android.app.Fragment; +import android.database.Cursor; +import android.support.v7.widget.RecyclerView; +import android.test.suitebuilder.annotation.LargeTest; + +import com.android.messaging.FakeFactory; +import com.android.messaging.R; +import com.android.messaging.datamodel.DataModel; +import com.android.messaging.datamodel.MemoryCacheManager; +import com.android.messaging.datamodel.data.ConversationData; +import com.android.messaging.datamodel.data.ConversationData.ConversationDataListener; +import com.android.messaging.datamodel.data.DraftMessageData; +import com.android.messaging.datamodel.data.TestDataFactory; +import com.android.messaging.datamodel.media.MediaResourceManager; +import com.android.messaging.ui.FragmentTestCase; +import com.android.messaging.ui.PlainTextEditText; +import com.android.messaging.ui.TestActivity.FragmentEventListener; +import com.android.messaging.ui.conversation.ConversationFragment.ConversationFragmentHost; +import com.android.messaging.ui.conversationlist.ConversationListFragment; +import com.android.messaging.util.BugleGservices; +import com.android.messaging.util.ImeUtil; + +import org.mockito.Matchers; +import org.mockito.Mock; +import org.mockito.Mockito; + + +/** + * Unit tests for {@link ConversationListFragment}. + */ +@LargeTest +public class ConversationFragmentTest extends FragmentTestCase { + + @Mock protected DataModel mockDataModel; + @Mock protected ConversationData mockConversationData; + @Mock protected DraftMessageData mockDraftMessageData; + @Mock protected MediaResourceManager mockMediaResourceManager; + @Mock protected BugleGservices mockBugleGservices; + @Mock protected ConversationFragmentHost mockHost; + @Mock protected MemoryCacheManager mockMemoryCacheManager; + + private ImeUtil mSpiedImeUtil; + + private static final String CONVERSATION_ID = "cid"; + + + public ConversationFragmentTest() { + super(ConversationFragment.class); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + ImeUtil.clearInstance(); + mSpiedImeUtil = Mockito.spy(new ImeUtil()); + FakeFactory.register(this.getInstrumentation().getTargetContext()) + .withDataModel(mockDataModel) + .withBugleGservices(mockBugleGservices) + .withMemoryCacheManager(mockMemoryCacheManager); + } + + /** + * Helper that will do the 'binding' of ConversationFragmentTest with ConversationData and + * leave fragment in 'ready' state. + * @param cursor + */ + private void loadWith(final Cursor cursor) { + Mockito.when(mockDraftMessageData.isBound(Matchers.anyString())) + .thenReturn(true); + Mockito.when(mockConversationData.isBound(Matchers.anyString())) + .thenReturn(true); + Mockito.doReturn(mockDraftMessageData) + .when(mockDataModel) + .createDraftMessageData(Mockito.anyString()); + Mockito.when(mockDataModel.createConversationData( + Matchers.any(Activity.class), + Matchers.any(ConversationDataListener.class), + Matchers.anyString())) + .thenReturn(mockConversationData); + + // Create fragment synchronously to avoid need for volatile, synchronization etc. + final ConversationFragment fragment = getFragment(); + // Binding to model happens when attaching fragment to activity, so hook into test + // activity to do so. + getActivity().setFragmentEventListener(new FragmentEventListener() { + @Override + public void onAttachFragment(final Fragment attachedFragment) { + if (fragment == attachedFragment) { + fragment.setConversationInfo(getActivity(), CONVERSATION_ID, null); + } + } + }); + + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + fragment.setHost(mockHost); + getActivity().setFragment(fragment); + Mockito.verify(mockDataModel).createConversationData( + getActivity(), fragment, CONVERSATION_ID); + Mockito.verify(mockConversationData).init(fragment.getLoaderManager(), + fragment.mBinding); + } + }); + // Wait for initial layout pass to work around crash in recycler view + getInstrumentation().waitForIdleSync(); + // Now load the cursor + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + fragment.onConversationMessagesCursorUpdated(mockConversationData, cursor, null, + false); + } + }); + getInstrumentation().waitForIdleSync(); + } + + /** + * Verifies that list view gets correctly populated given a cursor. + */ + public void testLoadListView() { + final Cursor cursor = TestDataFactory.getConversationMessageCursor(); + loadWith(cursor); + final RecyclerView listView = + (RecyclerView) getFragment().getView().findViewById(android.R.id.list); + assertEquals("bad cursor", cursor.getCount(), listView.getAdapter().getItemCount()); + assertEquals("bad cursor count", cursor.getCount(), listView.getChildCount()); + } + + public void testClickComposeMessageView() { + final Cursor cursor = TestDataFactory.getConversationMessageCursor(); + loadWith(cursor); + + final PlainTextEditText composeEditText = (PlainTextEditText) getFragment().getView() + .findViewById(R.id.compose_message_text); + setFocus(composeEditText, false); + Mockito.verify(mockHost, Mockito.never()).onStartComposeMessage(); + setFocus(composeEditText, true); + Mockito.verify(mockHost).onStartComposeMessage(); + } +} diff --git a/tests/src/com/android/messaging/ui/conversation/ConversationInputManagerTest.java b/tests/src/com/android/messaging/ui/conversation/ConversationInputManagerTest.java new file mode 100644 index 0000000..f335785 --- /dev/null +++ b/tests/src/com/android/messaging/ui/conversation/ConversationInputManagerTest.java @@ -0,0 +1,190 @@ +/* + * 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.ui.conversation; + +import android.app.FragmentManager; +import android.app.FragmentTransaction; +import android.os.Bundle; +import android.test.suitebuilder.annotation.SmallTest; +import android.widget.EditText; + +import com.android.messaging.BugleTestCase; +import com.android.messaging.FakeFactory; +import com.android.messaging.R; +import com.android.messaging.datamodel.binding.Binding; +import com.android.messaging.datamodel.binding.BindingBase; +import com.android.messaging.datamodel.data.ConversationData; +import com.android.messaging.datamodel.data.DraftMessageData; +import com.android.messaging.datamodel.data.SubscriptionListData; +import com.android.messaging.ui.conversation.ConversationInputManager.ConversationInputHost; +import com.android.messaging.ui.conversation.ConversationInputManager.ConversationInputSink; +import com.android.messaging.ui.mediapicker.MediaPicker; +import com.android.messaging.util.BugleGservices; +import com.android.messaging.util.ImeUtil; +import com.android.messaging.util.ImeUtil.ImeStateHost; + +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; + +@SmallTest +public class ConversationInputManagerTest extends BugleTestCase { + @Spy protected ImeUtil spyImeUtil; + @Mock protected BugleGservices mockBugleGservices; + @Mock protected FragmentManager mockFragmentManager; + @Mock protected ConversationInputHost mockConversationInputHost; + @Mock protected ConversationInputSink mockConversationInputSink; + @Mock protected ImeStateHost mockImeStateHost; + @Mock protected ConversationData mockConversationData; + @Mock protected DraftMessageData mockDraftMessageData; + @Mock protected MediaPicker mockMediaPicker; + @Mock protected SubscriptionListData mockSubscriptionListData; + @Mock protected FragmentTransaction mockFragmentTransaction; + + @Override + protected void setUp() throws Exception { + super.setUp(); + FakeFactory.register(getTestContext()) + .withBugleGservices(mockBugleGservices); + spyImeUtil = Mockito.spy(new ImeUtil()); + ImeUtil.set(spyImeUtil); + } + + private ConversationInputManager initNewInputManager(final Bundle savedState) { + // Set up the mocks. + Mockito.when(mockConversationInputHost.getSimSelectorView()) + .thenReturn(new SimSelectorView(getTestContext(), null)); + Mockito.when(mockConversationInputHost.createMediaPicker()).thenReturn(mockMediaPicker); + Mockito.when(mockConversationInputSink.getComposeEditText()) + .thenReturn(new EditText(getTestContext())); + Mockito.doReturn(mockFragmentTransaction).when(mockFragmentTransaction).replace( + Mockito.eq(R.id.mediapicker_container), Mockito.any(MediaPicker.class), + Mockito.anyString()); + Mockito.when(mockFragmentManager.findFragmentByTag(MediaPicker.FRAGMENT_TAG)) + .thenReturn(null); + Mockito.when(mockFragmentManager.beginTransaction()).thenReturn(mockFragmentTransaction); + Mockito.when(mockSubscriptionListData.hasData()).thenReturn(true); + Mockito.when(mockConversationData.getSubscriptionListData()) + .thenReturn(mockSubscriptionListData); + Mockito.doReturn(true).when(mockConversationData).isBound(Mockito.anyString()); + Mockito.doReturn(true).when(mockDraftMessageData).isBound(Mockito.anyString()); + + final Binding dataBinding = BindingBase.createBinding(this); + dataBinding.bind(mockConversationData); + final Binding draftBinding = BindingBase.createBinding(this); + draftBinding.bind(mockDraftMessageData); + final ConversationInputManager inputManager = new ConversationInputManager(getTestContext(), + mockConversationInputHost, mockConversationInputSink, mockImeStateHost, + mockFragmentManager, dataBinding, draftBinding, savedState); + return inputManager; + } + + public void testShowHideInputs() { + final ConversationInputManager inputManager = initNewInputManager(new Bundle()); + Mockito.when(mockMediaPicker.isOpen()).thenReturn(true); + inputManager.showHideMediaPicker(true /* show */, true /* animate */); + Mockito.verify(mockFragmentTransaction).replace( + Mockito.eq(R.id.mediapicker_container), Mockito.any(MediaPicker.class), + Mockito.anyString()); + Mockito.verify(mockMediaPicker).open(Mockito.anyInt(), Mockito.eq(true /* animate */)); + + assertEquals(true, inputManager.isMediaPickerVisible()); + assertEquals(false, inputManager.isSimSelectorVisible()); + assertEquals(false, inputManager.isImeKeyboardVisible()); + + Mockito.when(mockMediaPicker.isOpen()).thenReturn(false); + inputManager.showHideMediaPicker(false /* show */, true /* animate */); + Mockito.verify(mockMediaPicker).dismiss(Mockito.eq(true /* animate */)); + + assertEquals(false, inputManager.isMediaPickerVisible()); + assertEquals(false, inputManager.isSimSelectorVisible()); + assertEquals(false, inputManager.isImeKeyboardVisible()); + } + + public void testShowTwoInputsSequentially() { + // First show the media picker, then show the IME keyboard. + final ConversationInputManager inputManager = initNewInputManager(new Bundle()); + Mockito.when(mockMediaPicker.isOpen()).thenReturn(true); + inputManager.showHideMediaPicker(true /* show */, true /* animate */); + + assertEquals(true, inputManager.isMediaPickerVisible()); + assertEquals(false, inputManager.isSimSelectorVisible()); + assertEquals(false, inputManager.isImeKeyboardVisible()); + + Mockito.when(mockMediaPicker.isOpen()).thenReturn(false); + inputManager.showHideImeKeyboard(true /* show */, true /* animate */); + + assertEquals(false, inputManager.isMediaPickerVisible()); + assertEquals(false, inputManager.isSimSelectorVisible()); + assertEquals(true, inputManager.isImeKeyboardVisible()); + } + + public void testOnKeyboardShow() { + final ConversationInputManager inputManager = initNewInputManager(new Bundle()); + Mockito.when(mockMediaPicker.isOpen()).thenReturn(true); + inputManager.showHideMediaPicker(true /* show */, true /* animate */); + + assertEquals(true, inputManager.isMediaPickerVisible()); + assertEquals(false, inputManager.isSimSelectorVisible()); + assertEquals(false, inputManager.isImeKeyboardVisible()); + + Mockito.when(mockMediaPicker.isOpen()).thenReturn(false); + inputManager.testNotifyImeStateChanged(true /* imeOpen */); + + assertEquals(false, inputManager.isMediaPickerVisible()); + assertEquals(false, inputManager.isSimSelectorVisible()); + assertEquals(true, inputManager.isImeKeyboardVisible()); + } + + public void testRestoreState() { + final ConversationInputManager inputManager = initNewInputManager(new Bundle()); + Mockito.when(mockMediaPicker.isOpen()).thenReturn(true); + inputManager.showHideMediaPicker(true /* show */, true /* animate */); + + assertEquals(true, inputManager.isMediaPickerVisible()); + assertEquals(false, inputManager.isSimSelectorVisible()); + assertEquals(false, inputManager.isImeKeyboardVisible()); + + Bundle savedInstanceState = new Bundle(); + inputManager.onSaveInputState(savedInstanceState); + + // Now try to restore the state + final ConversationInputManager restoredInputManager = + initNewInputManager(savedInstanceState); + + // Make sure the state is preserved. + assertEquals(true, restoredInputManager.isMediaPickerVisible()); + assertEquals(false, restoredInputManager.isSimSelectorVisible()); + assertEquals(false, restoredInputManager.isImeKeyboardVisible()); + } + + public void testBackPress() { + final ConversationInputManager inputManager = initNewInputManager(new Bundle()); + Mockito.when(mockMediaPicker.isOpen()).thenReturn(true); + inputManager.showHideMediaPicker(true /* show */, true /* animate */); + + assertEquals(true, inputManager.isMediaPickerVisible()); + assertEquals(false, inputManager.isSimSelectorVisible()); + assertEquals(false, inputManager.isImeKeyboardVisible()); + + Mockito.when(mockMediaPicker.isOpen()).thenReturn(false); + assertEquals(true, inputManager.onBackPressed()); + + assertEquals(false, inputManager.isMediaPickerVisible()); + assertEquals(false, inputManager.isSimSelectorVisible()); + assertEquals(false, inputManager.isImeKeyboardVisible()); + } +} diff --git a/tests/src/com/android/messaging/ui/conversation/ConversationMessageViewTest.java b/tests/src/com/android/messaging/ui/conversation/ConversationMessageViewTest.java new file mode 100644 index 0000000..6b8b0c0 --- /dev/null +++ b/tests/src/com/android/messaging/ui/conversation/ConversationMessageViewTest.java @@ -0,0 +1,110 @@ +/* + * 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.ui.conversation; + +import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.Suppress; +import android.view.View; +import android.widget.TextView; + +import com.android.messaging.FakeFactory; +import com.android.messaging.R; +import com.android.messaging.datamodel.FakeCursor; +import com.android.messaging.datamodel.data.TestDataFactory; +import com.android.messaging.ui.ViewTest; +import com.android.messaging.ui.conversation.ConversationMessageView; +import com.android.messaging.ui.conversation.ConversationMessageView.ConversationMessageViewHost; +import com.android.messaging.util.Dates; + +import org.mockito.Mock; + +@MediumTest +public class ConversationMessageViewTest extends ViewTest { + @Mock ConversationMessageViewHost mockHost; + + @Override + protected void setUp() throws Exception { + super.setUp(); + FakeFactory.register(getInstrumentation().getTargetContext()); + } + + @Override + protected ConversationMessageView getView() { + final ConversationMessageView view = super.getView(); + view.setHost(mockHost); + return view; + } + + protected void verifyContent(final ConversationMessageView view, final String messageText, + final boolean showTimestamp, final String timestampText) { + + final TextView messageTextView = (TextView) view.findViewById(R.id.message_text); + final TextView statusTextView = (TextView) view.findViewById(R.id.message_status); + + assertNotNull(messageTextView); + assertEquals(messageText, messageTextView.getText()); + + if (showTimestamp) { + assertEquals(View.VISIBLE, statusTextView.getVisibility()); + assertEquals(timestampText, statusTextView.getText()); + } else { + assertEquals(View.GONE, statusTextView.getVisibility()); + } + } + + public void testBind() { + final ConversationMessageView view = getView(); + + final FakeCursor cursor = TestDataFactory.getConversationMessageCursor(); + cursor.moveToFirst(); + + view.bind(cursor); + verifyContent(view, TestDataFactory.getMessageText(cursor, 0), true, Dates + .getMessageTimeString((Long) cursor.getAt("received_timestamp", 0)).toString()); + } + + public void testBindTwice() { + final ConversationMessageView view = getView(); + + final FakeCursor cursor = TestDataFactory.getConversationMessageCursor(); + cursor.moveToFirst(); + view.bind(cursor); + + cursor.moveToNext(); + view.bind(cursor); + verifyContent(view, TestDataFactory.getMessageText(cursor, 1), true, Dates + .getMessageTimeString((Long) cursor.getAt("received_timestamp", 1)).toString()); + } + + public void testBindLast() { + final ConversationMessageView view = getView(); + + final FakeCursor cursor = TestDataFactory.getConversationMessageCursor(); + final int lastPos = cursor.getCount() - 1; + cursor.moveToPosition(lastPos); + + view.bind(cursor); + verifyContent(view, TestDataFactory.getMessageText(cursor, lastPos), true, Dates + .getMessageTimeString((Long) cursor.getAt("received_timestamp", lastPos)) + .toString()); + } + + @Override + protected int getLayoutIdForView() { + return R.layout.conversation_message_view; + } +} diff --git a/tests/src/com/android/messaging/ui/conversationlist/ConversationListFragmentTest.java b/tests/src/com/android/messaging/ui/conversationlist/ConversationListFragmentTest.java new file mode 100644 index 0000000..f9cc9e1 --- /dev/null +++ b/tests/src/com/android/messaging/ui/conversationlist/ConversationListFragmentTest.java @@ -0,0 +1,130 @@ +/* + * 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.ui.conversationlist; + +import android.content.Context; +import android.database.Cursor; +import android.support.v7.widget.RecyclerView; +import android.test.suitebuilder.annotation.LargeTest; +import android.view.View; +import android.widget.ImageView; +import android.widget.ListView; + +import com.android.messaging.FakeFactory; +import com.android.messaging.R; +import com.android.messaging.datamodel.FakeDataModel; +import com.android.messaging.datamodel.data.ConversationListData; +import com.android.messaging.datamodel.data.TestDataFactory; +import com.android.messaging.ui.FragmentTestCase; +import com.android.messaging.ui.UIIntents; +import com.android.messaging.ui.conversationlist.ConversationListFragment; +import com.android.messaging.ui.conversationlist.ConversationListFragment.ConversationListFragmentHost; + +import org.mockito.Matchers; +import org.mockito.Mock; +import org.mockito.Mockito; + + +/** + * Unit tests for {@link ConversationListFragment}. + */ +@LargeTest +public class ConversationListFragmentTest + extends FragmentTestCase { + + @Mock protected ConversationListData mMockConversationListData; + @Mock protected ConversationListFragmentHost mMockConversationHostListHost; + @Mock protected UIIntents mMockUIIntents; + protected FakeDataModel mFakeDataModel; + + public ConversationListFragmentTest() { + super(ConversationListFragment.class); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + final Context context = getInstrumentation().getTargetContext(); + mFakeDataModel = new FakeDataModel(context) + .withConversationListData(mMockConversationListData); + FakeFactory.register(context) + .withDataModel(mFakeDataModel) + .withUIIntents(mMockUIIntents); + } + + /** + * Helper that will do the 'binding' of ConversationListFragmentTest with ConversationListData + * and leave fragment in 'ready' state. + * @param cursor + */ + private void loadWith(final Cursor cursor) { + Mockito.when(mMockConversationListData.isBound(Matchers.anyString())) + .thenReturn(true); + + final ConversationListFragment fragment = getFragment(); + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + fragment.setHost(mMockConversationHostListHost); + getActivity().setFragment(fragment); + Mockito.verify(mMockConversationListData).init(fragment.getLoaderManager(), + fragment.mListBinding); + fragment.onConversationListCursorUpdated(mMockConversationListData, cursor); + } + }); + getInstrumentation().waitForIdleSync(); + } + + /** + * Verifies that list view gets correctly populated given a cursor. + */ + public void testLoadListView() { + final Cursor cursor = TestDataFactory.getConversationListCursor(); + loadWith(cursor); + final RecyclerView listView = + (RecyclerView) getFragment().getView().findViewById(android.R.id.list); + //assertEquals(cursor.getCount(), listView.getCount()); + assertEquals(cursor.getCount(), listView.getChildCount()); + } + + /** + * Verifies that 'empty list' promo is rendered with an empty cursor. + */ + public void testEmptyView() { + loadWith(TestDataFactory.getEmptyConversationListCursor()); + final RecyclerView listView = + (RecyclerView) getFragment().getView().findViewById(android.R.id.list); + final View emptyMessageView = + getFragment().getView().findViewById(R.id.no_conversations_view); + assertEquals(View.VISIBLE, emptyMessageView.getVisibility()); + assertEquals(0, listView.getChildCount()); + } + + /** + * Verifies that the button to start a new conversation works. + */ + public void testStartNewConversation() { + final Cursor cursor = TestDataFactory.getConversationListCursor(); + loadWith(cursor); + final ImageView startNewConversationButton = (ImageView) + getFragment().getView().findViewById(R.id.start_new_conversation_button); + + clickButton(startNewConversationButton); + Mockito.verify(mMockConversationHostListHost).onCreateConversationClick(); + } +} diff --git a/tests/src/com/android/messaging/ui/conversationlist/ConversationListItemViewTest.java b/tests/src/com/android/messaging/ui/conversationlist/ConversationListItemViewTest.java new file mode 100644 index 0000000..be054a8 --- /dev/null +++ b/tests/src/com/android/messaging/ui/conversationlist/ConversationListItemViewTest.java @@ -0,0 +1,181 @@ +/* + * 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.ui.conversationlist; + +import android.content.Context; +import android.test.suitebuilder.annotation.MediumTest; +import android.text.TextUtils; +import android.view.View; +import android.widget.TextView; + +import com.android.messaging.Factory; +import com.android.messaging.FakeFactory; +import com.android.messaging.R; +import com.android.messaging.datamodel.FakeCursor; +import com.android.messaging.datamodel.FakeDataModel; +import com.android.messaging.datamodel.data.ConversationListItemData; +import com.android.messaging.datamodel.data.TestDataFactory; +import com.android.messaging.ui.AsyncImageView; +import com.android.messaging.ui.UIIntentsImpl; +import com.android.messaging.ui.ViewTest; +import com.android.messaging.ui.conversationlist.ConversationListItemView; +import com.android.messaging.util.Dates; + +import org.mockito.ArgumentMatcher; +import org.mockito.Mock; +import org.mockito.Mockito; + +@MediumTest +public class ConversationListItemViewTest extends ViewTest { + + @Mock private ConversationListItemView.HostInterface mockHost; + private FakeCursor mCursor; + + @Override + protected void setUp() throws Exception { + super.setUp(); + final Context context = getInstrumentation().getTargetContext(); + FakeFactory.register(context) + .withDataModel(new FakeDataModel(context)) + .withUIIntents(new UIIntentsImpl()); + mCursor = TestDataFactory.getConversationListCursor(); + } + + + protected void verifyLaunchedConversationForId(final String id, + final ConversationListItemView conversationView) { + // Must be a short click. + final ArgumentMatcher itemDataIdMatcher = + new ArgumentMatcher() { + @Override + public boolean matches(final Object arg) { + return TextUtils.equals(id, ((ConversationListItemData) arg).getConversationId()); + } + }; + Mockito.verify(mockHost).onConversationClicked( + Mockito.argThat(itemDataIdMatcher), Mockito.eq(false), + Mockito.eq(conversationView)); + } + + protected void verifyContent( + final ConversationListItemView view, final FakeCursor cursor, final int index) { + /* ConversationQueryColumns.NAME */ + final String conversationQueryColumnsName = "name"; + final String name = (String) cursor.getAt(conversationQueryColumnsName, index); + + /* ConversationQueryColumns.SNIPPET_TEXT */ + final String conversationQueryColumnsSnippetText = "snippet_text"; + final String snippet = (String) cursor.getAt(conversationQueryColumnsSnippetText, index); + + /* ConversationQueryColumns.SORT_TIMESTAMP */ + final String conversationQueryColumnsSortTimestamp = "sort_timestamp"; + final String timestamp = Dates.getConversationTimeString( + (Long) cursor.getAt(conversationQueryColumnsSortTimestamp, index)).toString(); + + final boolean unread = !isRead(cursor, index); + verifyContent(view, name, snippet, timestamp, unread); + } + + protected void verifyContent( + final ConversationListItemView view, + final String conversationName, + final String snippet, + final String timestamp, + final boolean unread) { + final TextView conversationNameView = + (TextView) view.findViewById(R.id.conversation_name); + final TextView snippetTextView = (TextView) view.findViewById(R.id.conversation_snippet); + final TextView timestampTextView = (TextView) view.findViewById( + R.id.conversation_timestamp); + final AsyncImageView imagePreviewView = + (AsyncImageView) view.findViewById(R.id.conversation_image_preview); + + final Context context = Factory.get().getApplicationContext(); + assertNotNull(conversationNameView); + assertEquals(conversationName, conversationNameView.getText()); + assertNotNull(snippetTextView); + if (unread) { + assertEquals(ConversationListItemView.UNREAD_SNIPPET_LINE_COUNT, + snippetTextView.getMaxLines()); + assertEquals(context.getResources().getColor(R.color.conversation_list_item_unread), + snippetTextView.getCurrentTextColor()); + assertEquals(context.getResources().getColor(R.color.conversation_list_item_unread), + conversationNameView.getCurrentTextColor()); + + } else { + assertEquals(ConversationListItemView.NO_UNREAD_SNIPPET_LINE_COUNT, + snippetTextView.getMaxLines()); + assertEquals(context.getResources().getColor(R.color.conversation_list_item_read), + snippetTextView.getCurrentTextColor()); + assertEquals(context.getResources().getColor(R.color.conversation_list_item_read), + conversationNameView.getCurrentTextColor()); + } + + assertEquals(View.VISIBLE, imagePreviewView.getVisibility()); + assertTrue(snippetTextView.getText().toString().contains(snippet)); + assertEquals(timestamp, timestampTextView.getText()); + } + + protected boolean isRead(final FakeCursor cursor, final int index) { + return 1 == ((Integer) cursor.getAt("read", index)).intValue(); + } + + public void testBindUnread() { + final ConversationListItemView view = getView(); + final int messageIndex = TestDataFactory.CONVERSATION_LIST_CURSOR_UNREAD_MESSAGE_INDEX; + mCursor.moveToPosition(messageIndex); + assertFalse(isRead(mCursor, messageIndex)); + view.bind(mCursor, mockHost); + verifyContent(view, mCursor, messageIndex); + } + + public void testBindRead() { + final ConversationListItemView view = getView(); + + final int messageIndex = TestDataFactory.CONVERSATION_LIST_CURSOR_READ_MESSAGE_INDEX; + mCursor.moveToPosition(messageIndex); + assertTrue(isRead(mCursor, messageIndex)); + view.bind(mCursor, mockHost); + verifyContent(view, mCursor, messageIndex); + } + + public void testClickLaunchesConversation() { + final ConversationListItemView view = getView(); + final View swipeableContainer = view.findViewById(R.id.swipeableContainer); + mCursor.moveToFirst(); + view.bind(mCursor, mockHost); + swipeableContainer.performClick(); + verifyLaunchedConversationForId( + mCursor.getAt("_id" /* ConversationQueryColumns._ID */, 0).toString(), view); + } + + public void testBindTwice() { + final ConversationListItemView view = getView(); + + mCursor.moveToFirst(); + view.bind(mCursor, mockHost); + + mCursor.moveToNext(); + view.bind(mCursor, mockHost); + verifyContent(view, mCursor, mCursor.getPosition()); + } + + @Override + protected int getLayoutIdForView() { + return R.layout.conversation_list_item_view; + } +} diff --git a/tests/src/com/android/messaging/ui/mediapicker/AudioRecordViewTest.java b/tests/src/com/android/messaging/ui/mediapicker/AudioRecordViewTest.java new file mode 100644 index 0000000..a38dac2 --- /dev/null +++ b/tests/src/com/android/messaging/ui/mediapicker/AudioRecordViewTest.java @@ -0,0 +1,89 @@ +/* + * 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.ui.mediapicker; + +import android.media.MediaPlayer; +import android.media.MediaRecorder.OnErrorListener; +import android.media.MediaRecorder.OnInfoListener; +import android.net.Uri; + +import com.android.messaging.FakeFactory; +import com.android.messaging.R; +import com.android.messaging.datamodel.data.MessagePartData; +import com.android.messaging.ui.ViewTest; +import com.android.messaging.util.FakeMediaUtil; + +import org.mockito.Matchers; +import org.mockito.Mock; +import org.mockito.Mockito; + +public class AudioRecordViewTest extends ViewTest { + + @Mock AudioRecordView.HostInterface mockHost; + @Mock LevelTrackingMediaRecorder mockRecorder; + @Mock MediaPlayer mockMediaPlayer; + + @Override + protected void setUp() throws Exception { + super.setUp(); + FakeFactory.register(getInstrumentation().getContext()) + .withMediaUtil(new FakeMediaUtil(mockMediaPlayer)); + } + + private void verifyAudioSubmitted() { + Mockito.verify(mockHost).onAudioRecorded(Matchers.any(MessagePartData.class)); + } + + private AudioRecordView initView() { + final AudioRecordView view = getView(); + view.testSetMediaRecorder(mockRecorder); + view.setHostInterface(mockHost); + return view; + } + + public void testRecording() { + Mockito.when(mockRecorder.isRecording()).thenReturn(false); + Mockito.when(mockRecorder.startRecording(Matchers.any(), + Matchers.any(), Matchers.anyInt())).thenReturn(true); + Mockito.when(mockRecorder.stopRecording()).thenReturn(Uri.parse("content://someaudio/2")); + final AudioRecordView view = initView(); + view.onRecordButtonTouchDown(); + Mockito.verify(mockRecorder).startRecording(Matchers.any(), + Matchers.any(), Matchers.anyInt()); + Mockito.when(mockRecorder.isRecording()).thenReturn(true); + // Record for 1 second to make it meaningful. + sleepNoThrow(1000); + view.onRecordButtonTouchUp(); + // We added some buffer to the end of the audio recording, so sleep for sometime and + // verify audio is recorded. + sleepNoThrow(700); + Mockito.verify(mockRecorder).stopRecording(); + verifyAudioSubmitted(); + } + + private void sleepNoThrow(final long duration) { + try { + Thread.sleep(duration); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + @Override + protected int getLayoutIdForView() { + return R.layout.mediapicker_audio_chooser; + } +} diff --git a/tests/src/com/android/messaging/ui/mediapicker/CameraManagerTest.java b/tests/src/com/android/messaging/ui/mediapicker/CameraManagerTest.java new file mode 100644 index 0000000..951c694 --- /dev/null +++ b/tests/src/com/android/messaging/ui/mediapicker/CameraManagerTest.java @@ -0,0 +1,154 @@ +/* + * 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.ui.mediapicker; + +import android.hardware.Camera; +import android.hardware.Camera.CameraInfo; +import android.os.AsyncTask; +import android.test.suitebuilder.annotation.SmallTest; +import com.android.messaging.BugleTestCase; +import com.android.messaging.ui.mediapicker.CameraManager.CameraWrapper; +import org.mockito.InOrder; +import org.mockito.Mockito; + +@SmallTest +public class CameraManagerTest extends BugleTestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + // Force each test to set up a camera wrapper to match their needs + CameraManager.setCameraWrapper(null); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + MockCameraFactory.cleanup(); + } + + public void testNoCameraDeviceGetInfo() { + CameraManager.setCameraWrapper(MockCameraFactory.createCameraWrapper()); + assertEquals(false, CameraManager.get().hasAnyCamera()); + assertEquals(false, CameraManager.get().hasFrontAndBackCamera()); + try { + CameraManager.get().selectCamera(CameraInfo.CAMERA_FACING_BACK); + fail("selectCamera should have thrown"); + } catch (AssertionError e) { + } + } + + public void testFrontFacingOnlyGetInfo() { + CameraManager.setCameraWrapper(MockCameraFactory.createCameraWrapper( + MockCameraFactory.createCamera(CameraInfo.CAMERA_FACING_FRONT) + )); + assertEquals(true, CameraManager.get().hasAnyCamera()); + assertEquals(false, CameraManager.get().hasFrontAndBackCamera()); + CameraManager.get().selectCamera(CameraInfo.CAMERA_FACING_FRONT); + assertEquals(CameraInfo.CAMERA_FACING_FRONT, CameraManager.get().getCameraInfo().facing); + CameraManager.get().selectCamera(CameraInfo.CAMERA_FACING_BACK); + assertEquals(CameraInfo.CAMERA_FACING_FRONT, CameraManager.get().getCameraInfo().facing); + } + + public void testBackFacingOnlyGetInfo() { + CameraManager.setCameraWrapper(MockCameraFactory.createCameraWrapper( + MockCameraFactory.createCamera(CameraInfo.CAMERA_FACING_BACK) + )); + assertEquals(true, CameraManager.get().hasAnyCamera()); + assertEquals(false, CameraManager.get().hasFrontAndBackCamera()); + CameraManager.get().selectCamera(CameraInfo.CAMERA_FACING_FRONT); + assertEquals(CameraInfo.CAMERA_FACING_BACK, CameraManager.get().getCameraInfo().facing); + CameraManager.get().selectCamera(CameraInfo.CAMERA_FACING_BACK); + assertEquals(CameraInfo.CAMERA_FACING_BACK, CameraManager.get().getCameraInfo().facing); + } + + public void testFrontAndBackGetInfo() { + CameraManager.setCameraWrapper(MockCameraFactory.createCameraWrapper( + MockCameraFactory.createCamera(CameraInfo.CAMERA_FACING_FRONT), + MockCameraFactory.createCamera(CameraInfo.CAMERA_FACING_BACK) + )); + assertEquals(true, CameraManager.get().hasAnyCamera()); + assertEquals(true, CameraManager.get().hasFrontAndBackCamera()); + CameraManager.get().selectCamera(CameraInfo.CAMERA_FACING_FRONT); + assertEquals(CameraInfo.CAMERA_FACING_FRONT, CameraManager.get().getCameraInfo().facing); + CameraManager.get().selectCamera(CameraInfo.CAMERA_FACING_BACK); + assertEquals(CameraInfo.CAMERA_FACING_BACK, CameraManager.get().getCameraInfo().facing); + } + + public void testSwapCamera() { + CameraManager.setCameraWrapper(MockCameraFactory.createCameraWrapper( + MockCameraFactory.createCamera(CameraInfo.CAMERA_FACING_FRONT), + MockCameraFactory.createCamera(CameraInfo.CAMERA_FACING_BACK) + )); + CameraManager.get().selectCamera(CameraInfo.CAMERA_FACING_FRONT); + assertEquals(CameraInfo.CAMERA_FACING_FRONT, CameraManager.get().getCameraInfo().facing); + CameraManager.get().swapCamera(); + assertEquals(CameraInfo.CAMERA_FACING_BACK, CameraManager.get().getCameraInfo().facing); + } + + public void testOpenCamera() { + Camera backCamera = MockCameraFactory.createCamera(CameraInfo.CAMERA_FACING_BACK); + Camera frontCamera = MockCameraFactory.createCamera(CameraInfo.CAMERA_FACING_FRONT); + CameraWrapper wrapper = MockCameraFactory.createCameraWrapper(frontCamera, backCamera); + CameraManager.setCameraWrapper(wrapper); + CameraManager.get().selectCamera(CameraInfo.CAMERA_FACING_BACK); + CameraManager.get().openCamera(); + CameraManager.get().openCamera(); + CameraManager.get().openCamera(); + waitForPendingAsyncTasks(); + Mockito.verify(wrapper, Mockito.never()).open(0); + Mockito.verify(wrapper).open(1); + Mockito.verify(wrapper, Mockito.never()).release(frontCamera); + Mockito.verify(wrapper, Mockito.never()).release(backCamera); + CameraManager.get().swapCamera(); + waitForPendingAsyncTasks(); + Mockito.verify(wrapper).open(0); + Mockito.verify(wrapper).open(1); + Mockito.verify(wrapper, Mockito.never()).release(frontCamera); + Mockito.verify(wrapper).release(backCamera); + InOrder inOrder = Mockito.inOrder(wrapper); + inOrder.verify(wrapper).open(1); + inOrder.verify(wrapper).release(backCamera); + inOrder.verify(wrapper).open(0); + } + + private void waitForPendingAsyncTasks() { + try { + final Object lockObject = new Object(); + + new AsyncTask() { + @Override + protected Void doInBackground(Void... voids) { + return null; + } + + @Override + protected void onPostExecute(Void aVoid) { + super.onPostExecute(aVoid); + synchronized (lockObject) { + lockObject.notifyAll(); + } + } + }.execute(); + + synchronized (lockObject) { + lockObject.wait(500); + } + } catch (InterruptedException e) { + fail(); + } + } +} diff --git a/tests/src/com/android/messaging/ui/mediapicker/GalleryGridItemViewTest.java b/tests/src/com/android/messaging/ui/mediapicker/GalleryGridItemViewTest.java new file mode 100644 index 0000000..83d8ac9 --- /dev/null +++ b/tests/src/com/android/messaging/ui/mediapicker/GalleryGridItemViewTest.java @@ -0,0 +1,145 @@ +/* + * 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.ui.mediapicker; + +import android.content.Context; +import android.provider.MediaStore.Images.Media; +import android.view.View; +import android.widget.CheckBox; + +import com.android.messaging.FakeFactory; +import com.android.messaging.R; +import com.android.messaging.datamodel.FakeCursor; +import com.android.messaging.datamodel.FakeDataModel; +import com.android.messaging.datamodel.data.GalleryGridItemData; +import com.android.messaging.datamodel.data.TestDataFactory; +import com.android.messaging.ui.AsyncImageView; +import com.android.messaging.ui.ViewTest; +import com.android.messaging.util.UriUtil; + +import org.mockito.Matchers; +import org.mockito.Mock; +import org.mockito.Mockito; + +public class GalleryGridItemViewTest extends ViewTest { + + @Mock GalleryGridItemView.HostInterface mockHost; + + @Override + protected void setUp() throws Exception { + super.setUp(); + final Context context = getInstrumentation().getTargetContext(); + FakeFactory.register(context) + .withDataModel(new FakeDataModel(context)); + } + + protected void verifyClickedItem(final View view, final GalleryGridItemData data) { + Mockito.verify(mockHost).onItemClicked(view, data, false /* longClick */); + } + + protected void verifyContent( + final GalleryGridItemView view, + final String imageUrl, + final boolean showCheckbox, + final boolean isSelected) { + final AsyncImageView imageView = (AsyncImageView) view.findViewById(R.id.image); + final CheckBox checkBox = (CheckBox) view.findViewById(R.id.checkbox); + + assertNotNull(imageView); + assertTrue(imageView.mImageRequestBinding.isBound()); + assertTrue(imageView.mImageRequestBinding.getData().getKey().startsWith(imageUrl)); + assertNotNull(checkBox); + if (showCheckbox) { + assertEquals(View.VISIBLE, checkBox.getVisibility()); + assertEquals(isSelected, checkBox.isChecked()); + } else { + assertNotSame(View.VISIBLE, checkBox.getVisibility()); + } + } + + public void testBind() { + Mockito.when(mockHost.isMultiSelectEnabled()).thenReturn(false); + Mockito.when(mockHost.isItemSelected(Matchers.any())) + .thenReturn(false); + final GalleryGridItemView view = getView(); + final FakeCursor cursor = TestDataFactory.getGalleryGridCursor(); + cursor.moveToFirst(); + final String path = (String) cursor.getAt(Media.DATA, 0); + view.bind(cursor, mockHost); + verifyContent(view, UriUtil.getUriForResourceFile(path).toString(), + false, false); + } + + public void testBindMultiSelectUnSelected() { + Mockito.when(mockHost.isMultiSelectEnabled()).thenReturn(true); + Mockito.when(mockHost.isItemSelected(Matchers.any())) + .thenReturn(false); + final GalleryGridItemView view = getView(); + final FakeCursor cursor = TestDataFactory.getGalleryGridCursor(); + cursor.moveToFirst(); + final String path = (String) cursor.getAt(Media.DATA, 0); + view.bind(cursor, mockHost); + verifyContent(view, UriUtil.getUriForResourceFile(path).toString(), + true, false); + } + + public void testBindMultiSelectSelected() { + Mockito.when(mockHost.isMultiSelectEnabled()).thenReturn(true); + Mockito.when(mockHost.isItemSelected(Matchers.any())) + .thenReturn(true); + final GalleryGridItemView view = getView(); + final FakeCursor cursor = TestDataFactory.getGalleryGridCursor(); + cursor.moveToFirst(); + final String path = (String) cursor.getAt(Media.DATA, 0); + view.bind(cursor, mockHost); + verifyContent(view, UriUtil.getUriForResourceFile(path).toString(), + true, true); + } + + public void testClick() { + Mockito.when(mockHost.isMultiSelectEnabled()).thenReturn(false); + Mockito.when(mockHost.isItemSelected(Matchers.any())) + .thenReturn(false); + final GalleryGridItemView view = getView(); + final FakeCursor cursor = TestDataFactory.getGalleryGridCursor(); + cursor.moveToFirst(); + view.bind(cursor, mockHost); + view.performClick(); + verifyClickedItem(view, view.mData); + } + + public void testBindTwice() { + Mockito.when(mockHost.isMultiSelectEnabled()).thenReturn(true); + Mockito.when(mockHost.isItemSelected(Matchers.any())) + .thenReturn(false); + final GalleryGridItemView view = getView(); + final FakeCursor cursor = TestDataFactory.getGalleryGridCursor(); + + cursor.moveToFirst(); + view.bind(cursor, mockHost); + + cursor.moveToNext(); + final String path = (String) cursor.getAt(Media.DATA, 1); + view.bind(cursor, mockHost); + verifyContent(view, UriUtil.getUriForResourceFile(path).toString(), + true, false); + } + + @Override + protected int getLayoutIdForView() { + return R.layout.gallery_grid_item_view; + } +} diff --git a/tests/src/com/android/messaging/ui/mediapicker/MediaPickerTest.java b/tests/src/com/android/messaging/ui/mediapicker/MediaPickerTest.java new file mode 100644 index 0000000..4a7040e --- /dev/null +++ b/tests/src/com/android/messaging/ui/mediapicker/MediaPickerTest.java @@ -0,0 +1,129 @@ +/* + * 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.ui.mediapicker; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageButton; + +import com.android.messaging.FakeFactory; +import com.android.messaging.R; +import com.android.messaging.datamodel.FakeDataModel; +import com.android.messaging.datamodel.binding.Binding; +import com.android.messaging.datamodel.binding.BindingBase; +import com.android.messaging.datamodel.data.DraftMessageData; +import com.android.messaging.datamodel.data.MediaPickerData; +import com.android.messaging.ui.FragmentTestCase; + +import org.mockito.Matchers; +import org.mockito.Mock; +import org.mockito.Mockito; + +public class MediaPickerTest extends FragmentTestCase { + @Mock protected MediaPickerData mMockMediaPickerData; + @Mock protected DraftMessageData mMockDraftMessageData; + protected FakeDataModel mFakeDataModel; + + public MediaPickerTest() { + super(MediaPicker.class); + } + + @Override + protected MediaPicker getFragment() { + if (mFragment == null) { + mFragment = new MediaPicker(getInstrumentation().getTargetContext()); + } + return mFragment; + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + final Context context = getInstrumentation().getTargetContext(); + mFakeDataModel = new FakeDataModel(context) + .withMediaPickerData(mMockMediaPickerData); + FakeFactory.register(context) + .withDataModel(mFakeDataModel); + } + + /** + * Helper method to initialize the MediaPicker and its data. + */ + private void initFragment(final int supportedMediaTypes, final Integer[] expectedLoaderIds, + final boolean filterTabBeforeAttach) { + Mockito.when(mMockMediaPickerData.isBound(Matchers.anyString())) + .thenReturn(true); + Mockito.when(mMockDraftMessageData.isBound(Matchers.anyString())) + .thenReturn(true); + final Binding draftBinding = BindingBase.createBinding(this); + draftBinding.bind(mMockDraftMessageData); + + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + final MediaPicker fragment = getFragment(); + if (filterTabBeforeAttach) { + fragment.setSupportedMediaTypes(supportedMediaTypes); + getActivity().setFragment(fragment); + } else { + getActivity().setFragment(fragment); + fragment.setSupportedMediaTypes(supportedMediaTypes); + } + fragment.setDraftMessageDataModel(draftBinding); + Mockito.verify(mMockMediaPickerData, + Mockito.atLeastOnce()).init( + Matchers.eq(fragment.getLoaderManager())); + fragment.open(MediaPicker.MEDIA_TYPE_ALL, false); + } + }); + getInstrumentation().waitForIdleSync(); + } + + public void testDefaultTabs() { + Mockito.when(mMockMediaPickerData.getSelectedChooserIndex()).thenReturn(0); + initFragment(MediaPicker.MEDIA_TYPE_ALL, new Integer[] { + MediaPickerData.GALLERY_IMAGE_LOADER }, + false); + final MediaPicker mediaPicker = getFragment(); + final View view = mediaPicker.getView(); + assertNotNull(view); + final ViewGroup tabStrip = (ViewGroup) view.findViewById(R.id.mediapicker_tabstrip); + assertEquals(tabStrip.getChildCount(), 3); + for (int i = 0; i < tabStrip.getChildCount(); i++) { + final ImageButton tabButton = (ImageButton) tabStrip.getChildAt(i); + assertEquals(View.VISIBLE, tabButton.getVisibility()); + assertEquals(i == 0, tabButton.isSelected()); + } + } + + public void testFilterTabsBeforeAttach() { + Mockito.when(mMockMediaPickerData.getSelectedChooserIndex()).thenReturn(0); + initFragment(MediaPicker.MEDIA_TYPE_IMAGE, new Integer[] { + MediaPickerData.GALLERY_IMAGE_LOADER }, + true); + final MediaPicker mediaPicker = getFragment(); + final View view = mediaPicker.getView(); + assertNotNull(view); + final ViewGroup tabStrip = (ViewGroup) view.findViewById(R.id.mediapicker_tabstrip); + assertEquals(tabStrip.getChildCount(), 3); + for (int i = 0; i < tabStrip.getChildCount(); i++) { + final ImageButton tabButton = (ImageButton) tabStrip.getChildAt(i); + assertEquals(i == 0, tabButton.isSelected()); + } + } +} diff --git a/tests/src/com/android/messaging/ui/mediapicker/MockCameraFactory.java b/tests/src/com/android/messaging/ui/mediapicker/MockCameraFactory.java new file mode 100644 index 0000000..789a78f --- /dev/null +++ b/tests/src/com/android/messaging/ui/mediapicker/MockCameraFactory.java @@ -0,0 +1,73 @@ +/* + * 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.ui.mediapicker; + +import android.hardware.Camera; +import android.hardware.Camera.CameraInfo; + +import com.android.messaging.ui.mediapicker.CameraManager.CameraWrapper; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.util.HashMap; +import java.util.Map; + +class MockCameraFactory { + private static Map sCameraInfos = new HashMap(); + + public static Camera createCamera(int facing) { + Camera camera = Mockito.mock(Camera.class); + CameraInfo cameraInfo = new CameraInfo(); + cameraInfo.facing = facing; + sCameraInfos.put(camera, cameraInfo); + return camera; + } + + public static void getCameraInfo(Camera camera, CameraInfo outCameraInfo) { + CameraInfo cameraInfo = sCameraInfos.get(camera); + outCameraInfo.facing = cameraInfo.facing; + outCameraInfo.orientation = cameraInfo.orientation; + outCameraInfo.canDisableShutterSound = cameraInfo.canDisableShutterSound; + } + + public static CameraWrapper createCameraWrapper(final Camera... cameras) { + CameraWrapper wrapper = Mockito.mock(CameraWrapper.class); + Mockito.when(wrapper.getNumberOfCameras()).thenReturn(cameras.length); + Mockito.when(wrapper.open(Mockito.anyInt())).then(new Answer() { + @Override + public Camera answer(InvocationOnMock invocation) { + return cameras[(Integer) invocation.getArguments()[0]]; + } + }); + Mockito.doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + getCameraInfo( + cameras[(Integer) invocation.getArguments()[0]], + (CameraInfo) invocation.getArguments()[1] + ); + return null; + } + }).when(wrapper).getCameraInfo(Mockito.anyInt(), Mockito.any(CameraInfo.class)); + return wrapper; + } + + public static void cleanup() { + sCameraInfos.clear(); + } +} diff --git a/tests/src/com/android/messaging/util/BugleGservicesTest.java b/tests/src/com/android/messaging/util/BugleGservicesTest.java new file mode 100644 index 0000000..1a0a10e --- /dev/null +++ b/tests/src/com/android/messaging/util/BugleGservicesTest.java @@ -0,0 +1,33 @@ +/* + * 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.util; + +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.messaging.BugleTestCase; + +@SmallTest +public class BugleGservicesTest extends BugleTestCase { + + public void testGServiceGet() { + final BugleGservices bugleGservices = new FakeBugleGservices(); + + assertEquals(BugleGservicesKeys.SMS_IGNORE_MESSAGE_REGEX_DEFAULT, + bugleGservices.getString( + BugleGservicesKeys.SMS_IGNORE_MESSAGE_REGEX, + BugleGservicesKeys.SMS_IGNORE_MESSAGE_REGEX_DEFAULT)); + } +} diff --git a/tests/src/com/android/messaging/util/ContactUtilTest.java b/tests/src/com/android/messaging/util/ContactUtilTest.java new file mode 100644 index 0000000..48a7ced --- /dev/null +++ b/tests/src/com/android/messaging/util/ContactUtilTest.java @@ -0,0 +1,306 @@ +/* + * 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.util; + +import android.content.ContentProviderOperation; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; +import android.provider.ContactsContract; +import android.provider.ContactsContract.Contacts; +import android.test.suitebuilder.annotation.LargeTest; +import android.text.TextUtils; + +import com.android.messaging.BugleTestCase; +import com.android.messaging.FakeFactory; + +import org.junit.Assert; + +import java.util.ArrayList; + +/* + * Class for testing ContactUtil. + */ +@LargeTest +public class ContactUtilTest extends BugleTestCase { + private static final String TEST_NAME_PREFIX = "BugleTest:"; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + // TODO: This test will actually mess with contacts on your phone. + // Ideally we would use a fake content provider to give us contact data... + FakeFactory.registerWithoutFakeContext(getTestContext()); + + // add test contacts. + addTestContact("John", "650-123-1233", "john@gmail.com", false); + addTestContact("Joe", "(650)123-1233", "joe@gmail.com", false); + addTestContact("Jim", "650 123 1233", "jim@gmail.com", false); + addTestContact("Samantha", "650-123-1235", "samantha@gmail.com", true); + addTestContact("Adrienne", "650-123-1236", "adrienne@gmail.com", true); + } + + @Override + protected void tearDown() throws Exception { + deleteTestContacts(); + super.tearDown(); + } + + /** + * Add a test contact based on contact name, phone and email. + */ + private void addTestContact( + final String name, final String phone, final String email, final boolean starred) + throws Exception { + final ArrayList ops = new ArrayList(); + + ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) + .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null) + .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null) + .build()); + + ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + .withValue(ContactsContract.Data.MIMETYPE, + ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, + TEST_NAME_PREFIX + name).build()); + + if (phone != null) { + ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + .withValue(ContactsContract.Data.MIMETYPE, + ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone) + .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, + ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE) + .build()); + } + + if (email != null) { + ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + .withValue(ContactsContract.Data.MIMETYPE, + ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.Email.DATA, email) + .withValue(ContactsContract.CommonDataKinds.Email.TYPE, + ContactsContract.CommonDataKinds.Email.TYPE_WORK) + .build()); + } + + mContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); + + // Star the whole contact if needed. + if (starred) { + final ContentValues values = new ContentValues(); + values.put(Contacts.STARRED, 1); + getContext().getContentResolver().update(Contacts.CONTENT_URI, values, + Contacts.DISPLAY_NAME + "= ?", new String[] { TEST_NAME_PREFIX + name }); + } + } + + /** + * Remove test contacts added during test setup. + */ + private void deleteTestContacts() { + final Uri contactUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, + Uri.encode(TEST_NAME_PREFIX)); + final Cursor cur = + mContext.getContentResolver().query(contactUri, null, null, null, null); + try { + if (cur.moveToFirst()) { + do { + final String lookupKey = cur.getString(cur.getColumnIndex(Contacts.LOOKUP_KEY)); + final Uri uri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey); + mContext.getContentResolver().delete(uri, null, null); + } while (cur.moveToNext()); + } + } catch (final Exception e) { + System.out.println(e.getStackTrace()); + } + } + + /** + * Verify ContactUtil.getPhone will return all phones, including the ones added for test. + */ + public void ingoredTestGetPhones() { + final Cursor cur = ContactUtil.getPhones(getContext()) + .performSynchronousQuery(); + + LogUtil.i(LogUtil.BUGLE_TAG, "testGetPhones: Number of phones on the device:" + + cur.getCount()); + + verifyCursorContains(cur, TEST_NAME_PREFIX + "John"); + verifyCursorContains(cur, TEST_NAME_PREFIX + "Joe"); + verifyCursorContains(cur, TEST_NAME_PREFIX + "Jim"); + verifyCursorContains(cur, TEST_NAME_PREFIX + "Samantha"); + verifyCursorContains(cur, TEST_NAME_PREFIX + "Adrienne"); + } + + /** + * Verify ContactUtil.filterPhone will work on name based matches. + */ + public void ingoredTestFilterPhonesByName() { + final Cursor cur = ContactUtil.filterPhones(getContext(), TEST_NAME_PREFIX) + .performSynchronousQuery(); + + if (cur.getCount() != 5) { + Assert.fail("Cursor should have size of 5"); + return; + } + + verifyCursorContains(cur, TEST_NAME_PREFIX + "John"); + verifyCursorContains(cur, TEST_NAME_PREFIX + "Joe"); + verifyCursorContains(cur, TEST_NAME_PREFIX + "Jim"); + verifyCursorContains(cur, TEST_NAME_PREFIX + "Samantha"); + verifyCursorContains(cur, TEST_NAME_PREFIX + "Adrienne"); + } + + /** + * Verify ContactUtil.filterPhone will work on partial number matches. + */ + public void ingoredTestFilterPhonesByPartialNumber() { + final String[] filters = new String[] { "650123", "650-123", "(650)123", "650 123" }; + + for (final String filter : filters) { + final Cursor cur = ContactUtil.filterPhones(getContext(), filter) + .performSynchronousQuery(); + + LogUtil.i(LogUtil.BUGLE_TAG, "testFilterPhonesByPartialNumber: Number of phones:" + + cur.getCount()); + + verifyCursorContains(cur, TEST_NAME_PREFIX + "John"); + verifyCursorContains(cur, TEST_NAME_PREFIX + "Joe"); + verifyCursorContains(cur, TEST_NAME_PREFIX + "Jim"); + verifyCursorContains(cur, TEST_NAME_PREFIX + "Samantha"); + verifyCursorContains(cur, TEST_NAME_PREFIX + "Adrienne"); + } + } + + /** + * Verify ContactUtil.filterPhone will work on full number matches. + */ + public void ingoredTestFilterPhonesByFullNumber() { + final String[] filters = new String[] { + "6501231233", "650-123-1233", "(650)123-1233", "650 123 1233" }; + + for (final String filter : filters) { + final Cursor cur = ContactUtil.filterPhones(getContext(), filter) + .performSynchronousQuery(); + + LogUtil.i(LogUtil.BUGLE_TAG, "testFilterPhonesByFullNumber: Number of phones:" + + cur.getCount()); + + verifyCursorContains(cur, TEST_NAME_PREFIX + "John"); + verifyCursorContains(cur, TEST_NAME_PREFIX + "Joe"); + verifyCursorContains(cur, TEST_NAME_PREFIX + "Jim"); + } + } + + /** + * Verify ContactUtil.lookPhone will work on number including area code. + */ + public void ingoredTestLookupPhoneWithAreaCode() { + final String[] filters = new String[] { + "6501231233", "650-123-1233", "(650)123-1233", "650 123 1233" }; + + for (final String filter : filters) { + final Cursor cur = ContactUtil.lookupPhone(getContext(), filter) + .performSynchronousQuery(); + + LogUtil.i(LogUtil.BUGLE_TAG, "testLookupPhoneWithAreaCode: Number of phones:" + + cur.getCount()); + + verifyCursorContains(cur, TEST_NAME_PREFIX + "John"); + verifyCursorContains(cur, TEST_NAME_PREFIX + "Joe"); + verifyCursorContains(cur, TEST_NAME_PREFIX + "Jim"); + } + } + + /** + * Verify ContactUtil.lookPhone will work on number without area code. + */ + public void ingoredTestLookupPhoneWithoutAreaCode() { + final String[] filters = new String[] { + "1231233", "123-1233", "123 1233" }; + + for (final String filter : filters) { + final Cursor cur = ContactUtil.lookupPhone(getContext(), filter) + .performSynchronousQuery(); + + LogUtil.i(LogUtil.BUGLE_TAG, "testLookupPhoneWithoutAreaCode: Number of phones:" + + cur.getCount()); + + verifyCursorContains(cur, TEST_NAME_PREFIX + "John"); + verifyCursorContains(cur, TEST_NAME_PREFIX + "Joe"); + verifyCursorContains(cur, TEST_NAME_PREFIX + "Jim"); + } + } + + public void ingoredTestGetFrequentPhones() { + final Cursor cur = ContactUtil.getFrequentContacts(getContext()) + .performSynchronousQuery(); + + LogUtil.i(LogUtil.BUGLE_TAG, "testGetFrequentPhones: Number of phones on the device:" + + cur.getCount()); + + verifyCursorContains(cur, TEST_NAME_PREFIX + "Samantha"); + verifyCursorContains(cur, TEST_NAME_PREFIX + "Adrienne"); + } + + /** + * Verify ContactUtil.filterEmails will work on partial email. + */ + public void ingoredTestFilterEmails() { + final Cursor cur = ContactUtil.filterEmails(getContext(), "john@") + .performSynchronousQuery(); + + LogUtil.i(LogUtil.BUGLE_TAG, "testFilterEmails: Number of emails:" + + cur.getCount()); + + verifyCursorContains(cur, TEST_NAME_PREFIX + "John"); + } + + /** + * Verify ContactUtil.lookupEmail will work on full email. + */ + public void ingoredTestLookupEmail() { + final Cursor cur = ContactUtil.lookupEmail(getContext(), "john@gmail.com") + .performSynchronousQuery(); + + LogUtil.i(LogUtil.BUGLE_TAG, "testLookupEmail: Number of emails:" + + cur.getCount()); + + verifyCursorContains(cur, TEST_NAME_PREFIX + "John"); + } + + /** + * Utility method to check whether cursor contains a particular contact. + */ + private void verifyCursorContains(final Cursor cursor, final String nameToVerify) { + if (cursor.moveToFirst()) { + do { + final String name = cursor.getString(ContactUtil.INDEX_DISPLAY_NAME); + if (TextUtils.equals(name, nameToVerify)) { + return; + } + } while (cursor.moveToNext()); + } + Assert.fail("Cursor should have " + nameToVerify); + } +} diff --git a/tests/src/com/android/messaging/util/FakeBugleGservices.java b/tests/src/com/android/messaging/util/FakeBugleGservices.java new file mode 100644 index 0000000..f7d90da --- /dev/null +++ b/tests/src/com/android/messaging/util/FakeBugleGservices.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.util; + +/** + * Fake implementation which just returns the default values. + */ +public class FakeBugleGservices extends BugleGservices { + public FakeBugleGservices() { + } + + @Override + public void registerForChanges(final Runnable r) { + } + + @Override + public long getLong(final String key, final long defaultValue) { + return defaultValue; + } + + @Override + public int getInt(final String key, final int defaultValue) { + return defaultValue; + } + + @Override + public boolean getBoolean(final String key, final boolean defaultValue) { + return defaultValue; + } + + @Override + public String getString(final String key, final String defaultValue) { + return defaultValue; + } + + @Override + public float getFloat(String key, float defaultValue) { + return defaultValue; + } + +} diff --git a/tests/src/com/android/messaging/util/FakeBuglePrefs.java b/tests/src/com/android/messaging/util/FakeBuglePrefs.java new file mode 100644 index 0000000..b3429a6 --- /dev/null +++ b/tests/src/com/android/messaging/util/FakeBuglePrefs.java @@ -0,0 +1,84 @@ +/* + * 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.util; + + +/** + * Fake implementation which just returns the default values and ignores put operations. + */ +public class FakeBuglePrefs extends BuglePrefs { + public FakeBuglePrefs() { + } + + @Override + public int getInt(final String key, final int defaultValue) { + return defaultValue; + } + + @Override + public long getLong(final String key, final long defaultValue) { + return defaultValue; + } + + @Override + public boolean getBoolean(final String key, final boolean defaultValue) { + return defaultValue; + } + + @Override + public String getString(final String key, final String defaultValue) { + return defaultValue; + } + + @Override + public byte[] getBytes(String key) { + return null; + } + + @Override + public void putInt(final String key, final int value) { + } + + @Override + public void putLong(final String key, final long value) { + } + + @Override + public void putBoolean(final String key, final boolean value) { + } + + @Override + public void putString(final String key, final String value) { + } + + @Override + public void putBytes(String key, byte[] value) { + } + + @Override + public String getSharedPreferencesName() { + return "FakeBuglePrefs"; + } + + @Override + public void onUpgrade(int oldVersion, int newVersion) { + } + + @Override + public void remove(String key) { + } +} diff --git a/tests/src/com/android/messaging/util/FakeMediaUtil.java b/tests/src/com/android/messaging/util/FakeMediaUtil.java new file mode 100644 index 0000000..e37ff0c --- /dev/null +++ b/tests/src/com/android/messaging/util/FakeMediaUtil.java @@ -0,0 +1,33 @@ +/* + * 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.util; + +import android.content.Context; +import android.media.MediaPlayer; + +public class FakeMediaUtil extends MediaUtil { + private final MediaPlayer mMediaPlayer; + public FakeMediaUtil(final MediaPlayer mediaPlayer) { + mMediaPlayer = mediaPlayer; + } + + @Override + public void playSound(Context context, int resId, OnCompletionListener completionListener) { + if (completionListener != null) { + completionListener.onCompletion(); + } + } +} diff --git a/tests/src/com/android/messaging/util/YouTubeUtilTest.java b/tests/src/com/android/messaging/util/YouTubeUtilTest.java new file mode 100644 index 0000000..03c461a --- /dev/null +++ b/tests/src/com/android/messaging/util/YouTubeUtilTest.java @@ -0,0 +1,62 @@ +/* + * 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.util; + +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.messaging.BugleTestCase; + +/* + * Class for testing YouTubeUtil. + */ +@SmallTest +public class YouTubeUtilTest extends BugleTestCase { + public void testGetYoutubePreviewImageLink() { + final String videoId = "dQw4w9WgXcQ"; + final String videoThumbnailUrl = YouTubeUtil.YOUTUBE_STATIC_THUMBNAIL_PREFIX + videoId + + YouTubeUtil.YOUTUBE_STATIC_THUMBNAIL_END; + + // Check known valid youtube links to videos + assertEquals( + YouTubeUtil.getYoutubePreviewImageLink("http://www.youtube.com/watch?v=" + videoId), + videoThumbnailUrl); + assertEquals( + YouTubeUtil.getYoutubePreviewImageLink("https://www.youtube.com/watch?v=" + videoId + + "&feature=youtu.be"), videoThumbnailUrl); + assertEquals( + YouTubeUtil.getYoutubePreviewImageLink("www.youtube.com/watch?v=" + videoId), + videoThumbnailUrl); + assertEquals( + YouTubeUtil.getYoutubePreviewImageLink("http://www.youtube.com/embed/" + videoId), + videoThumbnailUrl); + assertEquals(YouTubeUtil.getYoutubePreviewImageLink("http://www.youtube.com/v/" + videoId), + videoThumbnailUrl); + assertEquals( + YouTubeUtil.getYoutubePreviewImageLink("https://youtube.googleapis.com/v/" + + videoId), videoThumbnailUrl); + assertEquals( + YouTubeUtil.getYoutubePreviewImageLink("http://www.youtube.com/apiplayer?video_id=" + + videoId), videoThumbnailUrl); + // This is the type of links that are used as shares from YouTube and will be the most + // likely case that we see + assertEquals(YouTubeUtil.getYoutubePreviewImageLink("http://youtu.be/" + videoId), + videoThumbnailUrl); + + // Try links that shouldn't work + assertNull(YouTubeUtil.getYoutubePreviewImageLink("http://www.youtube.com")); + } +} \ No newline at end of file -- cgit v1.1