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