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.dialer.database;
18
19import android.content.ContentUris;
20import android.content.ContentValues;
21import android.net.Uri;
22import android.provider.BlockedNumberContract;
23import android.provider.BlockedNumberContract.BlockedNumbers;
24import android.test.InstrumentationTestCase;
25import android.test.mock.MockContentResolver;
26import android.test.suitebuilder.annotation.SmallTest;
27
28import com.android.contacts.common.compat.CompatUtils;
29import com.android.contacts.common.test.mocks.MockContentProvider;
30import com.android.contacts.common.test.mocks.MockContentProvider.Query;
31import com.android.dialer.compat.FilteredNumberCompat;
32import com.android.dialer.database.FilteredNumberAsyncQueryHandler.OnBlockNumberListener;
33import com.android.dialer.database.FilteredNumberAsyncQueryHandler.OnCheckBlockedListener;
34import com.android.dialer.database.FilteredNumberAsyncQueryHandler.OnHasBlockedNumbersListener;
35import com.android.dialer.database.FilteredNumberAsyncQueryHandler.OnUnblockNumberListener;
36import com.android.dialer.database.FilteredNumberContract.FilteredNumberColumns;
37import com.android.dialer.database.FilteredNumberContract.FilteredNumberSources;
38import com.android.dialer.database.FilteredNumberContract.FilteredNumberTypes;
39
40import java.util.concurrent.CountDownLatch;
41import java.util.concurrent.TimeUnit;
42
43@SmallTest
44public class FilteredNumberAsyncQueryHandlerTest extends InstrumentationTestCase {
45
46    private static final String E164_NUMBER = "+16502530000";
47    private static final String NUMBER = "6502530000";
48    private static final String COUNTRY_ISO = "US";
49    private static final Integer ID = 1;
50    private static final Integer ID2 = 2;
51    private static final Uri BLOCKED_NUMBER_URI_N = CompatUtils.isNCompatible() ?
52            Uri.withAppendedPath(BlockedNumberContract.AUTHORITY_URI, "blocked") : null;
53    private static final Uri BLOCKED_NUMBER_URI_M =
54            Uri.withAppendedPath(FilteredNumberContract.AUTHORITY_URI, "filtered_numbers_table");
55    private static final Uri BLOCKED_NUMBER_URI = CompatUtils.isNCompatible() ? BLOCKED_NUMBER_URI_N
56            : BLOCKED_NUMBER_URI_M;
57    private static final Uri BLOCKED_NUMBER_URI_WITH_ID =
58            ContentUris.withAppendedId(BLOCKED_NUMBER_URI, ID);
59    private static final Uri EXPECTED_URI = Uri.fromParts("android", "google", "dialer");
60
61    private final MockContentResolver mContentResolver = new MockContentResolver();
62    private final MockContentProvider mContentProvider = new MockContentProvider();
63
64    @Override
65    public void setUp() throws Exception {
66        super.setUp();
67        FilteredNumberCompat.setIsEnabledForTest(true);
68        if (CompatUtils.isNCompatible()) {
69            mContentResolver.addProvider(BlockedNumberContract.AUTHORITY, mContentProvider);
70        } else {
71            mContentResolver.addProvider(FilteredNumberContract.AUTHORITY, mContentProvider);
72        }
73    }
74
75    public void testHasBlockedNumbers_Disabled() throws Throwable {
76        if (!CompatUtils.isNCompatible()) {
77            return;
78        }
79        FilteredNumberCompat.setIsEnabledForTest(false);
80        final MockContentResolver resolver = new MockContentResolver();
81        MockContentProvider disabledProvider = new MockContentProvider();
82        resolver.addProvider(FilteredNumberContract.AUTHORITY, disabledProvider);
83
84        disabledProvider.expectQuery(BLOCKED_NUMBER_URI_M).withProjection(FilteredNumberColumns._ID)
85                .withSelection(FilteredNumberColumns.TYPE + "="
86                        + FilteredNumberTypes.BLOCKED_NUMBER, null).returnRow(ID);
87        final HasBlockedNumbersListener listener = new HasBlockedNumbersListener();
88        runTestOnUiThread(new Runnable() {
89            @Override
90            public void run() {
91                new FilteredNumberAsyncQueryHandler(resolver).hasBlockedNumbers(listener);
92            }
93        });
94        assertTrue(listener.waitForCallback());
95        disabledProvider.verify();
96    }
97
98    public void testHasBlockedNumbers_NoResults() throws Throwable {
99        newHasBlockedNumbersExpectedQuery().returnEmptyCursor();
100        final HasBlockedNumbersListener listener = new HasBlockedNumbersListener();
101        runTestOnUiThread(new Runnable() {
102            @Override
103            public void run() {
104                new FilteredNumberAsyncQueryHandler(mContentResolver).hasBlockedNumbers(listener);
105            }
106        });
107        assertFalse(listener.waitForCallback());
108        mContentProvider.verify();
109    }
110
111    public void testHasBlockedNumbers() throws Throwable {
112        newHasBlockedNumbersExpectedQuery().returnRow(ID);
113        final HasBlockedNumbersListener listener = new HasBlockedNumbersListener();
114        runTestOnUiThread(new Runnable() {
115            @Override
116            public void run() {
117                new FilteredNumberAsyncQueryHandler(mContentResolver).hasBlockedNumbers(listener);
118            }
119        });
120        assertTrue(listener.waitForCallback());
121        mContentProvider.verify();
122    }
123
124    public void testIsBlockedNumber_Disabled() throws Throwable {
125        if (!CompatUtils.isNCompatible()) {
126            return;
127        }
128        FilteredNumberCompat.setIsEnabledForTest(false);
129        final MockContentResolver resolver = new MockContentResolver();
130        MockContentProvider disabledProvider = new MockContentProvider();
131        resolver.addProvider(FilteredNumberContract.AUTHORITY, disabledProvider);
132        disabledProvider.expectQuery(BLOCKED_NUMBER_URI_M)
133                .withProjection(FilteredNumberColumns._ID, FilteredNumberColumns.TYPE)
134                .withSelection(FilteredNumberColumns.NORMALIZED_NUMBER + " = ?", E164_NUMBER)
135                .returnRow(ID, FilteredNumberTypes.BLOCKED_NUMBER);
136        final CheckBlockedListener listener = new CheckBlockedListener();
137        runTestOnUiThread(new Runnable() {
138            @Override
139            public void run() {
140                new FilteredNumberAsyncQueryHandler(resolver)
141                        .isBlockedNumber(listener, NUMBER, COUNTRY_ISO);
142            }
143        });
144        assertEquals(ID, listener.waitForCallback());
145        mContentProvider.verify();
146    }
147
148    public void testIsBlockedNumber_NoResults() throws Throwable {
149        newIsBlockedNumberExpectedQuery().returnEmptyCursor();
150        final CheckBlockedListener listener = new CheckBlockedListener();
151
152        runTestOnUiThread(new Runnable() {
153            @Override
154            public void run() {
155                new FilteredNumberAsyncQueryHandler(mContentResolver)
156                        .isBlockedNumber(listener, NUMBER, COUNTRY_ISO);
157            }
158        });
159        assertNull(listener.waitForCallback());
160        mContentProvider.verify();
161    }
162
163    public void testIsBlockedNumber() throws Throwable {
164        if (CompatUtils.isNCompatible()) {
165            newIsBlockedNumberExpectedQuery().returnRow(ID);
166        } else {
167            newIsBlockedNumberExpectedQuery().returnRow(ID, FilteredNumberTypes.BLOCKED_NUMBER);
168        }
169        final CheckBlockedListener listener = new CheckBlockedListener();
170        runTestOnUiThread(new Runnable() {
171            @Override
172            public void run() {
173                new FilteredNumberAsyncQueryHandler(mContentResolver)
174                        .isBlockedNumber(listener, NUMBER, COUNTRY_ISO);
175           }
176        });
177        assertEquals(ID, listener.waitForCallback());
178        mContentProvider.verify();
179    }
180
181    public void testIsBlockedNumber_MultipleResults() throws Throwable {
182        if (CompatUtils.isNCompatible()) {
183            newIsBlockedNumberExpectedQuery().returnRow(ID).returnRow(ID2);
184        } else {
185            newIsBlockedNumberExpectedQuery().returnRow(ID, FilteredNumberTypes.BLOCKED_NUMBER)
186                    .returnRow(ID2, FilteredNumberTypes.BLOCKED_NUMBER);
187        }
188        final CheckBlockedListener listener = new CheckBlockedListener();
189        runTestOnUiThread(new Runnable() {
190            @Override
191            public void run() {
192                new FilteredNumberAsyncQueryHandler(mContentResolver)
193                        .isBlockedNumber(listener, NUMBER, COUNTRY_ISO);
194            }
195        });
196        // When there are multiple matches, the first is returned
197        assertEquals(ID, listener.waitForCallback());
198        mContentProvider.verify();
199    }
200
201    public void testBlockNumber_Disabled() throws Throwable {
202        if (!CompatUtils.isNCompatible()) {
203            return;
204        }
205        FilteredNumberCompat.setIsEnabledForTest(false);
206        final MockContentResolver resolver = new MockContentResolver();
207        MockContentProvider disabledProvider = new MockContentProvider();
208        resolver.addProvider(FilteredNumberContract.AUTHORITY, disabledProvider);
209
210        disabledProvider.expectInsert(BLOCKED_NUMBER_URI_M, newBlockNumberContentValuesM(),
211                EXPECTED_URI);
212        final BlockNumberListener listener = new BlockNumberListener();
213        runTestOnUiThread(new Runnable() {
214            @Override
215            public void run() {
216                new FilteredNumberAsyncQueryHandler(resolver).blockNumber(listener, E164_NUMBER,
217                        NUMBER, COUNTRY_ISO);
218            }
219        });
220        assertSame(EXPECTED_URI, listener.waitForCallback());
221        disabledProvider.verify();
222    }
223
224    public void testBlockNumber() throws Throwable {
225        mContentProvider.expectInsert(BLOCKED_NUMBER_URI, newBlockNumberContentValues(),
226                EXPECTED_URI);
227        final BlockNumberListener listener = new BlockNumberListener();
228        runTestOnUiThread(new Runnable() {
229            @Override
230            public void run() {
231                new FilteredNumberAsyncQueryHandler(mContentResolver).blockNumber(listener,
232                        E164_NUMBER, NUMBER, COUNTRY_ISO);
233            }
234        });
235        assertSame(EXPECTED_URI, listener.waitForCallback());
236        mContentProvider.verify();
237    }
238
239    public void testBlockNumber_NullNormalizedNumber() throws Throwable {
240        mContentProvider.expectInsert(BLOCKED_NUMBER_URI, newBlockNumberContentValues(),
241                EXPECTED_URI);
242        final BlockNumberListener listener = new BlockNumberListener();
243        runTestOnUiThread(new Runnable() {
244            @Override
245            public void run() {
246                new FilteredNumberAsyncQueryHandler(mContentResolver).blockNumber(listener,
247                        NUMBER, COUNTRY_ISO);
248            }
249        });
250        assertSame(EXPECTED_URI, listener.waitForCallback());
251        mContentProvider.verify();
252    }
253
254    public void testUnblockNumber_Disabled() throws Throwable {
255        if (!CompatUtils.isNCompatible()) {
256            return;
257        }
258        FilteredNumberCompat.setIsEnabledForTest(false);
259        final MockContentResolver resolver = new MockContentResolver();
260        MockContentProvider disabledProvider = new MockContentProvider();
261        resolver.addProvider(FilteredNumberContract.AUTHORITY, disabledProvider);
262
263        Uri uriWithId = ContentUris.withAppendedId(BLOCKED_NUMBER_URI_M, ID);
264        disabledProvider.expectQuery(uriWithId)
265                .withProjection(null)
266                .withDefaultProjection(FilteredNumberCompat.getIdColumnName())
267                .withSelection(null, null)
268                .withSortOrder(null)
269                .returnRow(ID);
270        disabledProvider.expectDelete(uriWithId).returnRowsAffected(1);
271        final UnblockNumberListener listener = new UnblockNumberListener();
272        runTestOnUiThread(new Runnable() {
273            @Override
274            public void run() {
275                new FilteredNumberAsyncQueryHandler(resolver).unblock(listener, ID);
276            }
277        });
278        assertNotNull(listener.waitForCallback());
279        disabledProvider.verify();
280    }
281
282    public void testUnblockNumber_NullId() {
283        try {
284            new FilteredNumberAsyncQueryHandler(mContentResolver).unblock(null, (Integer) null);
285            fail();
286        } catch (IllegalArgumentException e) {}
287    }
288
289    public void testUnblockNumber() throws Throwable {
290        mContentProvider.expectQuery(BLOCKED_NUMBER_URI_WITH_ID)
291                .withProjection(null)
292                .withDefaultProjection(FilteredNumberCompat.getIdColumnName())
293                .withSelection(null, null)
294                .withSortOrder(null)
295                .returnRow(ID);
296        mContentProvider.expectDelete(BLOCKED_NUMBER_URI_WITH_ID).returnRowsAffected(1);
297        final UnblockNumberListener listener = new UnblockNumberListener();
298        runTestOnUiThread(new Runnable() {
299            @Override
300            public void run() {
301                new FilteredNumberAsyncQueryHandler(mContentResolver).unblock(listener, ID);
302            }
303        });
304        assertNotNull(listener.waitForCallback());
305        mContentProvider.verify();
306    }
307
308    private Query newIsBlockedNumberExpectedQuery() {
309        if (CompatUtils.isNCompatible()) {
310            return newIsBlockedNumberExpectedQueryN();
311        }
312        return newIsBlockedNumberExpectedQueryM();
313    }
314
315    private Query newIsBlockedNumberExpectedQueryN() {
316        return mContentProvider.expectQuery(BLOCKED_NUMBER_URI)
317                .withProjection(BlockedNumbers.COLUMN_ID)
318                .withSelection(BlockedNumbers.COLUMN_E164_NUMBER + " = ?", E164_NUMBER);
319    }
320
321    private Query newIsBlockedNumberExpectedQueryM() {
322        return mContentProvider.expectQuery(BLOCKED_NUMBER_URI)
323                .withProjection(FilteredNumberColumns._ID, FilteredNumberColumns.TYPE)
324                .withSelection(FilteredNumberColumns.NORMALIZED_NUMBER + " = ?", E164_NUMBER);
325    }
326
327    private Query newHasBlockedNumbersExpectedQuery() {
328        if (CompatUtils.isNCompatible()) {
329            return newHasBlockedNumbersExpectedQueryN();
330        }
331        return newHasBlockedNumbersExpectedQueryM();
332    }
333
334    private Query newHasBlockedNumbersExpectedQueryN() {
335        return mContentProvider.expectQuery(BLOCKED_NUMBER_URI)
336                .withProjection(BlockedNumbers.COLUMN_ID)
337                .withSelection(null, null);
338    }
339
340    private Query newHasBlockedNumbersExpectedQueryM() {
341        return mContentProvider.expectQuery(BLOCKED_NUMBER_URI).withProjection(
342                FilteredNumberColumns._ID)
343                .withSelection(FilteredNumberColumns.TYPE + "="
344                        + FilteredNumberTypes.BLOCKED_NUMBER, null);
345    }
346
347    private ContentValues newBlockNumberContentValues() {
348        if (CompatUtils.isNCompatible()) {
349            return newBlockNumberContentValuesN();
350        }
351        return newBlockNumberContentValuesM();
352    }
353
354    private ContentValues newBlockNumberContentValuesN() {
355        ContentValues contentValues = new ContentValues();
356        contentValues.put(BlockedNumbers.COLUMN_ORIGINAL_NUMBER, NUMBER);
357        return contentValues;
358    }
359
360    private ContentValues newBlockNumberContentValuesM() {
361        ContentValues contentValues = new ContentValues();
362        contentValues.put(FilteredNumberColumns.NORMALIZED_NUMBER, E164_NUMBER);
363        contentValues.put(FilteredNumberColumns.NUMBER, NUMBER);
364        contentValues.put(FilteredNumberColumns.COUNTRY_ISO, COUNTRY_ISO);
365        contentValues.put(FilteredNumberColumns.TYPE, FilteredNumberTypes.BLOCKED_NUMBER);
366        contentValues.put(FilteredNumberColumns.SOURCE, FilteredNumberSources.USER);
367        return contentValues;
368    }
369
370    private class CheckBlockedListener implements OnCheckBlockedListener {
371        public final CountDownLatch onCheckCompleteCalled;
372        public Integer id;
373
374        public CheckBlockedListener() {
375            onCheckCompleteCalled = new CountDownLatch(1);
376        }
377
378        @Override
379        public void onCheckComplete(Integer id) {
380            this.id = id;
381            onCheckCompleteCalled.countDown();
382        }
383
384        public Integer waitForCallback() throws InterruptedException {
385            if (!onCheckCompleteCalled.await(5000, TimeUnit.MILLISECONDS)) {
386                throw new IllegalStateException("Waiting on callback timed out.");
387            }
388            return id;
389        }
390    }
391
392    private class HasBlockedNumbersListener implements OnHasBlockedNumbersListener {
393        public final CountDownLatch onHasBlockedNumbersCalled;
394        public boolean hasBlockedNumbers;
395
396        public HasBlockedNumbersListener() {
397            onHasBlockedNumbersCalled = new CountDownLatch(1);
398        }
399
400        @Override
401        public void onHasBlockedNumbers(boolean hasBlockedNumbers) {
402            this.hasBlockedNumbers = hasBlockedNumbers;
403            onHasBlockedNumbersCalled.countDown();
404        }
405
406        public boolean waitForCallback() throws InterruptedException {
407            if (!onHasBlockedNumbersCalled.await(5000, TimeUnit.MILLISECONDS)) {
408                throw new IllegalStateException("Waiting on callback timed out.");
409            }
410            return hasBlockedNumbers;
411        }
412    }
413
414    private class BlockNumberListener implements OnBlockNumberListener {
415        public final CountDownLatch onBlockCompleteCalled;
416        public Uri uri;
417
418        public BlockNumberListener() {
419            onBlockCompleteCalled = new CountDownLatch(1);
420        }
421
422        @Override
423        public void onBlockComplete(Uri uri) {
424            this.uri = uri;
425            onBlockCompleteCalled.countDown();
426        }
427
428        public Uri waitForCallback() throws InterruptedException {
429            if (!onBlockCompleteCalled.await(5000, TimeUnit.MILLISECONDS)) {
430                throw new IllegalStateException("Waiting on callback timed out.");
431            }
432            return uri;
433        }
434    }
435
436    private class UnblockNumberListener implements OnUnblockNumberListener {
437        public final CountDownLatch onUnblockCompleteCalled;
438        public Integer result;
439
440        public UnblockNumberListener() {
441            onUnblockCompleteCalled = new CountDownLatch(1);
442        }
443
444        @Override
445        public void onUnblockComplete(int rows, ContentValues values) {
446            result = rows;
447            onUnblockCompleteCalled.countDown();
448        }
449
450        public Integer waitForCallback() throws InterruptedException {
451            if (!onUnblockCompleteCalled.await(5000, TimeUnit.MILLISECONDS)) {
452                throw new IllegalStateException("Waiting on callback timed out.");
453            }
454            return result;
455        }
456    }
457}
458