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 com.android.calllogbackup;
18 
19 import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED;
20 
21 import android.app.backup.BackupAgent;
22 import android.app.backup.BackupDataInput;
23 import android.app.backup.BackupDataOutput;
24 import android.app.backup.BackupManager;
25 import android.app.backup.BackupRestoreEventLogger;
26 import android.content.ComponentName;
27 import android.content.ContentResolver;
28 import android.database.Cursor;
29 import android.os.ParcelFileDescriptor;
30 import android.provider.CallLog;
31 import android.provider.CallLog.Calls;
32 import android.telecom.PhoneAccountHandle;
33 import android.telephony.SubscriptionInfo;
34 import android.telephony.SubscriptionManager;
35 import android.util.Log;
36 
37 import com.android.internal.annotations.VisibleForTesting;
38 
39 import java.io.BufferedOutputStream;
40 import java.io.ByteArrayInputStream;
41 import java.io.ByteArrayOutputStream;
42 import java.io.DataInput;
43 import java.io.DataInputStream;
44 import java.io.DataOutput;
45 import java.io.DataOutputStream;
46 import java.io.EOFException;
47 import java.io.FileInputStream;
48 import java.io.FileOutputStream;
49 import java.io.IOException;
50 import java.util.LinkedList;
51 import java.util.List;
52 import java.util.HashMap;
53 import java.util.Map;
54 import java.util.SortedSet;
55 import java.util.TreeSet;
56 
57 /**
58  * Call log backup agent.
59  */
60 public class CallLogBackupAgent extends BackupAgent {
61 
62     @VisibleForTesting
63     static class CallLogBackupState {
64         int version;
65         SortedSet<Integer> callIds;
66     }
67 
68     @VisibleForTesting
69     static class Call {
70         int id;
71         long date;
72         long duration;
73         String number;
74         String postDialDigits = "";
75         String viaNumber = "";
76         int type;
77         int numberPresentation;
78         String accountComponentName;
79         String accountId;
80         String accountAddress;
81         Long dataUsage;
82         int features;
83         int addForAllUsers = 1;
84         int callBlockReason = Calls.BLOCK_REASON_NOT_BLOCKED;
85         String callScreeningAppName = null;
86         String callScreeningComponentName = null;
87         long missedReason = MISSED_REASON_NOT_MISSED;
88         int isPhoneAccountMigrationPending;
89         int isBusinessCall;
90         String assertedDisplayName = "";
91 
92         @Override
toString()93         public String toString() {
94             if (isDebug()) {
95                 return  "[" + id + ", account: [" + accountComponentName + " : " + accountId +
96                     "]," + number + ", " + date + "]";
97             } else {
98                 return "[" + id + "]";
99             }
100         }
101     }
102 
103     static class OEMData {
104         String namespace;
105         byte[] bytes;
106 
OEMData(String namespace, byte[] bytes)107         public OEMData(String namespace, byte[] bytes) {
108             this.namespace = namespace;
109             this.bytes = bytes == null ? ZERO_BYTE_ARRAY : bytes;
110         }
111     }
112 
113     private static final String TAG = "CallLogBackupAgent";
114 
115     /** Data types and errors used when reporting B&R success rate and errors.  */
116     @BackupRestoreEventLogger.BackupRestoreDataType
117     @VisibleForTesting
118     static final String CALLLOGS = "telecom_call_logs";
119 
120     @BackupRestoreEventLogger.BackupRestoreError
121     static final String ERROR_UNEXPECTED_KEY = "unexpected_key";
122     @BackupRestoreEventLogger.BackupRestoreError
123     static final String ERROR_END_OEM_MARKER_NOT_FOUND = "end_oem_marker_not_found";
124     @BackupRestoreEventLogger.BackupRestoreError
125     static final String ERROR_READING_CALL_DATA = "error_reading_call_data";
126     @BackupRestoreEventLogger.BackupRestoreError
127     static final String ERROR_BACKUP_CALL_FAILED = "backup_call_failed";
128 
129     private BackupRestoreEventLogger mLogger;
130 
131     /** Current version of CallLogBackup. Used to track the backup format. */
132     @VisibleForTesting
133     static final int VERSION = 1010;
134     /** Version indicating that there exists no previous backup entry. */
135     @VisibleForTesting
136     static final int VERSION_NO_PREVIOUS_STATE = 0;
137 
138     static final String NO_OEM_NAMESPACE = "no-oem-namespace";
139 
140     static final byte[] ZERO_BYTE_ARRAY = new byte[0];
141 
142     static final int END_OEM_DATA_MARKER = 0x60061E;
143 
144     static final String TELEPHONY_PHONE_ACCOUNT_HANDLE_COMPONENT_NAME =
145             "com.android.phone/com.android.services.telephony.TelephonyConnectionService";
146 
147     @VisibleForTesting
148     protected Map<Integer, String> mSubscriptionInfoMap;
149 
150     private static final String[] CALL_LOG_PROJECTION = new String[] {
151         CallLog.Calls._ID,
152         CallLog.Calls.DATE,
153         CallLog.Calls.DURATION,
154         CallLog.Calls.NUMBER,
155         CallLog.Calls.POST_DIAL_DIGITS,
156         CallLog.Calls.VIA_NUMBER,
157         CallLog.Calls.TYPE,
158         CallLog.Calls.COUNTRY_ISO,
159         CallLog.Calls.GEOCODED_LOCATION,
160         CallLog.Calls.NUMBER_PRESENTATION,
161         CallLog.Calls.PHONE_ACCOUNT_COMPONENT_NAME,
162         CallLog.Calls.PHONE_ACCOUNT_ID,
163         CallLog.Calls.PHONE_ACCOUNT_ADDRESS,
164         CallLog.Calls.DATA_USAGE,
165         CallLog.Calls.FEATURES,
166         CallLog.Calls.ADD_FOR_ALL_USERS,
167         CallLog.Calls.BLOCK_REASON,
168         CallLog.Calls.CALL_SCREENING_APP_NAME,
169         CallLog.Calls.CALL_SCREENING_COMPONENT_NAME,
170         CallLog.Calls.MISSED_REASON,
171         CallLog.Calls.IS_PHONE_ACCOUNT_MIGRATION_PENDING,
172         CallLog.Calls.IS_BUSINESS_CALL,
173         CallLog.Calls.ASSERTED_DISPLAY_NAME
174     };
175 
176     /**
177      * BackupRestoreEventLogger Dependencies for testing.
178      */
179     @VisibleForTesting
180     public interface BackupRestoreEventLoggerProxy {
logItemsBackedUp(String dataType, int count)181         void logItemsBackedUp(String dataType, int count);
logItemsBackupFailed(String dataType, int count, String error)182         void logItemsBackupFailed(String dataType, int count, String error);
logItemsRestored(String dataType, int count)183         void logItemsRestored(String dataType, int count);
logItemsRestoreFailed(String dataType, int count, String error)184         void logItemsRestoreFailed(String dataType, int count, String error);
185     }
186 
187     private BackupRestoreEventLoggerProxy mBackupRestoreEventLoggerProxy =
188             new BackupRestoreEventLoggerProxy() {
189         @Override
190         public void logItemsBackedUp(String dataType, int count) {
191             mLogger.logItemsBackedUp(dataType, count);
192         }
193 
194         @Override
195         public void logItemsBackupFailed(String dataType, int count, String error) {
196             mLogger.logItemsBackupFailed(dataType, count, error);
197         }
198 
199         @Override
200         public void logItemsRestored(String dataType, int count) {
201             mLogger.logItemsRestored(dataType, count);
202         }
203 
204         @Override
205         public void logItemsRestoreFailed(String dataType, int count, String error) {
206             mLogger.logItemsRestoreFailed(dataType, count, error);
207         }
208     };
209 
210     /**
211      * Overrides BackupRestoreEventLogger dependencies for testing.
212      */
213     @VisibleForTesting
setBackupRestoreEventLoggerProxy(BackupRestoreEventLoggerProxy proxy)214     public void setBackupRestoreEventLoggerProxy(BackupRestoreEventLoggerProxy proxy) {
215         mBackupRestoreEventLoggerProxy = proxy;
216     }
217 
218     @Override
onCreate()219     public void onCreate() {
220         super.onCreate();
221         Log.d(TAG, "onCreate");
222         BackupManager backupManager = new BackupManager(getApplicationContext());
223         mLogger = backupManager.getBackupRestoreEventLogger(/* backupAgent */ this);
224     }
225 
226     /** ${inheritDoc} */
227     @Override
onBackup(ParcelFileDescriptor oldStateDescriptor, BackupDataOutput data, ParcelFileDescriptor newStateDescriptor)228     public void onBackup(ParcelFileDescriptor oldStateDescriptor, BackupDataOutput data,
229             ParcelFileDescriptor newStateDescriptor) throws IOException {
230         // Get the list of the previous calls IDs which were backed up.
231         DataInputStream dataInput = new DataInputStream(
232                 new FileInputStream(oldStateDescriptor.getFileDescriptor()));
233         final CallLogBackupState state;
234         try {
235             state = readState(dataInput);
236         } finally {
237             dataInput.close();
238         }
239 
240         SubscriptionManager subscriptionManager = getBaseContext().getSystemService(
241                 SubscriptionManager.class);
242         if (subscriptionManager != null) {
243             mSubscriptionInfoMap = new HashMap<>();
244             // Use getAllSubscirptionInfoList() to get the mapping between iccId and subId
245             // from the subscription database
246             List<SubscriptionInfo> subscriptionInfos = subscriptionManager
247                     .getAllSubscriptionInfoList();
248             for (SubscriptionInfo subscriptionInfo : subscriptionInfos) {
249                 mSubscriptionInfoMap.put(
250                         subscriptionInfo.getSubscriptionId(), subscriptionInfo.getIccId());
251             }
252         }
253 
254         // Run the actual backup of data
255         runBackup(state, data, getAllCallLogEntries());
256 
257         // Rewrite the backup state.
258         DataOutputStream dataOutput = new DataOutputStream(new BufferedOutputStream(
259                 new FileOutputStream(newStateDescriptor.getFileDescriptor())));
260         try {
261             writeState(dataOutput, state);
262         } finally {
263             dataOutput.close();
264         }
265     }
266 
267     /**
268      * Restores a call log backup given provided backup data.
269      * @param data the call log backup data; must be in a format written by
270      * {@link #onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor)}.
271      *
272      * @param appVersionCode The OS version of the data to restore; not used here.
273      * @param newState See parent class; not used here.
274      * @throws IOException Not thrown by the call log backup agent, but required by the underlying
275      * interface -- throwing IOException will cause the existing call log data to be cleared.
276      */
277     @Override
onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)278     public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
279             throws IOException {
280 
281         if (isDebug()) {
282             Log.d(TAG, "Performing Restore");
283         }
284 
285         while (data.readNextHeader()) {
286             Call call = readCallFromData(data);
287             if (call != null && call.type != Calls.VOICEMAIL_TYPE) {
288                 writeCallToProvider(call);
289                 mBackupRestoreEventLoggerProxy.logItemsRestored(CALLLOGS, /* count */ 1);
290                 if (isDebug()) {
291                     Log.d(TAG, "Restored call: " + call);
292                 }
293             }
294         }
295     }
296 
297     @VisibleForTesting
runBackup(CallLogBackupState state, BackupDataOutput data, Iterable<Call> calls)298     void runBackup(CallLogBackupState state, BackupDataOutput data, Iterable<Call> calls) {
299         SortedSet<Integer> callsToRemove = new TreeSet<>(state.callIds);
300 
301         // Loop through all the call log entries to identify:
302         // (1) new calls
303         // (2) calls which have been deleted.
304         for (Call call : calls) {
305             if (!state.callIds.contains(call.id)) {
306 
307                 if (isDebug()) {
308                     Log.d(TAG, "Adding call to backup: " + call);
309                 }
310 
311                 // This call new (not in our list from the last backup), lets back it up.
312                 addCallToBackup(data, call);
313                 state.callIds.add(call.id);
314             } else {
315                 // This call still exists in the current call log so delete it from the
316                 // "callsToRemove" set since we want to keep it.
317                 callsToRemove.remove(call.id);
318                 mBackupRestoreEventLoggerProxy.logItemsBackedUp(CALLLOGS, /* count */ 1);
319             }
320         }
321 
322         // Remove calls which no longer exist in the set.
323         for (Integer i : callsToRemove) {
324             if (isDebug()) {
325                 Log.d(TAG, "Removing call from backup: " + i);
326             }
327 
328             removeCallFromBackup(data, i);
329             state.callIds.remove(i);
330         }
331     }
332 
333     @VisibleForTesting
getAllCallLogEntries()334     Iterable<Call> getAllCallLogEntries() {
335         List<Call> calls = new LinkedList<>();
336 
337         // We use the API here instead of querying ContactsDatabaseHelper directly because
338         // CallLogProvider has special locks in place for sychronizing when to read.  Using the APIs
339         // gives us that for free.
340         ContentResolver resolver = getContentResolver();
341         Cursor cursor = resolver.query(
342                 CallLog.Calls.CONTENT_URI, CALL_LOG_PROJECTION, null, null, null);
343         if (cursor != null) {
344             try {
345                 while (cursor.moveToNext()) {
346                     Call call = readCallFromCursor(cursor);
347                     if (call != null && call.type != Calls.VOICEMAIL_TYPE) {
348                         calls.add(call);
349                     }
350                 }
351             } finally {
352                 cursor.close();
353             }
354         }
355 
356         return calls;
357     }
358 
writeCallToProvider(Call call)359     private void writeCallToProvider(Call call) {
360         Long dataUsage = call.dataUsage == 0 ? null : call.dataUsage;
361 
362         PhoneAccountHandle handle = null;
363         if (call.accountComponentName != null && call.accountId != null) {
364             handle = new PhoneAccountHandle(
365                     ComponentName.unflattenFromString(call.accountComponentName), call.accountId);
366         }
367         boolean addForAllUsers = call.addForAllUsers == 1;
368 
369         // We backup the calllog in the user running this backup agent, so write calls to this user.
370         CallLog.AddCallParams.AddCallParametersBuilder builder =
371                 new CallLog.AddCallParams.AddCallParametersBuilder();
372         builder.setCallerInfo(null);
373         builder.setNumber(call.number);
374         builder.setPostDialDigits(call.postDialDigits);
375         builder.setViaNumber(call.viaNumber);
376         builder.setPresentation(call.numberPresentation);
377         builder.setCallType(call.type);
378         builder.setFeatures(call.features);
379         builder.setAccountHandle(handle);
380         builder.setStart(call.date);
381         builder.setDuration((int) call.duration);
382         builder.setDataUsage(dataUsage == null ? Long.MIN_VALUE : dataUsage);
383         builder.setAddForAllUsers(addForAllUsers);
384         builder.setUserToBeInsertedTo(null);
385         builder.setIsRead(true);
386         builder.setCallBlockReason(call.callBlockReason);
387         builder.setCallScreeningAppName(call.callScreeningAppName);
388         builder.setCallScreeningComponentName(call.callScreeningComponentName);
389         builder.setMissedReason(call.missedReason);
390         builder.setIsPhoneAccountMigrationPending(call.isPhoneAccountMigrationPending);
391         builder.setIsBusinessCall(call.isBusinessCall == 1);
392         builder.setAssertedDisplayName(call.assertedDisplayName);
393 
394         Calls.addCall(this, builder.build());
395     }
396 
397     @VisibleForTesting
readState(DataInput dataInput)398     CallLogBackupState readState(DataInput dataInput) throws IOException {
399         CallLogBackupState state = new CallLogBackupState();
400         state.callIds = new TreeSet<>();
401 
402         try {
403             // Read the version.
404             state.version = dataInput.readInt();
405 
406             if (state.version >= 1) {
407                 // Read the size.
408                 int size = dataInput.readInt();
409 
410                 // Read all of the call IDs.
411                 for (int i = 0; i < size; i++) {
412                     state.callIds.add(dataInput.readInt());
413                 }
414             }
415         } catch (EOFException e) {
416             state.version = VERSION_NO_PREVIOUS_STATE;
417         }
418 
419         return state;
420     }
421 
422     @VisibleForTesting
writeState(DataOutput dataOutput, CallLogBackupState state)423     void writeState(DataOutput dataOutput, CallLogBackupState state)
424             throws IOException {
425         // Write version first of all
426         dataOutput.writeInt(VERSION);
427 
428         // [Version 1]
429         // size + callIds
430         dataOutput.writeInt(state.callIds.size());
431         for (Integer i : state.callIds) {
432             dataOutput.writeInt(i);
433         }
434     }
435 
436     @VisibleForTesting
readCallFromData(BackupDataInput data)437     Call readCallFromData(BackupDataInput data) {
438         final int callId;
439         try {
440             callId = Integer.parseInt(data.getKey());
441         } catch (NumberFormatException e) {
442             mBackupRestoreEventLoggerProxy.logItemsRestoreFailed(
443                     CALLLOGS, /* count */ 1, ERROR_UNEXPECTED_KEY);
444             Log.e(TAG, "Unexpected key found in restore: " + data.getKey());
445             return null;
446         }
447 
448         try {
449             byte [] byteArray = new byte[data.getDataSize()];
450             data.readEntityData(byteArray, 0, byteArray.length);
451             DataInputStream dataInput = new DataInputStream(new ByteArrayInputStream(byteArray));
452 
453             Call call = new Call();
454             call.id = callId;
455 
456             int version = dataInput.readInt();
457 
458             // Don't allow downgrades when restoring except when the version is 1010; that version
459             // adds some rather inconsequential columns to the call log database and it is generally
460             // preferable to allow the restore knowing that those new columns will be skipped in the
461             // restore.
462             if (version > VERSION && version != 1010) {
463                 // If somehow we got a backed up row that is newer than the supported file format
464                 // we know of, we will log an error and return null to represent an invalid item.
465                 String errorMessage = "Backup version " + version + " is newer than the current "
466                         + "supported version, " + VERSION;
467                 Log.w(TAG, errorMessage);
468                 mBackupRestoreEventLoggerProxy.logItemsRestoreFailed(CALLLOGS, 1,
469                         errorMessage);
470                 return null;
471             }
472 
473             if (version >= 1) {
474                 call.date = dataInput.readLong();
475                 call.duration = dataInput.readLong();
476                 call.number = readString(dataInput);
477                 call.type = dataInput.readInt();
478                 call.numberPresentation = dataInput.readInt();
479                 call.accountComponentName = readString(dataInput);
480                 call.accountId = readString(dataInput);
481                 call.accountAddress = readString(dataInput);
482                 call.dataUsage = dataInput.readLong();
483                 call.features = dataInput.readInt();
484             }
485 
486             if (version >= 1002) {
487                 String namespace = dataInput.readUTF();
488                 int length = dataInput.readInt();
489                 byte[] buffer = new byte[length];
490                 dataInput.read(buffer);
491                 readOEMDataForCall(call, new OEMData(namespace, buffer));
492 
493                 int marker = dataInput.readInt();
494                 if (marker != END_OEM_DATA_MARKER) {
495                     mBackupRestoreEventLoggerProxy.logItemsRestoreFailed(CALLLOGS, /* count */ 1,
496                             ERROR_END_OEM_MARKER_NOT_FOUND);
497                     Log.e(TAG, "Did not find END-OEM marker for call " + call.id);
498                     // The marker does not match the expected value, ignore this call completely.
499                     return null;
500                 }
501             }
502 
503             if (version >= 1003) {
504                 call.addForAllUsers = dataInput.readInt();
505             }
506 
507             if (version >= 1004) {
508                 call.postDialDigits = readString(dataInput);
509             }
510 
511             if(version >= 1005) {
512                 call.viaNumber = readString(dataInput);
513             }
514 
515             if(version >= 1006) {
516                 call.callBlockReason = dataInput.readInt();
517                 call.callScreeningAppName = readString(dataInput);
518                 call.callScreeningComponentName = readString(dataInput);
519             }
520             if(version >= 1007) {
521                 // Version 1007 had call id columns early in the Q release; they were pulled so we
522                 // will just read the values out here if they exist in a backup and ignore them.
523                 readString(dataInput);
524                 readString(dataInput);
525                 readString(dataInput);
526                 readString(dataInput);
527                 readString(dataInput);
528                 readInteger(dataInput);
529             }
530             if (version >= 1008) {
531                 call.missedReason = dataInput.readLong();
532             }
533             if (version >= 1009) {
534                 call.isPhoneAccountMigrationPending = dataInput.readInt();
535             }
536             if (version >= 1010) {
537                 call.isBusinessCall = dataInput.readInt();
538                 call.assertedDisplayName = readString(dataInput);
539             }
540             /**
541              * In >=T Android, Telephony PhoneAccountHandle must use SubId as the ID (the unique
542              * identifier). Any version of Telephony call logs that are restored in >=T Android
543              * should set pending migration status as true and migrate to the subId later because
544              * different devices have different mappings between SubId and IccId.
545              *
546              * In <T Android, call log PhoneAccountHandle ID uses IccId, and backup with IccId;
547              * in >=T Android, call log PhoneAccountHandle ID uses SubId, and IccId is decided to
548              * use for backup for the reason mentioned above. Every time a call log is restored,
549              * the on-devie sub Id can be determined based on its IccId. The pending migration
550              * from IccId to SubId will be complete after the PhoneAccountHandle is registrated by
551              * Telecom and before CallLogProvider unhides it.
552              */
553             if (call.accountComponentName != null && call.accountComponentName.equals(
554                     TELEPHONY_PHONE_ACCOUNT_HANDLE_COMPONENT_NAME)) {
555                 call.isPhoneAccountMigrationPending = 1;
556             }
557             return call;
558         } catch (IOException e) {
559             mBackupRestoreEventLoggerProxy.logItemsRestoreFailed(
560                     CALLLOGS, /* count */ 1, ERROR_READING_CALL_DATA);
561             Log.e(TAG, "Error reading call data for " + callId, e);
562             return null;
563         }
564     }
565 
566     /**
567      * We need to use IccId for the PHONE_ACCOUNT_ID and set it as pending in backup when:
568      * 1) the phone account component name is telephony; AND
569      * 2) IS_PHONE_ACCOUNT_MIGRATION_PENDING status is not 1 ("1" means the ID is already IccId).
570      */
shouldConvertSubIdToIccIdForBackup( String accountComponentName, int isPhoneAccountMigrationPending)571     private boolean shouldConvertSubIdToIccIdForBackup(
572             String accountComponentName, int isPhoneAccountMigrationPending) {
573         if (mSubscriptionInfoMap == null) {
574             Log.e(TAG, "Subscription database is not available.");
575             return false;
576         }
577         if (accountComponentName != null
578                 && accountComponentName.equals(TELEPHONY_PHONE_ACCOUNT_HANDLE_COMPONENT_NAME)
579                 && isPhoneAccountMigrationPending != 1) {
580             return true;
581         }
582         return false;
583     }
584 
585     @VisibleForTesting
readCallFromCursor(Cursor cursor)586     Call readCallFromCursor(Cursor cursor) {
587         Call call = new Call();
588         call.id = cursor.getInt(cursor.getColumnIndex(CallLog.Calls._ID));
589         call.date = cursor.getLong(cursor.getColumnIndex(CallLog.Calls.DATE));
590         call.duration = cursor.getLong(cursor.getColumnIndex(CallLog.Calls.DURATION));
591         call.number = cursor.getString(cursor.getColumnIndex(CallLog.Calls.NUMBER));
592         call.postDialDigits = cursor.getString(
593                 cursor.getColumnIndex(CallLog.Calls.POST_DIAL_DIGITS));
594         call.viaNumber = cursor.getString(cursor.getColumnIndex(CallLog.Calls.VIA_NUMBER));
595         call.type = cursor.getInt(cursor.getColumnIndex(CallLog.Calls.TYPE));
596         call.numberPresentation =
597                 cursor.getInt(cursor.getColumnIndex(CallLog.Calls.NUMBER_PRESENTATION));
598         call.accountComponentName =
599                 cursor.getString(cursor.getColumnIndex(CallLog.Calls.PHONE_ACCOUNT_COMPONENT_NAME));
600         call.accountId =
601                 cursor.getString(cursor.getColumnIndex(CallLog.Calls.PHONE_ACCOUNT_ID));
602         call.accountAddress =
603                 cursor.getString(cursor.getColumnIndex(CallLog.Calls.PHONE_ACCOUNT_ADDRESS));
604         call.dataUsage = cursor.getLong(cursor.getColumnIndex(CallLog.Calls.DATA_USAGE));
605         call.features = cursor.getInt(cursor.getColumnIndex(CallLog.Calls.FEATURES));
606         call.addForAllUsers = cursor.getInt(cursor.getColumnIndex(Calls.ADD_FOR_ALL_USERS));
607         call.callBlockReason = cursor.getInt(cursor.getColumnIndex(CallLog.Calls.BLOCK_REASON));
608         call.callScreeningAppName = cursor
609             .getString(cursor.getColumnIndex(CallLog.Calls.CALL_SCREENING_APP_NAME));
610         call.callScreeningComponentName = cursor
611             .getString(cursor.getColumnIndex(CallLog.Calls.CALL_SCREENING_COMPONENT_NAME));
612         call.missedReason = cursor
613             .getInt(cursor.getColumnIndex(CallLog.Calls.MISSED_REASON));
614         call.isPhoneAccountMigrationPending = cursor.getInt(
615                 cursor.getColumnIndex(CallLog.Calls.IS_PHONE_ACCOUNT_MIGRATION_PENDING));
616         call.isBusinessCall = cursor.getInt(cursor.getColumnIndex(CallLog.Calls.IS_BUSINESS_CALL));
617         call.assertedDisplayName =
618                 cursor.getString(cursor.getColumnIndex(CallLog.Calls.ASSERTED_DISPLAY_NAME));
619         /*
620          * Starting Android T, the ID of Telephony PhoneAccountHandle need to migrate from IccId
621          * to SubId. Because the mapping between IccId and SubId in different devices is different,
622          * the Backup need to use IccId for the ID and set it as pending migration, and when the
623          * ID is restored, ID need migrated to SubId after the corresponding PhoneAccountHandle
624          * is registrated by Telecom and before CallLogProvider unhides them.
625          */
626         if (shouldConvertSubIdToIccIdForBackup(call.accountComponentName,
627                 call.isPhoneAccountMigrationPending)) {
628             Log.i(TAG, "Processing PhoneAccountMigration Backup accountId: " + call.accountId);
629             String iccId = null;
630             try {
631                 iccId = mSubscriptionInfoMap.get(Integer.parseInt(call.accountId));
632             } catch (NullPointerException e) {
633                 // Ignore, iccId will be null;
634             } catch(NumberFormatException e) {
635                 // Ignore, iccId will be null;
636             }
637 
638             if (iccId != null) {
639                 Log.i(TAG, "processing PhoneAccountMigration Found Subid during Backup: "
640                         + call.accountId);
641                 call.accountId = iccId;
642                 call.isPhoneAccountMigrationPending = 1;
643             }
644         }
645         return call;
646     }
647 
addCallToBackup(BackupDataOutput output, Call call)648     private void addCallToBackup(BackupDataOutput output, Call call) {
649         ByteArrayOutputStream baos = new ByteArrayOutputStream();
650         DataOutputStream data = new DataOutputStream(baos);
651 
652         try {
653             data.writeInt(VERSION);
654             data.writeLong(call.date);
655             data.writeLong(call.duration);
656             writeString(data, call.number);
657             data.writeInt(call.type);
658             data.writeInt(call.numberPresentation);
659             writeString(data, call.accountComponentName);
660             writeString(data, call.accountId);
661             writeString(data, call.accountAddress);
662             data.writeLong(call.dataUsage == null ? 0 : call.dataUsage);
663             data.writeInt(call.features);
664 
665             OEMData oemData = getOEMDataForCall(call);
666             data.writeUTF(oemData.namespace);
667             data.writeInt(oemData.bytes.length);
668             data.write(oemData.bytes);
669             data.writeInt(END_OEM_DATA_MARKER);
670 
671             data.writeInt(call.addForAllUsers);
672 
673             writeString(data, call.postDialDigits);
674 
675             writeString(data, call.viaNumber);
676 
677             data.writeInt(call.callBlockReason);
678             writeString(data, call.callScreeningAppName);
679             writeString(data, call.callScreeningComponentName);
680 
681             // Step 1007 used to write caller ID data; those were pulled.  Keeping that in here
682             // to maintain compatibility for backups which had this data.
683             writeString(data, "");
684             writeString(data, "");
685             writeString(data, "");
686             writeString(data, "");
687             writeString(data, "");
688             writeInteger(data, null);
689 
690             data.writeLong(call.missedReason);
691             data.writeInt(call.isPhoneAccountMigrationPending);
692 
693             data.writeInt(call.isBusinessCall);
694             writeString(data, call.assertedDisplayName);
695 
696             data.flush();
697 
698             output.writeEntityHeader(Integer.toString(call.id), baos.size());
699             output.writeEntityData(baos.toByteArray(), baos.size());
700 
701             mBackupRestoreEventLoggerProxy.logItemsBackedUp(CALLLOGS, /* count */ 1);
702 
703             if (isDebug()) {
704                 Log.d(TAG, "Wrote call to backup: " + call + " with byte array: " + baos);
705             }
706         } catch (Exception e) {
707             mBackupRestoreEventLoggerProxy.logItemsBackupFailed(
708                     CALLLOGS, /* count */ 1, ERROR_BACKUP_CALL_FAILED);
709             Log.e(TAG, "Failed to backup call: " + call, e);
710         }
711     }
712 
713     /**
714      * Allows OEMs to provide proprietary data to backup along with the rest of the call log
715      * data. Because there is no way to provide a Backup Transport implementation
716      * nor peek into the data format of backup entries without system-level permissions, it is
717      * not possible (at the time of this writing) to write CTS tests for this piece of code.
718      * It is, therefore, important that if you alter this portion of code that you
719      * test backup and restore of call log is working as expected; ideally this would be tested by
720      * backing up and restoring between two different Android phone devices running M+.
721      */
getOEMDataForCall(Call call)722     private OEMData getOEMDataForCall(Call call) {
723         return new OEMData(NO_OEM_NAMESPACE, ZERO_BYTE_ARRAY);
724 
725         // OEMs that want to add their own proprietary data to call log backup should replace the
726         // code above with their own namespace and add any additional data they need.
727         // Versioning and size-prefixing the data should be done here as needed.
728         //
729         // Example:
730 
731         /*
732         ByteArrayOutputStream baos = new ByteArrayOutputStream();
733         DataOutputStream data = new DataOutputStream(baos);
734 
735         String customData1 = "Generic OEM";
736         int customData2 = 42;
737 
738         // Write a version for the data
739         data.writeInt(OEM_DATA_VERSION);
740 
741         // Write the data and flush
742         data.writeUTF(customData1);
743         data.writeInt(customData2);
744         data.flush();
745 
746         String oemNamespace = "com.oem.namespace";
747         return new OEMData(oemNamespace, baos.toByteArray());
748         */
749     }
750 
751     /**
752      * Allows OEMs to read their own proprietary data when doing a call log restore. It is important
753      * that the implementation verify the namespace of the data matches their expected value before
754      * attempting to read the data or else you may risk reading invalid data.
755      *
756      * See {@link #getOEMDataForCall} for information concerning proper testing of this code.
757      */
readOEMDataForCall(Call call, OEMData oemData)758     private void readOEMDataForCall(Call call, OEMData oemData) {
759         // OEMs that want to read proprietary data from a call log restore should do so here.
760         // Before reading from the data, an OEM should verify that the data matches their
761         // expected namespace.
762         //
763         // Example:
764 
765         /*
766         if ("com.oem.expected.namespace".equals(oemData.namespace)) {
767             ByteArrayInputStream bais = new ByteArrayInputStream(oemData.bytes);
768             DataInputStream data = new DataInputStream(bais);
769 
770             // Check against this version as we read data.
771             int version = data.readInt();
772             String customData1 = data.readUTF();
773             int customData2 = data.readInt();
774             // do something with data
775         }
776         */
777     }
778 
779 
writeString(DataOutputStream data, String str)780     private void writeString(DataOutputStream data, String str) throws IOException {
781         if (str == null) {
782             data.writeBoolean(false);
783         } else {
784             data.writeBoolean(true);
785             data.writeUTF(str);
786         }
787     }
788 
readString(DataInputStream data)789     private String readString(DataInputStream data) throws IOException {
790         if (data.readBoolean()) {
791             return data.readUTF();
792         } else {
793             return null;
794         }
795     }
796 
writeInteger(DataOutputStream data, Integer num)797     private void writeInteger(DataOutputStream data, Integer num) throws IOException {
798         if (num == null) {
799             data.writeBoolean(false);
800         } else {
801             data.writeBoolean(true);
802             data.writeInt(num);
803         }
804     }
805 
readInteger(DataInputStream data)806     private Integer readInteger(DataInputStream data) throws IOException {
807         if (data.readBoolean()) {
808             return data.readInt();
809         } else {
810             return null;
811         }
812     }
813 
removeCallFromBackup(BackupDataOutput output, int callId)814     private void removeCallFromBackup(BackupDataOutput output, int callId) {
815         try {
816             output.writeEntityHeader(Integer.toString(callId), -1);
817         } catch (IOException e) {
818             Log.e(TAG, "Failed to remove call: " + callId, e);
819         }
820     }
821 
isDebug()822     private static boolean isDebug() {
823         return Log.isLoggable(TAG, Log.DEBUG);
824     }
825 }
826