1/*
2 * Copyright (C) 2016 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
17package com.android.internal.telephony.gsm;
18
19import android.content.BroadcastReceiver;
20import android.content.ContentValues;
21import android.content.Context;
22import android.content.Intent;
23import android.content.IntentFilter;
24import android.database.Cursor;
25import android.database.MatrixCursor;
26import android.net.Uri;
27import android.os.AsyncResult;
28import android.os.Bundle;
29import android.os.Handler;
30import android.os.HandlerThread;
31import android.os.UserManager;
32import android.os.RemoteException;
33import android.os.UserHandle;
34import android.provider.Telephony;
35import android.test.mock.MockContentProvider;
36import android.test.mock.MockContentResolver;
37import android.test.suitebuilder.annotation.MediumTest;
38
39import com.android.internal.telephony.InboundSmsHandler;
40import com.android.internal.telephony.InboundSmsTracker;
41import com.android.internal.telephony.SmsBroadcastUndelivered;
42import com.android.internal.telephony.SmsHeader;
43import com.android.internal.telephony.SmsStorageMonitor;
44import com.android.internal.telephony.TelephonyTest;
45import com.android.internal.telephony.cdma.CdmaInboundSmsHandler;
46import com.android.internal.util.HexDump;
47import com.android.internal.util.IState;
48import com.android.internal.util.StateMachine;
49
50import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
51import static org.junit.Assert.*;
52import static org.mockito.Mockito.*;
53
54import org.junit.After;
55import org.junit.Before;
56import org.junit.Test;
57import org.mockito.ArgumentCaptor;
58import org.mockito.Mock;
59
60import java.lang.reflect.Method;
61import java.util.ArrayList;
62import java.util.List;
63
64public class GsmInboundSmsHandlerTest extends TelephonyTest {
65    @Mock
66    private SmsStorageMonitor mSmsStorageMonitor;
67    @Mock
68    private android.telephony.SmsMessage mSmsMessage;
69    @Mock
70    private SmsMessage mGsmSmsMessage;
71    @Mock
72    private SmsHeader mSmsHeader;
73    @Mock
74    private InboundSmsTracker mInboundSmsTrackerPart1;
75    @Mock
76    private InboundSmsTracker mInboundSmsTrackerPart2;
77    @Mock
78    private CdmaInboundSmsHandler mCdmaInboundSmsHandler;
79
80    private GsmInboundSmsHandler mGsmInboundSmsHandler;
81
82    private FakeSmsContentProvider mContentProvider;
83    private static final Uri sRawUri = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw");
84    private static final Uri sRawUriPermanentDelete =
85            Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw/permanentDelete");
86
87    private ContentValues mInboundSmsTrackerCV = new ContentValues();
88    // For multi-part SMS
89    private ContentValues mInboundSmsTrackerCVPart1;
90    private ContentValues mInboundSmsTrackerCVPart2;
91    private String mMessageBody = "This is the message body of a single-part message";
92    private String mMessageBodyPart1 = "This is the first part of a multi-part message";
93    private String mMessageBodyPart2 = "This is the second part of a multi-part message";
94
95    byte[] mSmsPdu = new byte[]{(byte)0xFF, (byte)0xFF, (byte)0xFF};
96
97    public static class FakeSmsContentProvider extends MockContentProvider {
98        private String[] mRawColumns = {"_id",
99                "date",
100                "reference_number",
101                "count",
102                "sequence",
103                "destination_port",
104                "address",
105                "sub_id",
106                "pdu",
107                "deleted",
108                "message_body"};
109        private List<ArrayList<Object>> mListOfRows = new ArrayList<ArrayList<Object>>();
110        private int mNumRows = 0;
111
112        private int getColumnIndex(String columnName) {
113            int i = 0;
114            for (String s : mRawColumns) {
115                if (s.equals(columnName)) {
116                    break;
117                }
118                i++;
119            }
120            return i;
121        }
122
123        @Override
124        public int delete(Uri uri, String selection, String[] selectionArgs) {
125            int count = 0;
126            if (mNumRows > 0) {
127                // parse selection and selectionArgs
128                SelectionParams selectionParams = new SelectionParams();
129                selectionParams.parseSelectionParams(selection, selectionArgs);
130
131                List<Integer> deleteRows = new ArrayList<Integer>();
132                int i = -1;
133                for (ArrayList<Object> row : mListOfRows) {
134                    i++;
135                    // filter based on selection parameters if needed
136                    if (selection != null) {
137                        if (!selectionParams.isMatch(row)) {
138                            continue;
139                        }
140                    }
141                    if (uri.compareTo(sRawUri) == 0) {
142                        row.set(getColumnIndex("deleted"), "1");
143                    } else {
144                        // save index for removal
145                        deleteRows.add(i);
146                    }
147                    count++;
148                }
149
150                if (uri.compareTo(sRawUriPermanentDelete) == 0) {
151                    for (i = deleteRows.size() - 1; i >= 0; i--) {
152                        mListOfRows.remove(i);
153                    }
154                }
155            }
156            return count;
157        }
158
159        @Override
160        public Uri insert(Uri uri, ContentValues values) {
161            Uri newUri = null;
162            if (uri.compareTo(sRawUri) == 0) {
163                if (values != null) {
164                    mListOfRows.add(convertRawCVtoArrayList(values));
165                    mNumRows++;
166                    newUri = Uri.withAppendedPath(uri, "" + mNumRows);
167                }
168            }
169            logd("insert called, new numRows: " + mNumRows);
170            return newUri;
171        }
172
173        private ArrayList<Object> convertRawCVtoArrayList(ContentValues values) {
174            ArrayList<Object> newRow = new ArrayList<>();
175            for (String key : mRawColumns) {
176                if (values.containsKey(key)) {
177                    newRow.add(values.getAsString(key));
178                } else if (key.equals("_id")) {
179                    newRow.add(mNumRows + 1);
180                } else if (key.equals("deleted")) {
181                    newRow.add("0");
182                } else {
183                    newRow.add(null);
184                }
185            }
186            return newRow;
187        }
188
189        private class SelectionParams {
190            String[] paramName = null;
191            String[] paramValue = null;
192
193            private void parseSelectionParams(String selection, String[] selectionArgs) {
194                if (selection != null) {
195                    selection = selection.toLowerCase();
196                    String[] selectionParams = selection.toLowerCase().split("and");
197                    int i = 0;
198                    int j = 0;
199                    paramName = new String[selectionParams.length];
200                    paramValue = new String[selectionParams.length];
201                    for (String param : selectionParams) {
202                        String[] paramWithArg = param.split("=");
203                        paramName[i] = paramWithArg[0].trim();
204                        if (param.contains("?")) {
205                            paramValue[i] = selectionArgs[j];
206                            j++;
207                        } else {
208                            paramValue[i] = paramWithArg[1].trim();
209                        }
210                        //logd(paramName[i] + " = " + paramValue[i]);
211                        i++;
212                    }
213                }
214            }
215
216            private boolean isMatch(ArrayList<Object> row) {
217                for (int i = 0; i < paramName.length; i++) {
218                    int columnIndex = 0;
219                    for (String columnName : mRawColumns) {
220                        if (columnName.equals(paramName[i])) {
221                            if ((paramValue[i] == null && row.get(columnIndex) != null) ||
222                                    (paramValue[i] != null &&
223                                            !paramValue[i].equals(row.get(columnIndex)))) {
224                                logd("Not a match due to " + columnName + ": " + paramValue[i] +
225                                        ", " + row.get(columnIndex));
226                                return false;
227                            } else {
228                                // move on to next param
229                                break;
230                            }
231                        }
232                        columnIndex++;
233                    }
234                }
235                return true;
236            }
237        }
238
239        @Override
240        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
241                            String sortOrder) {
242            logd("query called for: " + selection);
243            MatrixCursor cursor = new MatrixCursor(projection);
244            if (mNumRows > 0) {
245                // parse selection and selectionArgs
246                SelectionParams selectionParams = new SelectionParams();
247                selectionParams.parseSelectionParams(selection, selectionArgs);
248
249                for (ArrayList<Object> row : mListOfRows) {
250                    ArrayList<Object> retRow = new ArrayList<>();
251                    // filter based on selection parameters if needed
252                    if (selection != null) {
253                        if (!selectionParams.isMatch(row)) {
254                            continue;
255                        }
256                    }
257
258                    for (String columnName : projection) {
259                        int columnIndex = getColumnIndex(columnName);
260                        retRow.add(row.get(columnIndex));
261                    }
262                    cursor.addRow(retRow);
263                }
264            }
265            if (cursor != null) {
266                logd("returning rows: " + cursor.getCount());
267            }
268            return cursor;
269        }
270
271        @Override
272        public Bundle call(String method, String request, Bundle args) {
273            return null;
274        }
275    }
276
277    private class GsmInboundSmsHandlerTestHandler extends HandlerThread {
278
279        private GsmInboundSmsHandlerTestHandler(String name) {
280            super(name);
281        }
282
283        @Override
284        public void onLooperPrepared() {
285            mGsmInboundSmsHandler = GsmInboundSmsHandler.makeInboundSmsHandler(mContext,
286                    mSmsStorageMonitor, mPhone);
287            setReady(true);
288        }
289    }
290
291    private IState getCurrentState() {
292        try {
293            Method method = StateMachine.class.getDeclaredMethod("getCurrentState");
294            method.setAccessible(true);
295            return (IState) method.invoke(mGsmInboundSmsHandler);
296        } catch (Exception e) {
297            fail(e.toString());
298            return null;
299        }
300    }
301
302    @Before
303    public void setUp() throws Exception {
304        super.setUp("GsmInboundSmsHandlerTest");
305
306        doReturn(true).when(mTelephonyManager).getSmsReceiveCapableForPhone(anyInt(), anyBoolean());
307        doReturn(true).when(mSmsStorageMonitor).isStorageAvailable();
308
309        UserManager userManager = (UserManager)mContext.getSystemService(Context.USER_SERVICE);
310        doReturn(true).when(userManager).isUserUnlocked();
311
312        try {
313            doReturn(new int[]{UserHandle.USER_SYSTEM}).when(mIActivityManager).getRunningUserIds();
314        } catch (RemoteException re) {
315            fail("Unexpected RemoteException: " + re.getStackTrace());
316        }
317
318        mSmsMessage.mWrappedSmsMessage = mGsmSmsMessage;
319        mInboundSmsTrackerCV.put("destination_port", 1 << 16);
320        mInboundSmsTrackerCV.put("pdu", HexDump.toHexString(mSmsPdu));
321        mInboundSmsTrackerCV.put("address", "1234567890");
322        mInboundSmsTrackerCV.put("reference_number", 1);
323        mInboundSmsTrackerCV.put("sequence", 1);
324        mInboundSmsTrackerCV.put("count", 1);
325        mInboundSmsTrackerCV.put("date", System.currentTimeMillis());
326        mInboundSmsTrackerCV.put("message_body", mMessageBody);
327
328        doReturn(1).when(mInboundSmsTracker).getMessageCount();
329        doReturn(1).when(mInboundSmsTracker).getReferenceNumber();
330        doReturn("1234567890").when(mInboundSmsTracker).getAddress();
331        doReturn(1).when(mInboundSmsTracker).getSequenceNumber();
332        doReturn(1).when(mInboundSmsTracker).getIndexOffset();
333        doReturn(-1).when(mInboundSmsTracker).getDestPort();
334        doReturn(mMessageBody).when(mInboundSmsTracker).getMessageBody();
335        doReturn(mSmsPdu).when(mInboundSmsTracker).getPdu();
336        doReturn(mInboundSmsTrackerCV.get("date")).when(mInboundSmsTracker).getTimestamp();
337        doReturn(mInboundSmsTrackerCV).when(mInboundSmsTracker).getContentValues();
338
339        mContentProvider = new FakeSmsContentProvider();
340        ((MockContentResolver)mContext.getContentResolver()).addProvider(
341                Telephony.Sms.CONTENT_URI.getAuthority(), mContentProvider);
342
343        new GsmInboundSmsHandlerTestHandler(TAG).start();
344        waitUntilReady();
345    }
346
347    @After
348    public void tearDown() throws Exception {
349        // wait for wakelock to be released; timeout at 10s
350        int i = 0;
351        while (mGsmInboundSmsHandler.getWakeLock().isHeld() && i < 100) {
352            waitForMs(100);
353            i++;
354        }
355        assertFalse(mGsmInboundSmsHandler.getWakeLock().isHeld());
356        mGsmInboundSmsHandler = null;
357        super.tearDown();
358    }
359
360    private void transitionFromStartupToIdle() {
361        // verify initially in StartupState
362        assertEquals("StartupState", getCurrentState().getName());
363
364        // trigger transition to IdleState
365        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_START_ACCEPTING_SMS);
366        waitForMs(50);
367
368        assertEquals("IdleState", getCurrentState().getName());
369    }
370
371    private void verifySmsIntentBroadcasts(int numPastBroadcasts) {
372        ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
373        verify(mContext, times(1 + numPastBroadcasts)).sendBroadcast(
374                intentArgumentCaptor.capture());
375        assertEquals(Telephony.Sms.Intents.SMS_DELIVER_ACTION,
376                intentArgumentCaptor.getAllValues().get(numPastBroadcasts).getAction());
377        assertEquals("WaitingState", getCurrentState().getName());
378
379        mContextFixture.sendBroadcastToOrderedBroadcastReceivers();
380
381        intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
382        verify(mContext, times(2 + numPastBroadcasts)).sendBroadcast(
383                intentArgumentCaptor.capture());
384        assertEquals(Telephony.Sms.Intents.SMS_RECEIVED_ACTION,
385                intentArgumentCaptor.getAllValues().get(numPastBroadcasts + 1).getAction());
386        assertEquals("WaitingState", getCurrentState().getName());
387
388        mContextFixture.sendBroadcastToOrderedBroadcastReceivers();
389        waitForMs(50);
390
391        assertEquals("IdleState", getCurrentState().getName());
392    }
393
394    @Test
395    @MediumTest
396    public void testNewSms() {
397        transitionFromStartupToIdle();
398
399        // send new SMS to state machine and verify that triggers SMS_DELIVER_ACTION
400        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS,
401                new AsyncResult(null, mSmsMessage, null));
402        waitForMs(100);
403
404        verifySmsIntentBroadcasts(0);
405
406        // send same SMS again, verify no broadcasts are sent
407        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS,
408                new AsyncResult(null, mSmsMessage, null));
409        waitForMs(100);
410
411        verify(mContext, times(2)).sendBroadcast(any(Intent.class));
412        assertEquals("IdleState", getCurrentState().getName());
413    }
414
415    @Test
416    @MediumTest
417    public void testNewSmsFromBlockedNumber_noBroadcastsSent() {
418        String blockedNumber = "123456789";
419        doReturn(blockedNumber).when(mInboundSmsTracker).getAddress();
420        mFakeBlockedNumberContentProvider.mBlockedNumbers.add(blockedNumber);
421
422        transitionFromStartupToIdle();
423
424        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS,
425                new AsyncResult(null, mSmsMessage, null));
426        waitForMs(100);
427
428        verify(mContext, never()).sendBroadcast(any(Intent.class));
429        assertEquals("IdleState", getCurrentState().getName());
430    }
431
432    private void verifyDataSmsIntentBroadcasts(int numPastBroadcasts) {
433        ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
434        verify(mContext, times(1 + numPastBroadcasts)).sendBroadcast(
435                intentArgumentCaptor.capture());
436        assertEquals(Telephony.Sms.Intents.DATA_SMS_RECEIVED_ACTION,
437                intentArgumentCaptor.getAllValues().get(numPastBroadcasts).getAction());
438        assertEquals("WaitingState", getCurrentState().getName());
439
440        mContextFixture.sendBroadcastToOrderedBroadcastReceivers();
441        waitForMs(50);
442
443        assertEquals("IdleState", getCurrentState().getName());
444    }
445
446    @Test
447    @MediumTest
448    public void testBroadcastSms() {
449        transitionFromStartupToIdle();
450
451        doReturn(0).when(mInboundSmsTracker).getDestPort();
452        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_BROADCAST_SMS,
453                mInboundSmsTracker);
454        waitForMs(100);
455
456        verifyDataSmsIntentBroadcasts(0);
457
458        // send same data sms again, and since it's not text sms it should be broadcast again
459        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_BROADCAST_SMS,
460                mInboundSmsTracker);
461        waitForMs(100);
462
463        verifyDataSmsIntentBroadcasts(1);
464    }
465
466    @Test
467    @MediumTest
468    public void testInjectSms() {
469        transitionFromStartupToIdle();
470
471        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_INJECT_SMS, new AsyncResult(null,
472                mSmsMessage, null));
473        waitForMs(100);
474
475        verifySmsIntentBroadcasts(0);
476
477        // inject same SMS again, verify no broadcasts are sent
478        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_INJECT_SMS, new AsyncResult(null,
479                mSmsMessage, null));
480        waitForMs(100);
481
482        verify(mContext, times(2)).sendBroadcast(any(Intent.class));
483        assertEquals("IdleState", getCurrentState().getName());
484    }
485
486    private void prepareMultiPartSms() {
487        // Part 1
488        mInboundSmsTrackerCVPart1 = new ContentValues();
489        mInboundSmsTrackerCVPart1.put("destination_port", 1 << 16);
490        mInboundSmsTrackerCVPart1.put("pdu", HexDump.toHexString(mSmsPdu));
491        mInboundSmsTrackerCVPart1.put("address", "1234567890");
492        mInboundSmsTrackerCVPart1.put("reference_number", 1);
493        mInboundSmsTrackerCVPart1.put("sequence", 1);
494        mInboundSmsTrackerCVPart1.put("count", 2);
495        mInboundSmsTrackerCVPart1.put("date", System.currentTimeMillis());
496        mInboundSmsTrackerCVPart1.put("message_body", mMessageBodyPart1);
497
498        doReturn(2).when(mInboundSmsTrackerPart1).getMessageCount();
499        doReturn(1).when(mInboundSmsTrackerPart1).getReferenceNumber();
500        doReturn("1234567890").when(mInboundSmsTrackerPart1).getAddress();
501        doReturn(1).when(mInboundSmsTrackerPart1).getSequenceNumber();
502        doReturn(1).when(mInboundSmsTrackerPart1).getIndexOffset();
503        doReturn(-1).when(mInboundSmsTrackerPart1).getDestPort();
504        doReturn(mMessageBodyPart1).when(mInboundSmsTrackerPart1).getMessageBody();
505        doReturn(mSmsPdu).when(mInboundSmsTrackerPart1).getPdu();
506        doReturn(mInboundSmsTrackerCVPart1.get("date")).when(mInboundSmsTrackerPart1).
507                getTimestamp();
508        doReturn(mInboundSmsTrackerCVPart1).when(mInboundSmsTrackerPart1).getContentValues();
509
510        // Part 2
511        mInboundSmsTrackerCVPart2 = new ContentValues();
512        mInboundSmsTrackerCVPart2.put("destination_port", 1 << 16);
513        mInboundSmsTrackerCVPart2.put("pdu", HexDump.toHexString(mSmsPdu));
514        mInboundSmsTrackerCVPart2.put("address", "1234567890");
515        mInboundSmsTrackerCVPart2.put("reference_number", 1);
516        mInboundSmsTrackerCVPart2.put("sequence", 2);
517        mInboundSmsTrackerCVPart2.put("count", 2);
518        mInboundSmsTrackerCVPart2.put("date", System.currentTimeMillis());
519        mInboundSmsTrackerCVPart2.put("message_body", mMessageBodyPart2);
520
521        doReturn(2).when(mInboundSmsTrackerPart2).getMessageCount();
522        doReturn(1).when(mInboundSmsTrackerPart2).getReferenceNumber();
523        doReturn("1234567890").when(mInboundSmsTrackerPart2).getAddress();
524        doReturn(2).when(mInboundSmsTrackerPart2).getSequenceNumber();
525        doReturn(1).when(mInboundSmsTrackerPart2).getIndexOffset();
526        doReturn(-1).when(mInboundSmsTrackerPart2).getDestPort();
527        doReturn(mMessageBodyPart2).when(mInboundSmsTrackerPart2).getMessageBody();
528        doReturn(mSmsPdu).when(mInboundSmsTrackerPart2).getPdu();
529        doReturn(mInboundSmsTrackerCVPart2.get("date")).when(mInboundSmsTrackerPart2).
530                getTimestamp();
531        doReturn(mInboundSmsTrackerCVPart2).when(mInboundSmsTrackerPart2).getContentValues();
532    }
533
534    @Test
535    @MediumTest
536    public void testMultiPartSms() {
537        transitionFromStartupToIdle();
538
539        // prepare SMS part 1 and part 2
540        prepareMultiPartSms();
541
542        mSmsHeader.concatRef = new SmsHeader.ConcatRef();
543        doReturn(mSmsHeader).when(mGsmSmsMessage).getUserDataHeader();
544
545        doReturn(mInboundSmsTrackerPart1).when(mTelephonyComponentFactory)
546                .makeInboundSmsTracker(any(byte[].class), anyLong(), anyInt(), anyBoolean(),
547                        anyString(), anyInt(), anyInt(), anyInt(), anyBoolean(), anyString());
548        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS, new AsyncResult(null,
549                mSmsMessage, null));
550        waitForMs(100);
551
552        // State machine should go back to idle and wait for second part
553        assertEquals("IdleState", getCurrentState().getName());
554
555        doReturn(mInboundSmsTrackerPart2).when(mTelephonyComponentFactory)
556                .makeInboundSmsTracker(any(byte[].class), anyLong(), anyInt(), anyBoolean(),
557                        anyString(), anyInt(), anyInt(), anyInt(), anyBoolean(), anyString());
558        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS, new AsyncResult(null,
559                mSmsMessage, null));
560        waitForMs(100);
561
562        // verify broadcast intents
563        verifySmsIntentBroadcasts(0);
564
565        // if an additional copy of one of the segments above is received, it should not be kept in
566        // the db and should not be combined with any subsequent messages received from the same
567        // sender
568
569        // additional copy of part 2 of message
570        doReturn(mInboundSmsTrackerPart2).when(mTelephonyComponentFactory)
571                .makeInboundSmsTracker(any(byte[].class), anyLong(), anyInt(), anyBoolean(),
572                        anyString(), anyInt(), anyInt(), anyInt(), anyBoolean(), anyString());
573        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS, new AsyncResult(null,
574                mSmsMessage, null));
575        waitForMs(100);
576
577        // verify no additional broadcasts sent
578        verify(mContext, times(2)).sendBroadcast(any(Intent.class));
579
580        // part 1 of new sms recieved from same sender with same parameters, just different
581        // timestamps, should not be combined with the additional part 2 received above
582
583        // call prepareMultiPartSms() to update timestamps
584        prepareMultiPartSms();
585
586        // part 1 of new sms
587        doReturn(mInboundSmsTrackerPart1).when(mTelephonyComponentFactory)
588                .makeInboundSmsTracker(any(byte[].class), anyLong(), anyInt(), anyBoolean(),
589                        anyString(), anyInt(), anyInt(), anyInt(), anyBoolean(), anyString());
590        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS, new AsyncResult(null,
591                mSmsMessage, null));
592        waitForMs(100);
593
594        // verify no additional broadcasts sent
595        verify(mContext, times(2)).sendBroadcast(any(Intent.class));
596
597        assertEquals("IdleState", getCurrentState().getName());
598    }
599
600    @Test
601    @MediumTest
602    public void testMultiPartIncompleteSms() {
603        /**
604         * Test scenario: 2 messages are received with same address, ref number, count, and
605         * seqNumber, with count = 2 and seqNumber = 1. We should not try to merge these.
606         */
607        transitionFromStartupToIdle();
608
609        // prepare SMS part 1 and part 2
610        prepareMultiPartSms();
611        // change seqNumber in part 2 to 1
612        mInboundSmsTrackerCVPart2.put("sequence", 1);
613
614        mSmsHeader.concatRef = new SmsHeader.ConcatRef();
615        doReturn(mSmsHeader).when(mGsmSmsMessage).getUserDataHeader();
616
617        doReturn(mInboundSmsTrackerPart1).when(mTelephonyComponentFactory)
618                .makeInboundSmsTracker(any(byte[].class), anyLong(), anyInt(), anyBoolean(),
619                        anyString(), anyInt(), anyInt(), anyInt(), anyBoolean(), anyString());
620        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS, new AsyncResult(null,
621                mSmsMessage, null));
622        waitForMs(100);
623
624        // State machine should go back to idle and wait for second part
625        assertEquals("IdleState", getCurrentState().getName());
626
627        doReturn(mInboundSmsTrackerPart2).when(mTelephonyComponentFactory)
628                .makeInboundSmsTracker(any(byte[].class), anyLong(), anyInt(), anyBoolean(),
629                        anyString(), anyInt(), anyInt(), anyInt(), anyBoolean(), anyString());
630        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS, new AsyncResult(null,
631                mSmsMessage, null));
632        waitForMs(100);
633
634        // verify no broadcasts sent
635        verify(mContext, never()).sendBroadcast(any(Intent.class));
636        // State machine should go back to idle
637        assertEquals("IdleState", getCurrentState().getName());
638    }
639
640    @Test
641    @MediumTest
642    public void testMultipartSmsFromBlockedNumber_noBroadcastsSent() {
643        mFakeBlockedNumberContentProvider.mBlockedNumbers.add("1234567890");
644
645        transitionFromStartupToIdle();
646
647        // prepare SMS part 1 and part 2
648        prepareMultiPartSms();
649
650        mSmsHeader.concatRef = new SmsHeader.ConcatRef();
651        doReturn(mSmsHeader).when(mGsmSmsMessage).getUserDataHeader();
652        doReturn(mInboundSmsTrackerPart1).when(mTelephonyComponentFactory)
653                .makeInboundSmsTracker(any(byte[].class), anyLong(), anyInt(), anyBoolean(),
654                        anyString(), anyInt(), anyInt(), anyInt(), anyBoolean(), anyString());
655
656        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS, new AsyncResult(null,
657                mSmsMessage, null));
658        waitForMs(100);
659
660        // State machine should go back to idle and wait for second part
661        assertEquals("IdleState", getCurrentState().getName());
662
663        doReturn(mInboundSmsTrackerPart2).when(mTelephonyComponentFactory)
664                .makeInboundSmsTracker(any(byte[].class), anyLong(), anyInt(), anyBoolean(),
665                        anyString(), anyInt(), anyInt(), anyInt(), anyBoolean(), anyString());
666        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS, new AsyncResult(null,
667                mSmsMessage, null));
668        waitForMs(100);
669
670        verify(mContext, never()).sendBroadcast(any(Intent.class));
671        assertEquals("IdleState", getCurrentState().getName());
672    }
673
674    @Test
675    @MediumTest
676    public void testBroadcastUndeliveredUserLocked() throws Exception {
677        replaceInstance(SmsBroadcastUndelivered.class, "instance", null, null);
678        doReturn(0).when(mInboundSmsTracker).getDestPort();
679
680        // add a fake entry to db
681        ContentValues rawSms = new ContentValues();
682        mContentProvider.insert(sRawUri, rawSms);
683
684        // make it a single-part message
685        doReturn(1).when(mInboundSmsTracker).getMessageCount();
686
687        // user locked
688        UserManager userManager = (UserManager)mContext.getSystemService(Context.USER_SERVICE);
689        doReturn(false).when(userManager).isUserUnlocked();
690
691        SmsBroadcastUndelivered.initialize(mContext, mGsmInboundSmsHandler, mCdmaInboundSmsHandler);
692
693        // verify that a broadcast receiver is registered for current user (user == null) based on
694        // implementation in ContextFixture
695        verify(mContext).registerReceiverAsUser(any(BroadcastReceiver.class), eq((UserHandle)null),
696                any(IntentFilter.class), eq((String)null), eq((Handler)null));
697
698        waitForMs(100);
699
700        // verify no broadcasts sent because due to !isUserUnlocked
701        verify(mContext, never()).sendBroadcast(any(Intent.class));
702
703        // when user unlocks the device, the message in db should be broadcast
704        doReturn(true).when(userManager).isUserUnlocked();
705        mContext.sendBroadcast(new Intent(Intent.ACTION_USER_UNLOCKED));
706        waitForMs(100);
707
708        verifyDataSmsIntentBroadcasts(1);
709    }
710
711    @Test
712    @MediumTest
713    public void testBroadcastUndeliveredUserUnlocked() throws Exception {
714        replaceInstance(SmsBroadcastUndelivered.class, "instance", null, null);
715        doReturn(0).when(mInboundSmsTracker).getDestPort();
716
717        // add a fake entry to db
718        ContentValues rawSms = new ContentValues();
719        mContentProvider.insert(sRawUri, rawSms);
720
721        // make it a single-part message
722        doReturn(1).when(mInboundSmsTracker).getMessageCount();
723
724        SmsBroadcastUndelivered.initialize(mContext, mGsmInboundSmsHandler, mCdmaInboundSmsHandler);
725        waitForMs(100);
726
727        // user is unlocked; intent should be broadcast right away
728        verifyDataSmsIntentBroadcasts(0);
729    }
730
731    @Test
732    @MediumTest
733    public void testBroadcastUndeliveredDeleted() throws Exception {
734        replaceInstance(SmsBroadcastUndelivered.class, "instance", null, null);
735        SmsBroadcastUndelivered.initialize(mContext, mGsmInboundSmsHandler, mCdmaInboundSmsHandler);
736        doReturn(0).when(mInboundSmsTracker).getDestPort();
737
738        //add a fake entry to db
739        ContentValues rawSms = new ContentValues();
740        rawSms.put("deleted", 1);
741        mContentProvider.insert(sRawUri, rawSms);
742
743        //make it a single-part message
744        doReturn(1).when(mInboundSmsTracker).getMessageCount();
745
746        //when user unlocks the device, broadcast should not be sent for new message
747        mContext.sendBroadcast(new Intent(Intent.ACTION_USER_UNLOCKED));
748        waitForMs(100);
749
750        verify(mContext, times(1)).sendBroadcast(any(Intent.class));
751        assertEquals("IdleState", getCurrentState().getName());
752
753    }
754
755    @Test
756    @MediumTest
757    public void testBroadcastUndeliveredMultiPart() throws Exception {
758        replaceInstance(SmsBroadcastUndelivered.class, "instance", null, null);
759
760        // prepare SMS part 1 and part 2
761        prepareMultiPartSms();
762
763        //add the 2 SMS parts to db
764        mContentProvider.insert(sRawUri, mInboundSmsTrackerCVPart1);
765        mContentProvider.insert(sRawUri, mInboundSmsTrackerCVPart2);
766
767        //return InboundSmsTracker objects corresponding to the 2 parts
768        doReturn(mInboundSmsTrackerPart1).doReturn(mInboundSmsTrackerPart2).
769                when(mTelephonyComponentFactory).makeInboundSmsTracker(any(Cursor.class),
770                anyBoolean());
771
772        SmsBroadcastUndelivered.initialize(mContext, mGsmInboundSmsHandler, mCdmaInboundSmsHandler);
773        waitForMs(100);
774
775        verifySmsIntentBroadcasts(0);
776    }
777}
778