| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.dialer.backup; |
| |
| import android.annotation.TargetApi; |
| import android.app.backup.BackupAgent; |
| import android.app.backup.BackupDataInput; |
| import android.app.backup.BackupDataOutput; |
| import android.app.backup.FullBackupDataOutput; |
| import android.content.ContentResolver; |
| import android.content.ContentValues; |
| import android.database.Cursor; |
| import android.net.Uri; |
| import android.os.Build.VERSION_CODES; |
| import android.os.ParcelFileDescriptor; |
| import android.provider.CallLog; |
| import android.provider.CallLog.Calls; |
| import android.provider.VoicemailContract; |
| import android.provider.VoicemailContract.Voicemails; |
| import android.util.Pair; |
| import com.android.dialer.backup.nano.VoicemailInfo; |
| import com.android.dialer.common.Assert; |
| import com.android.dialer.common.ConfigProviderBindings; |
| import com.android.dialer.common.LogUtil; |
| import com.android.dialer.logging.Logger; |
| import com.android.dialer.logging.nano.DialerImpression; |
| import com.android.dialer.telecom.TelecomUtil; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.util.Locale; |
| |
| /** |
| * The Dialer backup agent to backup voicemails, and files under files, shared prefs and databases |
| */ |
| public class DialerBackupAgent extends BackupAgent { |
| // File names suffix for backup/restore. |
| private static final String VOICEMAIL_BACKUP_FILE_SUFFIX = "_voicemail_backup.proto"; |
| // File name formats for backup. It looks like 000000_voicemail_backup.proto, 0000001... |
| private static final String VOICEMAIL_BACKUP_FILE_FORMAT = "%06d" + VOICEMAIL_BACKUP_FILE_SUFFIX; |
| // Order by Date entries from database. We start backup from the newest. |
| private static final String ORDER_BY_DATE = "date DESC"; |
| // Voicemail Uri Column |
| public static final String VOICEMAIL_URI = "voicemail_uri"; |
| // Voicemail packages to backup |
| public static final String VOICEMAIL_SOURCE_PACKAGE = "com.android.phone"; |
| |
| private long voicemailsBackedupSoFar = 0; |
| private long sizeOfVoicemailsBackedupSoFar = 0; |
| private boolean maxVoicemailBackupReached = false; |
| |
| /** |
| * onBackup is used for Key/Value backup. Since we are using Dolly/Android Auto backup, we do not |
| * need to implement this method and Dolly should not be calling this. Instead Dolly will be |
| * calling onFullBackup. |
| */ |
| @Override |
| public void onBackup( |
| ParcelFileDescriptor parcelFileDescriptor, |
| BackupDataOutput backupDataOutput, |
| ParcelFileDescriptor parcelFileDescriptor1) |
| throws IOException { |
| Logger.get(this).logImpression(DialerImpression.Type.BACKUP_ON_BACKUP); |
| Assert.fail("Android Backup should not call DialerBackupAgent.onBackup"); |
| } |
| |
| /** |
| * onRestore is used for Key/Value restore. Since we are using Dolly/Android Auto backup/restore, |
| * we do not need to implement this method as Dolly should not be calling this method. Instead |
| * onFileRestore will be called by Dolly. |
| */ |
| @Override |
| public void onRestore( |
| BackupDataInput backupDataInput, int i, ParcelFileDescriptor parcelFileDescriptor) |
| throws IOException { |
| Logger.get(this).logImpression(DialerImpression.Type.BACKUP_ON_RESTORE); |
| Assert.fail("Android Backup should not call DialerBackupAgent.onRestore"); |
| } |
| |
| @TargetApi(VERSION_CODES.M) |
| @Override |
| public void onFullBackup(FullBackupDataOutput data) throws IOException { |
| Logger.get(this).logImpression(DialerImpression.Type.BACKUP_ON_FULL_BACKUP); |
| LogUtil.i("DialerBackupAgent.onFullBackup", "performing dialer backup"); |
| boolean autoBackupEnabled = |
| ConfigProviderBindings.get(this).getBoolean("enable_autobackup", true); |
| boolean vmBackupEnabled = |
| ConfigProviderBindings.get(this).getBoolean("enable_vm_backup", false); |
| |
| if (autoBackupEnabled) { |
| if (!maxVoicemailBackupReached && vmBackupEnabled) { |
| voicemailsBackedupSoFar = 0; |
| sizeOfVoicemailsBackedupSoFar = 0; |
| |
| LogUtil.i("DialerBackupAgent.onFullBackup", "autoBackup is enabled"); |
| ContentResolver contentResolver = getContentResolver(); |
| int limit = 1000; |
| |
| Uri uri = |
| TelecomUtil.getCallLogUri(this) |
| .buildUpon() |
| .appendQueryParameter(Calls.LIMIT_PARAM_KEY, Integer.toString(limit)) |
| .build(); |
| |
| LogUtil.i("DialerBackupAgent.onFullBackup", "backing up from: " + uri); |
| |
| try (Cursor cursor = |
| contentResolver.query( |
| uri, |
| null, |
| String.format( |
| "(%s = ? AND deleted = 0 AND %s = ?)", Calls.TYPE, Voicemails.SOURCE_PACKAGE), |
| new String[] { |
| Integer.toString(CallLog.Calls.VOICEMAIL_TYPE), VOICEMAIL_SOURCE_PACKAGE |
| }, |
| ORDER_BY_DATE, |
| null)) { |
| |
| if (cursor == null) { |
| LogUtil.i("DialerBackupAgent.onFullBackup", "cursor was null"); |
| return; |
| } |
| |
| LogUtil.i("DialerBackupAgent.onFullBackup", "cursor count: " + cursor.getCount()); |
| if (cursor.moveToFirst()) { |
| int fileNum = 0; |
| do { |
| backupRow( |
| data, cursor, String.format(Locale.US, VOICEMAIL_BACKUP_FILE_FORMAT, fileNum++)); |
| } while (cursor.moveToNext() && !maxVoicemailBackupReached); |
| } else { |
| LogUtil.i("DialerBackupAgent.onFullBackup", "cursor.moveToFirst failed"); |
| } |
| } |
| } |
| LogUtil.i( |
| "DialerBackupAgent.onFullBackup", |
| "vm files backed up: %d, vm size backed up:%d, " |
| + "max vm backup reached:%b, vm backup enabled:%b", |
| voicemailsBackedupSoFar, |
| sizeOfVoicemailsBackedupSoFar, |
| maxVoicemailBackupReached, |
| vmBackupEnabled); |
| super.onFullBackup(data); |
| Logger.get(this).logImpression(DialerImpression.Type.BACKUP_FULL_BACKED_UP); |
| } else { |
| Logger.get(this).logImpression(DialerImpression.Type.BACKUP_ON_BACKUP_DISABLED); |
| LogUtil.i("DialerBackupAgent.onFullBackup", "autoBackup is disabled"); |
| } |
| } |
| |
| private void backupRow(FullBackupDataOutput data, Cursor cursor, String fileName) |
| throws IOException { |
| |
| VoicemailInfo cursorRowInProto = |
| DialerBackupUtils.convertVoicemailCursorRowToProto(cursor, getContentResolver()); |
| |
| File file = new File(getFilesDir(), fileName); |
| DialerBackupUtils.writeProtoToFile(file, cursorRowInProto); |
| |
| if (sizeOfVoicemailsBackedupSoFar + file.length() |
| > DialerBackupUtils.maxVoicemailSizeToBackup) { |
| Logger.get(this).logImpression(DialerImpression.Type.BACKUP_MAX_VM_BACKUP_REACHED); |
| maxVoicemailBackupReached = true; |
| file.delete(); |
| return; |
| } |
| |
| backupFile(file, data); |
| } |
| |
| // TODO: Write to FullBackupDataOutput directly (b/33849960) |
| private void backupFile(File file, FullBackupDataOutput data) throws IOException { |
| try { |
| super.fullBackupFile(file, data); |
| sizeOfVoicemailsBackedupSoFar = sizeOfVoicemailsBackedupSoFar + file.length(); |
| voicemailsBackedupSoFar++; |
| Logger.get(this).logImpression(DialerImpression.Type.BACKUP_VOICEMAIL_BACKED_UP); |
| LogUtil.i("DialerBackupAgent.backupFile", "file backed up:" + file.getAbsolutePath()); |
| } finally { |
| file.delete(); |
| } |
| } |
| |
| // Being tracked in b/33839952 |
| @Override |
| public void onQuotaExceeded(long backupDataBytes, long quotaBytes) { |
| Logger.get(this).logImpression(DialerImpression.Type.BACKUP_ON_QUOTA_EXCEEDED); |
| LogUtil.i("DialerBackupAgent.onQuotaExceeded", "does nothing"); |
| } |
| |
| @TargetApi(VERSION_CODES.M) |
| @Override |
| public void onRestoreFile( |
| ParcelFileDescriptor data, long size, File destination, int type, long mode, long mtime) |
| throws IOException { |
| LogUtil.i("DialerBackupAgent.onRestoreFile", "size:" + size + " destination: " + destination); |
| |
| String fileName = destination.getName(); |
| LogUtil.i("DialerBackupAgent.onRestoreFile", "file name: " + fileName); |
| |
| if (ConfigProviderBindings.get(this).getBoolean("enable_autobackup", true)) { |
| if (fileName.endsWith(VOICEMAIL_BACKUP_FILE_SUFFIX) |
| && ConfigProviderBindings.get(this).getBoolean("enable_vm_restore", true)) { |
| if (DialerBackupUtils.canRestoreVoicemails(getContentResolver(), this)) { |
| try { |
| super.onRestoreFile(data, size, destination, type, mode, mtime); |
| restoreVoicemail(destination); |
| destination.delete(); |
| } catch (IOException e) { |
| Logger.get(this).logImpression(DialerImpression.Type.BACKUP_ON_RESTORE_IO_EXCEPTION); |
| LogUtil.e( |
| "DialerBackupAgent.onRestoreFile", |
| "could not restore voicemail - IOException: ", |
| e); |
| } |
| } else { |
| LogUtil.i( |
| "DialerBackupAgent.onRestoreFile", "build does not support restoring voicemails"); |
| } |
| |
| } else { |
| super.onRestoreFile(data, size, destination, type, mode, mtime); |
| LogUtil.i("DialerBackupAgent.onRestoreFile", "restored: " + fileName); |
| Logger.get(this).logImpression(DialerImpression.Type.BACKUP_RESTORED_FILE); |
| } |
| } else { |
| Logger.get(this).logImpression(DialerImpression.Type.BACKUP_ON_RESTORE_DISABLED); |
| LogUtil.i("DialerBackupAgent.onRestoreFile", "autoBackup is disabled"); |
| } |
| } |
| |
| @Override |
| public void onRestoreFinished() { |
| Logger.get(this).logImpression(DialerImpression.Type.BACKUP_ON_RESTORE_FINISHED); |
| LogUtil.i("DialerBackupAgent.onRestoreFinished", "do nothing"); |
| } |
| |
| @TargetApi(VERSION_CODES.M) |
| private void restoreVoicemail(File file) throws IOException { |
| Pair<ContentValues, byte[]> pair = |
| DialerBackupUtils.convertVoicemailProtoFileToContentValueAndAudioBytes( |
| file, getApplicationContext()); |
| |
| if (pair == null) { |
| LogUtil.i("DialerBackupAgent.restoreVoicemail", "not restoring VM due to duplicate"); |
| Logger.get(this) |
| .logImpression(DialerImpression.Type.BACKUP_ON_RESTORE_VM_DUPLICATE_NOT_RESTORING); |
| return; |
| } |
| |
| // TODO: Uniquely identify backup agent as the creator of this voicemail b/34084298 |
| try (OutputStream restoreStream = |
| getContentResolver() |
| .openOutputStream( |
| getContentResolver() |
| .insert(VoicemailContract.Voicemails.CONTENT_URI, pair.first))) { |
| DialerBackupUtils.copyAudioBytesToContentUri(pair.second, restoreStream); |
| Logger.get(this).logImpression(DialerImpression.Type.BACKUP_RESTORED_VOICEMAIL); |
| } |
| } |
| } |