Home | History | Annotate | Download | only in blockednumber
      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.providers.blockednumber;
     18 
     19 import android.annotation.Nullable;
     20 import android.app.backup.BackupAgent;
     21 import android.app.backup.BackupDataInput;
     22 import android.app.backup.BackupDataOutput;
     23 import android.content.ContentResolver;
     24 import android.content.ContentValues;
     25 import android.database.Cursor;
     26 import android.os.ParcelFileDescriptor;
     27 import android.provider.BlockedNumberContract;
     28 import android.util.Log;
     29 
     30 import libcore.io.IoUtils;
     31 
     32 import java.io.ByteArrayInputStream;
     33 import java.io.ByteArrayOutputStream;
     34 import java.io.DataInputStream;
     35 import java.io.DataOutputStream;
     36 import java.io.FileInputStream;
     37 import java.io.FileOutputStream;
     38 import java.io.IOException;
     39 import java.util.ArrayList;
     40 import java.util.List;
     41 import java.util.SortedSet;
     42 import java.util.TreeSet;
     43 
     44 /**
     45  * A backup agent to enable backup and restore of blocked numbers.
     46  */
     47 public class BlockedNumberBackupAgent extends BackupAgent {
     48     private static final String[] BLOCKED_NUMBERS_PROJECTION = new String[] {
     49             BlockedNumberContract.BlockedNumbers.COLUMN_ID,
     50             BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER,
     51             BlockedNumberContract.BlockedNumbers.COLUMN_E164_NUMBER,
     52     };
     53     private static final String TAG = "BlockedNumberBackup";
     54     private static final int VERSION = 1;
     55     private static final boolean DEBUG = false; // DO NOT SUBMIT WITH TRUE.
     56 
     57     @Override
     58     public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput backupDataOutput,
     59                          ParcelFileDescriptor newState) throws IOException {
     60         logV("Backing up blocked numbers.");
     61 
     62         DataInputStream dataInputStream =
     63                 new DataInputStream(new FileInputStream(oldState.getFileDescriptor()));
     64         final BackupState state;
     65         try {
     66             state = readState(dataInputStream);
     67         } finally {
     68             IoUtils.closeQuietly(dataInputStream);
     69         }
     70 
     71         runBackup(state, backupDataOutput, getAllBlockedNumbers());
     72 
     73         DataOutputStream dataOutputStream =
     74                 new DataOutputStream(new FileOutputStream(newState.getFileDescriptor()));
     75         try {
     76             writeNewState(dataOutputStream, state);
     77         } finally {
     78             dataOutputStream.close();
     79         }
     80     }
     81 
     82     @Override
     83     public void onRestore(BackupDataInput data, int appVersionCode,
     84                           ParcelFileDescriptor newState) throws IOException {
     85         logV("Restoring blocked numbers.");
     86 
     87         while (data.readNextHeader()) {
     88             BackedUpBlockedNumber blockedNumber = readBlockedNumberFromData(data);
     89             if (blockedNumber != null) {
     90                 writeToProvider(blockedNumber);
     91             }
     92         }
     93     }
     94 
     95     private BackupState readState(DataInputStream dataInputStream) throws IOException {
     96         int version = VERSION;
     97         if (dataInputStream.available() > 0) {
     98             version = dataInputStream.readInt();
     99         }
    100         BackupState state = new BackupState(version, new TreeSet<Integer>());
    101         while (dataInputStream.available() > 0) {
    102             state.ids.add(dataInputStream.readInt());
    103         }
    104         return state;
    105     }
    106 
    107     private void runBackup(BackupState state, BackupDataOutput backupDataOutput,
    108                            Iterable<BackedUpBlockedNumber> allBlockedNumbers) throws IOException {
    109         SortedSet<Integer> deletedBlockedNumbers = new TreeSet<>(state.ids);
    110 
    111         for (BackedUpBlockedNumber blockedNumber : allBlockedNumbers) {
    112             if (state.ids.contains(blockedNumber.id)) {
    113                 // Existing blocked number: do not delete.
    114                 deletedBlockedNumbers.remove(blockedNumber.id);
    115             } else {
    116                 logV("Adding blocked number to backup: " + blockedNumber);
    117                 // New blocked number
    118                 addToBackup(backupDataOutput, blockedNumber);
    119                 state.ids.add(blockedNumber.id);
    120             }
    121         }
    122 
    123         for (int id : deletedBlockedNumbers) {
    124             logV("Removing blocked number from backup: " + id);
    125             removeFromBackup(backupDataOutput, id);
    126             state.ids.remove(id);
    127         }
    128     }
    129 
    130     private void addToBackup(BackupDataOutput output, BackedUpBlockedNumber blockedNumber)
    131             throws IOException {
    132         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    133         DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
    134         dataOutputStream.writeInt(VERSION);
    135         writeString(dataOutputStream, blockedNumber.originalNumber);
    136         writeString(dataOutputStream, blockedNumber.e164Number);
    137         dataOutputStream.flush();
    138 
    139         output.writeEntityHeader(Integer.toString(blockedNumber.id), outputStream.size());
    140         output.writeEntityData(outputStream.toByteArray(), outputStream.size());
    141     }
    142 
    143     private void writeString(DataOutputStream dataOutputStream, @Nullable String value)
    144             throws IOException {
    145         if (value == null) {
    146             dataOutputStream.writeBoolean(false);
    147         } else {
    148             dataOutputStream.writeBoolean(true);
    149             dataOutputStream.writeUTF(value);
    150         }
    151     }
    152 
    153     @Nullable
    154     private String readString(DataInputStream dataInputStream)
    155             throws IOException {
    156         if (dataInputStream.readBoolean()) {
    157             return dataInputStream.readUTF();
    158         } else {
    159             return null;
    160         }
    161     }
    162 
    163     private void removeFromBackup(BackupDataOutput output, int id) throws IOException {
    164         output.writeEntityHeader(Integer.toString(id), -1);
    165     }
    166 
    167     private Iterable<BackedUpBlockedNumber> getAllBlockedNumbers() {
    168         List<BackedUpBlockedNumber> blockedNumbers = new ArrayList<>();
    169         ContentResolver resolver = getContentResolver();
    170         Cursor cursor = resolver.query(
    171                 BlockedNumberContract.BlockedNumbers.CONTENT_URI, BLOCKED_NUMBERS_PROJECTION, null,
    172                 null, null);
    173         if (cursor != null) {
    174             try {
    175                 while (cursor.moveToNext()) {
    176                     blockedNumbers.add(createBlockedNumberFromCursor(cursor));
    177                 }
    178             } finally {
    179                 cursor.close();
    180             }
    181         }
    182         return blockedNumbers;
    183     }
    184 
    185     private BackedUpBlockedNumber createBlockedNumberFromCursor(Cursor cursor) {
    186         return new BackedUpBlockedNumber(
    187                 cursor.getInt(0), cursor.getString(1), cursor.getString(2));
    188     }
    189 
    190     private void writeNewState(DataOutputStream dataOutputStream, BackupState state)
    191             throws IOException {
    192         dataOutputStream.writeInt(VERSION);
    193         for (int i : state.ids) {
    194             dataOutputStream.writeInt(i);
    195         }
    196     }
    197 
    198     @Nullable
    199     private BackedUpBlockedNumber readBlockedNumberFromData(BackupDataInput data) {
    200         int id;
    201         try {
    202             id = Integer.parseInt(data.getKey());
    203         } catch (NumberFormatException e) {
    204             Log.e(TAG, "Unexpected key found in restore: " + data.getKey());
    205             return null;
    206         }
    207 
    208         try {
    209             byte[] byteArray = new byte[data.getDataSize()];
    210             data.readEntityData(byteArray, 0, byteArray.length);
    211             DataInputStream dataInput = new DataInputStream(new ByteArrayInputStream(byteArray));
    212             dataInput.readInt(); // Ignore version.
    213             BackedUpBlockedNumber blockedNumber =
    214                     new BackedUpBlockedNumber(id, readString(dataInput), readString(dataInput));
    215             logV("Restoring blocked number: " + blockedNumber);
    216             return blockedNumber;
    217         } catch (IOException e) {
    218             Log.e(TAG, "Error reading blocked number for: " + id + ": " + e.getMessage());
    219             return null;
    220         }
    221     }
    222 
    223     private void writeToProvider(BackedUpBlockedNumber blockedNumber) {
    224         ContentValues contentValues = new ContentValues();
    225         contentValues.put(BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER,
    226                 blockedNumber.originalNumber);
    227         contentValues.put(BlockedNumberContract.BlockedNumbers.COLUMN_E164_NUMBER,
    228                 blockedNumber.e164Number);
    229         try {
    230             getContentResolver().insert(
    231                     BlockedNumberContract.BlockedNumbers.CONTENT_URI, contentValues);
    232         } catch (Exception e) {
    233             Log.e(TAG, "Unable to insert blocked number " + blockedNumber + " :" + e.getMessage());
    234         }
    235     }
    236 
    237     private static boolean isDebug() {
    238         return Log.isLoggable(TAG, Log.DEBUG);
    239     }
    240 
    241     private static void logV(String msg) {
    242         if (DEBUG) {
    243             Log.v(TAG, msg);
    244         }
    245     }
    246 
    247     private static class BackupState {
    248         final int version;
    249         final SortedSet<Integer> ids;
    250 
    251         BackupState(int version, SortedSet<Integer> ids) {
    252             this.version = version;
    253             this.ids = ids;
    254         }
    255     }
    256 
    257     private static class BackedUpBlockedNumber {
    258         final int id;
    259         final String originalNumber;
    260         final String e164Number;
    261 
    262         BackedUpBlockedNumber(int id, String originalNumber, String e164Number) {
    263             this.id = id;
    264             this.originalNumber = originalNumber;
    265             this.e164Number = e164Number;
    266         }
    267 
    268         @Override
    269         public String toString() {
    270             if (isDebug()) {
    271                 return String.format("[%d, original number: %s, e164 number: %s]",
    272                         id, originalNumber, e164Number);
    273             } else {
    274                 return String.format("[%d]", id);
    275             }
    276         }
    277     }
    278 }
    279