blob: f2e392b7535089f87d493160e439f711b21cea23 [file] [log] [blame]
/*
* 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.providers.blockednumber;
import android.annotation.Nullable;
import android.app.backup.BackupAgent;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.os.ParcelFileDescriptor;
import android.provider.BlockedNumberContract;
import android.util.Log;
import libcore.io.IoUtils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
/**
* A backup agent to enable backup and restore of blocked numbers.
*/
public class BlockedNumberBackupAgent extends BackupAgent {
private static final String[] BLOCKED_NUMBERS_PROJECTION = new String[] {
BlockedNumberContract.BlockedNumbers.COLUMN_ID,
BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER,
BlockedNumberContract.BlockedNumbers.COLUMN_E164_NUMBER,
};
private static final String TAG = "BlockedNumberBackup";
private static final int VERSION = 1;
private static final boolean DEBUG = false; // DO NOT SUBMIT WITH TRUE.
@Override
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput backupDataOutput,
ParcelFileDescriptor newState) throws IOException {
logV("Backing up blocked numbers.");
DataInputStream dataInputStream =
new DataInputStream(new FileInputStream(oldState.getFileDescriptor()));
final BackupState state;
try {
state = readState(dataInputStream);
} finally {
IoUtils.closeQuietly(dataInputStream);
}
runBackup(state, backupDataOutput, getAllBlockedNumbers());
DataOutputStream dataOutputStream =
new DataOutputStream(new FileOutputStream(newState.getFileDescriptor()));
try {
writeNewState(dataOutputStream, state);
} finally {
dataOutputStream.close();
}
}
@Override
public void onRestore(BackupDataInput data, int appVersionCode,
ParcelFileDescriptor newState) throws IOException {
logV("Restoring blocked numbers.");
while (data.readNextHeader()) {
BackedUpBlockedNumber blockedNumber = readBlockedNumberFromData(data);
if (blockedNumber != null) {
writeToProvider(blockedNumber);
}
}
}
private BackupState readState(DataInputStream dataInputStream) throws IOException {
int version = VERSION;
if (dataInputStream.available() > 0) {
version = dataInputStream.readInt();
}
BackupState state = new BackupState(version, new TreeSet<Integer>());
while (dataInputStream.available() > 0) {
state.ids.add(dataInputStream.readInt());
}
return state;
}
private void runBackup(BackupState state, BackupDataOutput backupDataOutput,
Iterable<BackedUpBlockedNumber> allBlockedNumbers) throws IOException {
SortedSet<Integer> deletedBlockedNumbers = new TreeSet<>(state.ids);
for (BackedUpBlockedNumber blockedNumber : allBlockedNumbers) {
if (state.ids.contains(blockedNumber.id)) {
// Existing blocked number: do not delete.
deletedBlockedNumbers.remove(blockedNumber.id);
} else {
logV("Adding blocked number to backup: " + blockedNumber);
// New blocked number
addToBackup(backupDataOutput, blockedNumber);
state.ids.add(blockedNumber.id);
}
}
for (int id : deletedBlockedNumbers) {
logV("Removing blocked number from backup: " + id);
removeFromBackup(backupDataOutput, id);
state.ids.remove(id);
}
}
private void addToBackup(BackupDataOutput output, BackedUpBlockedNumber blockedNumber)
throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
dataOutputStream.writeInt(VERSION);
writeString(dataOutputStream, blockedNumber.originalNumber);
writeString(dataOutputStream, blockedNumber.e164Number);
dataOutputStream.flush();
output.writeEntityHeader(Integer.toString(blockedNumber.id), outputStream.size());
output.writeEntityData(outputStream.toByteArray(), outputStream.size());
}
private void writeString(DataOutputStream dataOutputStream, @Nullable String value)
throws IOException {
if (value == null) {
dataOutputStream.writeBoolean(false);
} else {
dataOutputStream.writeBoolean(true);
dataOutputStream.writeUTF(value);
}
}
@Nullable
private String readString(DataInputStream dataInputStream)
throws IOException {
if (dataInputStream.readBoolean()) {
return dataInputStream.readUTF();
} else {
return null;
}
}
private void removeFromBackup(BackupDataOutput output, int id) throws IOException {
output.writeEntityHeader(Integer.toString(id), -1);
}
private Iterable<BackedUpBlockedNumber> getAllBlockedNumbers() {
List<BackedUpBlockedNumber> blockedNumbers = new ArrayList<>();
ContentResolver resolver = getContentResolver();
Cursor cursor = resolver.query(
BlockedNumberContract.BlockedNumbers.CONTENT_URI, BLOCKED_NUMBERS_PROJECTION, null,
null, null);
if (cursor != null) {
try {
while (cursor.moveToNext()) {
blockedNumbers.add(createBlockedNumberFromCursor(cursor));
}
} finally {
cursor.close();
}
}
return blockedNumbers;
}
private BackedUpBlockedNumber createBlockedNumberFromCursor(Cursor cursor) {
return new BackedUpBlockedNumber(
cursor.getInt(0), cursor.getString(1), cursor.getString(2));
}
private void writeNewState(DataOutputStream dataOutputStream, BackupState state)
throws IOException {
dataOutputStream.writeInt(VERSION);
for (int i : state.ids) {
dataOutputStream.writeInt(i);
}
}
@Nullable
private BackedUpBlockedNumber readBlockedNumberFromData(BackupDataInput data) {
int id;
try {
id = Integer.parseInt(data.getKey());
} catch (NumberFormatException e) {
Log.e(TAG, "Unexpected key found in restore: " + data.getKey());
return null;
}
try {
byte[] byteArray = new byte[data.getDataSize()];
data.readEntityData(byteArray, 0, byteArray.length);
DataInputStream dataInput = new DataInputStream(new ByteArrayInputStream(byteArray));
dataInput.readInt(); // Ignore version.
BackedUpBlockedNumber blockedNumber =
new BackedUpBlockedNumber(id, readString(dataInput), readString(dataInput));
logV("Restoring blocked number: " + blockedNumber);
return blockedNumber;
} catch (IOException e) {
Log.e(TAG, "Error reading blocked number for: " + id + ": " + e.getMessage());
return null;
}
}
private void writeToProvider(BackedUpBlockedNumber blockedNumber) {
ContentValues contentValues = new ContentValues();
contentValues.put(BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER,
blockedNumber.originalNumber);
contentValues.put(BlockedNumberContract.BlockedNumbers.COLUMN_E164_NUMBER,
blockedNumber.e164Number);
try {
getContentResolver().insert(
BlockedNumberContract.BlockedNumbers.CONTENT_URI, contentValues);
} catch (Exception e) {
Log.e(TAG, "Unable to insert blocked number " + blockedNumber + " :" + e.getMessage());
}
}
private static boolean isDebug() {
return Log.isLoggable(TAG, Log.DEBUG);
}
private static void logV(String msg) {
if (DEBUG) {
Log.v(TAG, msg);
}
}
private static class BackupState {
final int version;
final SortedSet<Integer> ids;
BackupState(int version, SortedSet<Integer> ids) {
this.version = version;
this.ids = ids;
}
}
private static class BackedUpBlockedNumber {
final int id;
final String originalNumber;
final String e164Number;
BackedUpBlockedNumber(int id, String originalNumber, String e164Number) {
this.id = id;
this.originalNumber = originalNumber;
this.e164Number = e164Number;
}
@Override
public String toString() {
if (isDebug()) {
return String.format("[%d, original number: %s, e164 number: %s]",
id, originalNumber, e164Number);
} else {
return String.format("[%d]", id);
}
}
}
}