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