blob: 5b0e0c7cd781414ef1f5ef67e677aecb9693b5c0 [file] [log] [blame]
/*
* Copyright (C) 2015 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.calllog.cts;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.provider.CallLog;
import android.provider.CallLog.Calls;
import android.test.InstrumentationTestCase;
import android.util.Log;
import com.android.org.bouncycastle.util.encoders.Base64;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Verifies the behavior of CallLogBackup.
*
* This tests three import things:
* 1. That call log gets backed up and restored using the standard BackupAgent implementation.
* - To test we create call-log entries back them up, clear the call log, and restore them.
* - We leverage the LocalTransport backup implementation to do this.
* 2. The call log backup is implemented by the expected package: {@link #CALLLOG_BACKUP_PACKAGE}.
* - We always trigger the expected package for backup/restore within the tests.
* 3. The backup format for call log is as expected so that backup works across android devices
* by different OEMs.
* - We peek into the backup files saved by LocalTransport and verify their binary output is
* as expected.
*/
public class CallLogBackupTest extends InstrumentationTestCase {
private static final String TAG = "CallLogBackupTest";
private static final String LOCAL_BACKUP_COMPONENT =
"android/com.android.internal.backup.LocalTransport";
private static final String CALLLOG_BACKUP_PACKAGE = "com.android.providers.calllogbackup";
private static final String TEST_NUMBER = "555-1234";
private static final int CALL_START_TIME = 0;
private static final int CALL_DURATION = 2000;
private static final int TIMEOUT_BACKUP = 4000;
private static final Pattern BMGR_ENABLED_PATTERN = Pattern.compile(
"^Backup Manager currently (enabled|disabled)$");
private static final String[] CALL_LOG_PROJECTION = new String[] {
CallLog.Calls._ID,
CallLog.Calls.DATE,
CallLog.Calls.DURATION,
CallLog.Calls.NUMBER,
CallLog.Calls.TYPE,
CallLog.Calls.COUNTRY_ISO,
CallLog.Calls.GEOCODED_LOCATION,
CallLog.Calls.NUMBER_PRESENTATION,
CallLog.Calls.PHONE_ACCOUNT_COMPONENT_NAME,
CallLog.Calls.PHONE_ACCOUNT_ID,
CallLog.Calls.DATA_USAGE,
CallLog.Calls.FEATURES
};
class Call {
int id;
long date;
long duration;
String number;
int type;
String phoneAccountComponent;
String phoneAccountId;
int presentation;
}
@Override
protected void setUp() throws Exception {
super.setUp();
}
/**
* Test:
* 1) Clear the call log
* 2) Add a single call
* 3) Run backup
* 4) clear the call log
* 5) Run restore
* 6) Verify that we the call from step (2)
*/
public void testSingleCallBackup() throws Exception {
// This CTS test depends on the local transport and so if it is not present,
// skip the test with success.
if (!hasBackupTransport(LOCAL_BACKUP_COMPONENT)) {
Log.i(TAG, "skipping calllog tests");
return;
}
// Turn on backup and set to the local backup agent.
boolean previouslyEnabled = enableBackup(true /* enable */);
String oldTransport = setBackupTransport(LOCAL_BACKUP_COMPONENT);
// Clear the call log
Log.i(TAG, "Clearing the call log");
clearCallLog();
// Add a single call and verify it exists
Log.i(TAG, "Adding a call");
addCall();
verifyCall();
// Run backup for the call log (saves the single call).
Log.i(TAG, "Running backup");
runBackupFor(CALLLOG_BACKUP_PACKAGE);
Thread.sleep(TIMEOUT_BACKUP); // time for backup
// Clear the call log and verify that it is empty
Log.i(TAG, "Clearing the call log");
clearCallLog();
assertEquals(0, getCalls().size());
// Restore from the previous backup and verify we have the new call again.
Log.i(TAG, "Restoring the single call");
runRestoreFor(CALLLOG_BACKUP_PACKAGE);
Thread.sleep(TIMEOUT_BACKUP); // time for restore
verifyCall();
// Reset backup manager to original state.
Log.i(TAG, "Reseting backup");
setBackupTransport(oldTransport);
enableBackup(previouslyEnabled);
}
private Call verifyCall() {
List<Call> calls = getCalls();
assertEquals(1, calls.size());
Call call = calls.get(0);
assertEquals(TEST_NUMBER, call.number);
assertEquals(CALL_START_TIME, call.date);
assertEquals(CALL_DURATION, call.duration);
assertEquals(Calls.OUTGOING_TYPE, call.type);
return call;
}
private boolean enableBackup(boolean enable) throws Exception {
// Check to see the previous state of the backup service
boolean previouslyEnabled = false;
String output = exec("bmgr enabled");
Matcher matcher = BMGR_ENABLED_PATTERN.matcher(output.trim());
if (matcher.find()) {
previouslyEnabled = "enabled".equals(matcher.group(1));
} else {
throw new RuntimeException("Backup output format changed. No longer matches"
+ " expected regex: " + BMGR_ENABLED_PATTERN + "\nactual: '" + output + "'");
}
exec("bmgr enable " + enable);
return previouslyEnabled;
}
private void runBackupFor(String packageName) throws Exception {
exec("bmgr backup " + packageName);
exec("bmgr run");
}
private void runRestoreFor(String packageName) throws Exception {
exec("bmgr restore " + packageName);
}
private String setBackupTransport(String transport) throws Exception {
String output = exec("bmgr transport " + transport);
Pattern pattern = Pattern.compile("\\(formerly (.*)\\)$");
Matcher matcher = pattern.matcher(output);
if (matcher.find()) {
return matcher.group(1);
} else {
throw new Exception("non-parsable output setting bmgr transport: " + output);
}
}
/**
* Checks the list of supported transports and verifies that the specified transport
* is included.
*/
private boolean hasBackupTransport(String transport) throws Exception {
String output = exec("bmgr list transports");
for (String t : output.split(" ")) {
if ("*".equals(t)) {
// skip the current selection marker.
continue;
} else if (Objects.equals(transport, t)) {
return true;
}
}
return false;
}
private void clearCallLog() {
ContentResolver resolver = getContext().getContentResolver();
resolver.delete(Calls.CONTENT_URI, null, null);
}
private void addCall() {
ContentValues values = new ContentValues(6);
values.put(Calls.NUMBER, TEST_NUMBER);
values.put(Calls.NUMBER_PRESENTATION, Calls.PRESENTATION_ALLOWED);
values.put(Calls.TYPE, Integer.valueOf(Calls.OUTGOING_TYPE));
values.put(Calls.DATE, Long.valueOf(CALL_START_TIME));
values.put(Calls.DURATION, Long.valueOf(CALL_DURATION));
values.put(Calls.NEW, Integer.valueOf(1));
getContext().getContentResolver().insert(Calls.CONTENT_URI, values);
}
private List<Call> getCalls() {
List<Call> calls = new LinkedList<>();
ContentResolver resolver = getContext().getContentResolver();
Cursor cursor = resolver.query(Calls.CONTENT_URI, CALL_LOG_PROJECTION, null, null, null);
if (cursor != null) {
try {
while (cursor.moveToNext()) {
Call call = new Call();
call.id = cursor.getInt(cursor.getColumnIndex(Calls._ID));
call.number = cursor.getString(cursor.getColumnIndex(Calls.NUMBER));
call.date = cursor.getLong(cursor.getColumnIndex(Calls.DATE));
call.duration = cursor.getLong(cursor.getColumnIndex(Calls.DURATION));
call.type = cursor.getInt(cursor.getColumnIndex(Calls.TYPE));
call.phoneAccountComponent = cursor.getString(
cursor.getColumnIndex(Calls.PHONE_ACCOUNT_COMPONENT_NAME));
call.phoneAccountId = cursor.getString(
cursor.getColumnIndex(Calls.PHONE_ACCOUNT_ID));
call.presentation = cursor.getInt(
cursor.getColumnIndex(Calls.NUMBER_PRESENTATION));
calls.add(call);
}
} finally {
cursor.close();
}
}
return calls;
}
private Context getContext() {
return getInstrumentation().getContext();
}
private String exec(String command) throws Exception {
return TestUtils.executeShellCommand(getInstrumentation(), command);
}
}