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