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