• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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