1/*
2 * Copyright (C) 2009 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.providers.contacts;
18
19import com.android.internal.telephony.CallerInfo;
20import com.android.internal.telephony.PhoneConstants;
21import com.android.providers.contacts.testutil.CommonDatabaseUtils;
22
23import android.content.ComponentName;
24import android.content.ContentProvider;
25import android.content.ContentUris;
26import android.content.ContentValues;
27import android.content.Context;
28import android.content.ContextWrapper;
29import android.content.Intent;
30import android.content.pm.PackageManager;
31import android.database.Cursor;
32import android.database.MatrixCursor;
33import android.net.Uri;
34import android.provider.CallLog;
35import android.provider.CallLog.Calls;
36import android.provider.ContactsContract;
37import android.provider.ContactsContract.CommonDataKinds.Phone;
38import android.provider.VoicemailContract.Voicemails;
39import android.telecom.PhoneAccountHandle;
40import android.test.suitebuilder.annotation.MediumTest;
41
42import java.util.Arrays;
43import java.util.List;
44
45/**
46 * Unit tests for {@link CallLogProvider}.
47 *
48 * Run the test like this:
49 * <code>
50 * adb shell am instrument -e class com.android.providers.contacts.CallLogProviderTest -w \
51 *         com.android.providers.contacts.tests/android.test.InstrumentationTestRunner
52 * </code>
53 */
54@MediumTest
55public class CallLogProviderTest extends BaseContactsProvider2Test {
56    /** Fields specific to voicemail provider that should not be exposed by call_log*/
57    private static final String[] VOICEMAIL_PROVIDER_SPECIFIC_COLUMNS = new String[] {
58            Voicemails._DATA,
59            Voicemails.HAS_CONTENT,
60            Voicemails.MIME_TYPE,
61            Voicemails.SOURCE_PACKAGE,
62            Voicemails.SOURCE_DATA,
63            Voicemails.STATE};
64    /** Total number of columns exposed by call_log provider. */
65    private static final int NUM_CALLLOG_FIELDS = 24;
66
67    private CallLogProvider mCallLogProvider;
68
69    @Override
70    protected Class<? extends ContentProvider> getProviderClass() {
71       return SynchronousContactsProvider2.class;
72    }
73
74    @Override
75    protected String getAuthority() {
76        return ContactsContract.AUTHORITY;
77    }
78
79    @Override
80    protected void setUp() throws Exception {
81        super.setUp();
82        mCallLogProvider = (CallLogProvider) addProvider(TestCallLogProvider.class,
83                CallLog.AUTHORITY);
84    }
85
86    @Override
87    protected void tearDown() throws Exception {
88        setUpWithVoicemailPermissions();
89        mResolver.delete(Calls.CONTENT_URI_WITH_VOICEMAIL, null, null);
90        super.tearDown();
91    }
92
93    public void testInsert_RegularCallRecord() {
94        ContentValues values = getDefaultCallValues();
95        Uri uri = mResolver.insert(Calls.CONTENT_URI, values);
96        values.put(Calls.COUNTRY_ISO, "us");
97        assertStoredValues(uri, values);
98        assertSelection(uri, values, Calls._ID, ContentUris.parseId(uri));
99    }
100
101    private void setUpWithVoicemailPermissions() {
102        mActor.addPermissions(ADD_VOICEMAIL_PERMISSION);
103        mActor.addPermissions(READ_VOICEMAIL_PERMISSION);
104        mActor.addPermissions(WRITE_VOICEMAIL_PERMISSION);
105    }
106
107    public void testInsert_VoicemailCallRecord() {
108        setUpWithVoicemailPermissions();
109        final ContentValues values = getDefaultCallValues();
110        values.put(Calls.TYPE, Calls.VOICEMAIL_TYPE);
111        values.put(Calls.VOICEMAIL_URI, "content://foo/voicemail/2");
112
113        // Should fail with the base content uri without the voicemail param.
114        EvenMoreAsserts.assertThrows(IllegalArgumentException.class, new Runnable() {
115            @Override
116            public void run() {
117                mResolver.insert(Calls.CONTENT_URI, values);
118            }
119        });
120
121        // Now grant voicemail permission - should succeed.
122        Uri uri  = mResolver.insert(Calls.CONTENT_URI_WITH_VOICEMAIL, values);
123        assertStoredValues(uri, values);
124        assertSelection(uri, values, Calls._ID, ContentUris.parseId(uri));
125    }
126
127    public void testUpdate() {
128        Uri uri = insertCallRecord();
129        ContentValues values = new ContentValues();
130        values.put(Calls.TYPE, Calls.OUTGOING_TYPE);
131        values.put(Calls.NUMBER, "1-800-263-7643");
132        values.put(Calls.NUMBER_PRESENTATION, Calls.PRESENTATION_ALLOWED);
133        values.put(Calls.DATE, 2000);
134        values.put(Calls.DURATION, 40);
135        values.put(Calls.CACHED_NAME, "1-800-GOOG-411");
136        values.put(Calls.CACHED_NUMBER_TYPE, Phone.TYPE_CUSTOM);
137        values.put(Calls.CACHED_NUMBER_LABEL, "Directory");
138
139        int count = mResolver.update(uri, values, null, null);
140        assertEquals(1, count);
141        assertStoredValues(uri, values);
142    }
143
144    public void testDelete() {
145        Uri uri = insertCallRecord();
146        try {
147            mResolver.delete(uri, null, null);
148            fail();
149        } catch (UnsupportedOperationException ex) {
150            // Expected
151        }
152
153        int count = mResolver.delete(Calls.CONTENT_URI, Calls._ID + "="
154                + ContentUris.parseId(uri), null);
155        assertEquals(1, count);
156        assertEquals(0, getCount(uri, null, null));
157    }
158
159    public void testCallLogFilter() {
160        ContentValues values = getDefaultCallValues();
161        mResolver.insert(Calls.CONTENT_URI, values);
162
163        Uri filterUri = Uri.withAppendedPath(Calls.CONTENT_FILTER_URI, "1-800-4664-411");
164        Cursor c = mResolver.query(filterUri, null, null, null, null);
165        assertEquals(1, c.getCount());
166        c.moveToFirst();
167        assertCursorValues(c, values);
168        c.close();
169
170        filterUri = Uri.withAppendedPath(Calls.CONTENT_FILTER_URI, "1-888-4664-411");
171        c = mResolver.query(filterUri, null, null, null, null);
172        assertEquals(0, c.getCount());
173        c.close();
174    }
175
176    public void testAddCall() {
177        CallerInfo ci = new CallerInfo();
178        ci.name = "1-800-GOOG-411";
179        ci.numberType = Phone.TYPE_CUSTOM;
180        ci.numberLabel = "Directory";
181        final ComponentName sComponentName = new ComponentName(
182                "com.android.server.telecom",
183                "TelecomServiceImpl");
184        PhoneAccountHandle subscription = new PhoneAccountHandle(
185                sComponentName, "sub0");
186
187        Uri uri = Calls.addCall(ci, getMockContext(), "1-800-263-7643",
188                PhoneConstants.PRESENTATION_ALLOWED, Calls.OUTGOING_TYPE, 0, subscription, 2000,
189                40, null);
190
191        ContentValues values = new ContentValues();
192        values.put(Calls.TYPE, Calls.OUTGOING_TYPE);
193        values.put(Calls.FEATURES, 0);
194        values.put(Calls.NUMBER, "1-800-263-7643");
195        values.put(Calls.NUMBER_PRESENTATION, Calls.PRESENTATION_ALLOWED);
196        values.put(Calls.DATE, 2000);
197        values.put(Calls.DURATION, 40);
198        values.put(Calls.CACHED_NAME, "1-800-GOOG-411");
199        values.put(Calls.CACHED_NUMBER_TYPE, Phone.TYPE_CUSTOM);
200        values.put(Calls.CACHED_NUMBER_LABEL, "Directory");
201        values.put(Calls.COUNTRY_ISO, "us");
202        values.put(Calls.GEOCODED_LOCATION, "usa");
203        values.put(Calls.PHONE_ACCOUNT_COMPONENT_NAME,
204                "com.android.server.telecom/TelecomServiceImpl");
205        values.put(Calls.PHONE_ACCOUNT_ID, "sub0");
206        // Casting null to Long as there are many forms of "put" which have nullable second
207        // parameters and the compiler needs a hint as to which form is correct.
208        values.put(Calls.DATA_USAGE, (Long) null);
209        assertStoredValues(uri, values);
210    }
211
212    // Test to check that the calls and voicemail uris returns expected results.
213    public void testDifferentContentUris() {
214        setUpWithVoicemailPermissions();
215        // Insert one voicemaail and two regular call record.
216        insertVoicemailRecord();
217        insertCallRecord();
218        insertCallRecord();
219
220        // With the default uri, only 2 call entries should be returned.
221        // With the voicemail uri all 3 should be returned.
222        assertEquals(2, getCount(Calls.CONTENT_URI, null, null));
223        assertEquals(3, getCount(Calls.CONTENT_URI_WITH_VOICEMAIL, null, null));
224    }
225
226    public void testLimitParamReturnsCorrectLimit() {
227        for (int i=0; i<10; i++) {
228            insertCallRecord();
229        }
230        Uri uri = Calls.CONTENT_URI.buildUpon()
231                .appendQueryParameter(Calls.LIMIT_PARAM_KEY, "4")
232                .build();
233        assertEquals(4, getCount(uri, null, null));
234    }
235
236    public void testLimitAndOffsetParamReturnsCorrectEntries() {
237        for (int i=0; i<10; i++) {
238            mResolver.insert(Calls.CONTENT_URI, getDefaultValues(Calls.INCOMING_TYPE));
239        }
240        for (int i=0; i<10; i++) {
241            mResolver.insert(Calls.CONTENT_URI, getDefaultValues(Calls.MISSED_TYPE));
242        }
243        // Limit 4 records.  Discard first 8.
244        Uri uri = Calls.CONTENT_URI.buildUpon()
245                .appendQueryParameter(Calls.LIMIT_PARAM_KEY, "4")
246                .appendQueryParameter(Calls.OFFSET_PARAM_KEY, "8")
247                .build();
248        String[] projection = new String[] {Calls._ID, Calls.TYPE};
249        Cursor c = mResolver.query(uri, projection, null, null, null);
250        try {
251            // First two should be incoming, next two should be missed.
252            for (int i = 0; i < 2; i++) {
253                c.moveToNext();
254                assertEquals(Calls.INCOMING_TYPE, c.getInt(1));
255            }
256            for (int i = 0; i < 2; i++) {
257                c.moveToNext();
258                assertEquals(Calls.MISSED_TYPE, c.getInt(1));
259            }
260        } finally {
261            c.close();
262        }
263    }
264
265    public void testUriWithBadLimitParamThrowsException() {
266        assertParamThrowsIllegalArgumentException(Calls.LIMIT_PARAM_KEY, "notvalid");
267    }
268
269    public void testUriWithBadOffsetParamThrowsException() {
270        assertParamThrowsIllegalArgumentException(Calls.OFFSET_PARAM_KEY, "notvalid");
271    }
272
273    private void assertParamThrowsIllegalArgumentException(String key, String value) {
274        Uri uri = Calls.CONTENT_URI.buildUpon()
275                .appendQueryParameter(key, value)
276                .build();
277        try {
278            mResolver.query(uri, null, null, null, null);
279            fail();
280        } catch (IllegalArgumentException e) {
281            assertTrue("Error does not contain value in question.",
282                    e.toString().contains(value));
283        }
284    }
285
286    // Test to check that none of the voicemail provider specific fields are
287    // insertable through call_log provider.
288    public void testCannotAccessVoicemailSpecificFields_Insert() {
289        for (String voicemailColumn : VOICEMAIL_PROVIDER_SPECIFIC_COLUMNS) {
290            final ContentValues values = getDefaultCallValues();
291            values.put(voicemailColumn, "foo");
292            EvenMoreAsserts.assertThrows("Column: " + voicemailColumn,
293                    IllegalArgumentException.class, new Runnable() {
294                    @Override
295                    public void run() {
296                        mResolver.insert(Calls.CONTENT_URI, values);
297                    }
298                });
299        }
300    }
301
302    // Test to check that none of the voicemail provider specific fields are
303    // exposed through call_log provider query.
304    public void testCannotAccessVoicemailSpecificFields_Query() {
305        // Query.
306        Cursor cursor = mResolver.query(Calls.CONTENT_URI, null, null, null, null);
307        List<String> columnNames = Arrays.asList(cursor.getColumnNames());
308        assertEquals(NUM_CALLLOG_FIELDS, columnNames.size());
309        // None of the voicemail provider specific columns should be present.
310        for (String voicemailColumn : VOICEMAIL_PROVIDER_SPECIFIC_COLUMNS) {
311            assertFalse("Unexpected column: '" + voicemailColumn + "' returned.",
312                    columnNames.contains(voicemailColumn));
313        }
314    }
315
316    // Test to check that none of the voicemail provider specific fields are
317    // updatable through call_log provider.
318    public void testCannotAccessVoicemailSpecificFields_Update() {
319        for (String voicemailColumn : VOICEMAIL_PROVIDER_SPECIFIC_COLUMNS) {
320            final Uri insertedUri = insertCallRecord();
321            final ContentValues values = new ContentValues();
322            values.put(voicemailColumn, "foo");
323            EvenMoreAsserts.assertThrows("Column: " + voicemailColumn,
324                    IllegalArgumentException.class, new Runnable() {
325                    @Override
326                    public void run() {
327                        mResolver.update(insertedUri, values, null, null);
328                    }
329                });
330        }
331    }
332
333    public void testVoicemailPermissions_Insert() {
334        EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
335            @Override
336            public void run() {
337                mResolver.insert(Calls.CONTENT_URI_WITH_VOICEMAIL, getDefaultVoicemailValues());
338            }
339        });
340        // Should now succeed with permissions granted.
341        setUpWithVoicemailPermissions();
342        mResolver.insert(Calls.CONTENT_URI_WITH_VOICEMAIL, getDefaultVoicemailValues());
343    }
344
345    public void testVoicemailPermissions_Update() {
346        EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
347            @Override
348            public void run() {
349                mResolver.update(Calls.CONTENT_URI_WITH_VOICEMAIL, getDefaultVoicemailValues(),
350                        null, null);
351            }
352        });
353
354        // Should succeed with manage permission granted
355        mActor.addPermissions(WRITE_VOICEMAIL_PERMISSION);
356        mResolver.update(Calls.CONTENT_URI_WITH_VOICEMAIL, getDefaultCallValues(), null, null);
357        mActor.removePermissions(WRITE_VOICEMAIL_PERMISSION);
358
359        // Should also succeed with full permissions granted.
360        setUpWithVoicemailPermissions();
361        mResolver.update(Calls.CONTENT_URI_WITH_VOICEMAIL, getDefaultCallValues(), null, null);
362    }
363
364    public void testVoicemailPermissions_Query() {
365        EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
366            @Override
367            public void run() {
368                mResolver.query(Calls.CONTENT_URI_WITH_VOICEMAIL, null, null, null, null);
369            }
370        });
371
372        // Should succeed with read_all permission granted
373        mActor.addPermissions(READ_VOICEMAIL_PERMISSION);
374        mResolver.query(Calls.CONTENT_URI_WITH_VOICEMAIL, null, null, null, null);
375        mActor.removePermissions(READ_VOICEMAIL_PERMISSION);
376
377        // Should also succeed with full permissions granted.
378        setUpWithVoicemailPermissions();
379        mResolver.query(Calls.CONTENT_URI_WITH_VOICEMAIL, null, null, null, null);
380    }
381
382    public void testVoicemailPermissions_Delete() {
383        EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
384            @Override
385            public void run() {
386                mResolver.delete(Calls.CONTENT_URI_WITH_VOICEMAIL, null, null);
387            }
388        });
389
390        // Should succeed with manage permission granted
391        mActor.addPermissions(WRITE_VOICEMAIL_PERMISSION);
392        mResolver.delete(Calls.CONTENT_URI_WITH_VOICEMAIL, null, null);
393        mActor.removePermissions(WRITE_VOICEMAIL_PERMISSION);
394
395        // Should now succeed with permissions granted.
396        setUpWithVoicemailPermissions();
397        mResolver.delete(Calls.CONTENT_URI_WITH_VOICEMAIL, null, null);
398    }
399
400    public void testCopyEntriesFromCursor_ReturnsMostRecentEntryTimestamp() {
401        assertEquals(10, mCallLogProvider.copyEntriesFromCursor(getTestCallLogCursor()));
402    }
403
404    public void testCopyEntriesFromCursor_AllEntriesSyncedWithoutDuplicatesPresent() {
405        assertStoredValues(Calls.CONTENT_URI);
406        mCallLogProvider.copyEntriesFromCursor(getTestCallLogCursor());
407        assertStoredValues(Calls.CONTENT_URI,
408                getTestCallLogValues(2),
409                getTestCallLogValues(1),
410                getTestCallLogValues(0));
411    }
412
413    public void testCopyEntriesFromCursor_DuplicatesIgnoredCorrectly() {
414        mResolver.insert(Calls.CONTENT_URI, getTestCallLogValues(1));
415        assertStoredValues(Calls.CONTENT_URI, getTestCallLogValues(1));
416        mCallLogProvider.copyEntriesFromCursor(getTestCallLogCursor());
417        assertStoredValues(Calls.CONTENT_URI,
418                getTestCallLogValues(2),
419                getTestCallLogValues(1),
420                getTestCallLogValues(0));
421    }
422
423    private ContentValues getDefaultValues(int callType) {
424        ContentValues values = new ContentValues();
425        values.put(Calls.TYPE, callType);
426        values.put(Calls.NUMBER, "1-800-4664-411");
427        values.put(Calls.NUMBER_PRESENTATION, Calls.PRESENTATION_ALLOWED);
428        values.put(Calls.DATE, 1000);
429        values.put(Calls.DURATION, 30);
430        values.put(Calls.NEW, 1);
431        return values;
432    }
433
434    private ContentValues getDefaultCallValues() {
435        return getDefaultValues(Calls.INCOMING_TYPE);
436    }
437
438    private ContentValues getDefaultVoicemailValues() {
439        return getDefaultValues(Calls.VOICEMAIL_TYPE);
440    }
441
442    private Uri insertCallRecord() {
443        return mResolver.insert(Calls.CONTENT_URI, getDefaultCallValues());
444    }
445
446    private Uri insertVoicemailRecord() {
447        return mResolver.insert(Calls.CONTENT_URI_WITH_VOICEMAIL, getDefaultVoicemailValues());
448    }
449
450    public static class TestCallLogProvider extends CallLogProvider {
451        private static ContactsDatabaseHelper mDbHelper;
452
453        @Override
454        protected ContactsDatabaseHelper getDatabaseHelper(final Context context) {
455            if (mDbHelper == null) {
456                mDbHelper = ContactsDatabaseHelper.getNewInstanceForTest(context);
457            }
458            return mDbHelper;
459        }
460
461        @Override
462        protected CallLogInsertionHelper createCallLogInsertionHelper(Context context) {
463            return new CallLogInsertionHelper() {
464                @Override
465                public String getGeocodedLocationFor(String number, String countryIso) {
466                    return "usa";
467                }
468
469                @Override
470                public void addComputedValues(ContentValues values) {
471                    values.put(Calls.COUNTRY_ISO, "us");
472                    values.put(Calls.GEOCODED_LOCATION, "usa");
473                }
474            };
475        }
476
477        @Override
478        protected Context context() {
479            return new ContextWrapper(super.context()) {
480                @Override
481                public PackageManager getPackageManager() {
482                    return new MockPackageManager("com.test.package1", "com.test.package2");
483                }
484
485                @Override
486                public void sendBroadcast(Intent intent, String receiverPermission) {
487                   // Do nothing for now.
488                }
489            };
490        }
491    }
492
493    private Cursor getTestCallLogCursor() {
494        final MatrixCursor cursor = new MatrixCursor(CallLogProvider.CALL_LOG_SYNC_PROJECTION);
495        for (int i = 2; i >= 0; i--) {
496            cursor.addRow(CommonDatabaseUtils.getArrayFromContentValues(getTestCallLogValues(i),
497                    CallLogProvider.CALL_LOG_SYNC_PROJECTION));
498        }
499        return cursor;
500    }
501
502    /**
503     * Returns a predefined {@link ContentValues} object based on the provided index.
504     */
505    private ContentValues getTestCallLogValues(int i) {
506        ContentValues values = new ContentValues();
507        switch (i) {
508            case 0:
509                values.put(Calls.NUMBER, "123456");
510                values.put(Calls.NUMBER_PRESENTATION, Calls.PRESENTATION_ALLOWED);
511                values.put(Calls.TYPE, Calls.MISSED_TYPE);
512                values.put(Calls.FEATURES, 0);
513                values.put(Calls.DATE, 10);
514                values.put(Calls.DURATION, 100);
515                values.put(Calls.DATA_USAGE, 1000);
516                values.put(Calls.PHONE_ACCOUNT_COMPONENT_NAME, (String) null);
517                values.put(Calls.PHONE_ACCOUNT_ID, (Long) null);
518                break;
519            case 1:
520                values.put(Calls.NUMBER, "654321");
521                values.put(Calls.NUMBER_PRESENTATION, Calls.PRESENTATION_ALLOWED);
522                values.put(Calls.TYPE, Calls.INCOMING_TYPE);
523                values.put(Calls.FEATURES, 0);
524                values.put(Calls.DATE, 5);
525                values.put(Calls.DURATION, 200);
526                values.put(Calls.DATA_USAGE, 0);
527                values.put(Calls.PHONE_ACCOUNT_COMPONENT_NAME, (String) null);
528                values.put(Calls.PHONE_ACCOUNT_ID, (Long) null);
529                break;
530            case 2:
531                values.put(Calls.NUMBER, "123456");
532                values.put(Calls.NUMBER_PRESENTATION, Calls.PRESENTATION_ALLOWED);
533                values.put(Calls.TYPE, Calls.OUTGOING_TYPE);
534                values.put(Calls.FEATURES, Calls.FEATURES_VIDEO);
535                values.put(Calls.DATE, 1);
536                values.put(Calls.DURATION, 50);
537                values.put(Calls.DATA_USAGE, 2000);
538                values.put(Calls.PHONE_ACCOUNT_COMPONENT_NAME, (String) null);
539                values.put(Calls.PHONE_ACCOUNT_ID, (Long) null);
540                break;
541        }
542        return values;
543    }
544}
545