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