diff options
author | aae-comm-eng <aae-comm-eng@google.com> | 2022-02-02 16:34:30 -0800 |
---|---|---|
committer | Peter Li <pyli@google.com> | 2022-02-02 16:35:22 -0800 |
commit | 254f4c5bca773b99bafe8664a0be516f893679dd (patch) | |
tree | dc480450ab9d607a935fdf99680cf00b7eece389 | |
parent | b2f8b467b07c361ae653bda7f4325823cf843c14 (diff) | |
download | Messenger-ub_automotive.tar.gz |
Project import generated by Copybara.ub_automotive
PiperOrigin-RevId: 426007610
Change-Id: Id8f8d4874269205a5eeb37e821f1818026ca0a93
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(); + } +} |