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