Home | History | Annotate | Download | only in contacts
      1 /*
      2  * Copyright (C) 2011 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.providers.contacts;
     18 
     19 import com.android.common.io.MoreCloseables;
     20 
     21 import android.content.ContentUris;
     22 import android.content.ContentValues;
     23 import android.database.Cursor;
     24 import android.net.Uri;
     25 import android.os.ParcelFileDescriptor;
     26 import android.provider.CallLog.Calls;
     27 import android.provider.VoicemailContract;
     28 import android.provider.VoicemailContract.Status;
     29 import android.provider.VoicemailContract.Voicemails;
     30 import android.test.MoreAsserts;
     31 import android.test.suitebuilder.annotation.SmallTest;
     32 
     33 import java.io.FileNotFoundException;
     34 import java.io.IOException;
     35 import java.io.InputStream;
     36 import java.io.OutputStream;
     37 import java.util.Arrays;
     38 import java.util.List;
     39 
     40 /**
     41  * Unit tests for {@link VoicemailContentProvider}.
     42  *
     43  * Run the test like this:
     44  * <code>
     45  * runtest -c com.android.providers.contacts.VoicemailProviderTest contactsprov
     46  * </code>
     47  */
     48 // TODO: Test that calltype and voicemail_uri are auto populated by the provider.
     49 @SmallTest
     50 public class VoicemailProviderTest extends BaseVoicemailProviderTest {
     51     /** Fields specific to call_log provider that should not be exposed by voicemail provider. */
     52     private static final String[] CALLLOG_PROVIDER_SPECIFIC_COLUMNS = {
     53             Calls.CACHED_NAME,
     54             Calls.CACHED_NUMBER_LABEL,
     55             Calls.CACHED_NUMBER_TYPE,
     56             Calls.TYPE,
     57             Calls.VOICEMAIL_URI,
     58             Calls.COUNTRY_ISO
     59     };
     60     /** Total number of columns exposed by voicemail provider. */
     61     private static final int NUM_VOICEMAIL_FIELDS = 13;
     62 
     63     @Override
     64     protected void setUp() throws Exception {
     65         super.setUp();
     66         setUpForOwnPermission();
     67     }
     68 
     69     /** Returns the appropriate /voicemail URI. */
     70     private Uri voicemailUri() {
     71         return mUseSourceUri ?
     72                 Voicemails.buildSourceUri(mActor.packageName) : Voicemails.CONTENT_URI;
     73     }
     74 
     75     /** Returns the appropriate /status URI. */
     76     private Uri statusUri() {
     77         return mUseSourceUri ?
     78                 Status.buildSourceUri(mActor.packageName) : Status.CONTENT_URI;
     79     }
     80 
     81     public void testInsert() throws Exception {
     82         Uri uri = mResolver.insert(voicemailUri(), getTestVoicemailValues());
     83         // We create on purpose a new set of ContentValues here, because the code above modifies
     84         // the copy it gets.
     85         assertStoredValues(uri, getTestVoicemailValues());
     86         assertSelection(uri, getTestVoicemailValues(), Voicemails._ID, ContentUris.parseId(uri));
     87         assertEquals(1, countFilesInTestDirectory());
     88     }
     89 
     90     // Test to ensure that media content can be written and read back.
     91     public void testFileContent() throws Exception {
     92         Uri uri = insertVoicemail();
     93         OutputStream out = mResolver.openOutputStream(uri);
     94         byte[] outBuffer = {0x1, 0x2, 0x3, 0x4};
     95         out.write(outBuffer);
     96         out.flush();
     97         out.close();
     98         InputStream in = mResolver.openInputStream(uri);
     99         byte[] inBuffer = new byte[4];
    100         int numBytesRead = in.read(inBuffer);
    101         assertEquals(numBytesRead, outBuffer.length);
    102         MoreAsserts.assertEquals(outBuffer, inBuffer);
    103         // No more data should be left.
    104         assertEquals(-1, in.read(inBuffer));
    105         in.close();
    106     }
    107 
    108     public void testUpdate() {
    109         Uri uri = insertVoicemail();
    110         ContentValues values = new ContentValues();
    111         values.put(Voicemails.NUMBER, "1-800-263-7643");
    112         values.put(Voicemails.DATE, 2000);
    113         values.put(Voicemails.DURATION, 40);
    114         values.put(Voicemails.STATE, 2);
    115         values.put(Voicemails.HAS_CONTENT, 1);
    116         values.put(Voicemails.SOURCE_DATA, "foo");
    117         int count = mResolver.update(uri, values, null, null);
    118         assertEquals(1, count);
    119         assertStoredValues(uri, values);
    120     }
    121 
    122     public void testDelete() {
    123         Uri uri = insertVoicemail();
    124         int count = mResolver.delete(voicemailUri(), Voicemails._ID + "="
    125                 + ContentUris.parseId(uri), null);
    126         assertEquals(1, count);
    127         assertEquals(0, getCount(uri, null, null));
    128     }
    129 
    130     public void testGetType_ItemUri() throws Exception {
    131         // Random item uri.
    132         assertEquals(Voicemails.ITEM_TYPE,
    133                 mResolver.getType(ContentUris.withAppendedId(Voicemails.CONTENT_URI, 100)));
    134         // Item uri of an inserted voicemail.
    135         ContentValues values = getTestVoicemailValues();
    136         values.put(Voicemails.MIME_TYPE, "foo/bar");
    137         Uri uri = mResolver.insert(voicemailUri(), values);
    138         assertEquals(Voicemails.ITEM_TYPE, mResolver.getType(uri));
    139     }
    140 
    141     public void testGetType_DirUri() throws Exception {
    142         assertEquals(Voicemails.DIR_TYPE, mResolver.getType(Voicemails.CONTENT_URI));
    143         assertEquals(Voicemails.DIR_TYPE, mResolver.getType(Voicemails.buildSourceUri("foo")));
    144     }
    145 
    146     // Test to ensure that without full permission it is not possible to use the base uri (i.e. with
    147     // no package URI specified).
    148     public void testMustUsePackageUriWithoutFullPermission() {
    149         setUpForOwnPermission();
    150         EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
    151             @Override
    152             public void run() {
    153                 mResolver.insert(Voicemails.CONTENT_URI, getTestVoicemailValues());
    154             }
    155         });
    156 
    157         EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
    158             @Override
    159             public void run() {
    160                 mResolver.update(Voicemails.CONTENT_URI, getTestVoicemailValues(), null, null);
    161             }
    162         });
    163 
    164         EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
    165             @Override
    166             public void run() {
    167                 mResolver.query(Voicemails.CONTENT_URI, null, null, null, null);
    168             }
    169         });
    170 
    171         EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
    172             @Override
    173             public void run() {
    174                 mResolver.delete(Voicemails.CONTENT_URI, null, null);
    175             }
    176         });
    177     }
    178 
    179     public void testPermissions_InsertAndQuery() {
    180         setUpForFullPermission();
    181         // Insert two records - one each with own and another package.
    182         insertVoicemail();
    183         insertVoicemailForSourcePackage("another-package");
    184         assertEquals(2, getCount(voicemailUri(), null, null));
    185 
    186         // Now give away full permission and check that only 1 message is accessible.
    187         setUpForOwnPermission();
    188         assertEquals(1, getCount(voicemailUri(), null, null));
    189 
    190         // Once again try to insert message for another package. This time
    191         // it should fail.
    192         EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
    193             @Override
    194             public void run() {
    195                 insertVoicemailForSourcePackage("another-package");
    196             }
    197         });
    198     }
    199 
    200     public void testPermissions_UpdateAndDelete() {
    201         setUpForFullPermission();
    202         // Insert two records - one each with own and another package.
    203         final Uri ownVoicemail = insertVoicemail();
    204         final Uri anotherVoicemail = insertVoicemailForSourcePackage("another-package");
    205         assertEquals(2, getCount(voicemailUri(), null, null));
    206 
    207         // Now give away full permission and check that we can update and delete only
    208         // the own voicemail.
    209         setUpForOwnPermission();
    210         mResolver.update(withSourcePackageParam(ownVoicemail),
    211                 getTestVoicemailValues(), null, null);
    212         mResolver.delete(withSourcePackageParam(ownVoicemail), null, null);
    213 
    214         // However, attempting to update or delete another-package's voicemail should fail.
    215         EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
    216             @Override
    217             public void run() {
    218                 mResolver.update(anotherVoicemail, null, null, null);
    219             }
    220         });
    221         EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
    222             @Override
    223             public void run() {
    224                 mResolver.delete(anotherVoicemail, null, null);
    225             }
    226         });
    227     }
    228 
    229     private Uri withSourcePackageParam(Uri uri) {
    230         return uri.buildUpon()
    231             .appendQueryParameter(VoicemailContract.PARAM_KEY_SOURCE_PACKAGE, mActor.packageName)
    232             .build();
    233     }
    234 
    235     public void testUriPermissions() {
    236         setUpForFullPermission();
    237         final Uri uri1 = insertVoicemail();
    238         final Uri uri2 = insertVoicemail();
    239         // Give away all permissions before querying. Access to both uris should be denied.
    240         setUpForNoPermission();
    241         checkHasNoAccessToUri(uri1);
    242         checkHasNoAccessToUri(uri2);
    243 
    244         // Just grant permission to uri1. uri1 should pass but uri2 should still fail.
    245         mActor.addUriPermissions(uri1);
    246         checkHasReadOnlyAccessToUri(uri1);
    247         checkHasNoAccessToUri(uri2);
    248 
    249         // Cleanup.
    250         mActor.removeUriPermissions(uri1);
    251     }
    252 
    253     private void checkHasNoAccessToUri(final Uri uri) {
    254         checkHasNoReadAccessToUri(uri);
    255         checkHasNoWriteAccessToUri(uri);
    256     }
    257 
    258     private void checkHasReadOnlyAccessToUri(final Uri uri) {
    259         checkHasReadAccessToUri(uri);
    260         checkHasNoWriteAccessToUri(uri);
    261     }
    262 
    263     private void checkHasReadAccessToUri(final Uri uri) {
    264         Cursor cursor = null;
    265         try {
    266             cursor = mResolver.query(uri, null, null ,null, null);
    267             assertEquals(1, cursor.getCount());
    268             try {
    269                 ParcelFileDescriptor fd = mResolver.openFileDescriptor(uri, "r");
    270                 assertNotNull(fd);
    271                 fd.close();
    272             } catch (FileNotFoundException e) {
    273                 fail(e.getMessage());
    274             } catch (IOException e) {
    275                 fail(e.getMessage());
    276             }
    277         } finally {
    278             MoreCloseables.closeQuietly(cursor);
    279         }
    280     }
    281 
    282     private void checkHasNoReadAccessToUri(final Uri uri) {
    283         EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
    284             @Override
    285             public void run() {
    286                 mResolver.query(uri, null, null ,null, null);
    287             }
    288         });
    289         EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
    290             @Override
    291             public void run() {
    292                 try {
    293                     mResolver.openFileDescriptor(uri, "r");
    294                 } catch (FileNotFoundException e) {
    295                     fail(e.getMessage());
    296                 }
    297             }
    298         });
    299     }
    300 
    301     private void checkHasNoWriteAccessToUri(final Uri uri) {
    302         EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
    303             @Override
    304             public void run() {
    305                 mResolver.update(uri, getTestVoicemailValues(), null, null);
    306             }
    307         });
    308         EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
    309             @Override
    310             public void run() {
    311                 mResolver.delete(uri, null, null);
    312             }
    313         });
    314         EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
    315             @Override
    316             public void run() {
    317                 try {
    318                     mResolver.openFileDescriptor(uri, "w");
    319                 } catch (FileNotFoundException e) {
    320                     fail(e.getMessage());
    321                 }
    322             }
    323         });
    324         EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
    325             @Override
    326             public void run() {
    327                 try {
    328                     mResolver.openFileDescriptor(uri, "rw");
    329                 } catch (FileNotFoundException e) {
    330                     fail(e.getMessage());
    331                 }
    332             }
    333         });
    334     }
    335 
    336     // Test to ensure that all operations fail when no voicemail permission is granted.
    337     public void testNoPermissions() {
    338         setUpForNoPermission();
    339         EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
    340             @Override
    341             public void run() {
    342                 mResolver.insert(voicemailUri(), getTestVoicemailValues());
    343             }
    344         });
    345         EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
    346             @Override
    347             public void run() {
    348                 mResolver.update(voicemailUri(), getTestVoicemailValues(), null, null);
    349             }
    350         });
    351         EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
    352             @Override
    353             public void run() {
    354                 mResolver.query(voicemailUri(), null, null, null, null);
    355             }
    356         });
    357         EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
    358             @Override
    359             public void run() {
    360                 mResolver.delete(voicemailUri(), null, null);
    361             }
    362         });
    363     }
    364 
    365     // Test to check that none of the call_log provider specific fields are
    366     // insertable through voicemail provider.
    367     public void testCannotAccessCallLogSpecificFields_Insert() {
    368         for (String callLogColumn : CALLLOG_PROVIDER_SPECIFIC_COLUMNS) {
    369             final ContentValues values = getTestVoicemailValues();
    370             values.put(callLogColumn, "foo");
    371             EvenMoreAsserts.assertThrows("Column: " + callLogColumn,
    372                     IllegalArgumentException.class, new Runnable() {
    373                     @Override
    374                     public void run() {
    375                         mResolver.insert(voicemailUri(), values);
    376                     }
    377                 });
    378         }
    379     }
    380 
    381     // Test to check that none of the call_log provider specific fields are
    382     // exposed through voicemail provider query.
    383     public void testCannotAccessCallLogSpecificFields_Query() {
    384         // Query.
    385         Cursor cursor = mResolver.query(voicemailUri(), null, null, null, null);
    386         List<String> columnNames = Arrays.asList(cursor.getColumnNames());
    387         assertEquals(NUM_VOICEMAIL_FIELDS, columnNames.size());
    388         // None of the call_log provider specific columns should be present.
    389         for (String callLogColumn : CALLLOG_PROVIDER_SPECIFIC_COLUMNS) {
    390             assertFalse("Unexpected column: '" + callLogColumn + "' returned.",
    391                     columnNames.contains(callLogColumn));
    392         }
    393     }
    394 
    395     // Test to check that none of the call_log provider specific fields are
    396     // updatable through voicemail provider.
    397     public void testCannotAccessCallLogSpecificFields_Update() {
    398         for (String callLogColumn : CALLLOG_PROVIDER_SPECIFIC_COLUMNS) {
    399             final Uri insertedUri = insertVoicemail();
    400             final ContentValues values = getTestVoicemailValues();
    401             values.put(callLogColumn, "foo");
    402             EvenMoreAsserts.assertThrows("Column: " + callLogColumn,
    403                     IllegalArgumentException.class, new Runnable() {
    404                     @Override
    405                     public void run() {
    406                         mResolver.update(insertedUri, values, null, null);
    407                     }
    408                 });
    409         }
    410     }
    411 
    412     // Tests for voicemail status table.
    413 
    414     public void testStatusInsert() throws Exception {
    415         ContentValues values = getTestStatusValues();
    416         Uri uri = mResolver.insert(statusUri(), values);
    417         assertStoredValues(uri, values);
    418         assertSelection(uri, values, Status._ID, ContentUris.parseId(uri));
    419     }
    420 
    421     // Test to ensure that duplicate entries for the same package still end up as the same record.
    422     public void testStatusInsertDuplicate() throws Exception {
    423         setUpForFullPermission();
    424         ContentValues values = getTestStatusValues();
    425         assertNotNull(mResolver.insert(statusUri(), values));
    426         assertEquals(1, getCount(statusUri(), null, null));
    427 
    428         // Insertion request for the same package should fail with no change in count.
    429         values.put(Status.DATA_CHANNEL_STATE, Status.DATA_CHANNEL_STATE_NO_CONNECTION);
    430         assertNull(mResolver.insert(statusUri(), values));
    431         assertEquals(1, getCount(statusUri(), null, null));
    432 
    433         // Now insert entry for another source package, and it should end up as a separate record.
    434         values.put(Status.SOURCE_PACKAGE, "another.package");
    435         assertNotNull(mResolver.insert(statusUri(), values));
    436         assertEquals(2, getCount(statusUri(), null, null));
    437     }
    438 
    439     public void testStatusUpdate() throws Exception {
    440         Uri uri = insertTestStatusEntry();
    441         ContentValues values = getTestStatusValues();
    442         values.put(Status.DATA_CHANNEL_STATE, Status.DATA_CHANNEL_STATE_NO_CONNECTION);
    443         values.put(Status.NOTIFICATION_CHANNEL_STATE,
    444                 Status.NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING);
    445         int count = mResolver.update(uri, values, null, null);
    446         assertEquals(1, count);
    447         assertStoredValues(uri, values);
    448     }
    449 
    450     public void testStatusDelete() {
    451         Uri uri = insertTestStatusEntry();
    452         int count = mResolver.delete(statusUri(), Status._ID + "="
    453                 + ContentUris.parseId(uri), null);
    454         assertEquals(1, count);
    455         assertEquals(0, getCount(uri, null, null));
    456     }
    457 
    458     public void testStatusGetType() throws Exception {
    459         // Item URI.
    460         Uri uri = insertTestStatusEntry();
    461         assertEquals(Status.ITEM_TYPE, mResolver.getType(uri));
    462 
    463         // base URIs.
    464         assertEquals(Status.DIR_TYPE, mResolver.getType(Status.CONTENT_URI));
    465         assertEquals(Status.DIR_TYPE, mResolver.getType(Status.buildSourceUri("foo")));
    466     }
    467 
    468     // Basic permission checks for the status table.
    469     public void testStatusPermissions() throws Exception {
    470         final ContentValues values = getTestStatusValues();
    471         // Inserting for another package should fail with any of the URIs.
    472         values.put(Status.SOURCE_PACKAGE, "another.package");
    473         EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
    474             @Override
    475             public void run() {
    476                 mResolver.insert(Status.CONTENT_URI, values);
    477             }
    478         });
    479         EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
    480             @Override
    481             public void run() {
    482                 mResolver.insert(Status.buildSourceUri(mActor.packageName), values);
    483             }
    484         });
    485 
    486         // But insertion with own package should succeed with the right uri.
    487         values.put(Status.SOURCE_PACKAGE, mActor.packageName);
    488         final Uri uri = mResolver.insert(Status.buildSourceUri(mActor.packageName), values);
    489         assertNotNull(uri);
    490 
    491         // Updating source_package should not work as well.
    492         values.put(Status.SOURCE_PACKAGE, "another.package");
    493         EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
    494             @Override
    495             public void run() {
    496                 mResolver.update(uri, values, null, null);
    497             }
    498         });
    499     }
    500 
    501     // File operation is not supported by /status URI.
    502     public void testStatusFileOperation() throws Exception {
    503         final Uri uri = insertTestStatusEntry();
    504         EvenMoreAsserts.assertThrows(UnsupportedOperationException.class, new Runnable() {
    505             @Override
    506             public void run() {
    507                 try {
    508                     mResolver.openOutputStream(uri);
    509                 } catch (FileNotFoundException e) {
    510                     fail("Unexpected exception " + e);
    511                 }
    512             }
    513         });
    514 
    515         EvenMoreAsserts.assertThrows(UnsupportedOperationException.class, new Runnable() {
    516             @Override
    517             public void run() {
    518                 try {
    519                     mResolver.openInputStream(uri);
    520                 } catch (FileNotFoundException e) {
    521                     fail("Unexpected exception " + e);
    522                 }
    523             }
    524         });
    525     }
    526 
    527     /**
    528      * Inserts a voicemail record with no source package set. The content provider
    529      * will detect source package.
    530      */
    531     private Uri insertVoicemail() {
    532         return mResolver.insert(voicemailUri(), getTestVoicemailValues());
    533     }
    534 
    535     /** Inserts a voicemail record for the specified source package. */
    536     private Uri insertVoicemailForSourcePackage(String sourcePackage) {
    537         ContentValues values = getTestVoicemailValues();
    538         values.put(Voicemails.SOURCE_PACKAGE, sourcePackage);
    539         return mResolver.insert(voicemailUri(), values);
    540     }
    541 
    542     private ContentValues getTestVoicemailValues() {
    543         ContentValues values = new ContentValues();
    544         values.put(Voicemails.NUMBER, "1-800-4664-411");
    545         values.put(Voicemails.DATE, 1000);
    546         values.put(Voicemails.DURATION, 30);
    547         values.put(Voicemails.IS_READ, 0);
    548         values.put(Voicemails.HAS_CONTENT, 0);
    549         values.put(Voicemails.SOURCE_DATA, "1234");
    550         values.put(Voicemails.STATE, Voicemails.STATE_INBOX);
    551         return values;
    552     }
    553 
    554     private Uri insertTestStatusEntry() {
    555         return mResolver.insert(statusUri(), getTestStatusValues());
    556     }
    557 
    558     private ContentValues getTestStatusValues() {
    559         ContentValues values = new ContentValues();
    560         values.put(Status.SOURCE_PACKAGE, mActor.packageName);
    561         values.put(Status.VOICEMAIL_ACCESS_URI, "tel:901");
    562         values.put(Status.SETTINGS_URI, "com.example.voicemail.source.SettingsActivity");
    563         values.put(Status.CONFIGURATION_STATE, Status.CONFIGURATION_STATE_OK);
    564         values.put(Status.DATA_CHANNEL_STATE, Status.DATA_CHANNEL_STATE_OK);
    565         values.put(Status.NOTIFICATION_CHANNEL_STATE, Status.NOTIFICATION_CHANNEL_STATE_OK);
    566         return values;
    567     }
    568 }
    569