1 /* 2 * Copyright (C) 2016 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.dialer.backup; 18 19 import android.annotation.TargetApi; 20 import android.app.backup.BackupAgent; 21 import android.app.backup.BackupDataInput; 22 import android.app.backup.BackupDataOutput; 23 import android.app.backup.FullBackupDataOutput; 24 import android.content.ContentResolver; 25 import android.content.ContentValues; 26 import android.database.Cursor; 27 import android.net.Uri; 28 import android.os.Build.VERSION_CODES; 29 import android.os.ParcelFileDescriptor; 30 import android.provider.CallLog; 31 import android.provider.CallLog.Calls; 32 import android.provider.VoicemailContract; 33 import android.provider.VoicemailContract.Voicemails; 34 import android.telecom.PhoneAccountHandle; 35 import android.util.Pair; 36 import com.android.dialer.common.Assert; 37 import com.android.dialer.common.ConfigProviderBindings; 38 import com.android.dialer.common.LogUtil; 39 import com.android.dialer.logging.DialerImpression; 40 import com.android.dialer.logging.Logger; 41 import com.android.dialer.telecom.TelecomUtil; 42 import java.io.File; 43 import java.io.IOException; 44 import java.io.OutputStream; 45 import java.util.List; 46 import java.util.Locale; 47 48 /** 49 * The Dialer backup agent to backup voicemails, and files under files, shared prefs and databases 50 */ 51 public class DialerBackupAgent extends BackupAgent { 52 // File names suffix for backup/restore. 53 private static final String VOICEMAIL_BACKUP_FILE_SUFFIX = "_voicemail_backup.proto"; 54 // File name formats for backup. It looks like 000000_voicemail_backup.proto, 0000001... 55 private static final String VOICEMAIL_BACKUP_FILE_FORMAT = "%06d" + VOICEMAIL_BACKUP_FILE_SUFFIX; 56 // Order by Date entries from database. We start backup from the newest. 57 private static final String ORDER_BY_DATE = "date DESC"; 58 // Voicemail Uri Column 59 public static final String VOICEMAIL_URI = "voicemail_uri"; 60 // Voicemail packages to backup 61 public static final String VOICEMAIL_SOURCE_PACKAGE = "com.google.android.dialer"; 62 63 private long voicemailsBackedupSoFar = 0; 64 private long sizeOfVoicemailsBackedupSoFar = 0; 65 private boolean maxVoicemailBackupReached = false; 66 67 /** 68 * onBackup is used for Key/Value backup. Since we are using Dolly/Android Auto backup, we do not 69 * need to implement this method and Dolly should not be calling this. Instead Dolly will be 70 * calling onFullBackup. 71 */ 72 @Override onBackup( ParcelFileDescriptor parcelFileDescriptor, BackupDataOutput backupDataOutput, ParcelFileDescriptor parcelFileDescriptor1)73 public void onBackup( 74 ParcelFileDescriptor parcelFileDescriptor, 75 BackupDataOutput backupDataOutput, 76 ParcelFileDescriptor parcelFileDescriptor1) 77 throws IOException { 78 Logger.get(this).logImpression(DialerImpression.Type.BACKUP_ON_BACKUP); 79 Assert.fail("Android Backup should not call DialerBackupAgent.onBackup"); 80 } 81 82 /** 83 * onRestore is used for Key/Value restore. Since we are using Dolly/Android Auto backup/restore, 84 * we need to implement this method only for backwards compatibility. Dolly should be calling 85 * onFileRestore during its restore. 86 */ 87 @Override onRestore( BackupDataInput backupDataInput, int i, ParcelFileDescriptor parcelFileDescriptor)88 public void onRestore( 89 BackupDataInput backupDataInput, int i, ParcelFileDescriptor parcelFileDescriptor) 90 throws IOException { 91 Logger.get(this).logImpression(DialerImpression.Type.BACKUP_ON_RESTORE); 92 } 93 94 @TargetApi(VERSION_CODES.M) 95 @Override onFullBackup(FullBackupDataOutput data)96 public void onFullBackup(FullBackupDataOutput data) throws IOException { 97 Logger.get(this).logImpression(DialerImpression.Type.BACKUP_ON_FULL_BACKUP); 98 LogUtil.i("DialerBackupAgent.onFullBackup", "performing dialer backup"); 99 boolean autoBackupEnabled = 100 ConfigProviderBindings.get(this).getBoolean("enable_autobackup", true); 101 boolean vmBackupEnabled = ConfigProviderBindings.get(this).getBoolean("enable_vm_backup", true); 102 List<PhoneAccountHandle> phoneAccountsToArchive = 103 DialerBackupUtils.getPhoneAccountsToArchive(this); 104 105 if (autoBackupEnabled) { 106 if (!maxVoicemailBackupReached && vmBackupEnabled && !phoneAccountsToArchive.isEmpty()) { 107 voicemailsBackedupSoFar = 0; 108 sizeOfVoicemailsBackedupSoFar = 0; 109 110 LogUtil.i("DialerBackupAgent.onFullBackup", "autoBackup is enabled"); 111 ContentResolver contentResolver = getContentResolver(); 112 int limit = 1000; 113 114 Uri uri = 115 TelecomUtil.getCallLogUri(this) 116 .buildUpon() 117 .appendQueryParameter(Calls.LIMIT_PARAM_KEY, Integer.toString(limit)) 118 .build(); 119 120 LogUtil.i("DialerBackupAgent.onFullBackup", "backing up from: " + uri); 121 122 try (Cursor cursor = 123 contentResolver.query( 124 uri, 125 null, 126 String.format( 127 "(%s = ? AND deleted = 0 AND %s = ? AND ?)", 128 Calls.TYPE, Voicemails.SOURCE_PACKAGE), 129 new String[] { 130 Integer.toString(CallLog.Calls.VOICEMAIL_TYPE), 131 VOICEMAIL_SOURCE_PACKAGE, 132 DialerBackupUtils.getPhoneAccountClause(phoneAccountsToArchive) 133 }, 134 ORDER_BY_DATE, 135 null)) { 136 137 if (cursor == null) { 138 LogUtil.i("DialerBackupAgent.onFullBackup", "cursor was null"); 139 return; 140 } 141 142 LogUtil.i("DialerBackupAgent.onFullBackup", "cursor count: " + cursor.getCount()); 143 if (cursor.moveToFirst()) { 144 int fileNum = 0; 145 do { 146 backupRow( 147 data, cursor, String.format(Locale.US, VOICEMAIL_BACKUP_FILE_FORMAT, fileNum++)); 148 } while (cursor.moveToNext() && !maxVoicemailBackupReached); 149 } else { 150 LogUtil.i("DialerBackupAgent.onFullBackup", "cursor.moveToFirst failed"); 151 } 152 } 153 } 154 LogUtil.i( 155 "DialerBackupAgent.onFullBackup", 156 "vm files backed up: %d, vm size backed up:%d, " 157 + "max vm backup reached:%b, vm backup enabled:%b phone accounts to archive: %d", 158 voicemailsBackedupSoFar, 159 sizeOfVoicemailsBackedupSoFar, 160 maxVoicemailBackupReached, 161 vmBackupEnabled, 162 phoneAccountsToArchive.size()); 163 super.onFullBackup(data); 164 Logger.get(this).logImpression(DialerImpression.Type.BACKUP_FULL_BACKED_UP); 165 } else { 166 Logger.get(this).logImpression(DialerImpression.Type.BACKUP_ON_BACKUP_DISABLED); 167 LogUtil.i("DialerBackupAgent.onFullBackup", "autoBackup is disabled"); 168 } 169 } 170 backupRow(FullBackupDataOutput data, Cursor cursor, String fileName)171 private void backupRow(FullBackupDataOutput data, Cursor cursor, String fileName) 172 throws IOException { 173 174 VoicemailInfo cursorRowInProto = 175 DialerBackupUtils.convertVoicemailCursorRowToProto(cursor, getContentResolver()); 176 177 File file = new File(getFilesDir(), fileName); 178 DialerBackupUtils.writeProtoToFile(file, cursorRowInProto); 179 180 if (sizeOfVoicemailsBackedupSoFar + file.length() 181 > DialerBackupUtils.maxVoicemailSizeToBackup) { 182 Logger.get(this).logImpression(DialerImpression.Type.BACKUP_MAX_VM_BACKUP_REACHED); 183 maxVoicemailBackupReached = true; 184 file.delete(); 185 return; 186 } 187 188 backupFile(file, data); 189 } 190 191 // TODO: Write to FullBackupDataOutput directly (b/33849960) backupFile(File file, FullBackupDataOutput data)192 private void backupFile(File file, FullBackupDataOutput data) throws IOException { 193 try { 194 super.fullBackupFile(file, data); 195 sizeOfVoicemailsBackedupSoFar = sizeOfVoicemailsBackedupSoFar + file.length(); 196 voicemailsBackedupSoFar++; 197 Logger.get(this).logImpression(DialerImpression.Type.BACKUP_VOICEMAIL_BACKED_UP); 198 LogUtil.i("DialerBackupAgent.backupFile", "file backed up:" + file.getAbsolutePath()); 199 } finally { 200 file.delete(); 201 } 202 } 203 204 // Being tracked in b/33839952 205 @Override onQuotaExceeded(long backupDataBytes, long quotaBytes)206 public void onQuotaExceeded(long backupDataBytes, long quotaBytes) { 207 Logger.get(this).logImpression(DialerImpression.Type.BACKUP_ON_QUOTA_EXCEEDED); 208 LogUtil.i("DialerBackupAgent.onQuotaExceeded", "does nothing"); 209 } 210 211 @TargetApi(VERSION_CODES.M) 212 @Override onRestoreFile( ParcelFileDescriptor data, long size, File destination, int type, long mode, long mtime)213 public void onRestoreFile( 214 ParcelFileDescriptor data, long size, File destination, int type, long mode, long mtime) 215 throws IOException { 216 LogUtil.i("DialerBackupAgent.onRestoreFile", "size:" + size + " destination: " + destination); 217 218 String fileName = destination.getName(); 219 LogUtil.i("DialerBackupAgent.onRestoreFile", "file name: " + fileName); 220 221 if (ConfigProviderBindings.get(this).getBoolean("enable_autobackup", true)) { 222 if (fileName.endsWith(VOICEMAIL_BACKUP_FILE_SUFFIX) 223 && ConfigProviderBindings.get(this).getBoolean("enable_vm_restore", true)) { 224 if (DialerBackupUtils.canRestoreVoicemails(getContentResolver(), this)) { 225 try { 226 super.onRestoreFile(data, size, destination, type, mode, mtime); 227 restoreVoicemail(destination); 228 destination.delete(); 229 } catch (IOException e) { 230 Logger.get(this).logImpression(DialerImpression.Type.BACKUP_ON_RESTORE_IO_EXCEPTION); 231 LogUtil.e( 232 "DialerBackupAgent.onRestoreFile", 233 "could not restore voicemail - IOException: ", 234 e); 235 } 236 } else { 237 LogUtil.i( 238 "DialerBackupAgent.onRestoreFile", "build does not support restoring voicemails"); 239 } 240 241 } else { 242 super.onRestoreFile(data, size, destination, type, mode, mtime); 243 LogUtil.i("DialerBackupAgent.onRestoreFile", "restored: " + fileName); 244 Logger.get(this).logImpression(DialerImpression.Type.BACKUP_RESTORED_FILE); 245 } 246 } else { 247 Logger.get(this).logImpression(DialerImpression.Type.BACKUP_ON_RESTORE_DISABLED); 248 LogUtil.i("DialerBackupAgent.onRestoreFile", "autoBackup is disabled"); 249 } 250 } 251 252 @Override onRestoreFinished()253 public void onRestoreFinished() { 254 Logger.get(this).logImpression(DialerImpression.Type.BACKUP_ON_RESTORE_FINISHED); 255 LogUtil.i("DialerBackupAgent.onRestoreFinished", "do nothing"); 256 } 257 258 @TargetApi(VERSION_CODES.M) restoreVoicemail(File file)259 private void restoreVoicemail(File file) throws IOException { 260 Pair<ContentValues, byte[]> pair = 261 DialerBackupUtils.convertVoicemailProtoFileToContentValueAndAudioBytes( 262 file, getApplicationContext()); 263 264 if (pair == null) { 265 LogUtil.i("DialerBackupAgent.restoreVoicemail", "not restoring VM due to duplicate"); 266 Logger.get(this) 267 .logImpression(DialerImpression.Type.BACKUP_ON_RESTORE_VM_DUPLICATE_NOT_RESTORING); 268 return; 269 } 270 271 // TODO: Uniquely identify backup agent as the creator of this voicemail b/34084298 272 try (OutputStream restoreStream = 273 getContentResolver() 274 .openOutputStream( 275 getContentResolver() 276 .insert(VoicemailContract.Voicemails.CONTENT_URI, pair.first))) { 277 DialerBackupUtils.copyAudioBytesToContentUri(pair.second, restoreStream); 278 Logger.get(this).logImpression(DialerImpression.Type.BACKUP_RESTORED_VOICEMAIL); 279 } 280 } 281 } 282