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