cts tests for CallLogProvider#queryInternal
changes:
- phoneNumber is now a selctionArgument
- if the cursor is empty && SQL is detected, a SE is thrown
Bug: 224771921
Test: 2 manual,
manual 1: test app 1 can still make valid call filter query
manual 2: test app 2 with invalid query crashes b/c of SE
2 CTS tests,
test 1: ensures the existing functionality still works
test 2: ensures a SE is thrown on an invalid query for call filter
bug: 224771921
Test: 2
Change-Id: I84f5c10ad6214ccc46208ca378c9fdb4951a0279
Merged-In: I84f5c10ad6214ccc46208ca378c9fdb4951a0279
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/CallLogTest.java b/tests/tests/provider/src/android/provider/cts/contacts/CallLogTest.java
new file mode 100644
index 0000000..b713028
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/contacts/CallLogTest.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.provider.cts.contacts;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import android.Manifest;
+import android.app.ActivityManager;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.os.UserHandle;
+import android.provider.CallLog;
+import android.provider.CallLog.Calls;
+import android.provider.cts.R;
+import android.test.InstrumentationTestCase;
+import android.util.Pair;
+
+import com.android.compatibility.common.util.ShellIdentityUtils;
+import com.android.compatibility.common.util.ShellUtils;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+public class CallLogTest extends InstrumentationTestCase {
+ // Test Call Log Entry
+ private static final String TEST_NUMBER = "5625698388";
+ private static final int TEST_DATE = 1000;
+ private static final int TEST_DURATION = 30;
+ // Test Voicemail Log Entry
+ private static final String TEST_VOICEMAIL_NUMBER = "1119871234";
+ private static final int TEST_VOCIEMAIL_DATE = 1;
+ private static final int TEST_VOICEMAIL_DURATION = 5;
+ // Timeout
+ private static final long CONTENT_RESOLVER_TIMEOUT_MS = 5000;
+ // SQL Selection Column Names
+ private static final String SELECTION_TYPE = "type";
+ private static final String SELECTION_NUMBER = "number";
+ private static final String SELECTION_DATE = "date";
+ private static final String SELECTION_DURATION = "duration";
+ private static final String SELECTION_NEW = "new";
+ // SQL Selection as array
+ private static final String[] SELECTION =
+ new String[]{SELECTION_TYPE, SELECTION_NUMBER, SELECTION_DATE,
+ SELECTION_DURATION, SELECTION_NEW};
+ // Test filter URI that throws Security Exception
+ private static final Uri INVALID_FILTER_URI = Uri.parse(
+ "content://call_log/calls/filter/test\uD83D')) union select type,name,"
+ + "tbl_name,rootpage,sql FROM SQLITE_MASTER; --");
+ // Test call composer URI that throws Security Exception
+ private static final Uri INVALID_CALL_LOG_URI = Uri.parse(
+ "content://call_log/call_composer/%2fdata%2fdata%2fcom.android.providers"
+ + ".contacts%2fshared_prefs%2fContactsUpgradeReceiver.xml");
+ // Test Failure Error
+ private static final String TEST_FAIL_DID_NOT_TRHOW_SE =
+ "fail test because Security Exception was not throw";
+ // Instance vars
+ private ContentResolver mContentResolver;
+
+ // Class to objectify the call log data (returned from a Cursor object)
+ public class LogEntry {
+ // properties
+ public Integer type;
+ public String number;
+ public Integer date;
+ public Integer duration;
+ public Integer newCount;
+ public String extras;
+
+ // setter
+ public void setValue(String selectionColumn, String value) {
+ if (value == null) {
+ // Integer.valueOf(value) throws NumberFormatException if string is null.
+ // so return early if value is null.
+ return;
+ }
+ try {
+ switch (selectionColumn) {
+ case SELECTION_TYPE:
+ type = Integer.valueOf(value);
+ break;
+ case SELECTION_NUMBER:
+ number = value;
+ break;
+ case SELECTION_DATE:
+ date = Integer.valueOf(value);
+ break;
+ case SELECTION_DURATION:
+ duration = Integer.valueOf(value);
+ break;
+ case SELECTION_NEW:
+ newCount = Integer.valueOf(value);
+ break;
+ default:
+ extras = value;
+ }
+ } catch (NumberFormatException e) {
+ // pass through
+ }
+ }
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ // Sets up this package as default dialer in super.
+ super.setUp();
+ mContentResolver = getInstrumentation().getContext().getContentResolver();
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ /**
+ * Ensure that the existing query functionality still works. To verify the functionality,
+ * this test adds a single call and voicemail entry to the logs, queries the logs,
+ * and asserts the entries are returned.
+ */
+ public void testPopulateAndQueryCallAndVoicemailLogs() {
+ try {
+ // needed in order to populate call log database
+ ShellUtils.runShellCommand("telecom set-default-dialer %s",
+ getInstrumentation().getContext().getPackageName());
+
+ populateLogsWithDefaults();
+
+ // query and get cursor
+ Cursor cursor = mContentResolver
+ .query(Calls.CONTENT_URI_WITH_VOICEMAIL, SELECTION, null, null);
+
+ // extract the data from the cursor and put the objects in a map
+ Map<String, LogEntry> entries = collectCursorEntries(cursor);
+
+ // cleanup
+ cursor.close();
+
+ // call entry
+ assertEquals(TEST_NUMBER, entries.get(TEST_NUMBER).number);
+ // voicemail entry
+ assertEquals(TEST_VOICEMAIL_NUMBER, entries.get(TEST_VOICEMAIL_NUMBER).number);
+ } finally {
+ //cleanup
+ deletePopulatedLogs();
+ ShellUtils.runShellCommand("telecom set-default-dialer default");
+ }
+ }
+
+ /**
+ * Test scenario where an app calls {@link ContentResolver#query} with an invalid URI.
+ *
+ * The URI is invalid because it attempts to bypass voicemail permissions and grab the voicemail
+ * log data without the proper voicemail permissions.
+ *
+ * Therefore, a Security Exception is thrown.
+ */
+ public void testInvalidQueryToCallLog() {
+ try {
+ // needed in order to populate call log database
+ ShellUtils.runShellCommand("telecom set-default-dialer %s",
+ getInstrumentation().getContext().getPackageName());
+
+ populateLogsWithDefaults();
+
+ // drop voicemail permissions
+ ShellUtils.runShellCommand("telecom set-default-dialer default");
+
+ // query and get cursor (expecting to hit Security Exception with call)
+ Cursor cursor = mContentResolver
+ .query(INVALID_FILTER_URI, SELECTION, null, null);
+
+ // the previous line should throw an exception
+ fail(TEST_FAIL_DID_NOT_TRHOW_SE);
+ } catch (SecurityException e) {
+ // success...
+ assertNotNull(e.toString());
+ } finally {
+ //cleanup
+ ShellUtils.runShellCommand("telecom set-default-dialer %s",
+ getInstrumentation().getContext().getPackageName());
+ deletePopulatedLogs();
+ ShellUtils.runShellCommand("telecom set-default-dialer default");
+ }
+ }
+
+ private ContentValues getDefaultValues(int type, String number, int date, int duration) {
+ ContentValues values = new ContentValues();
+ values.put(Calls.TYPE, type);
+ values.put(Calls.NUMBER, number);
+ values.put(Calls.NUMBER_PRESENTATION, Calls.PRESENTATION_ALLOWED);
+ values.put(Calls.DATE, date);
+ values.put(Calls.DURATION, duration);
+ values.put(Calls.NEW, 1);
+ return values;
+ }
+
+ private ContentValues getDefaultCallValues() {
+ return getDefaultValues(Calls.INCOMING_TYPE, TEST_NUMBER, TEST_DATE, TEST_DURATION);
+ }
+
+ private ContentValues getDefaultVoicemailValues() {
+ return getDefaultValues(Calls.VOICEMAIL_TYPE, TEST_VOICEMAIL_NUMBER, TEST_VOCIEMAIL_DATE,
+ TEST_VOICEMAIL_DURATION);
+ }
+
+ private void deletePopulatedLogs() {
+ // delete TEST_NUMBER in the call logs
+ mContentResolver.delete(CallLog.Calls.CONTENT_URI,
+ Calls.NUMBER + "=" + TEST_NUMBER, null);
+ // delete TEST_VOICEMAIL_NUMBER in the voicemail logs
+ mContentResolver.delete(Calls.CONTENT_URI_WITH_VOICEMAIL,
+ Calls.NUMBER + "=" + TEST_VOICEMAIL_NUMBER, null);
+ // cleanup extra entry created in this test that does not have a Calls.NUMBER
+ mContentResolver.delete(Calls.CONTENT_URI_WITH_VOICEMAIL,
+ Calls.DATE + "=" + "0", null);
+ }
+
+ private void populateLogsWithDefaults() {
+ // add call log entry
+ mContentResolver.insert(Calls.CONTENT_URI, getDefaultCallValues());
+ // add voicemail entry
+ mContentResolver.insert(Calls.CONTENT_URI_WITH_VOICEMAIL, getDefaultVoicemailValues());
+ }
+
+ /**
+ * Helper method for a test that wants to objectify the cursor data into LogEntry objects.
+ * NOTE: The key for the map is the phone number, so you can only store one object per number.
+ *
+ * @return all the data in the cursor in a LogEntry map
+ */
+ public Map<String, LogEntry> collectCursorEntries(Cursor cursor) {
+ Map<String, LogEntry> entries = new HashMap<>();
+ // iterate through every row in the cursor
+ while (cursor.moveToNext()) {
+ LogEntry e = new LogEntry();
+ // iterate through each column (should be the SELECTION given to query)
+ for (int i = 0; i < cursor.getColumnCount(); i++) {
+ e.setValue(cursor.getColumnName(i), cursor.getString(i));
+ }
+ // don't add if bad number (should never happen)
+ if (e.number != null || !e.number.isEmpty()) {
+ entries.put(e.number, e);
+ }
+ }
+ return entries;
+ }
+}