• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.bluetooth.mapclient;
18 
19 import static android.Manifest.permission.BLUETOOTH_CONNECT;
20 
21 import static com.google.common.truth.Truth.assertThat;
22 
23 import static org.mockito.ArgumentMatchers.eq;
24 import static org.mockito.Mockito.*;
25 
26 import android.annotation.Nullable;
27 import android.app.BroadcastOptions;
28 import android.bluetooth.BluetoothAdapter;
29 import android.bluetooth.BluetoothDevice;
30 import android.bluetooth.BluetoothMapClient;
31 import android.bluetooth.BluetoothProfile;
32 import android.bluetooth.SdpMasRecord;
33 import android.content.ContentValues;
34 import android.content.Context;
35 import android.content.Intent;
36 import android.database.Cursor;
37 import android.net.Uri;
38 import android.os.Handler;
39 import android.os.Looper;
40 import android.os.Message;
41 import android.provider.Telephony.Sms;
42 import android.provider.Telephony.Mms;
43 import android.telephony.TelephonyManager;
44 import android.telephony.SubscriptionManager;
45 import android.test.mock.MockContentProvider;
46 import android.test.mock.MockContentResolver;
47 import android.util.Log;
48 
49 import androidx.test.InstrumentationRegistry;
50 import androidx.test.filters.MediumTest;
51 import androidx.test.rule.ServiceTestRule;
52 import androidx.test.runner.AndroidJUnit4;
53 
54 import com.android.bluetooth.R;
55 import com.android.bluetooth.TestUtils;
56 import com.android.bluetooth.btservice.AdapterService;
57 import com.android.bluetooth.btservice.storage.DatabaseManager;
58 import com.android.obex.HeaderSet;
59 import com.android.vcard.VCardConstants;
60 import com.android.vcard.VCardEntry;
61 import com.android.vcard.VCardProperty;
62 
63 import com.google.common.truth.Correspondence;
64 
65 import org.junit.After;
66 import org.junit.Assert;
67 import org.junit.Assume;
68 import org.junit.Before;
69 import org.junit.Rule;
70 import org.junit.Test;
71 import org.junit.runner.RunWith;
72 import org.mockito.ArgumentCaptor;
73 import org.mockito.Mock;
74 import org.mockito.Mockito;
75 import org.mockito.MockitoAnnotations;
76 
77 import java.time.Instant;
78 import java.util.ArrayList;
79 import java.util.Date;
80 import java.util.HashMap;
81 import java.util.List;
82 import java.util.Map;
83 
84 @MediumTest
85 @RunWith(AndroidJUnit4.class)
86 public class MapClientStateMachineTest {
87 
88     private static final String TAG = "MapStateMachineTest";
89     private static final String FOLDER_SENT = "sent";
90 
91     private static final int ASYNC_CALL_TIMEOUT_MILLIS = 100;
92     private static final int DISCONNECT_TIMEOUT = 3000;
93 
94     private Bmessage mTestIncomingSmsBmessage;
95     private Bmessage mTestIncomingMmsBmessage;
96     private String mTestMessageSmsHandle = "0001";
97     private String mTestMessageMmsHandle = "0002";
98 
99     private static final boolean MESSAGE_SEEN = true;
100     private static final boolean MESSAGE_NOT_SEEN = false;
101 
102     private VCardEntry mOriginator;
103 
104     @Rule
105     public final ServiceTestRule mServiceRule = new ServiceTestRule();
106     private BluetoothAdapter mAdapter;
107     private MceStateMachine mMceStateMachine = null;
108     private BluetoothDevice mTestDevice;
109     private Context mTargetContext;
110     private Handler mHandler;
111     private ArgumentCaptor<Intent> mIntentArgument = ArgumentCaptor.forClass(Intent.class);
112     @Mock
113     private AdapterService mAdapterService;
114     @Mock
115     private DatabaseManager mDatabaseManager;
116     @Mock
117     private MapClientService mMockMapClientService;
118     @Mock
119     private MapClientContent mMockDatabase;
120     private MockContentResolver mMockContentResolver;
121     private MockSmsContentProvider mMockContentProvider;
122 
123     @Mock
124     private TelephonyManager mMockTelephonyManager;
125 
126     @Mock
127     private MasClient mMockMasClient;
128 
129     @Mock
130     private RequestPushMessage mMockRequestPushMessage;
131 
132     @Mock
133     private SubscriptionManager mMockSubscriptionManager;
134 
135     private static final String TEST_OWN_PHONE_NUMBER = "555-1234";
136     @Mock
137     private RequestGetMessagesListingForOwnNumber mMockRequestOwnNumberCompletedWithNumber;
138     @Mock
139     private RequestGetMessagesListingForOwnNumber mMockRequestOwnNumberIncompleteSearch;
140     @Mock
141     private RequestGetMessage mMockRequestGetMessage;
142     @Mock
143     private RequestGetMessagesListing mMockRequestGetMessagesListing;
144 
145     private static final Correspondence<Request, String> GET_FOLDER_NAME =
146             Correspondence.transforming(
147             MapClientStateMachineTest::getFolderNameFromRequestGetMessagesListing,
148             "has folder name of");
149 
150     @Before
setUp()151     public void setUp() throws Exception {
152         mTargetContext = InstrumentationRegistry.getTargetContext();
153         MockitoAnnotations.initMocks(this);
154         mMockContentProvider = new MockSmsContentProvider();
155         mMockContentResolver = new MockContentResolver();
156         TestUtils.setAdapterService(mAdapterService);
157         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
158         doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
159         TestUtils.startService(mServiceRule, MapClientService.class);
160         mMockContentResolver.addProvider("sms", mMockContentProvider);
161         mMockContentResolver.addProvider("mms", mMockContentProvider);
162         mMockContentResolver.addProvider("mms-sms", mMockContentProvider);
163 
164         when(mMockMapClientService.getContentResolver()).thenReturn(mMockContentResolver);
165         when(mMockMapClientService.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE))
166                 .thenReturn(mMockSubscriptionManager);
167         when(mMockMapClientService.getSystemServiceName(SubscriptionManager.class))
168                 .thenReturn(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
169 
170         doReturn(mTargetContext.getResources()).when(mMockMapClientService).getResources();
171 
172         // This line must be called to make sure relevant objects are initialized properly
173         mAdapter = BluetoothAdapter.getDefaultAdapter();
174         // Get a device for testing
175         mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05");
176 
177         when(mMockMasClient.makeRequest(any(Request.class))).thenReturn(true);
178         mMceStateMachine = new MceStateMachine(mMockMapClientService, mTestDevice, mMockMasClient,
179                 mMockDatabase);
180         TestUtils.waitForLooperToFinishScheduledTask(mMceStateMachine.getHandler().getLooper());
181         Assert.assertNotNull(mMceStateMachine);
182         if (Looper.myLooper() == null) {
183             Looper.prepare();
184         }
185         mHandler = new Handler();
186 
187         when(mMockRequestOwnNumberCompletedWithNumber.isSearchCompleted()).thenReturn(true);
188         when(mMockRequestOwnNumberCompletedWithNumber.getOwnNumber()).thenReturn(
189                 TEST_OWN_PHONE_NUMBER);
190         when(mMockRequestOwnNumberIncompleteSearch.isSearchCompleted()).thenReturn(false);
191         when(mMockRequestOwnNumberIncompleteSearch.getOwnNumber()).thenReturn(null);
192 
193         createTestMessages();
194 
195         when(mMockRequestGetMessage.getMessage()).thenReturn(mTestIncomingSmsBmessage);
196         when(mMockRequestGetMessage.getHandle()).thenReturn(mTestMessageSmsHandle);
197 
198         when(mMockMapClientService.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(
199                 mMockTelephonyManager);
200         when(mMockTelephonyManager.isSmsCapable()).thenReturn(false);
201 
202     }
203 
204     @After
tearDown()205     public void tearDown() throws Exception {
206         if (mMceStateMachine != null) {
207             mMceStateMachine.doQuit();
208         }
209         TestUtils.stopService(mServiceRule, MapClientService.class);
210         TestUtils.clearAdapterService(mAdapterService);
211     }
212 
213     /**
214      * Test that default state is STATE_CONNECTING
215      */
216     @Test
testDefaultConnectingState()217     public void testDefaultConnectingState() {
218         Log.i(TAG, "in testDefaultConnectingState");
219         Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, mMceStateMachine.getState());
220     }
221 
222     /**
223      * Test transition from STATE_CONNECTING --> (receive MSG_MAS_DISCONNECTED) -->
224      * STATE_DISCONNECTED
225      */
226     @Test
testStateTransitionFromConnectingToDisconnected()227     public void testStateTransitionFromConnectingToDisconnected() {
228         Log.i(TAG, "in testStateTransitionFromConnectingToDisconnected");
229         setupSdpRecordReceipt();
230         Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_DISCONNECTED);
231         mMceStateMachine.sendMessage(msg);
232 
233         // Wait until the message is processed and a broadcast request is sent to
234         // to MapClientService to change
235         // state from STATE_CONNECTING to STATE_DISCONNECTED
236         verify(mMockMapClientService,
237                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcastMultiplePermissions(
238                 mIntentArgument.capture(), any(String[].class),
239                 any(BroadcastOptions.class));
240         Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, mMceStateMachine.getState());
241     }
242 
243     /**
244      * Test transition from STATE_CONNECTING --> (receive MSG_MAS_CONNECTED) --> STATE_CONNECTED
245      */
246     @Test
testStateTransitionFromConnectingToConnected()247     public void testStateTransitionFromConnectingToConnected() {
248         Log.i(TAG, "in testStateTransitionFromConnectingToConnected");
249 
250         setupSdpRecordReceipt();
251         Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED);
252         mMceStateMachine.sendMessage(msg);
253 
254         // Wait until the message is processed and a broadcast request is sent to
255         // to MapClientService to change
256         // state from STATE_CONNECTING to STATE_CONNECTED
257         verify(mMockMapClientService,
258                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcastMultiplePermissions(
259                 mIntentArgument.capture(), any(String[].class),
260                 any(BroadcastOptions.class));
261         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
262     }
263 
264     /**
265      * Test transition from STATE_CONNECTING --> (receive MSG_MAS_CONNECTED) --> STATE_CONNECTED -->
266      * (receive MSG_MAS_DISCONNECTED) --> STATE_DISCONNECTED
267      */
268     @Test
testStateTransitionFromConnectedWithMasDisconnected()269     public void testStateTransitionFromConnectedWithMasDisconnected() {
270         Log.i(TAG, "in testStateTransitionFromConnectedWithMasDisconnected");
271 
272         setupSdpRecordReceipt();
273         Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED);
274         mMceStateMachine.sendMessage(msg);
275 
276         // Wait until the message is processed and a broadcast request is sent to
277         // to MapClientService to change
278         // state from STATE_CONNECTING to STATE_CONNECTED
279         verify(mMockMapClientService,
280                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcastMultiplePermissions(
281                 mIntentArgument.capture(), any(String[].class),
282                 any(BroadcastOptions.class));
283         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
284 
285         msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_DISCONNECTED);
286         mMceStateMachine.sendMessage(msg);
287         verify(mMockMapClientService,
288                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(4)).sendBroadcastMultiplePermissions(
289                 mIntentArgument.capture(), any(String[].class),
290                 any(BroadcastOptions.class));
291 
292         Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, mMceStateMachine.getState());
293     }
294 
295     /**
296      * Test receiving an empty event report
297      */
298     @Test
testReceiveEmptyEvent()299     public void testReceiveEmptyEvent() {
300         setupSdpRecordReceipt();
301         Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED);
302         mMceStateMachine.sendMessage(msg);
303 
304         // Wait until the message is processed and a broadcast request is sent to
305         // to MapClientService to change
306         // state from STATE_CONNECTING to STATE_CONNECTED
307         verify(mMockMapClientService,
308                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcastMultiplePermissions(
309                 mIntentArgument.capture(), any(String[].class),
310                 any(BroadcastOptions.class));
311         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
312 
313         // Send an empty notification event, verify the mMceStateMachine is still connected
314         Message notification = Message.obtain(mHandler, MceStateMachine.MSG_NOTIFICATION);
315         mMceStateMachine.getCurrentState().processMessage(msg);
316         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
317     }
318 
319     /**
320      * Test set message status
321      */
322     @Test
testSetMessageStatus()323     public void testSetMessageStatus() {
324         setupSdpRecordReceipt();
325         Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED);
326         mMceStateMachine.sendMessage(msg);
327 
328         // Wait until the message is processed and a broadcast request is sent to
329         // to MapClientService to change
330         // state from STATE_CONNECTING to STATE_CONNECTED
331         verify(mMockMapClientService,
332                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcastMultiplePermissions(
333                 mIntentArgument.capture(), any(String[].class),
334                 any(BroadcastOptions.class));
335         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
336         Assert.assertTrue(
337                 mMceStateMachine.setMessageStatus("123456789AB", BluetoothMapClient.READ));
338     }
339 
340     /**
341      * Test disconnect
342      */
343     @Test
testDisconnect()344     public void testDisconnect() {
345         setupSdpRecordReceipt();
346         doAnswer(invocation -> {
347             mMceStateMachine.sendMessage(MceStateMachine.MSG_MAS_DISCONNECTED);
348             return null;
349         }).when(mMockMasClient).shutdown();
350         Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED);
351         mMceStateMachine.sendMessage(msg);
352 
353         // Wait until the message is processed and a broadcast request is sent to
354         // to MapClientService to change
355         // state from STATE_CONNECTING to STATE_CONNECTED
356         verify(mMockMapClientService,
357                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcastMultiplePermissions(
358                 mIntentArgument.capture(), any(String[].class),
359                 any(BroadcastOptions.class));
360         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
361 
362         mMceStateMachine.disconnect();
363         verify(mMockMapClientService,
364                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(4)).sendBroadcastMultiplePermissions(
365                 mIntentArgument.capture(), any(String[].class),
366                 any(BroadcastOptions.class));
367         Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, mMceStateMachine.getState());
368     }
369 
370     /**
371      * Test disconnect timeout
372      */
373     @Test
testDisconnectTimeout()374     public void testDisconnectTimeout() {
375         setupSdpRecordReceipt();
376         Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED);
377         mMceStateMachine.sendMessage(msg);
378 
379         // Wait until the message is processed and a broadcast request is sent to
380         // to MapClientService to change
381         // state from STATE_CONNECTING to STATE_CONNECTED
382         verify(mMockMapClientService,
383                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcastMultiplePermissions(
384                 mIntentArgument.capture(), any(String[].class),
385                 any(BroadcastOptions.class));
386         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
387 
388         mMceStateMachine.disconnect();
389         verify(mMockMapClientService,
390                 after(DISCONNECT_TIMEOUT / 2).times(3)).sendBroadcastMultiplePermissions(
391                 mIntentArgument.capture(), any(String[].class),
392                 any(BroadcastOptions.class));
393         Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTING, mMceStateMachine.getState());
394 
395         verify(mMockMapClientService,
396                 timeout(DISCONNECT_TIMEOUT).times(4)).sendBroadcastMultiplePermissions(
397                 mIntentArgument.capture(), any(String[].class),
398                 any(BroadcastOptions.class));
399         Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, mMceStateMachine.getState());
400     }
401 
402     /**
403      * Test sending a message to a phone
404      */
405     @Test
testSendSMSMessageToPhone()406     public void testSendSMSMessageToPhone() {
407         setupSdpRecordReceipt();
408         Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED);
409         mMceStateMachine.sendMessage(msg);
410         TestUtils.waitForLooperToFinishScheduledTask(mMceStateMachine.getHandler().getLooper());
411         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
412 
413         String testMessage = "Hello World!";
414         Uri[] contacts = new Uri[] {Uri.parse("tel://5551212")};
415 
416         verify(mMockMasClient, times(0)).makeRequest(any(RequestPushMessage.class));
417         mMceStateMachine.sendMapMessage(contacts, testMessage, null, null);
418         verify(mMockMasClient, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1))
419                 .makeRequest(any(RequestPushMessage.class));
420     }
421 
422     /**
423      * Test sending a message to an email
424      */
425     @Test
testSendSMSMessageToEmail()426     public void testSendSMSMessageToEmail() {
427         setupSdpRecordReceipt();
428         Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED);
429         mMceStateMachine.sendMessage(msg);
430         TestUtils.waitForLooperToFinishScheduledTask(mMceStateMachine.getHandler().getLooper());
431         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
432 
433         String testMessage = "Hello World!";
434         Uri[] contacts = new Uri[] {Uri.parse("mailto://sms-test@google.com")};
435 
436         verify(mMockMasClient, times(0)).makeRequest(any(RequestPushMessage.class));
437         mMceStateMachine.sendMapMessage(contacts, testMessage, null, null);
438         verify(mMockMasClient, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1))
439                 .makeRequest(any(RequestPushMessage.class));
440     }
441 
442     /**
443      * Test message sent successfully
444      */
445     @Test
testSMSMessageSent()446     public void testSMSMessageSent() {
447         setupSdpRecordReceipt();
448         Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED);
449         mMceStateMachine.sendMessage(msg);
450         TestUtils.waitForLooperToFinishScheduledTask(mMceStateMachine.getHandler().getLooper());
451         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
452 
453         RequestPushMessage testRequest =
454                 new RequestPushMessage(FOLDER_SENT, mTestIncomingSmsBmessage, null, false, false);
455         when(mMockRequestPushMessage.getMsgHandle()).thenReturn(mTestMessageSmsHandle);
456         when(mMockRequestPushMessage.getBMsg()).thenReturn(mTestIncomingSmsBmessage);
457         Message msgSent = Message.obtain(mHandler, MceStateMachine.MSG_MAS_REQUEST_COMPLETED,
458                 mMockRequestPushMessage);
459 
460         mMceStateMachine.sendMessage(msgSent);
461 
462         TestUtils.waitForLooperToFinishScheduledTask(mMceStateMachine.getHandler().getLooper());
463         verify(mMockDatabase,  times(1)).storeMessage(eq(mTestIncomingSmsBmessage),
464                 eq(mTestMessageSmsHandle), any(), eq(MESSAGE_SEEN));
465     }
466 
467     /**
468      * Preconditions:
469      * - In {@code STATE_CONNECTED}.
470      * - {@code MSG_SEARCH_OWN_NUMBER_TIMEOUT} has been set.
471      * - Next stage of connection process has NOT begun, i.e.:
472      *   - Request for Notification Registration not sent
473      *   - Request for MessageListing of SENT folder not sent
474      *   - Request for MessageListing of INBOX folder not sent
475      */
testGetOwnNumber_setup()476     private void testGetOwnNumber_setup() {
477         testStateTransitionFromConnectingToConnected();
478         verify(mMockMasClient, after(ASYNC_CALL_TIMEOUT_MILLIS).never()).makeRequest(
479                 any(RequestSetNotificationRegistration.class));
480         verify(mMockMasClient, never()).makeRequest(any(RequestGetMessagesListing.class));
481         assertThat(mMceStateMachine.getHandler().hasMessages(
482                 MceStateMachine.MSG_SEARCH_OWN_NUMBER_TIMEOUT)).isTrue();
483     }
484 
485     /**
486      * Assert whether the next stage of connection process has begun, i.e., whether the following
487      * {@link Request} are sent or not:
488      * - Request for Notification Registration,
489      * - Request for MessageListing of SENT folder (to start downloading),
490      * - Request for MessageListing of INBOX folder (to start downloading).
491      */
testGetOwnNumber_assertNextStageStarted(boolean hasStarted)492     private void testGetOwnNumber_assertNextStageStarted(boolean hasStarted) {
493         if (hasStarted) {
494             verify(mMockMasClient).makeRequest(any(RequestSetNotificationRegistration.class));
495             verify(mMockMasClient, times(2)).makeRequest(any(RequestGetMessagesListing.class));
496 
497             ArgumentCaptor<Request> requestCaptor = ArgumentCaptor.forClass(Request.class);
498             verify(mMockMasClient, atLeastOnce()).makeRequest(requestCaptor.capture());
499             // There will be multiple calls to {@link MasClient#makeRequest} with different
500             // {@link Request} subtypes; not all of them will be {@link
501             // RequestGetMessagesListing}.
502             List<Request> capturedRequests = requestCaptor.getAllValues();
503             assertThat(capturedRequests).comparingElementsUsing(GET_FOLDER_NAME).contains(
504                     MceStateMachine.FOLDER_INBOX);
505             assertThat(capturedRequests).comparingElementsUsing(GET_FOLDER_NAME).contains(
506                     MceStateMachine.FOLDER_SENT);
507         } else {
508             verify(mMockMasClient, never()).makeRequest(
509                     any(RequestSetNotificationRegistration.class));
510             verify(mMockMasClient, never()).makeRequest(any(RequestGetMessagesListing.class));
511         }
512     }
513 
514     /**
515      * Preconditions:
516      * - See {@link testGetOwnNumber_setup}.
517      *
518      * Actions:
519      * - Send a {@code MSG_MAS_REQUEST_COMPLETED} with a {@link
520      *   RequestGetMessagesListingForOwnNumber} object that has completed its search.
521      *
522      * Outcome:
523      * - {@code MSG_SEARCH_OWN_NUMBER_TIMEOUT} has been cancelled.
524      * - Next stage of connection process has begun, i.e.:
525      *   - Request for Notification Registration is made.
526      *   - Request for MessageListing of SENT folder is made (to start downloading).
527      *   - Request for MessageListing of INBOX folder is made (to start downloading).
528      */
529     @Test
testGetOwnNumberCompleted()530     public void testGetOwnNumberCompleted() {
531         testGetOwnNumber_setup();
532 
533         Message requestCompletedMsg = Message.obtain(mHandler,
534                 MceStateMachine.MSG_MAS_REQUEST_COMPLETED,
535                 mMockRequestOwnNumberCompletedWithNumber);
536         mMceStateMachine.sendMessage(requestCompletedMsg);
537 
538         verify(mMockMasClient, after(ASYNC_CALL_TIMEOUT_MILLIS).never()).makeRequest(
539                 eq(mMockRequestOwnNumberCompletedWithNumber));
540         assertThat(mMceStateMachine.getHandler().hasMessages(
541                 MceStateMachine.MSG_SEARCH_OWN_NUMBER_TIMEOUT)).isFalse();
542         testGetOwnNumber_assertNextStageStarted(true);
543     }
544 
545     /**
546      * Preconditions:
547      * - See {@link testGetOwnNumber_setup}.
548      *
549      * Actions:
550      * - Send a {@code MSG_SEARCH_OWN_NUMBER_TIMEOUT}.
551      *
552      * Outcome:
553      * - {@link MasClient#abortRequest} invoked on a {@link RequestGetMessagesListingForOwnNumber}.
554      * - Any existing {@code MSG_MAS_REQUEST_COMPLETED} (corresponding to a
555      *   {@link RequestGetMessagesListingForOwnNumber}) has been dropped.
556      * - Next stage of connection process has begun, i.e.:
557      *   - Request for Notification Registration is made.
558      *   - Request for MessageListing of SENT folder is made (to start downloading).
559      *   - Request for MessageListing of INBOX folder is made (to start downloading).
560      */
561     @Test
testGetOwnNumberTimedOut()562     public void testGetOwnNumberTimedOut() {
563         testGetOwnNumber_setup();
564         Message requestIncompleteMsg = Message.obtain(mHandler,
565                 MceStateMachine.MSG_MAS_REQUEST_COMPLETED,
566                 mMockRequestOwnNumberIncompleteSearch);
567         mMceStateMachine.sendMessage(requestIncompleteMsg);
568         assertThat(mMceStateMachine.getHandler().hasMessages(
569                 MceStateMachine.MSG_MAS_REQUEST_COMPLETED)).isTrue();
570 
571         Message timeoutMsg = Message.obtain(mHandler,
572                 MceStateMachine.MSG_SEARCH_OWN_NUMBER_TIMEOUT,
573                 mMockRequestOwnNumberIncompleteSearch);
574         mMceStateMachine.sendMessage(timeoutMsg);
575 
576         verify(mMockMasClient, after(ASYNC_CALL_TIMEOUT_MILLIS)).abortRequest(
577                 mMockRequestOwnNumberIncompleteSearch);
578         assertThat(mMceStateMachine.getHandler().hasMessages(
579                 MceStateMachine.MSG_MAS_REQUEST_COMPLETED)).isFalse();
580         testGetOwnNumber_assertNextStageStarted(true);
581     }
582 
583     /**
584      * Preconditions:
585      * - See {@link testGetOwnNumber_setup}.
586      *
587      * Actions:
588      * - Send a {@code MSG_MAS_REQUEST_COMPLETED} with a {@link
589      *   RequestGetMessagesListingForOwnNumber} object that has not completed its search.
590      *
591      * Outcome:
592      * - {@link Request} made to continue searching for own number (using existing/same
593      *   {@link Request}).
594      * - {@code MSG_SEARCH_OWN_NUMBER_TIMEOUT} has not been cancelled.
595      * - Next stage of connection process has not begun, i.e.:
596      *   - No Request for Notification Registration,
597      *   - No Request for MessageListing of SENT folder is made (to start downloading),
598      *   - No Request for MessageListing of INBOX folder is made (to start downloading).
599      */
600     @Test
testGetOwnNumberIncomplete()601     public void testGetOwnNumberIncomplete() {
602         testGetOwnNumber_setup();
603 
604         Message requestIncompleteMsg = Message.obtain(mHandler,
605                 MceStateMachine.MSG_MAS_REQUEST_COMPLETED,
606                 mMockRequestOwnNumberIncompleteSearch);
607         mMceStateMachine.sendMessage(requestIncompleteMsg);
608 
609         verify(mMockMasClient, after(ASYNC_CALL_TIMEOUT_MILLIS)).makeRequest(
610                 eq(mMockRequestOwnNumberIncompleteSearch));
611         assertThat(mMceStateMachine.getHandler().hasMessages(
612                 MceStateMachine.MSG_SEARCH_OWN_NUMBER_TIMEOUT)).isTrue();
613         testGetOwnNumber_assertNextStageStarted(false);
614     }
615 
616     /**
617      * Test seen status set for new SMS
618      */
619      @Test
testReceivedNewSms_messageStoredAsUnseen()620      public void testReceivedNewSms_messageStoredAsUnseen() {
621         setupSdpRecordReceipt();
622         Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED);
623         mMceStateMachine.sendMessage(msg);
624 
625         //verifying that state machine is in the Connected state
626         verify(mMockMapClientService,
627                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcastMultiplePermissions(
628                 mIntentArgument.capture(), any(String[].class),
629                 any(BroadcastOptions.class));
630         assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED);
631 
632         String dateTime = new ObexTime(Instant.now()).toString();
633         EventReport event = createNewEventReport("NewMessage", dateTime, mTestMessageSmsHandle,
634                 "telecom/msg/inbox", null, "SMS_GSM");
635 
636         mMceStateMachine.receiveEvent(event);
637 
638         TestUtils.waitForLooperToBeIdle(mMceStateMachine.getHandler().getLooper());
639         verify(mMockMasClient, times(1)).makeRequest
640                 (any(RequestGetMessage.class));
641 
642         msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_REQUEST_COMPLETED,
643                 mMockRequestGetMessage);
644         mMceStateMachine.sendMessage(msg);
645 
646         TestUtils.waitForLooperToBeIdle(mMceStateMachine.getHandler().getLooper());
647         verify(mMockDatabase, times(1)).storeMessage(eq(mTestIncomingSmsBmessage),
648                 eq(mTestMessageSmsHandle), any(), eq(MESSAGE_NOT_SEEN));
649      }
650 
651      /**
652      * Test seen status set for new MMS
653      */
654      @Test
testReceivedNewMms_messageStoredAsUnseen()655      public void testReceivedNewMms_messageStoredAsUnseen() {
656         setupSdpRecordReceipt();
657         Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED);
658         mMceStateMachine.sendMessage(msg);
659 
660         //verifying that state machine is in the Connected state
661         verify(mMockMapClientService,
662                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcastMultiplePermissions(
663                 mIntentArgument.capture(), any(String[].class),
664                 any(BroadcastOptions.class));
665         assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED);
666 
667         String dateTime = new ObexTime(Instant.now()).toString();
668         EventReport event = createNewEventReport("NewMessage", dateTime, mTestMessageMmsHandle,
669                 "telecom/msg/inbox", null, "MMS");
670 
671         when(mMockRequestGetMessage.getMessage()).thenReturn(mTestIncomingMmsBmessage);
672         when(mMockRequestGetMessage.getHandle()).thenReturn(mTestMessageMmsHandle);
673 
674         mMceStateMachine.receiveEvent(event);
675 
676         TestUtils.waitForLooperToBeIdle(mMceStateMachine.getHandler().getLooper());
677         verify(mMockMasClient, times(1)).makeRequest
678                (any(RequestGetMessage.class));
679 
680         msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_REQUEST_COMPLETED,
681                 mMockRequestGetMessage);
682         mMceStateMachine.sendMessage(msg);
683 
684         TestUtils.waitForLooperToBeIdle(mMceStateMachine.getHandler().getLooper());
685         verify(mMockDatabase, times(1)).storeMessage(eq(mTestIncomingMmsBmessage),
686                 eq(mTestMessageMmsHandle), any(), eq(MESSAGE_NOT_SEEN));
687      }
688 
689      /**
690      * Test seen status set in database on initial download
691      */
692      @Test
testDownloadExistingSms_messageStoredAsSeen()693      public void testDownloadExistingSms_messageStoredAsSeen() {
694         setupSdpRecordReceipt();
695         Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED);
696         mMceStateMachine.sendMessage(msg);
697 
698         verify(mMockMapClientService,
699                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcastMultiplePermissions(
700                 mIntentArgument.capture(), any(String[].class),
701                 any(BroadcastOptions.class));
702         assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED);
703 
704         com.android.bluetooth.mapclient.Message testMessageListingSms = createNewMessage("SMS_GSM",
705                 mTestMessageSmsHandle);
706         ArrayList<com.android.bluetooth.mapclient.Message> messageListSms = new ArrayList<>();
707         messageListSms.add(testMessageListingSms);
708         when(mMockRequestGetMessagesListing.getList()).thenReturn(messageListSms);
709 
710         msg = Message.obtain(mHandler, MceStateMachine.MSG_GET_MESSAGE_LISTING,
711                 MceStateMachine.FOLDER_INBOX);
712         mMceStateMachine.sendMessage(msg);
713 
714         TestUtils.waitForLooperToBeIdle(mMceStateMachine.getHandler().getLooper());
715         verify(mMockMasClient, times(1)).makeRequest(any(
716                 RequestGetMessagesListing.class));
717 
718         msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_REQUEST_COMPLETED,
719                 mMockRequestGetMessagesListing);
720         mMceStateMachine.sendMessage(msg);
721 
722         TestUtils.waitForLooperToBeIdle(mMceStateMachine.getHandler().getLooper());
723         verify(mMockMasClient, times(1)).makeRequest(any(
724                 RequestGetMessage.class));
725 
726         msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_REQUEST_COMPLETED,
727                 mMockRequestGetMessage);
728         mMceStateMachine.sendMessage(msg);
729 
730         TestUtils.waitForLooperToBeIdle(mMceStateMachine.getHandler().getLooper());
731         verify(mMockDatabase, times(1)).storeMessage(any(), any(),
732                 any(), eq(MESSAGE_SEEN));
733      }
734 
735      /**
736      * Test seen status set in database on initial download
737      */
738      @Test
testDownloadExistingMms_messageStoredAsSeen()739      public void testDownloadExistingMms_messageStoredAsSeen() {
740         setupSdpRecordReceipt();
741         Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED);
742         mMceStateMachine.sendMessage(msg);
743 
744         verify(mMockMapClientService,
745                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcastMultiplePermissions(
746                 mIntentArgument.capture(), any(String[].class),
747                 any(BroadcastOptions.class));
748         assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED);
749 
750         com.android.bluetooth.mapclient.Message testMessageListingMms = createNewMessage("MMS",
751                 mTestMessageMmsHandle);
752         ArrayList<com.android.bluetooth.mapclient.Message> messageListMms = new ArrayList<>();
753         messageListMms.add(testMessageListingMms);
754 
755         when(mMockRequestGetMessage.getMessage()).thenReturn(mTestIncomingMmsBmessage);
756         when(mMockRequestGetMessage.getHandle()).thenReturn(mTestMessageMmsHandle);
757         when(mMockRequestGetMessagesListing.getList()).thenReturn(messageListMms);
758 
759         msg = Message.obtain(mHandler, MceStateMachine.MSG_GET_MESSAGE_LISTING,
760                 MceStateMachine.FOLDER_INBOX);
761         mMceStateMachine.sendMessage(msg);
762 
763         TestUtils.waitForLooperToBeIdle(mMceStateMachine.getHandler().getLooper());
764         verify(mMockMasClient, times(1)).makeRequest(any(
765                 RequestGetMessagesListing.class));
766 
767         msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_REQUEST_COMPLETED,
768                 mMockRequestGetMessagesListing);
769         mMceStateMachine.sendMessage(msg);
770 
771         TestUtils.waitForLooperToBeIdle(mMceStateMachine.getHandler().getLooper());
772         verify(mMockMasClient, times(1)).makeRequest(any(
773                 RequestGetMessage.class));
774 
775         msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_REQUEST_COMPLETED,
776                 mMockRequestGetMessage);
777         mMceStateMachine.sendMessage(msg);
778 
779         TestUtils.waitForLooperToBeIdle(mMceStateMachine.getHandler().getLooper());
780         verify(mMockDatabase, times(1)).storeMessage(any(), any(),
781                 any(), eq(MESSAGE_SEEN));
782      }
783 
784     /**
785      * Test receiving a new message notification.
786      */
787     @Test
testReceiveNewMessageNotification()788     public void testReceiveNewMessageNotification() {
789         setupSdpRecordReceipt();
790         Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED);
791         mMceStateMachine.sendMessage(msg);
792 
793         verify(mMockMapClientService,
794                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcastMultiplePermissions(
795                 mIntentArgument.capture(), any(String[].class),
796                 any(BroadcastOptions.class));
797         assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED);
798 
799         // Receive a new message notification.
800         String dateTime = new ObexTime(Instant.now()).toString();
801         EventReport event = createNewEventReport("NewMessage", dateTime, mTestMessageSmsHandle,
802                 "telecom/msg/inbox", null, "SMS_GSM");
803 
804         Message notificationMessage =
805                 Message.obtain(mHandler, MceStateMachine.MSG_NOTIFICATION, (Object)event);
806 
807         mMceStateMachine.getCurrentState().processMessage(notificationMessage);
808 
809         verify(mMockMasClient,
810                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1))
811                         .makeRequest(any(RequestGetMessage.class));
812 
813         MceStateMachine.MessageMetadata messageMetadata =
814                 mMceStateMachine.mMessages.get(mTestMessageSmsHandle);
815         Assert.assertEquals(messageMetadata.getHandle(), mTestMessageSmsHandle);
816         Assert.assertEquals(
817                 new ObexTime(Instant.ofEpochMilli(messageMetadata.getTimestamp())).toString(),
818                 dateTime);
819     }
820 
821     @Test
testReceivedNewMmsNoSMSDefaultPackage_broadcastToSMSReplyPackage()822     public void testReceivedNewMmsNoSMSDefaultPackage_broadcastToSMSReplyPackage() {
823         setupSdpRecordReceipt();
824         Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED);
825         mMceStateMachine.sendMessage(msg);
826 
827         //verifying that state machine is in the Connected state
828         verify(mMockMapClientService,
829                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcastMultiplePermissions(
830                 mIntentArgument.capture(), any(String[].class),
831                 any(BroadcastOptions.class));
832         assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED);
833 
834         String dateTime = new ObexTime(Instant.now()).toString();
835         EventReport event = createNewEventReport("NewMessage", dateTime, mTestMessageSmsHandle,
836                 "telecom/msg/inbox", null, "SMS_GSM");
837 
838         mMceStateMachine.receiveEvent(event);
839 
840         TestUtils.waitForLooperToBeIdle(mMceStateMachine.getHandler().getLooper());
841         verify(mMockMasClient, times(1)).makeRequest
842                 (any(RequestGetMessage.class));
843 
844         msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_REQUEST_COMPLETED,
845                 mMockRequestGetMessage);
846         mMceStateMachine.sendMessage(msg);
847 
848         TestUtils.waitForLooperToBeIdle(mMceStateMachine.getHandler().getLooper());
849         verify(mMockMapClientService,
850                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendBroadcast(
851                 mIntentArgument.capture(),
852                 eq(android.Manifest.permission.RECEIVE_SMS));
853         Assert.assertNull(mIntentArgument.getValue().getPackage());
854     }
855 
setupSdpRecordReceipt()856     private void setupSdpRecordReceipt() {
857         // Perform first part of MAP connection logic.
858         verify(mMockMapClientService,
859                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendBroadcastMultiplePermissions(
860                 mIntentArgument.capture(), any(String[].class),
861                 any(BroadcastOptions.class));
862         Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, mMceStateMachine.getState());
863 
864         // Setup receipt of SDP record
865         SdpMasRecord record = new SdpMasRecord(1, 1, 1, 1, 1, 1, "MasRecord");
866         Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_SDP_DONE, record);
867         mMceStateMachine.sendMessage(msg);
868     }
869 
870     private class MockSmsContentProvider extends MockContentProvider {
871         Map<Uri, ContentValues> mContentValues = new HashMap<>();
872         int mInsertOperationCount = 0;
873 
874         @Override
delete(Uri uri, String selection, String[] selectionArgs)875         public int delete(Uri uri, String selection, String[] selectionArgs) {
876             return 0;
877         }
878 
879         @Override
insert(Uri uri, ContentValues values)880         public Uri insert(Uri uri, ContentValues values) {
881             mInsertOperationCount++;
882             return Uri.withAppendedPath(Sms.CONTENT_URI, String.valueOf(mInsertOperationCount));
883         }
884 
885         @Override
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)886         public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
887                 String sortOrder) {
888             Cursor cursor = Mockito.mock(Cursor.class);
889 
890             when(cursor.moveToFirst()).thenReturn(true);
891             when(cursor.moveToNext()).thenReturn(true).thenReturn(false);
892 
893             when(cursor.getLong(anyInt())).thenReturn((long) mContentValues.size());
894             when(cursor.getString(anyInt())).thenReturn(String.valueOf(mContentValues.size()));
895             return cursor;
896         }
897     }
898 
getFolderNameFromRequestGetMessagesListing( Request request)899     private static String getFolderNameFromRequestGetMessagesListing(
900             Request request) {
901         Log.d(TAG, "getFolderName, Request type=" + request);
902         String folderName = null;
903         if (request instanceof RequestGetMessagesListing) {
904             try {
905                 folderName = (String) request.mHeaderSet.getHeader(HeaderSet.NAME);
906             } catch (Exception e) {
907                 Log.e(TAG, "in getFolderNameFromRequestGetMessagesListing", e);
908             }
909         }
910         Log.d(TAG, "getFolderName, name=" + folderName);
911         return folderName;
912     }
913 
914     // create new Messages from given input
createNewMessage(String mType, String mHandle)915     com.android.bluetooth.mapclient.Message createNewMessage(String mType, String mHandle){
916         HashMap<String, String> attrs = new HashMap<String, String>();
917 
918         attrs.put("type", mType);
919         attrs.put("handle", mHandle);
920         attrs.put("datetime", "20230223T160000");
921 
922         com.android.bluetooth.mapclient.Message message = new com.android.bluetooth.mapclient.
923                 Message(attrs);
924 
925         return message;
926     }
927 
createNewEventReport(String mType, String mDateTime, String mHandle, String mFolder, String mOldFolder, String mMsgType)928     EventReport createNewEventReport(String mType, String mDateTime, String mHandle, String mFolder,
929             String mOldFolder, String mMsgType){
930 
931         HashMap<String, String> attrs = new HashMap<String, String>();
932 
933         attrs.put("type", mType);
934         attrs.put("datetime", mDateTime);
935         attrs.put("handle", mHandle);
936         attrs.put("folder", mFolder);
937         attrs.put("old_folder", mOldFolder);
938         attrs.put("msg_type", mMsgType);
939 
940         EventReport event = new EventReport(attrs);
941 
942         return event;
943 
944     }
945 
946     //create new Bmessages for testing
createTestMessages()947     void createTestMessages() {
948         mOriginator = new VCardEntry();
949         VCardProperty property = new VCardProperty();
950         property.setName(VCardConstants.PROPERTY_TEL);
951         property.addValues("555-1212");
952         mOriginator.addProperty(property);
953 
954         mTestIncomingSmsBmessage = new Bmessage();
955         mTestIncomingSmsBmessage.setBodyContent("HelloWorld");
956         mTestIncomingSmsBmessage.setType(Bmessage.Type.SMS_GSM);
957         mTestIncomingSmsBmessage.setFolder("telecom/msg/inbox");
958         mTestIncomingSmsBmessage.addOriginator(mOriginator);
959         mTestIncomingSmsBmessage.addRecipient(mOriginator);
960 
961         mTestIncomingMmsBmessage = new Bmessage();
962         mTestIncomingMmsBmessage.setBodyContent("HelloWorld");
963         mTestIncomingMmsBmessage.setType(Bmessage.Type.MMS);
964         mTestIncomingMmsBmessage.setFolder("telecom/msg/inbox");
965         mTestIncomingMmsBmessage.addOriginator(mOriginator);
966         mTestIncomingMmsBmessage.addRecipient(mOriginator);
967     }
968 }
969