summaryrefslogtreecommitdiff
path: root/java/com/android/dialer/backup
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/android/dialer/backup')
-rw-r--r--java/com/android/dialer/backup/AndroidManifest.xml27
-rw-r--r--java/com/android/dialer/backup/DialerBackupAgent.java276
-rw-r--r--java/com/android/dialer/backup/DialerBackupUtils.java320
-rw-r--r--java/com/android/dialer/backup/proto/VoicemailInfo.java377
4 files changed, 1000 insertions, 0 deletions
diff --git a/java/com/android/dialer/backup/AndroidManifest.xml b/java/com/android/dialer/backup/AndroidManifest.xml
new file mode 100644
index 000000000..cfdb3d93d
--- /dev/null
+++ b/java/com/android/dialer/backup/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<!--
+ ~ 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
+ -->
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.dialer.backup">
+
+ <application
+ android:backupAgent="com.android.dialer.backup.DialerBackupAgent"
+ android:fullBackupOnly="true"
+ android:restoreAnyVersion="true"
+ android:name="com.android.dialer.app.DialerApplication"
+ />
+
+</manifest> \ No newline at end of file
diff --git a/java/com/android/dialer/backup/DialerBackupAgent.java b/java/com/android/dialer/backup/DialerBackupAgent.java
new file mode 100644
index 000000000..391a93f29
--- /dev/null
+++ b/java/com/android/dialer/backup/DialerBackupAgent.java
@@ -0,0 +1,276 @@
+/*
+ * 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);
+ }
+ }
+}
diff --git a/java/com/android/dialer/backup/DialerBackupUtils.java b/java/com/android/dialer/backup/DialerBackupUtils.java
new file mode 100644
index 000000000..ff0cb4f7c
--- /dev/null
+++ b/java/com/android/dialer/backup/DialerBackupUtils.java
@@ -0,0 +1,320 @@
+/*
+ * 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.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build.VERSION_CODES;
+import android.provider.VoicemailContract;
+import android.provider.VoicemailContract.Voicemails;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.Pair;
+import com.android.dialer.backup.nano.VoicemailInfo;
+import com.android.dialer.common.ConfigProviderBindings;
+import com.android.dialer.common.LogUtil;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Files;
+import com.google.protobuf.nano.MessageNano;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/** Helper functions for DialerBackupAgent */
+public class DialerBackupUtils {
+ // Backup voicemails up to 20MB
+ static long maxVoicemailSizeToBackup = 20000000L;
+ static final String RESTORED_COLUMN = "restored";
+
+ private DialerBackupUtils() {}
+
+ public static void copyAudioBytesToContentUri(
+ @NonNull byte[] audioBytesArray, @NonNull OutputStream restoreStream) throws IOException {
+ LogUtil.i("DialerBackupUtils.copyStream", "audioByteArray length: " + audioBytesArray.length);
+
+ ByteArrayInputStream decodedStream = new ByteArrayInputStream(audioBytesArray);
+ LogUtil.i(
+ "DialerBackupUtils.copyStream", "decodedStream.available: " + decodedStream.available());
+
+ ByteStreams.copy(decodedStream, restoreStream);
+ }
+
+ public static @Nullable byte[] audioStreamToByteArray(@NonNull InputStream stream)
+ throws IOException {
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ if (stream.available() > 0) {
+ ByteStreams.copy(stream, buffer);
+ } else {
+ LogUtil.i("DialerBackupUtils.audioStreamToByteArray", "no audio stream to backup");
+ }
+ return buffer.toByteArray();
+ }
+
+ public static void writeProtoToFile(@NonNull File file, @NonNull VoicemailInfo voicemailInfo)
+ throws IOException {
+ LogUtil.i(
+ "DialerBackupUtils.writeProtoToFile",
+ "backup " + voicemailInfo + " to " + file.getAbsolutePath());
+
+ byte[] bytes = MessageNano.toByteArray(voicemailInfo);
+ Files.write(bytes, file);
+ }
+
+ /** Only restore voicemails that have the restored column in calllog (NMR2+ builds) */
+ @TargetApi(VERSION_CODES.M)
+ public static boolean canRestoreVoicemails(ContentResolver contentResolver, Context context) {
+ try (Cursor cursor = contentResolver.query(Voicemails.CONTENT_URI, null, null, null, null)) {
+ // Restored column only exists in NMR2 and above builds.
+ if (cursor.getColumnIndex(RESTORED_COLUMN) != -1) {
+ LogUtil.i("DialerBackupUtils.canRestoreVoicemails", "Build supports restore");
+ return true;
+ } else {
+ LogUtil.i("DialerBackupUtils.canRestoreVoicemails", "Build does not support restore");
+ return false;
+ }
+ }
+ }
+
+ public static VoicemailInfo protoFileToVoicemailInfo(@NonNull File file) throws IOException {
+ byte[] byteArray = Files.toByteArray(file);
+ return VoicemailInfo.parseFrom(byteArray);
+ }
+
+ @TargetApi(VERSION_CODES.M)
+ public static VoicemailInfo convertVoicemailCursorRowToProto(
+ @NonNull Cursor cursor, @NonNull ContentResolver contentResolver) throws IOException {
+
+ VoicemailInfo voicemailInfo = new VoicemailInfo();
+
+ for (int i = 0; i < cursor.getColumnCount(); ++i) {
+ String name = cursor.getColumnName(i);
+ String value = cursor.getString(i);
+
+ LogUtil.i(
+ "DialerBackupUtils.convertVoicemailCursorRowToProto",
+ "column index: %d, column name: %s, column value: %s",
+ i,
+ name,
+ value);
+
+ switch (name) {
+ case Voicemails.DATE:
+ voicemailInfo.date = value;
+ break;
+ case Voicemails.DELETED:
+ voicemailInfo.deleted = value;
+ break;
+ case Voicemails.DIRTY:
+ voicemailInfo.dirty = value;
+ break;
+ case Voicemails.DIR_TYPE:
+ voicemailInfo.dirType = value;
+ break;
+ case Voicemails.DURATION:
+ voicemailInfo.duration = value;
+ break;
+ case Voicemails.HAS_CONTENT:
+ voicemailInfo.hasContent = value;
+ break;
+ case Voicemails.IS_READ:
+ voicemailInfo.isRead = value;
+ break;
+ case Voicemails.ITEM_TYPE:
+ voicemailInfo.itemType = value;
+ break;
+ case Voicemails.LAST_MODIFIED:
+ voicemailInfo.lastModified = value;
+ break;
+ case Voicemails.MIME_TYPE:
+ voicemailInfo.mimeType = value;
+ break;
+ case Voicemails.NUMBER:
+ voicemailInfo.number = value;
+ break;
+ case Voicemails.PHONE_ACCOUNT_COMPONENT_NAME:
+ voicemailInfo.phoneAccountComponentName = value;
+ break;
+ case Voicemails.PHONE_ACCOUNT_ID:
+ voicemailInfo.phoneAccountId = value;
+ break;
+ case Voicemails.SOURCE_DATA:
+ voicemailInfo.sourceData = value;
+ break;
+ case Voicemails.SOURCE_PACKAGE:
+ voicemailInfo.sourcePackage = value;
+ break;
+ case Voicemails.TRANSCRIPTION:
+ voicemailInfo.transcription = value;
+ break;
+ case DialerBackupAgent.VOICEMAIL_URI:
+ try (InputStream audioStream = contentResolver.openInputStream(Uri.parse(value))) {
+ voicemailInfo.encodedVoicemailKey = audioStreamToByteArray(audioStream);
+ }
+ break;
+ default:
+ LogUtil.i(
+ "DialerBackupUtils.convertVoicemailCursorRowToProto",
+ "Not backing up column: %s, with value: %s",
+ name,
+ value);
+ break;
+ }
+ }
+ return voicemailInfo;
+ }
+
+ public static Pair<ContentValues, byte[]> convertVoicemailProtoFileToContentValueAndAudioBytes(
+ @NonNull File file, Context context) throws IOException {
+
+ VoicemailInfo voicemailInfo = DialerBackupUtils.protoFileToVoicemailInfo(file);
+ LogUtil.i(
+ "DialerBackupUtils.convertVoicemailProtoFileToContentValueAndEncodedAudio",
+ "file name: "
+ + file.getName()
+ + " voicemailInfo size: "
+ + voicemailInfo.getSerializedSize());
+
+ if (isDuplicate(context, voicemailInfo)) {
+ LogUtil.i(
+ "DialerBackupUtils.convertVoicemailProtoFileToContentValueAndEncodedAudio",
+ "voicemail already exists");
+ return null;
+ } else {
+ ContentValues contentValues = new ContentValues();
+
+ if (!voicemailInfo.date.isEmpty()) {
+ contentValues.put(Voicemails.DATE, voicemailInfo.date);
+ }
+ if (!voicemailInfo.deleted.isEmpty()) {
+ contentValues.put(Voicemails.DELETED, voicemailInfo.deleted);
+ }
+ if (!voicemailInfo.dirty.isEmpty()) {
+ contentValues.put(Voicemails.DIRTY, voicemailInfo.dirty);
+ }
+ if (!voicemailInfo.duration.isEmpty()) {
+ contentValues.put(Voicemails.DURATION, voicemailInfo.duration);
+ }
+ if (!voicemailInfo.isRead.isEmpty()) {
+ contentValues.put(Voicemails.IS_READ, voicemailInfo.isRead);
+ }
+ if (!voicemailInfo.lastModified.isEmpty()) {
+ contentValues.put(Voicemails.LAST_MODIFIED, voicemailInfo.lastModified);
+ }
+ if (!voicemailInfo.mimeType.isEmpty()) {
+ contentValues.put(Voicemails.MIME_TYPE, voicemailInfo.mimeType);
+ }
+ if (!voicemailInfo.number.isEmpty()) {
+ contentValues.put(Voicemails.NUMBER, voicemailInfo.number);
+ }
+ if (!voicemailInfo.phoneAccountComponentName.isEmpty()) {
+ contentValues.put(
+ Voicemails.PHONE_ACCOUNT_COMPONENT_NAME, voicemailInfo.phoneAccountComponentName);
+ }
+ if (!voicemailInfo.phoneAccountId.isEmpty()) {
+ contentValues.put(Voicemails.PHONE_ACCOUNT_ID, voicemailInfo.phoneAccountId);
+ }
+ if (!voicemailInfo.sourceData.isEmpty()) {
+ contentValues.put(Voicemails.SOURCE_DATA, voicemailInfo.sourceData);
+ }
+ if (!voicemailInfo.sourcePackage.isEmpty()) {
+ contentValues.put(Voicemails.SOURCE_PACKAGE, voicemailInfo.sourcePackage);
+ }
+ if (!voicemailInfo.transcription.isEmpty()) {
+ contentValues.put(Voicemails.TRANSCRIPTION, voicemailInfo.transcription);
+ }
+ contentValues.put(VoicemailContract.Voicemails.HAS_CONTENT, 1);
+ contentValues.put(RESTORED_COLUMN, "1");
+ contentValues.put(Voicemails.SOURCE_PACKAGE, getSourcePackage(context, voicemailInfo));
+
+ LogUtil.i(
+ "DialerBackupUtils.convertVoicemailProtoFileToContentValueAndEncodedAudio",
+ "cv: " + contentValues);
+
+ return Pair.create(contentValues, voicemailInfo.encodedVoicemailKey);
+ }
+ }
+
+ /**
+ * We should be using the system package name as the source package if there is no endless VM/VM
+ * archive present on the device. This is to separate pre-O (no endless VM) and O+ (endless VM)
+ * devices. This ensures that the source of truth for VMs is the VM server when endless VM is not
+ * enabled, and when endless VM/archived VMs is present, the source of truth for VMs is the device
+ * itself.
+ */
+ private static String getSourcePackage(Context context, VoicemailInfo voicemailInfo) {
+ if (ConfigProviderBindings.get(context)
+ .getBoolean("voicemail_restore_force_system_source_package", false)) {
+ LogUtil.i("DialerBackupUtils.getSourcePackage", "forcing system source package");
+ return "com.android.phone";
+ }
+ if (ConfigProviderBindings.get(context)
+ .getBoolean("voicemail_restore_check_archive_for_source_package", true)) {
+ if ("1".equals(voicemailInfo.archived)) {
+ LogUtil.i(
+ "DialerBackupUtils.getSourcePackage",
+ "voicemail was archived, using app source package");
+ // Using our app's source package will prevent the archived voicemail from being deleted by
+ // the system when it syncs with the voicemail server. In most cases the user will not see
+ // duplicate voicemails because this voicemail was archived and likely deleted from the
+ // voicemail server.
+ return context.getPackageName();
+ } else {
+ // Use the system source package. This means that if the voicemail is not present on the
+ // voicemail server then the system will delete it when it syncs.
+ LogUtil.i(
+ "DialerBackupUtils.getSourcePackage",
+ "voicemail was not archived, using system source package");
+ return "com.android.phone";
+ }
+ }
+ // Use our app's source package. This means that if the system syncs voicemail from the server
+ // the user could potentially get duplicate voicemails.
+ LogUtil.i("DialerBackupUtils.getSourcePackage", "defaulting to using app source package");
+ return context.getPackageName();
+ }
+
+ @TargetApi(VERSION_CODES.M)
+ private static boolean isDuplicate(Context context, VoicemailInfo voicemailInfo) {
+ // This checks for VM that might already exist, and doesn't restore them
+ try (Cursor cursor =
+ context
+ .getContentResolver()
+ .query(
+ VoicemailContract.Voicemails.CONTENT_URI,
+ null,
+ String.format(
+ "(%s = ? AND %s = ? AND %s = ?)",
+ Voicemails.NUMBER, Voicemails.DATE, Voicemails.DURATION),
+ new String[] {voicemailInfo.number, voicemailInfo.date, voicemailInfo.duration},
+ null,
+ null)) {
+ if (cursor.moveToFirst()
+ && ConfigProviderBindings.get(context)
+ .getBoolean("enable_vm_restore_no_duplicate", true)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/java/com/android/dialer/backup/proto/VoicemailInfo.java b/java/com/android/dialer/backup/proto/VoicemailInfo.java
new file mode 100644
index 000000000..9ff8423f3
--- /dev/null
+++ b/java/com/android/dialer/backup/proto/VoicemailInfo.java
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+// Generated by the protocol buffer compiler. DO NOT EDIT!
+
+package com.android.dialer.backup.nano;
+
+@SuppressWarnings("hiding")
+public final class VoicemailInfo extends
+ com.google.protobuf.nano.ExtendableMessageNano<VoicemailInfo> {
+
+ private static volatile VoicemailInfo[] _emptyArray;
+ public static VoicemailInfo[] emptyArray() {
+ // Lazily initializes the empty array
+ if (_emptyArray == null) {
+ synchronized (
+ com.google.protobuf.nano.InternalNano.LAZY_INIT_LOCK) {
+ if (_emptyArray == null) {
+ _emptyArray = new VoicemailInfo[0];
+ }
+ }
+ }
+ return _emptyArray;
+ }
+
+ // optional string date = 1;
+ public java.lang.String date;
+
+ // optional string deleted = 2;
+ public java.lang.String deleted;
+
+ // optional string dirty = 3;
+ public java.lang.String dirty;
+
+ // optional string dir_type = 4;
+ public java.lang.String dirType;
+
+ // optional string duration = 5;
+ public java.lang.String duration;
+
+ // optional string has_content = 6;
+ public java.lang.String hasContent;
+
+ // optional string is_read = 7;
+ public java.lang.String isRead;
+
+ // optional string item_type = 8;
+ public java.lang.String itemType;
+
+ // optional string last_modified = 9;
+ public java.lang.String lastModified;
+
+ // optional string mime_type = 10;
+ public java.lang.String mimeType;
+
+ // optional string number = 11;
+ public java.lang.String number;
+
+ // optional string phone_account_component_name = 12;
+ public java.lang.String phoneAccountComponentName;
+
+ // optional string phone_account_id = 13;
+ public java.lang.String phoneAccountId;
+
+ // optional string source_data = 14;
+ public java.lang.String sourceData;
+
+ // optional string source_package = 15;
+ public java.lang.String sourcePackage;
+
+ // optional string transcription = 16;
+ public java.lang.String transcription;
+
+ // optional string voicemail_uri = 17;
+ public java.lang.String voicemailUri;
+
+ // optional bytes encoded_voicemail_key = 18;
+ public byte[] encodedVoicemailKey;
+
+ // optional string archived = 19;
+ public java.lang.String archived;
+
+ // @@protoc_insertion_point(class_scope:com.android.dialer.backup.VoicemailInfo)
+
+ public VoicemailInfo() {
+ clear();
+ }
+
+ public VoicemailInfo clear() {
+ date = "";
+ deleted = "";
+ dirty = "";
+ dirType = "";
+ duration = "";
+ hasContent = "";
+ isRead = "";
+ itemType = "";
+ lastModified = "";
+ mimeType = "";
+ number = "";
+ phoneAccountComponentName = "";
+ phoneAccountId = "";
+ sourceData = "";
+ sourcePackage = "";
+ transcription = "";
+ voicemailUri = "";
+ encodedVoicemailKey = com.google.protobuf.nano.WireFormatNano.EMPTY_BYTES;
+ archived = "";
+ unknownFieldData = null;
+ cachedSize = -1;
+ return this;
+ }
+
+ @Override
+ public void writeTo(com.google.protobuf.nano.CodedOutputByteBufferNano output)
+ throws java.io.IOException {
+ if (this.date != null && !this.date.equals("")) {
+ output.writeString(1, this.date);
+ }
+ if (this.deleted != null && !this.deleted.equals("")) {
+ output.writeString(2, this.deleted);
+ }
+ if (this.dirty != null && !this.dirty.equals("")) {
+ output.writeString(3, this.dirty);
+ }
+ if (this.dirType != null && !this.dirType.equals("")) {
+ output.writeString(4, this.dirType);
+ }
+ if (this.duration != null && !this.duration.equals("")) {
+ output.writeString(5, this.duration);
+ }
+ if (this.hasContent != null && !this.hasContent.equals("")) {
+ output.writeString(6, this.hasContent);
+ }
+ if (this.isRead != null && !this.isRead.equals("")) {
+ output.writeString(7, this.isRead);
+ }
+ if (this.itemType != null && !this.itemType.equals("")) {
+ output.writeString(8, this.itemType);
+ }
+ if (this.lastModified != null && !this.lastModified.equals("")) {
+ output.writeString(9, this.lastModified);
+ }
+ if (this.mimeType != null && !this.mimeType.equals("")) {
+ output.writeString(10, this.mimeType);
+ }
+ if (this.number != null && !this.number.equals("")) {
+ output.writeString(11, this.number);
+ }
+ if (this.phoneAccountComponentName != null && !this.phoneAccountComponentName.equals("")) {
+ output.writeString(12, this.phoneAccountComponentName);
+ }
+ if (this.phoneAccountId != null && !this.phoneAccountId.equals("")) {
+ output.writeString(13, this.phoneAccountId);
+ }
+ if (this.sourceData != null && !this.sourceData.equals("")) {
+ output.writeString(14, this.sourceData);
+ }
+ if (this.sourcePackage != null && !this.sourcePackage.equals("")) {
+ output.writeString(15, this.sourcePackage);
+ }
+ if (this.transcription != null && !this.transcription.equals("")) {
+ output.writeString(16, this.transcription);
+ }
+ if (this.voicemailUri != null && !this.voicemailUri.equals("")) {
+ output.writeString(17, this.voicemailUri);
+ }
+ if (!java.util.Arrays.equals(this.encodedVoicemailKey, com.google.protobuf.nano.WireFormatNano.EMPTY_BYTES)) {
+ output.writeBytes(18, this.encodedVoicemailKey);
+ }
+ if (this.archived != null && !this.archived.equals("")) {
+ output.writeString(19, this.archived);
+ }
+ super.writeTo(output);
+ }
+
+ @Override
+ protected int computeSerializedSize() {
+ int size = super.computeSerializedSize();
+ if (this.date != null && !this.date.equals("")) {
+ size += com.google.protobuf.nano.CodedOutputByteBufferNano
+ .computeStringSize(1, this.date);
+ }
+ if (this.deleted != null && !this.deleted.equals("")) {
+ size += com.google.protobuf.nano.CodedOutputByteBufferNano
+ .computeStringSize(2, this.deleted);
+ }
+ if (this.dirty != null && !this.dirty.equals("")) {
+ size += com.google.protobuf.nano.CodedOutputByteBufferNano
+ .computeStringSize(3, this.dirty);
+ }
+ if (this.dirType != null && !this.dirType.equals("")) {
+ size += com.google.protobuf.nano.CodedOutputByteBufferNano
+ .computeStringSize(4, this.dirType);
+ }
+ if (this.duration != null && !this.duration.equals("")) {
+ size += com.google.protobuf.nano.CodedOutputByteBufferNano
+ .computeStringSize(5, this.duration);
+ }
+ if (this.hasContent != null && !this.hasContent.equals("")) {
+ size += com.google.protobuf.nano.CodedOutputByteBufferNano
+ .computeStringSize(6, this.hasContent);
+ }
+ if (this.isRead != null && !this.isRead.equals("")) {
+ size += com.google.protobuf.nano.CodedOutputByteBufferNano
+ .computeStringSize(7, this.isRead);
+ }
+ if (this.itemType != null && !this.itemType.equals("")) {
+ size += com.google.protobuf.nano.CodedOutputByteBufferNano
+ .computeStringSize(8, this.itemType);
+ }
+ if (this.lastModified != null && !this.lastModified.equals("")) {
+ size += com.google.protobuf.nano.CodedOutputByteBufferNano
+ .computeStringSize(9, this.lastModified);
+ }
+ if (this.mimeType != null && !this.mimeType.equals("")) {
+ size += com.google.protobuf.nano.CodedOutputByteBufferNano
+ .computeStringSize(10, this.mimeType);
+ }
+ if (this.number != null && !this.number.equals("")) {
+ size += com.google.protobuf.nano.CodedOutputByteBufferNano
+ .computeStringSize(11, this.number);
+ }
+ if (this.phoneAccountComponentName != null && !this.phoneAccountComponentName.equals("")) {
+ size += com.google.protobuf.nano.CodedOutputByteBufferNano
+ .computeStringSize(12, this.phoneAccountComponentName);
+ }
+ if (this.phoneAccountId != null && !this.phoneAccountId.equals("")) {
+ size += com.google.protobuf.nano.CodedOutputByteBufferNano
+ .computeStringSize(13, this.phoneAccountId);
+ }
+ if (this.sourceData != null && !this.sourceData.equals("")) {
+ size += com.google.protobuf.nano.CodedOutputByteBufferNano
+ .computeStringSize(14, this.sourceData);
+ }
+ if (this.sourcePackage != null && !this.sourcePackage.equals("")) {
+ size += com.google.protobuf.nano.CodedOutputByteBufferNano
+ .computeStringSize(15, this.sourcePackage);
+ }
+ if (this.transcription != null && !this.transcription.equals("")) {
+ size += com.google.protobuf.nano.CodedOutputByteBufferNano
+ .computeStringSize(16, this.transcription);
+ }
+ if (this.voicemailUri != null && !this.voicemailUri.equals("")) {
+ size += com.google.protobuf.nano.CodedOutputByteBufferNano
+ .computeStringSize(17, this.voicemailUri);
+ }
+ if (!java.util.Arrays.equals(this.encodedVoicemailKey, com.google.protobuf.nano.WireFormatNano.EMPTY_BYTES)) {
+ size += com.google.protobuf.nano.CodedOutputByteBufferNano
+ .computeBytesSize(18, this.encodedVoicemailKey);
+ }
+ if (this.archived != null && !this.archived.equals("")) {
+ size += com.google.protobuf.nano.CodedOutputByteBufferNano
+ .computeStringSize(19, this.archived);
+ }
+ return size;
+ }
+
+ @Override
+ public VoicemailInfo mergeFrom(
+ com.google.protobuf.nano.CodedInputByteBufferNano input)
+ throws java.io.IOException {
+ while (true) {
+ int tag = input.readTag();
+ switch (tag) {
+ case 0:
+ return this;
+ default: {
+ if (!super.storeUnknownField(input, tag)) {
+ return this;
+ }
+ break;
+ }
+ case 10: {
+ this.date = input.readString();
+ break;
+ }
+ case 18: {
+ this.deleted = input.readString();
+ break;
+ }
+ case 26: {
+ this.dirty = input.readString();
+ break;
+ }
+ case 34: {
+ this.dirType = input.readString();
+ break;
+ }
+ case 42: {
+ this.duration = input.readString();
+ break;
+ }
+ case 50: {
+ this.hasContent = input.readString();
+ break;
+ }
+ case 58: {
+ this.isRead = input.readString();
+ break;
+ }
+ case 66: {
+ this.itemType = input.readString();
+ break;
+ }
+ case 74: {
+ this.lastModified = input.readString();
+ break;
+ }
+ case 82: {
+ this.mimeType = input.readString();
+ break;
+ }
+ case 90: {
+ this.number = input.readString();
+ break;
+ }
+ case 98: {
+ this.phoneAccountComponentName = input.readString();
+ break;
+ }
+ case 106: {
+ this.phoneAccountId = input.readString();
+ break;
+ }
+ case 114: {
+ this.sourceData = input.readString();
+ break;
+ }
+ case 122: {
+ this.sourcePackage = input.readString();
+ break;
+ }
+ case 130: {
+ this.transcription = input.readString();
+ break;
+ }
+ case 138: {
+ this.voicemailUri = input.readString();
+ break;
+ }
+ case 146: {
+ this.encodedVoicemailKey = input.readBytes();
+ break;
+ }
+ case 154: {
+ this.archived = input.readString();
+ break;
+ }
+ }
+ }
+ }
+
+ public static VoicemailInfo parseFrom(byte[] data)
+ throws com.google.protobuf.nano.InvalidProtocolBufferNanoException {
+ return com.google.protobuf.nano.MessageNano.mergeFrom(new VoicemailInfo(), data);
+ }
+
+ public static VoicemailInfo parseFrom(
+ com.google.protobuf.nano.CodedInputByteBufferNano input)
+ throws java.io.IOException {
+ return new VoicemailInfo().mergeFrom(input);
+ }
+}