summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoraae-comm-eng <aae-comm-eng@google.com>2022-02-02 16:34:30 -0800
committerPeter Li <pyli@google.com>2022-02-02 16:35:22 -0800
commit254f4c5bca773b99bafe8664a0be516f893679dd (patch)
treedc480450ab9d607a935fdf99680cf00b7eece389
parentb2f8b467b07c361ae653bda7f4325823cf843c14 (diff)
downloadMessenger-ub_automotive.tar.gz
Project import generated by Copybara.ub_automotive
PiperOrigin-RevId: 426007610 Change-Id: Id8f8d4874269205a5eeb37e821f1818026ca0a93
-rw-r--r--src/com/android/car/messenger/impl/datamodels/NewMessageLiveData.java16
-rw-r--r--src/com/android/car/messenger/impl/datamodels/util/MessageUtils.java6
-rw-r--r--src/com/android/car/messenger/impl/datamodels/util/MmsUtils.java4
-rw-r--r--tests/src/com/android/car/messenger/core/service/MessengerServiceTest.java183
-rw-r--r--tests/src/com/android/car/messenger/core/service/OnBootReceiverTest.java61
-rw-r--r--tests/src/com/android/car/messenger/impl/AppFactoryTestImpl.java72
-rw-r--r--tests/src/com/android/car/messenger/impl/datamodels/ConversationListLiveDataTest.java176
-rw-r--r--tests/src/com/android/car/messenger/impl/datamodels/NewMessageLiveDataTest.java271
-rw-r--r--tests/src/com/android/car/messenger/impl/datamodels/util/ConversationFetchUtilTest.java180
-rw-r--r--tests/src/com/android/car/messenger/impl/datamodels/util/MessageUtilsTest.java147
-rw-r--r--tests/src/com/android/car/messenger/impl/datamodels/util/MmsUtilsTest.java143
-rw-r--r--tests/src/com/android/car/messenger/impl/datamodels/util/SmsUtilsTest.java98
12 files changed, 1349 insertions, 8 deletions
diff --git a/src/com/android/car/messenger/impl/datamodels/NewMessageLiveData.java b/src/com/android/car/messenger/impl/datamodels/NewMessageLiveData.java
index a5e0922..772a9e5 100644
--- a/src/com/android/car/messenger/impl/datamodels/NewMessageLiveData.java
+++ b/src/com/android/car/messenger/impl/datamodels/NewMessageLiveData.java
@@ -27,6 +27,7 @@ import android.provider.Telephony;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import com.android.car.messenger.common.Conversation;
import com.android.car.messenger.core.interfaces.AppFactory;
@@ -51,8 +52,13 @@ public class NewMessageLiveData extends ContentProviderLiveData<Conversation> {
@NonNull
private final UserAccountLiveData mUserAccountLiveData = UserAccountLiveData.getInstance();
- @NonNull private Collection<UserAccount> mUserAccounts = new ArrayList<>();
- @NonNull private final HashMap<Integer, Instant> mOffsetMap = new HashMap<>();
+ @VisibleForTesting
+ @NonNull
+ Collection<UserAccount> mUserAccounts = new ArrayList<>();
+
+ @VisibleForTesting
+ @NonNull
+ final HashMap<Integer, Instant> mOffsetMap = new HashMap<>();
@NonNull
private static final String MESSAGE_QUERY =
@@ -139,13 +145,15 @@ public class NewMessageLiveData extends ContentProviderLiveData<Conversation> {
/** Get the last message cursor, taking into account the last message posted */
@Nullable
- private Cursor getMmsCursor(@NonNull UserAccount userAccount, @NonNull Instant offset) {
+ @VisibleForTesting
+ Cursor getMmsCursor(@NonNull UserAccount userAccount, @NonNull Instant offset) {
return getCursor(Telephony.Mms.Inbox.CONTENT_URI, userAccount, offset.getEpochSecond());
}
/** Get the last message cursor, taking into account the last message posted */
@Nullable
- private Cursor getSmsCursor(@NonNull UserAccount userAccount, @NonNull Instant offset) {
+ @VisibleForTesting
+ Cursor getSmsCursor(@NonNull UserAccount userAccount, @NonNull Instant offset) {
return getCursor(Telephony.Sms.Inbox.CONTENT_URI, userAccount, offset.toEpochMilli());
}
/** Get the last message cursor, taking into account an offset and subscription id */
diff --git a/src/com/android/car/messenger/impl/datamodels/util/MessageUtils.java b/src/com/android/car/messenger/impl/datamodels/util/MessageUtils.java
index 0bb454b..b3f2eaa 100644
--- a/src/com/android/car/messenger/impl/datamodels/util/MessageUtils.java
+++ b/src/com/android/car/messenger/impl/datamodels/util/MessageUtils.java
@@ -48,7 +48,7 @@ import java.util.function.Function;
public final class MessageUtils {
/**
- * Returns all messages in the given cursors.
+ * Returns all messages in the given cursors in descending order.
*
* @param limit The maximum number of messages
* @param messageCursors The messageCursors of messages in descending order
@@ -89,13 +89,13 @@ public final class MessageUtils {
/**
- * Gets Read Messages and Last Reply
+ * Gets Read Messages in ascending order and Last Reply
*
* @param messages List of messages in descending order
*/
@NonNull
public static Pair<List<Message>, Message> getReadMessagesAndReplyTimestamp(
- @Nullable List<Message> messages) {
+ @NonNull List<Message> messages) {
List<Message> readMessages = new ArrayList<>();
AtomicReference<Message> replyMessage = new AtomicReference<>();
AtomicReference<Long> lastReply = new AtomicReference<>(0L);
diff --git a/src/com/android/car/messenger/impl/datamodels/util/MmsUtils.java b/src/com/android/car/messenger/impl/datamodels/util/MmsUtils.java
index 7f87e69..0b82e69 100644
--- a/src/com/android/car/messenger/impl/datamodels/util/MmsUtils.java
+++ b/src/com/android/car/messenger/impl/datamodels/util/MmsUtils.java
@@ -80,7 +80,9 @@ class MmsUtils {
StringBuilder stringBuilder = new StringBuilder();
while (cursor != null && cursor.moveToNext()) {
stringBuilder.append(cursor.getString(cursor.getColumnIndex(Part.TEXT)));
- stringBuilder.append(" ");
+ if (!cursor.isLast()) {
+ stringBuilder.append(" ");
+ }
}
return stringBuilder.toString().replace(REPLACE_CHARS, "");
diff --git a/tests/src/com/android/car/messenger/core/service/MessengerServiceTest.java b/tests/src/com/android/car/messenger/core/service/MessengerServiceTest.java
new file mode 100644
index 0000000..f4e6d62
--- /dev/null
+++ b/tests/src/com/android/car/messenger/core/service/MessengerServiceTest.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2022 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.car.messenger.core.service;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.when;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+
+import androidx.lifecycle.MutableLiveData;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ServiceTestRule;
+
+import com.android.car.messenger.common.Conversation;
+import com.android.car.messenger.core.shared.MessageConstants;
+import com.android.car.messenger.core.util.VoiceUtil;
+import com.android.car.messenger.impl.AppFactoryTestImpl;
+import com.android.car.messenger.impl.datamodels.TelephonyDataModel;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.util.concurrent.TimeoutException;
+
+@RunWith(AndroidJUnit4.class)
+public class MessengerServiceTest {
+
+ private MessengerService mMessengerService;
+ private AppFactoryTestImpl mAppFactory;
+ private Context mContext;
+ @Rule
+ public final ServiceTestRule mServiceTestRule = new ServiceTestRule();
+ @Captor
+ private ArgumentCaptor<Intent> mCaptor;
+ @Mock
+ private TelephonyDataModel mDataModel;
+
+ @Before
+ public void setup() throws TimeoutException {
+ MockitoAnnotations.initMocks(this);
+
+ mAppFactory = new AppFactoryTestImpl(null, mDataModel, null, null);
+ mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ Intent intent = new Intent(mContext, MessengerService.class);
+ IBinder binder = mServiceTestRule.bindService(intent);
+ mMessengerService = ((MessengerService.LocalBinder) binder).getService();
+ }
+
+ @After
+ public void teardown() {
+ mAppFactory.teardown();
+ }
+
+ @Test
+ public void testSubscribeToNotifications() {
+ MutableLiveData<Conversation> unreadLiveData = mock(MutableLiveData.class);
+ MutableLiveData<String> removedLiveData = mock(MutableLiveData.class);
+
+ when(mDataModel.getUnreadMessages()).thenReturn(unreadLiveData);
+ when(mDataModel.onConversationRemoved()).thenReturn(removedLiveData);
+
+ // These calls are delayed as defined by MessengerService#DELAY_FETCH_DURATION
+ verify(unreadLiveData, timeout(5000)).observeForever(any());
+ // TODO: b/216550137 Make delay configurable to speed up test
+ verify(removedLiveData, timeout(5000)).observeForever(any());
+ }
+
+ @Test
+ public void testOnStartCommand_nullAction() {
+ int result = mMessengerService.onStartCommand(null, 0, 0);
+ assertThat(result).isEqualTo(Service.START_STICKY);
+
+ result = mMessengerService.onStartCommand(new Intent(), 0, 0);
+ assertThat(result).isEqualTo(Service.START_STICKY);
+ }
+
+ @Test
+ public void testOnStartCommand_actionReply() {
+ MockitoSession session = mockitoSession().strictness(Strictness.LENIENT)
+ .spyStatic(VoiceUtil.class)
+ .startMocking();
+ try {
+ Intent intent = new Intent();
+ intent.setAction(MessageConstants.ACTION_REPLY);
+ int result = mMessengerService.onStartCommand(intent, 0, 0);
+
+ assertThat(result).isEqualTo(Service.START_STICKY);
+ verify(() -> VoiceUtil.voiceReply(mCaptor.capture()));
+ assertThat(mCaptor.getValue()).isEqualTo(intent);
+ } finally {
+ session.finishMocking();
+ }
+ }
+
+ @Test
+ public void testOnStartCommand_actionMute() {
+ MockitoSession session = mockitoSession().strictness(Strictness.LENIENT)
+ .spyStatic(VoiceUtil.class)
+ .startMocking();
+ try {
+ Intent intent = new Intent();
+ intent.setAction(MessageConstants.ACTION_MUTE);
+ int result = mMessengerService.onStartCommand(intent, 0, 0);
+
+ assertThat(result).isEqualTo(Service.START_STICKY);
+ verify(() -> VoiceUtil.mute(mCaptor.capture()));
+ assertThat(mCaptor.getValue()).isEqualTo(intent);
+ } finally {
+ session.finishMocking();
+ }
+ }
+
+ @Test
+ public void testOnStartCommand_actionMarkRead() {
+ MockitoSession session = mockitoSession().strictness(Strictness.LENIENT)
+ .spyStatic(VoiceUtil.class)
+ .startMocking();
+ try {
+ Intent intent = new Intent();
+ intent.setAction(MessageConstants.ACTION_MARK_AS_READ);
+ int result = mMessengerService.onStartCommand(intent, 0, 0);
+
+ assertThat(result).isEqualTo(Service.START_STICKY);
+ verify(() -> VoiceUtil.markAsRead(mCaptor.capture()));
+ assertThat(mCaptor.getValue()).isEqualTo(intent);
+ } finally {
+ session.finishMocking();
+ }
+ }
+
+ @Test
+ public void testOnStartCommand_actionDirectSend() {
+ MockitoSession session = mockitoSession().strictness(Strictness.LENIENT)
+ .spyStatic(VoiceUtil.class)
+ .startMocking();
+ try {
+ Intent intent = new Intent();
+ intent.setAction(MessageConstants.ACTION_DIRECT_SEND);
+ int result = mMessengerService.onStartCommand(intent, 0, 0);
+
+ assertThat(result).isEqualTo(Service.START_STICKY);
+ verify(() -> VoiceUtil.directSend(mCaptor.capture()));
+ assertThat(mCaptor.getValue()).isEqualTo(intent);
+ } finally {
+ session.finishMocking();
+ }
+ }
+}
diff --git a/tests/src/com/android/car/messenger/core/service/OnBootReceiverTest.java b/tests/src/com/android/car/messenger/core/service/OnBootReceiverTest.java
new file mode 100644
index 0000000..4657065
--- /dev/null
+++ b/tests/src/com/android/car/messenger/core/service/OnBootReceiverTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 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.car.messenger.core.service;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class OnBootReceiverTest {
+
+ private Context mContext;
+ @Captor
+ private ArgumentCaptor<Intent> mCaptor;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(ApplicationProvider.getApplicationContext());
+ }
+
+ @Test
+ public void testOnReceive() {
+ OnBootReceiver onBootReceiver = new OnBootReceiver();
+ Intent intent = new Intent();
+ intent.setAction(Intent.ACTION_BOOT_COMPLETED);
+
+ onBootReceiver.onReceive(mContext, intent);
+ verify(mContext).startService(mCaptor.capture());
+ assertThat(mCaptor.getValue().getComponent().getClassName())
+ .isEqualTo(MessengerService.class.getName());
+ }
+}
diff --git a/tests/src/com/android/car/messenger/impl/AppFactoryTestImpl.java b/tests/src/com/android/car/messenger/impl/AppFactoryTestImpl.java
new file mode 100644
index 0000000..16ace50
--- /dev/null
+++ b/tests/src/com/android/car/messenger/impl/AppFactoryTestImpl.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2022 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.car.messenger.impl;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import com.android.car.messenger.core.interfaces.AppFactory;
+import com.android.car.messenger.core.interfaces.DataModel;
+import com.android.car.messenger.core.util.CarStateListener;
+
+/**
+ * Test implementation of AppFactory to provide mocked Context, DataModel, SharedPreferences,
+ * and CarStateListener.
+ *
+ * {@link #teardown()} must be run at the end of each test to ensure subsequent register calls go
+ * through.
+ */
+public class AppFactoryTestImpl extends AppFactory {
+
+ private final Context mContext;
+ private final DataModel mDataModel;
+ private final SharedPreferences mSharedPreferences;
+
+ public AppFactoryTestImpl(Context context,
+ DataModel dataModel,
+ SharedPreferences prefs,
+ CarStateListener listener) {
+ AppFactory.setInstance(this);
+ sRegistered = true;
+ sInitialized = true;
+
+ mContext = context;
+ mDataModel = dataModel;
+ mSharedPreferences = prefs;
+ mCarStateListener = listener;
+ }
+
+ public void teardown() {
+ sRegistered = false;
+ sInitialized = false;
+ }
+
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public DataModel getDataModel() {
+ return mDataModel;
+ }
+
+ @Override
+ public SharedPreferences getSharedPreferences() {
+ return mSharedPreferences;
+ }
+}
diff --git a/tests/src/com/android/car/messenger/impl/datamodels/ConversationListLiveDataTest.java b/tests/src/com/android/car/messenger/impl/datamodels/ConversationListLiveDataTest.java
new file mode 100644
index 0000000..1bc7a51
--- /dev/null
+++ b/tests/src/com/android/car/messenger/impl/datamodels/ConversationListLiveDataTest.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2022 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.car.messenger.impl.datamodels;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.Telephony;
+
+import androidx.core.app.Person;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
+import androidx.lifecycle.Observer;
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.car.apps.common.testutils.InstantTaskExecutorRule;
+import com.android.car.messenger.common.Conversation;
+import com.android.car.messenger.core.models.UserAccount;
+import com.android.car.messenger.impl.AppFactoryTestImpl;
+import com.android.car.messenger.impl.datamodels.util.ConversationFetchUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class ConversationListLiveDataTest {
+
+ private ConversationListLiveData mConversationListLiveData;
+ private AppFactoryTestImpl mAppFactory;
+
+ /** Used to execute livedata.postValue() synchronously */
+ @Rule
+ public TestRule rule = new InstantTaskExecutorRule();
+
+ private LifecycleRegistry mLifecycleRegistry;
+ @Mock
+ private UserAccount mMockUserAccount;
+ @Mock
+ private LifecycleOwner mMockLifecycleOwner;
+ @Mock
+ private Observer<Collection<Conversation>> mMockObserver;
+ private Context mContext;
+ @Mock
+ private Cursor mMockCursor;
+ @Mock
+ private ContentResolver mMockContentResolver;
+ @Mock
+ private SharedPreferences mMockSharedPreferences;
+ @Captor
+ private ArgumentCaptor<Uri> mCaptor;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(ApplicationProvider.getApplicationContext());
+ mAppFactory = new AppFactoryTestImpl(mContext, null, mMockSharedPreferences, null);
+
+ when(mMockUserAccount.getId()).thenReturn(0);
+ mConversationListLiveData = new ConversationListLiveData(mMockUserAccount);
+ mLifecycleRegistry = new LifecycleRegistry(mMockLifecycleOwner);
+ when(mMockLifecycleOwner.getLifecycle()).thenReturn(mLifecycleRegistry);
+ }
+
+ @After
+ public void teardown() {
+ mAppFactory.teardown();
+ }
+
+ @Test
+ public void testUri() {
+ when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+ doNothing().when(mMockContentResolver).registerContentObserver(any(), eq(true), any());
+
+ mConversationListLiveData.observe(mMockLifecycleOwner,
+ (value) -> mMockObserver.onChanged(value));
+ mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+ verify(mMockContentResolver).registerContentObserver(
+ mCaptor.capture(), eq(true), any());
+ assertThat(mCaptor.getValue()).isEqualTo(Telephony.MmsSms.CONTENT_URI);
+ }
+
+ @Test
+ @UiThreadTest
+ public void testOnDataChange() {
+ when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+ when(mMockCursor.getColumnIndex(any())).thenReturn(0);
+ when(mMockCursor.getString(anyInt())).thenReturn("0");
+
+ // allow fetchConversation to return mock conversations below, then exit loop
+ when(mMockCursor.moveToNext()).thenReturn(true, true, true, false);
+
+ MockitoSession session = mockitoSession().strictness(Strictness.LENIENT)
+ .spyStatic(ConversationFetchUtil.class)
+ .spyStatic(ConversationsPerDeviceFetchManager.class)
+ .startMocking();
+ try {
+ doReturn(mMockCursor).when(() ->
+ ConversationsPerDeviceFetchManager.getCursor(anyInt()));
+
+ Conversation conv1 = buildConversation(/* id= */ "1", /* timestamp */ 300);
+ Conversation conv2 = buildConversation(/* id= */ "2", /* timestamp */ 100);
+ Conversation conv3 = buildConversation(/* id= */ "3", /* timestamp */ 200);
+
+ doReturn(conv1, conv2, conv3).when(
+ () -> ConversationFetchUtil.fetchConversation(any()));
+
+ mConversationListLiveData.observe(mMockLifecycleOwner,
+ (value) -> mMockObserver.onChanged(value));
+ assertThat(mConversationListLiveData.getValue()).isNull();
+ mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+ // verify results are sorted by timestamp desc
+ assertThat(mConversationListLiveData.getValue())
+ .containsExactly(conv1, conv3, conv2).inOrder();
+ } finally {
+ session.finishMocking();
+ }
+ }
+
+ private Conversation buildConversation(String id, long timestamp) {
+ Person person = mock(Person.class);
+ Conversation.Message message = new Conversation.Message("", timestamp, person);
+ List<Conversation.Message> messages = Arrays.asList(message);
+
+ return new Conversation.Builder(person, id)
+ .setMessages(messages)
+ .build();
+ }
+}
diff --git a/tests/src/com/android/car/messenger/impl/datamodels/NewMessageLiveDataTest.java b/tests/src/com/android/car/messenger/impl/datamodels/NewMessageLiveDataTest.java
new file mode 100644
index 0000000..2a8cfa9
--- /dev/null
+++ b/tests/src/com/android/car/messenger/impl/datamodels/NewMessageLiveDataTest.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2022 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.car.messenger.impl.datamodels;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Telephony;
+
+import androidx.core.app.Person;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
+import androidx.lifecycle.Observer;
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.car.apps.common.testutils.InstantTaskExecutorRule;
+import com.android.car.messenger.common.Conversation;
+import com.android.car.messenger.core.models.UserAccount;
+import com.android.car.messenger.core.shared.MessageConstants;
+import com.android.car.messenger.core.util.CarStateListener;
+import com.android.car.messenger.impl.datamodels.util.ConversationFetchUtil;
+import com.android.car.messenger.impl.AppFactoryTestImpl;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class NewMessageLiveDataTest {
+
+ private static final int USER_ACCOUNT_ID = 0;
+
+ private NewMessageLiveData mNewMessageLiveData;
+ private AppFactoryTestImpl mAppFactory;
+
+ /** Used to execute livedata.postValue() synchronously */
+ @Rule
+ public TestRule rule = new InstantTaskExecutorRule();
+
+ private LifecycleRegistry mLifecycleRegistry;
+ @Mock
+ private LifecycleOwner mMockLifecycleOwner;
+ @Mock
+ private Observer<Conversation> mMockObserver;
+ private Context mContext;
+ @Mock
+ private ContentResolver mMockContentResolver;
+ @Mock
+ private Cursor mMockCursor;
+ @Mock
+ private UserAccount mMockUserAccount;
+ @Mock
+ private UserAccountLiveData mMockUserAccountLiveData;
+ @Mock
+ private CarStateListener mMockCarStateListener;
+ private ArrayList<UserAccount> mUserAccountList;
+ @Captor
+ private ArgumentCaptor<Uri> mUriCaptor;
+ @Captor
+ private ArgumentCaptor<String> mQueryCaptor;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(ApplicationProvider.getApplicationContext());
+ mAppFactory = new AppFactoryTestImpl(mContext, null, null, mMockCarStateListener);
+
+ when(mMockUserAccount.getId()).thenReturn(USER_ACCOUNT_ID);
+ when(mMockUserAccount.getConnectionTime()).thenReturn(Instant.ofEpochMilli(0));
+
+ mLifecycleRegistry = new LifecycleRegistry(mMockLifecycleOwner);
+ when(mMockLifecycleOwner.getLifecycle()).thenReturn(mLifecycleRegistry);
+
+ mUserAccountList = new ArrayList<>();
+ mUserAccountList.add(mMockUserAccount);
+
+ // Sets up default values for stubbed objects related to database queries.
+ when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+ when(mMockContentResolver.query(any(), any(), any(), any(), any()))
+ .thenReturn(mMockCursor);
+ when(mMockCursor.getColumnIndex(any())).thenReturn(0);
+ when(mMockCursor.getString(anyInt())).thenReturn("0");
+ when(mMockCursor.moveToFirst()).thenReturn(true);
+ }
+
+ @After
+ public void teardown() {
+ mAppFactory.teardown();
+ }
+
+ @Test
+ public void testUri() {
+ doNothing().when(mMockContentResolver).registerContentObserver(any(), eq(true), any());
+
+ MockitoSession session = mockitoSession().strictness(Strictness.LENIENT)
+ .spyStatic(UserAccountLiveData.class)
+ .startMocking();
+ try {
+ doReturn(mMockUserAccountLiveData).when(() -> UserAccountLiveData.getInstance());
+
+ mNewMessageLiveData = new NewMessageLiveData();
+ mNewMessageLiveData.observe(mMockLifecycleOwner,
+ (value) -> mMockObserver.onChanged(value));
+ mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+ verify(mMockContentResolver).registerContentObserver(
+ mUriCaptor.capture(), eq(true), any());
+ assertThat(mUriCaptor.getValue()).isEqualTo(Telephony.MmsSms.CONTENT_URI);
+ } finally {
+ session.finishMocking();
+ }
+ }
+
+ @Test
+ @UiThreadTest
+ public void testOnDataChanged() {
+ MockitoSession session = mockitoSession().strictness(Strictness.LENIENT)
+ .spyStatic(ConversationFetchUtil.class)
+ .spyStatic(UserAccountLiveData.class)
+ .startMocking();
+ try {
+ Conversation conversation = new Conversation.Builder(
+ new Person.Builder().build(), /* conversationId= */ "0").build();
+ doReturn(conversation).when(() -> ConversationFetchUtil.fetchConversation(any()));
+ doReturn(mMockUserAccountLiveData).when(() -> UserAccountLiveData.getInstance());
+
+ mNewMessageLiveData = new NewMessageLiveData();
+ mNewMessageLiveData.mUserAccounts = mUserAccountList;
+ mNewMessageLiveData.observe(mMockLifecycleOwner,
+ (value) -> mMockObserver.onChanged(value));
+ assertThat(mNewMessageLiveData.getValue()).isNull();
+ mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+ assertThat(mNewMessageLiveData.getValue()).isEqualTo(conversation);
+ } finally {
+ session.finishMocking();
+ }
+ }
+
+ @Test
+ public void testOnDataChanged_hasProjection() {
+ when(mMockCarStateListener.isProjectionInActiveForeground(any())).thenReturn(true);
+
+ MockitoSession session = mockitoSession().strictness(Strictness.LENIENT)
+ .spyStatic(UserAccountLiveData.class)
+ .startMocking();
+ try {
+ doReturn(mMockUserAccountLiveData).when(() -> UserAccountLiveData.getInstance());
+
+ mNewMessageLiveData = new NewMessageLiveData();
+ mNewMessageLiveData.mUserAccounts = mUserAccountList;
+ assertThat(mNewMessageLiveData.getValue()).isNull();
+ mNewMessageLiveData.onDataChange();
+ assertThat(mNewMessageLiveData.getValue()).isNull();
+ } finally {
+ session.finishMocking();
+ }
+ }
+
+ @Test
+ public void testOffset_map() {
+ Bundle bundle = new Bundle();
+ Conversation.Message message = new Conversation.Message(
+ /* text= */ "", /* timestamp= */ 800, /* person= */ null);
+ Conversation conversation = new Conversation.Builder(
+ new Person.Builder().build(), /* conversationId= */ "0")
+ .setMessages(List.of(message))
+ .setExtras(bundle)
+ .build();
+
+ MockitoSession session = mockitoSession().strictness(Strictness.LENIENT)
+ .spyStatic(ConversationFetchUtil.class)
+ .spyStatic(UserAccountLiveData.class)
+ .startMocking();
+ try {
+ doReturn(conversation).when(() -> ConversationFetchUtil.fetchConversation(any()));
+ doReturn(mMockUserAccountLiveData).when(() -> UserAccountLiveData.getInstance());
+
+ mNewMessageLiveData = new NewMessageLiveData();
+ mNewMessageLiveData.mUserAccounts = mUserAccountList;
+ mNewMessageLiveData.mOffsetMap.clear();
+
+ when(mMockUserAccount.getConnectionTime()).thenReturn(Instant.ofEpochMilli(200));
+
+ // Adding last reply timestamp (500) that is less than existing entry timestamp (800).
+ bundle.putLong(MessageConstants.LAST_REPLY_TIMESTAMP_EXTRA, 500);
+ mNewMessageLiveData.onDataChange();
+ assertThat(mNewMessageLiveData.mOffsetMap).containsEntry(
+ USER_ACCOUNT_ID, Instant.ofEpochMilli(800));
+
+ // Adding last reply timestamp (900) that is larger than existing entry timestamp (800).
+ bundle.putLong(MessageConstants.LAST_REPLY_TIMESTAMP_EXTRA, 900);
+ mNewMessageLiveData.onDataChange();
+ assertThat(mNewMessageLiveData.mOffsetMap).containsEntry(
+ USER_ACCOUNT_ID, Instant.ofEpochMilli(900));
+ } finally {
+ session.finishMocking();
+ }
+ }
+
+ @Test
+ public void testOffset_query() {
+ MockitoSession session = mockitoSession().strictness(Strictness.LENIENT)
+ .spyStatic(ConversationFetchUtil.class)
+ .spyStatic(UserAccountLiveData.class)
+ .startMocking();
+ try {
+ Conversation conversation = new Conversation.Builder(
+ new Person.Builder().build(), /* conversationId= */ "0").build();
+ doReturn(conversation).when(() -> ConversationFetchUtil.fetchConversation(any()));
+ doReturn(mMockUserAccountLiveData).when(() -> UserAccountLiveData.getInstance());
+
+ mNewMessageLiveData = new NewMessageLiveData();
+ mNewMessageLiveData.getMmsCursor(mMockUserAccount, Instant.ofEpochMilli(5000));
+ verify(mMockContentResolver).query(
+ any(), any(), mQueryCaptor.capture(), isNull(), any());
+ assertThat(mQueryCaptor.getValue()).contains("date > 5 AND");
+
+ mNewMessageLiveData.getSmsCursor(mMockUserAccount, Instant.ofEpochMilli(5000));
+ verify(mMockContentResolver, times(2)).query(
+ any(), any(), mQueryCaptor.capture(), isNull(), any());
+ assertThat(mQueryCaptor.getValue()).contains("date > 5000 AND");
+ } finally {
+ session.finishMocking();
+ }
+ }
+}
diff --git a/tests/src/com/android/car/messenger/impl/datamodels/util/ConversationFetchUtilTest.java b/tests/src/com/android/car/messenger/impl/datamodels/util/ConversationFetchUtilTest.java
new file mode 100644
index 0000000..da77e26
--- /dev/null
+++ b/tests/src/com/android/car/messenger/impl/datamodels/util/ConversationFetchUtilTest.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2022 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.car.messenger.impl.datamodels.util;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import androidx.core.app.Person;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.car.messenger.common.Conversation;
+import com.android.car.messenger.common.Conversation.Message;
+import com.android.car.messenger.common.Conversation.Message.MessageStatus;
+import com.android.car.messenger.common.Conversation.Message.MessageType;
+import com.android.car.messenger.core.shared.MessageConstants;
+import com.android.car.messenger.impl.AppFactoryTestImpl;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+@RunWith(AndroidJUnit4.class)
+public class ConversationFetchUtilTest {
+
+ private static final String TEST_CONTACT_ID = "TEST_CONTACT_1";
+ private static final String TEST_CONTACT_ID2 = "TEST_CONTACT_2";
+
+ private AppFactoryTestImpl mAppFactory;
+ private Context mContext;
+ @Mock
+ private SharedPreferences mMockSharedPreferences;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mContext = ApplicationProvider.getApplicationContext();
+ mAppFactory = new AppFactoryTestImpl(
+ mContext, /* dataModel= */ null, mMockSharedPreferences, /* listener= */null);
+ }
+
+ @After
+ public void teardown() {
+ mAppFactory.teardown();
+ }
+
+ @Test
+ public void testFetchConversation_noMessages() {
+ MockitoSession session = mockitoSession().strictness(Strictness.LENIENT)
+ .spyStatic(CursorUtils.class)
+ .spyStatic(MessageUtils.class)
+ .spyStatic(ContactUtils.class)
+ .startMocking();
+
+ Person person = new Person.Builder().build();
+ List<Message> messages = new ArrayList<>();
+
+ try {
+ setupFetch(person);
+ doReturn(messages).when(() -> MessageUtils.getMessages(anyInt(), any(), any()));
+
+ Conversation conversation = ConversationFetchUtil.fetchConversation(TEST_CONTACT_ID);
+ assertThat(conversation.getMessages()).isEmpty();
+ assertThat(conversation.getExtras().containsKey(MessageConstants.LAST_REPLY_TEXT_EXTRA))
+ .isFalse();
+ } finally {
+ session.finishMocking();
+ }
+ }
+
+ @Test
+ public void testFetchConversation_unreadMessages() {
+ MockitoSession session = mockitoSession().strictness(Strictness.LENIENT)
+ .spyStatic(CursorUtils.class)
+ .spyStatic(MessageUtils.class)
+ .spyStatic(ContactUtils.class)
+ .startMocking();
+
+ Person person = new Person.Builder().build();
+ Message msg = new Message("test1", /* timestamp= */ 1, person)
+ .setMessageStatus(MessageStatus.MESSAGE_STATUS_UNREAD);
+ List<Message> messages = Arrays.asList(msg);
+
+ try {
+ setupFetch(person);
+ doReturn(messages).when(() -> MessageUtils.getMessages(anyInt(), any(), any()));
+
+ Conversation conversation = ConversationFetchUtil.fetchConversation(TEST_CONTACT_ID);
+ assertThat(conversation.getMessages()).containsExactly(msg).inOrder();
+ assertThat(conversation.getExtras().containsKey(MessageConstants.LAST_REPLY_TEXT_EXTRA))
+ .isFalse();
+ } finally {
+ session.finishMocking();
+ }
+ }
+
+ @Test
+ public void testFetchConversation_readAndReplyMessages() {
+ MockitoSession session = mockitoSession().strictness(Strictness.LENIENT)
+ .spyStatic(CursorUtils.class)
+ .spyStatic(MessageUtils.class)
+ .spyStatic(ContactUtils.class)
+ .startMocking();
+
+ Person person = new Person.Builder().build();
+ Message msg = new Message("test1", /* timestamp= */ 1, person)
+ .setMessageStatus(MessageStatus.MESSAGE_STATUS_READ);
+ Message reply = new Message("reply1", /* timestamp= */ 2, person)
+ .setMessageType(MessageType.MESSAGE_TYPE_SENT);
+ List<Message> messages = Arrays.asList(msg, reply);
+
+ try {
+ setupFetch(person);
+ doReturn(messages).when(() -> MessageUtils.getMessages(anyInt(), any(), any()));
+
+ Conversation conversation = ConversationFetchUtil.fetchConversation(TEST_CONTACT_ID);
+ assertThat(conversation.getMessages()).containsExactly(msg);
+ assertThat(conversation.getExtras().getString(MessageConstants.LAST_REPLY_TEXT_EXTRA))
+ .isEqualTo(reply.getText());
+ } finally {
+ session.finishMocking();
+ }
+ }
+
+ @Test
+ public void testLoadMutedLists() {
+ Set<String> mutedList = new HashSet<>();
+ mutedList.add(TEST_CONTACT_ID);
+ mutedList.add(TEST_CONTACT_ID2);
+
+ when(mMockSharedPreferences.getStringSet(
+ eq(MessageConstants.KEY_MUTED_CONVERSATIONS), any())).thenReturn(mutedList);
+
+ assertThat(ConversationFetchUtil.loadMutedList()).isEqualTo(mutedList);
+ }
+
+ private void setupFetch(Person person) {
+ doReturn(Arrays.asList(person)).when(
+ () -> ContactUtils.getRecipients(anyString(), any()));
+ doReturn(null).when(() -> CursorUtils.getMessagesCursor(
+ anyString(), anyInt(), eq(0L), eq(CursorUtils.ContentType.MMS)));
+ doReturn(null).when(() -> CursorUtils.getMessagesCursor(
+ anyString(), anyInt(), eq(0L), eq(CursorUtils.ContentType.SMS)));
+ }
+}
diff --git a/tests/src/com/android/car/messenger/impl/datamodels/util/MessageUtilsTest.java b/tests/src/com/android/car/messenger/impl/datamodels/util/MessageUtilsTest.java
new file mode 100644
index 0000000..7bd53c7
--- /dev/null
+++ b/tests/src/com/android/car/messenger/impl/datamodels/util/MessageUtilsTest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2022 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.car.messenger.impl.datamodels.util;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.database.Cursor;
+import android.util.Pair;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.car.messenger.common.Conversation.Message;
+import com.android.car.messenger.common.Conversation.Message.MessageStatus;
+import com.android.car.messenger.common.Conversation.Message.MessageType;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class MessageUtilsTest {
+
+ private static final int MESSAGE_LIMIT = 10;
+
+ @Test
+ public void testGetMessages() {
+ MmsSmsMessage msg1 = createMessage(
+ /* id= */ "1",
+ /* timestamp= */ 1,
+ /* body= */ "",
+ /* type= */ MessageType.MESSAGE_TYPE_SENT,
+ /* isRead= */ true);
+ MmsSmsMessage msg2 = createMessage(
+ /* id= */ "2",
+ /* timestamp= */ 2,
+ /* body= */ "text2",
+ /* type= */ MessageType.MESSAGE_TYPE_ALL,
+ /* isRead= */ true);
+ MmsSmsMessage msg3 = createMessage(
+ /* id= */ "3",
+ /* timestamp= */ 3,
+ /* body= */ "text3",
+ /* type= */ MessageType.MESSAGE_TYPE_INBOX,
+ /* isRead= */ false);
+
+
+ MockitoSession session = mockitoSession().strictness(Strictness.LENIENT)
+ .spyStatic(MmsUtils.class)
+ .spyStatic(SmsUtils.class)
+ .startMocking();
+
+ try {
+ Cursor smsCursor = mock(Cursor.class);
+ Cursor mmsCursor = mock(Cursor.class);
+
+ // Mocks smsCursor to return a single message, and mmsCursor to return two messages.
+ doReturn(true).when(() -> MmsUtils.isMms(mmsCursor));
+ doReturn(false).when(() -> MmsUtils.isMms(smsCursor));
+ doReturn(msg2, msg1).when(() -> MmsUtils.parseMms(any(), eq(mmsCursor)));
+ doReturn(msg3).when(() -> SmsUtils.parseSms(smsCursor));
+ when(smsCursor.moveToFirst()).thenReturn(true);
+ when(smsCursor.moveToNext()).thenReturn(false);
+ when(mmsCursor.moveToFirst()).thenReturn(true);
+ when(mmsCursor.moveToNext()).thenReturn(true, false);
+
+ // Tests that empty messages are skipped and returned messages are in descending order
+ List<Message> messages = MessageUtils.getMessages(MESSAGE_LIMIT, mmsCursor, smsCursor);
+ assertThat(messages).hasSize(2);
+ assertThat(messages.get(0).getText()).isEqualTo("text3");
+ assertThat(messages.get(1).getText()).isEqualTo("text2");
+ } finally {
+ session.finishMocking();
+ }
+ }
+
+ @Test
+ public void testGetUnreadMessages() {
+ Message msg = new Message("text1", /* timestamp= */ 1, /* person= */ null)
+ .setMessageStatus(MessageStatus.MESSAGE_STATUS_READ);
+ Message msg2 = new Message("text2", /* timestamp= */ 2, /* person= */ null)
+ .setMessageStatus(MessageStatus.MESSAGE_STATUS_UNREAD);
+ Message msg3 = new Message("text3", /* timestamp= */ 3, /* person= */ null)
+ .setMessageStatus(MessageStatus.MESSAGE_STATUS_UNREAD);
+
+ List<Message> messages = MessageUtils.getUnreadMessages(Arrays.asList(msg3, msg2, msg));
+
+ assertThat(messages).containsExactly(msg2, msg3).inOrder();
+ }
+
+ @Test
+ public void testGetReadMessagesAndReplyTimestamp() {
+ Message msg = new Message("text1", /* timestamp= */ 1, /* person= */ null)
+ .setMessageType(MessageType.MESSAGE_TYPE_SENT);
+ Message msg2 = new Message("text2", /* timestamp= */ 2, /* person= */ null)
+ .setMessageStatus(MessageStatus.MESSAGE_STATUS_READ);
+ Message msg3 = new Message("text3", /* timestamp= */ 3, /* person= */ null)
+ .setMessageStatus(MessageStatus.MESSAGE_STATUS_READ);
+ Message msg4 = new Message("text4", /* timestamp= */ 4, /* person= */ null)
+ .setMessageType(MessageType.MESSAGE_TYPE_SENT);
+
+ Pair<List<Message>, Message> pair =
+ MessageUtils.getReadMessagesAndReplyTimestamp(Arrays.asList(msg4, msg3, msg2, msg));
+ List<Message> messages = pair.first;
+ Message reply = pair.second;
+
+ assertThat(messages).containsExactly(msg2, msg3).inOrder();
+ assertThat(reply).isEqualTo(msg4);
+ }
+
+ private MmsSmsMessage createMessage(
+ String id, long timestamp, String body, int type, boolean isRead) {
+ MmsSmsMessage message = new MmsSmsMessage();
+ message.mId = id;
+ message.mThreadId = Integer.parseInt(id);
+ message.mType = type;
+ message.mRead = isRead;
+ message.mDate = Instant.ofEpochMilli(timestamp);
+ message.mBody = body;
+ return message;
+ }
+}
diff --git a/tests/src/com/android/car/messenger/impl/datamodels/util/MmsUtilsTest.java b/tests/src/com/android/car/messenger/impl/datamodels/util/MmsUtilsTest.java
new file mode 100644
index 0000000..83efdcf
--- /dev/null
+++ b/tests/src/com/android/car/messenger/impl/datamodels/util/MmsUtilsTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2022 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.car.messenger.impl.datamodels.util;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.provider.Telephony;
+import android.provider.Telephony.Mms;
+import android.provider.Telephony.Sms;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.car.messenger.common.Conversation.Message.MessageType;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.time.Instant;
+
+@RunWith(AndroidJUnit4.class)
+public class MmsUtilsTest {
+
+ private static final int ID_INDEX = 0;
+ private static final int THREAD_ID_INDEX = 1;
+ private static final int RECIPIENTS_INDEX = 2;
+ private static final int BODY_INDEX = 3;
+ private static final int SUBSCRIPTION_ID_INDEX = 4;
+ private static final int DATE_INDEX = 5;
+ private static final int TYPE_INDEX = 6;
+ private static final int READ_INDEX = 7;
+ private static final int CONTENT_TYPE_INDEX = 8;
+
+ private Context mContext;
+ @Mock
+ private Cursor mMockCursor;
+ @Mock
+ private Cursor mMockBodyCursor;
+ @Mock
+ private Cursor mMockPNCursor;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mContext = ApplicationProvider.getApplicationContext();
+
+ when(mMockCursor.getColumnIndex(Sms._ID)).thenReturn(ID_INDEX);
+ when(mMockCursor.getColumnIndex(Sms.THREAD_ID)).thenReturn(THREAD_ID_INDEX);
+ when(mMockCursor.getColumnIndex(Mms.MESSAGE_BOX)).thenReturn(TYPE_INDEX);
+ when(mMockCursor.getColumnIndex(Sms.SUBSCRIPTION_ID)).thenReturn(SUBSCRIPTION_ID_INDEX);
+ when(mMockCursor.getColumnIndex(Sms.DATE)).thenReturn(DATE_INDEX);
+ when(mMockCursor.getColumnIndex(Sms.READ)).thenReturn(READ_INDEX);
+ when(mMockCursor.getColumnIndex(Telephony.BaseMmsColumns.CONTENT_TYPE))
+ .thenReturn(CONTENT_TYPE_INDEX);
+ }
+
+ @Test
+ public void testParseMms() {
+ String id = "0";
+ int threadId = 0;
+ String phoneNumber = "1234567890";
+ String body = "text";
+ int subscriptionId = 0;
+ int type = MessageType.MESSAGE_TYPE_SENT;
+ long timestamp = 123;
+ int read = 1;
+
+ MockitoSession session = mockitoSession().strictness(Strictness.LENIENT)
+ .spyStatic(CursorUtils.class)
+ .startMocking();
+ try {
+ doReturn(mMockBodyCursor).when(() -> CursorUtils.simpleQuery(any(), any()));
+ doReturn(mMockPNCursor).when(() ->
+ CursorUtils.simpleQueryWithSelection(any(), any(), any()));
+
+ // Mock pnCursor and bodyCursor to only return one row
+ when(mMockBodyCursor.moveToNext()).thenReturn(true, false);
+ when(mMockBodyCursor.getColumnIndex(Mms.Part.TEXT)).thenReturn(BODY_INDEX);
+ when(mMockBodyCursor.isLast()).thenReturn(true);
+ when(mMockPNCursor.moveToFirst()).thenReturn(true);
+ when(mMockPNCursor.getColumnIndex(Mms.Addr.ADDRESS)).thenReturn(RECIPIENTS_INDEX);
+
+ when(mMockCursor.getString(ID_INDEX)).thenReturn(id);
+ when(mMockCursor.getInt(THREAD_ID_INDEX)).thenReturn(threadId);
+ when(mMockPNCursor.getString(RECIPIENTS_INDEX)).thenReturn(phoneNumber);
+ when(mMockBodyCursor.getString(BODY_INDEX)).thenReturn(body);
+ when(mMockCursor.getInt(SUBSCRIPTION_ID_INDEX)).thenReturn(subscriptionId);
+ when(mMockCursor.getInt(TYPE_INDEX)).thenReturn(type);
+ when(mMockCursor.getLong(DATE_INDEX)).thenReturn(timestamp);
+ when(mMockCursor.getInt(READ_INDEX)).thenReturn(read);
+
+ MmsSmsMessage message = MmsUtils.parseMms(mContext, mMockCursor);
+
+ assertThat(message.mId).isEqualTo(id);
+ assertThat(message.mThreadId).isEqualTo(threadId);
+ assertThat(message.mPhoneNumber).isEqualTo(phoneNumber);
+ assertThat(message.mBody).isEqualTo(body);
+ assertThat(message.mSubscriptionId).isEqualTo(subscriptionId);
+ assertThat(message.mType).isEqualTo(type);
+ assertThat(message.mDate).isEqualTo(Instant.ofEpochSecond(timestamp));
+ assertThat(message.mRead).isTrue();
+
+ } finally {
+ session.finishMocking();
+ }
+ }
+
+ @Test
+ public void testIsMms() {
+ when(mMockCursor.getString(CONTENT_TYPE_INDEX)).thenReturn(MmsUtils.MMS_CONTENT_TYPE);
+ assertThat(MmsUtils.isMms(mMockCursor)).isTrue();
+
+ when(mMockCursor.getString(CONTENT_TYPE_INDEX)).thenReturn("");
+ assertThat(MmsUtils.isMms(mMockCursor)).isFalse();
+ }
+}
diff --git a/tests/src/com/android/car/messenger/impl/datamodels/util/SmsUtilsTest.java b/tests/src/com/android/car/messenger/impl/datamodels/util/SmsUtilsTest.java
new file mode 100644
index 0000000..24501b0
--- /dev/null
+++ b/tests/src/com/android/car/messenger/impl/datamodels/util/SmsUtilsTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 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.car.messenger.impl.datamodels.util;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.database.Cursor;
+import android.provider.Telephony.Sms;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.car.messenger.common.Conversation.Message.MessageType;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.time.Instant;
+
+@RunWith(AndroidJUnit4.class)
+public class SmsUtilsTest {
+
+ private static final int ID_INDEX = 0;
+ private static final int THREAD_ID_INDEX = 1;
+ private static final int RECIPIENTS_INDEX = 2;
+ private static final int BODY_INDEX = 3;
+ private static final int SUBSCRIPTION_ID_INDEX = 4;
+ private static final int DATE_INDEX = 5;
+ private static final int TYPE_INDEX = 6;
+ private static final int READ_INDEX = 7;
+
+ @Mock
+ private Cursor mMockCursor;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mMockCursor.getColumnIndex(Sms.THREAD_ID)).thenReturn(THREAD_ID_INDEX);
+ when(mMockCursor.getColumnIndex(Sms.ADDRESS)).thenReturn(RECIPIENTS_INDEX);
+ when(mMockCursor.getColumnIndex(Sms.BODY)).thenReturn(BODY_INDEX);
+ when(mMockCursor.getColumnIndex(Sms.SUBSCRIPTION_ID)).thenReturn(SUBSCRIPTION_ID_INDEX);
+ when(mMockCursor.getColumnIndex(Sms.DATE)).thenReturn(DATE_INDEX);
+ when(mMockCursor.getColumnIndex(Sms.TYPE)).thenReturn(TYPE_INDEX);
+ when(mMockCursor.getColumnIndex(Sms.READ)).thenReturn(READ_INDEX);
+ when(mMockCursor.getColumnIndex(Sms._ID)).thenReturn(ID_INDEX);
+ }
+
+ @Test
+ public void parseSmsTest() {
+ String id = "0";
+ int threadId = 0;
+ String phoneNumber = "1234567890";
+ String body = "text";
+ int subscriptionId = 0;
+ int type = MessageType.MESSAGE_TYPE_SENT;
+ long timestamp = 123;
+ int read = 1;
+
+ when(mMockCursor.getString(ID_INDEX)).thenReturn(id);
+ when(mMockCursor.getInt(THREAD_ID_INDEX)).thenReturn(threadId);
+ when(mMockCursor.getString(RECIPIENTS_INDEX)).thenReturn(phoneNumber);
+ when(mMockCursor.getString(BODY_INDEX)).thenReturn(body);
+ when(mMockCursor.getInt(SUBSCRIPTION_ID_INDEX)).thenReturn(subscriptionId);
+ when(mMockCursor.getInt(TYPE_INDEX)).thenReturn(type);
+ when(mMockCursor.getLong(DATE_INDEX)).thenReturn(timestamp);
+ when(mMockCursor.getInt(READ_INDEX)).thenReturn(read);
+
+ MmsSmsMessage message = SmsUtils.parseSms(mMockCursor);
+
+ assertThat(message.mId).isEqualTo(id);
+ assertThat(message.mThreadId).isEqualTo(threadId);
+ assertThat(message.mPhoneNumber).isEqualTo(phoneNumber);
+ assertThat(message.mBody).isEqualTo(body);
+ assertThat(message.mSubscriptionId).isEqualTo(subscriptionId);
+ assertThat(message.mType).isEqualTo(type);
+ assertThat(message.mDate).isEqualTo(Instant.ofEpochMilli(timestamp));
+ assertThat(message.mRead).isTrue();
+ }
+}