1 /* 2 * Copyright (C) 2015 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 android.calllog.cts; 18 19 import android.content.ContentResolver; 20 import android.content.ContentValues; 21 import android.content.Context; 22 import android.database.Cursor; 23 import android.provider.CallLog; 24 import android.provider.CallLog.Calls; 25 import android.test.InstrumentationTestCase; 26 import android.util.Log; 27 28 import com.android.org.bouncycastle.util.encoders.Base64; 29 30 import java.util.LinkedList; 31 import java.util.List; 32 import java.util.Objects; 33 import java.util.regex.Matcher; 34 import java.util.regex.Pattern; 35 36 /** 37 * Verifies the behavior of CallLogBackup. 38 * 39 * This tests three import things: 40 * 1. That call log gets backed up and restored using the standard BackupAgent implementation. 41 * - To test we create call-log entries back them up, clear the call log, and restore them. 42 * - We leverage the LocalTransport backup implementation to do this. 43 * 2. The call log backup is implemented by the expected package: {@link #CALLLOG_BACKUP_PACKAGE}. 44 * - We always trigger the expected package for backup/restore within the tests. 45 * 3. The backup format for call log is as expected so that backup works across android devices 46 * by different OEMs. 47 * - We peek into the backup files saved by LocalTransport and verify their binary output is 48 * as expected. 49 */ 50 public class CallLogBackupTest extends InstrumentationTestCase { 51 private static final String TAG = "CallLogBackupTest"; 52 53 private static final String LOCAL_BACKUP_COMPONENT = 54 "android/com.android.internal.backup.LocalTransport"; 55 private static final String CALLLOG_BACKUP_PACKAGE = "com.android.providers.calllogbackup"; 56 57 private static final String TEST_NUMBER = "555-1234"; 58 private static final int CALL_START_TIME = 0; 59 private static final int CALL_DURATION = 2000; 60 private static final int TIMEOUT_BACKUP = 4000; 61 62 private static final Pattern BMGR_ENABLED_PATTERN = Pattern.compile( 63 "^Backup Manager currently (enabled|disabled)$"); 64 65 private static final String[] CALL_LOG_PROJECTION = new String[] { 66 CallLog.Calls._ID, 67 CallLog.Calls.DATE, 68 CallLog.Calls.DURATION, 69 CallLog.Calls.NUMBER, 70 CallLog.Calls.TYPE, 71 CallLog.Calls.COUNTRY_ISO, 72 CallLog.Calls.GEOCODED_LOCATION, 73 CallLog.Calls.NUMBER_PRESENTATION, 74 CallLog.Calls.PHONE_ACCOUNT_COMPONENT_NAME, 75 CallLog.Calls.PHONE_ACCOUNT_ID, 76 CallLog.Calls.DATA_USAGE, 77 CallLog.Calls.FEATURES 78 }; 79 80 class Call { 81 int id; 82 long date; 83 long duration; 84 String number; 85 int type; 86 String phoneAccountComponent; 87 String phoneAccountId; 88 int presentation; 89 } 90 91 @Override setUp()92 protected void setUp() throws Exception { 93 super.setUp(); 94 } 95 96 /** 97 * Test: 98 * 1) Clear the call log 99 * 2) Add a single call 100 * 3) Run backup 101 * 4) clear the call log 102 * 5) Run restore 103 * 6) Verify that we the call from step (2) 104 */ testSingleCallBackup()105 public void testSingleCallBackup() throws Exception { 106 // This CTS test depends on the local transport and so if it is not present, 107 // skip the test with success. 108 if (!hasBackupTransport(LOCAL_BACKUP_COMPONENT)) { 109 Log.i(TAG, "skipping calllog tests"); 110 return; 111 } 112 113 // Turn on backup and set to the local backup agent. 114 boolean previouslyEnabled = enableBackup(true /* enable */); 115 String oldTransport = setBackupTransport(LOCAL_BACKUP_COMPONENT); 116 117 // Clear the call log 118 Log.i(TAG, "Clearing the call log"); 119 clearCallLog(); 120 121 // Add a single call and verify it exists 122 Log.i(TAG, "Adding a call"); 123 addCall(); 124 verifyCall(); 125 126 // Run backup for the call log (saves the single call). 127 Log.i(TAG, "Running backup"); 128 runBackupFor(CALLLOG_BACKUP_PACKAGE); 129 Thread.sleep(TIMEOUT_BACKUP); // time for backup 130 131 // Clear the call log and verify that it is empty 132 Log.i(TAG, "Clearing the call log"); 133 clearCallLog(); 134 assertEquals(0, getCalls().size()); 135 136 // Restore from the previous backup and verify we have the new call again. 137 Log.i(TAG, "Restoring the single call"); 138 runRestoreFor(CALLLOG_BACKUP_PACKAGE); 139 Thread.sleep(TIMEOUT_BACKUP); // time for restore 140 141 verifyCall(); 142 143 // Reset backup manager to original state. 144 Log.i(TAG, "Reseting backup"); 145 setBackupTransport(oldTransport); 146 enableBackup(previouslyEnabled); 147 } 148 verifyCall()149 private Call verifyCall() { 150 List<Call> calls = getCalls(); 151 assertEquals(1, calls.size()); 152 153 Call call = calls.get(0); 154 assertEquals(TEST_NUMBER, call.number); 155 assertEquals(CALL_START_TIME, call.date); 156 assertEquals(CALL_DURATION, call.duration); 157 assertEquals(Calls.OUTGOING_TYPE, call.type); 158 return call; 159 } 160 enableBackup(boolean enable)161 private boolean enableBackup(boolean enable) throws Exception { 162 // Check to see the previous state of the backup service 163 boolean previouslyEnabled = false; 164 String output = exec("bmgr enabled"); 165 Matcher matcher = BMGR_ENABLED_PATTERN.matcher(output.trim()); 166 if (matcher.find()) { 167 previouslyEnabled = "enabled".equals(matcher.group(1)); 168 } else { 169 throw new RuntimeException("Backup output format changed. No longer matches" 170 + " expected regex: " + BMGR_ENABLED_PATTERN + "\nactual: '" + output + "'"); 171 } 172 173 exec("bmgr enable " + enable); 174 return previouslyEnabled; 175 } 176 runBackupFor(String packageName)177 private void runBackupFor(String packageName) throws Exception { 178 exec("bmgr backup " + packageName); 179 exec("bmgr run"); 180 } 181 runRestoreFor(String packageName)182 private void runRestoreFor(String packageName) throws Exception { 183 exec("bmgr restore " + packageName); 184 } 185 setBackupTransport(String transport)186 private String setBackupTransport(String transport) throws Exception { 187 String output = exec("bmgr transport " + transport); 188 Pattern pattern = Pattern.compile("\\(formerly (.*)\\)$"); 189 Matcher matcher = pattern.matcher(output); 190 if (matcher.find()) { 191 return matcher.group(1); 192 } else { 193 throw new Exception("non-parsable output setting bmgr transport: " + output); 194 } 195 } 196 197 /** 198 * Checks the list of supported transports and verifies that the specified transport 199 * is included. 200 */ hasBackupTransport(String transport)201 private boolean hasBackupTransport(String transport) throws Exception { 202 String output = exec("bmgr list transports"); 203 for (String t : output.split(" ")) { 204 if ("*".equals(t)) { 205 // skip the current selection marker. 206 continue; 207 } else if (Objects.equals(transport, t)) { 208 return true; 209 } 210 } 211 return false; 212 } 213 clearCallLog()214 private void clearCallLog() { 215 ContentResolver resolver = getContext().getContentResolver(); 216 resolver.delete(Calls.CONTENT_URI, null, null); 217 } 218 addCall()219 private void addCall() { 220 ContentValues values = new ContentValues(6); 221 values.put(Calls.NUMBER, TEST_NUMBER); 222 values.put(Calls.NUMBER_PRESENTATION, Calls.PRESENTATION_ALLOWED); 223 values.put(Calls.TYPE, Integer.valueOf(Calls.OUTGOING_TYPE)); 224 values.put(Calls.DATE, Long.valueOf(CALL_START_TIME)); 225 values.put(Calls.DURATION, Long.valueOf(CALL_DURATION)); 226 values.put(Calls.NEW, Integer.valueOf(1)); 227 228 getContext().getContentResolver().insert(Calls.CONTENT_URI, values); 229 } 230 getCalls()231 private List<Call> getCalls() { 232 List<Call> calls = new LinkedList<>(); 233 234 ContentResolver resolver = getContext().getContentResolver(); 235 Cursor cursor = resolver.query(Calls.CONTENT_URI, CALL_LOG_PROJECTION, null, null, null); 236 if (cursor != null) { 237 try { 238 while (cursor.moveToNext()) { 239 Call call = new Call(); 240 call.id = cursor.getInt(cursor.getColumnIndex(Calls._ID)); 241 call.number = cursor.getString(cursor.getColumnIndex(Calls.NUMBER)); 242 call.date = cursor.getLong(cursor.getColumnIndex(Calls.DATE)); 243 call.duration = cursor.getLong(cursor.getColumnIndex(Calls.DURATION)); 244 call.type = cursor.getInt(cursor.getColumnIndex(Calls.TYPE)); 245 call.phoneAccountComponent = cursor.getString( 246 cursor.getColumnIndex(Calls.PHONE_ACCOUNT_COMPONENT_NAME)); 247 call.phoneAccountId = cursor.getString( 248 cursor.getColumnIndex(Calls.PHONE_ACCOUNT_ID)); 249 call.presentation = cursor.getInt( 250 cursor.getColumnIndex(Calls.NUMBER_PRESENTATION)); 251 calls.add(call); 252 } 253 } finally { 254 cursor.close(); 255 } 256 } 257 return calls; 258 } 259 getContext()260 private Context getContext() { 261 return getInstrumentation().getContext(); 262 } 263 exec(String command)264 private String exec(String command) throws Exception { 265 return TestUtils.executeShellCommand(getInstrumentation(), command); 266 } 267 } 268