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.content.pm.PackageManager; 23 import android.database.Cursor; 24 import android.provider.CallLog; 25 import android.provider.CallLog.Calls; 26 import android.provider.Settings; 27 import android.test.InstrumentationTestCase; 28 import android.util.Log; 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 56 private static final String CALLLOG_BACKUP_PACKAGE = "com.android.providers.calllogbackup"; 57 private static final String ALT_CALLLOG_BACKUP_PACKAGE = "com.android.calllogbackup"; 58 59 private static final String TEST_NUMBER = "555-1234"; 60 private static final int CALL_START_TIME = 0; 61 private static final int CALL_DURATION = 2000; 62 private static final int TIMEOUT_BACKUP = 10000; 63 private static final String TEST_POST_DIAL_DIGITS = ";1234"; 64 private static final String TEST_VIA_NUMBER = "555-1112"; 65 66 private static final Pattern BMGR_ENABLED_PATTERN = Pattern.compile( 67 "^Backup Manager currently (enabled|disabled)$"); 68 69 private static final String[] CALL_LOG_PROJECTION = new String[] { 70 CallLog.Calls._ID, 71 CallLog.Calls.DATE, 72 CallLog.Calls.DURATION, 73 CallLog.Calls.NUMBER, 74 CallLog.Calls.TYPE, 75 CallLog.Calls.COUNTRY_ISO, 76 CallLog.Calls.GEOCODED_LOCATION, 77 CallLog.Calls.NUMBER_PRESENTATION, 78 CallLog.Calls.PHONE_ACCOUNT_COMPONENT_NAME, 79 CallLog.Calls.PHONE_ACCOUNT_ID, 80 CallLog.Calls.DATA_USAGE, 81 CallLog.Calls.FEATURES, 82 CallLog.Calls.POST_DIAL_DIGITS, 83 CallLog.Calls.VIA_NUMBER 84 }; 85 86 protected interface Condition { expected()87 Object expected(); actual()88 Object actual(); 89 } 90 91 class Call { 92 int id; 93 long date; 94 long duration; 95 String number; 96 int type; 97 String phoneAccountComponent; 98 String phoneAccountId; 99 int presentation; 100 String postDialDigits; 101 String viaNumber; 102 } 103 104 private String mCallLogBackupPackageName; 105 106 @Override setUp()107 protected void setUp() throws Exception { 108 super.setUp(); 109 PackageManager pm = getContext().getPackageManager(); 110 try { 111 pm.getPackageInfo(CALLLOG_BACKUP_PACKAGE, 0); 112 mCallLogBackupPackageName = CALLLOG_BACKUP_PACKAGE; 113 } catch (PackageManager.NameNotFoundException e) { 114 mCallLogBackupPackageName = ALT_CALLLOG_BACKUP_PACKAGE; 115 } 116 } 117 118 /** 119 * Test: 120 * 1) Clear the call log 121 * 2) Add a single call 122 * 3) Run backup 123 * 4) clear the call log 124 * 5) Run restore 125 * 6) Verify that we the call from step (2) 126 */ testSingleCallBackup()127 public void testSingleCallBackup() throws Exception { 128 if (!getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) { 129 Log.i(TAG, "Skipping calllog tests: no telephony feature"); 130 return; 131 } 132 // This CTS test depends on the local transport and so if it is not present, 133 // skip the test with success. 134 if (!hasBackupTransport(LOCAL_BACKUP_COMPONENT)) { 135 Log.i(TAG, "skipping calllog tests: no local transport"); 136 return; 137 } 138 139 // Turn on backup and set to the local backup agent. 140 boolean previouslyEnabled = enableBackup(true /* enable */); 141 String oldTransport = setBackupTransport(LOCAL_BACKUP_COMPONENT); 142 int previousFullDataBackupAware = Settings.Secure.getInt(getContext().getContentResolver(), 143 "user_full_data_backup_aware", 0); 144 enableFullDataBackupAware(1); 145 146 // Clear the call log 147 Log.i(TAG, "Clearing the call log"); 148 clearCallLog(); 149 clearBackups(); 150 151 // Add a single call and verify it exists 152 Log.i(TAG, "Adding a call"); 153 addCall(); 154 verifyCall(); 155 156 // Run backup for the call log (saves the single call). 157 Log.i(TAG, "Running backup"); 158 runBackupFor(mCallLogBackupPackageName); 159 160 // Clear the call log and verify that it is empty 161 Log.i(TAG, "Clearing the call log"); 162 clearCallLog(); 163 assertEquals(0, getCalls().size()); 164 165 // Restore from the previous backup and verify we have the new call again. 166 Log.i(TAG, "Restoring the single call"); 167 runRestoreFor(mCallLogBackupPackageName); 168 169 verifyCall(); 170 171 // Clean up after ourselves 172 clearCallLog(); 173 174 // Reset backup manager to original state. 175 Log.i(TAG, "Reseting backup"); 176 setBackupTransport(oldTransport); 177 enableBackup(previouslyEnabled); 178 enableFullDataBackupAware(previousFullDataBackupAware); 179 } 180 verifyCall()181 private Call verifyCall() { 182 waitUntilConditionIsTrueOrTimeout(new Condition() { 183 @Override 184 public Object expected() { 185 return 1; 186 } 187 188 @Override 189 public Object actual() { 190 return getCalls().size(); 191 } 192 }, TIMEOUT_BACKUP); 193 194 List<Call> calls = getCalls(); 195 Call call = calls.get(0); 196 assertEquals(TEST_NUMBER, call.number); 197 assertEquals(CALL_START_TIME, call.date); 198 assertEquals(CALL_DURATION, call.duration); 199 assertEquals(Calls.OUTGOING_TYPE, call.type); 200 assertEquals(TEST_POST_DIAL_DIGITS, call.postDialDigits); 201 assertEquals(TEST_VIA_NUMBER, call.viaNumber); 202 return call; 203 } 204 enableBackup(boolean enable)205 private boolean enableBackup(boolean enable) throws Exception { 206 // Check to see the previous state of the backup service 207 boolean previouslyEnabled = false; 208 String output = exec("bmgr enabled"); 209 Matcher matcher = BMGR_ENABLED_PATTERN.matcher(output.trim()); 210 if (matcher.find()) { 211 previouslyEnabled = "enabled".equals(matcher.group(1)); 212 } else { 213 throw new RuntimeException("Backup output format changed. No longer matches" 214 + " expected regex: " + BMGR_ENABLED_PATTERN + "\nactual: '" + output + "'"); 215 } 216 217 exec("bmgr enable " + enable); 218 return previouslyEnabled; 219 } 220 runBackupFor(String packageName)221 private void runBackupFor(String packageName) throws Exception { 222 exec("bmgr backupnow " + packageName); 223 } 224 runRestoreFor(String packageName)225 private void runRestoreFor(String packageName) throws Exception { 226 exec("bmgr restore " + packageName); 227 } 228 enableFullDataBackupAware(int status)229 private void enableFullDataBackupAware(int status) throws Exception { 230 exec("settings put secure user_full_data_backup_aware " + status); 231 } 232 setBackupTransport(String transport)233 private String setBackupTransport(String transport) throws Exception { 234 String output = exec("bmgr transport " + transport); 235 Pattern pattern = Pattern.compile("\\(formerly (.*)\\)$"); 236 Matcher matcher = pattern.matcher(output); 237 if (matcher.find()) { 238 return matcher.group(1); 239 } else { 240 throw new Exception("non-parsable output setting bmgr transport: " + output); 241 } 242 } 243 244 /** 245 * Checks the list of supported transports and verifies that the specified transport 246 * is included. 247 */ hasBackupTransport(String transport)248 private boolean hasBackupTransport(String transport) throws Exception { 249 String output = exec("bmgr list transports"); 250 for (String t : output.split(" ")) { 251 if ("*".equals(t)) { 252 // skip the current selection marker. 253 continue; 254 } else if (Objects.equals(transport, t)) { 255 return true; 256 } 257 } 258 return false; 259 } 260 clearCallLog()261 private void clearCallLog() { 262 ContentResolver resolver = getContext().getContentResolver(); 263 resolver.delete(Calls.CONTENT_URI, null, null); 264 } 265 clearBackups()266 private void clearBackups() throws Exception { 267 exec("bmgr wipe " + LOCAL_BACKUP_COMPONENT + " " + mCallLogBackupPackageName); 268 } 269 addCall()270 private void addCall() { 271 ContentValues values = new ContentValues(6); 272 values.put(Calls.NUMBER, TEST_NUMBER); 273 values.put(Calls.NUMBER_PRESENTATION, Calls.PRESENTATION_ALLOWED); 274 values.put(Calls.TYPE, Integer.valueOf(Calls.OUTGOING_TYPE)); 275 values.put(Calls.DATE, Long.valueOf(CALL_START_TIME)); 276 values.put(Calls.DURATION, Long.valueOf(CALL_DURATION)); 277 values.put(Calls.NEW, Integer.valueOf(1)); 278 values.put(Calls.POST_DIAL_DIGITS, TEST_POST_DIAL_DIGITS); 279 values.put(Calls.VIA_NUMBER, TEST_VIA_NUMBER); 280 281 getContext().getContentResolver().insert(Calls.CONTENT_URI, values); 282 } 283 waitUntilConditionIsTrueOrTimeout(Condition condition, long timeout)284 void waitUntilConditionIsTrueOrTimeout(Condition condition, long timeout) { 285 final long start = System.currentTimeMillis(); 286 while (!condition.expected().equals(condition.actual()) 287 && System.currentTimeMillis() - start < timeout) { 288 try { 289 Thread.sleep(100); 290 } catch (InterruptedException e) { 291 // ignore 292 } 293 } 294 assertEquals(condition.expected(), condition.actual()); 295 } 296 getCalls()297 private List<Call> getCalls() { 298 List<Call> calls = new LinkedList<>(); 299 300 ContentResolver resolver = getContext().getContentResolver(); 301 Cursor cursor = resolver.query(Calls.CONTENT_URI, CALL_LOG_PROJECTION, null, null, null); 302 if (cursor != null) { 303 try { 304 while (cursor.moveToNext()) { 305 Call call = new Call(); 306 call.id = cursor.getInt(cursor.getColumnIndex(Calls._ID)); 307 call.number = cursor.getString(cursor.getColumnIndex(Calls.NUMBER)); 308 call.date = cursor.getLong(cursor.getColumnIndex(Calls.DATE)); 309 call.duration = cursor.getLong(cursor.getColumnIndex(Calls.DURATION)); 310 call.type = cursor.getInt(cursor.getColumnIndex(Calls.TYPE)); 311 call.phoneAccountComponent = cursor.getString( 312 cursor.getColumnIndex(Calls.PHONE_ACCOUNT_COMPONENT_NAME)); 313 call.phoneAccountId = cursor.getString( 314 cursor.getColumnIndex(Calls.PHONE_ACCOUNT_ID)); 315 call.presentation = cursor.getInt( 316 cursor.getColumnIndex(Calls.NUMBER_PRESENTATION)); 317 call.postDialDigits = cursor.getString( 318 cursor.getColumnIndex(Calls.POST_DIAL_DIGITS)); 319 call.viaNumber = cursor.getString( 320 cursor.getColumnIndex(Calls.VIA_NUMBER)); 321 calls.add(call); 322 } 323 } finally { 324 cursor.close(); 325 } 326 } 327 return calls; 328 } 329 getContext()330 private Context getContext() { 331 return getInstrumentation().getContext(); 332 } 333 exec(String command)334 private String exec(String command) throws Exception { 335 return TestUtils.executeShellCommand(getInstrumentation(), command); 336 } 337 } 338