blob: b7130284dba9cfcab4067a1e93aa37dbf3e18e74 [file] [log] [blame]
/*
* 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;
}
}