Home | History | Annotate | Download | only in backup
      1 /*
      2  * Copyright (C) 2009 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.internal.backup;
     18 
     19 import android.app.backup.BackupDataInput;
     20 import android.app.backup.BackupDataOutput;
     21 import android.app.backup.RestoreSet;
     22 import android.content.Context;
     23 import android.content.pm.PackageInfo;
     24 import android.content.pm.PackageManager;
     25 import android.content.pm.PackageManager.NameNotFoundException;
     26 import android.os.Environment;
     27 import android.os.ParcelFileDescriptor;
     28 import android.os.RemoteException;
     29 import android.util.Log;
     30 
     31 import org.bouncycastle.util.encoders.Base64;
     32 
     33 import java.io.File;
     34 import java.io.FileFilter;
     35 import java.io.FileInputStream;
     36 import java.io.FileOutputStream;
     37 import java.io.IOException;
     38 import java.util.ArrayList;
     39 
     40 /**
     41  * Backup transport for stashing stuff into a known location on disk, and
     42  * later restoring from there.  For testing only.
     43  */
     44 
     45 public class LocalTransport extends IBackupTransport.Stub {
     46     private static final String TAG = "LocalTransport";
     47     private static final boolean DEBUG = true;
     48 
     49     private static final String TRANSPORT_DIR_NAME
     50             = "com.android.internal.backup.LocalTransport";
     51 
     52     // The single hardcoded restore set always has the same (nonzero!) token
     53     private static final long RESTORE_TOKEN = 1;
     54 
     55     private Context mContext;
     56     private PackageManager mPackageManager;
     57     private File mDataDir = new File(Environment.getDownloadCacheDirectory(), "backup");
     58     private PackageInfo[] mRestorePackages = null;
     59     private int mRestorePackage = -1;  // Index into mRestorePackages
     60 
     61 
     62     public LocalTransport(Context context) {
     63         mContext = context;
     64         mPackageManager = context.getPackageManager();
     65     }
     66 
     67 
     68     public String transportDirName() {
     69         return TRANSPORT_DIR_NAME;
     70     }
     71 
     72     public long requestBackupTime() {
     73         // any time is a good time for local backup
     74         return 0;
     75     }
     76 
     77     public int initializeDevice() {
     78         if (DEBUG) Log.v(TAG, "wiping all data");
     79         deleteContents(mDataDir);
     80         return BackupConstants.TRANSPORT_OK;
     81     }
     82 
     83     public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data) {
     84         if (DEBUG) Log.v(TAG, "performBackup() pkg=" + packageInfo.packageName);
     85 
     86         File packageDir = new File(mDataDir, packageInfo.packageName);
     87         packageDir.mkdirs();
     88 
     89         // Each 'record' in the restore set is kept in its own file, named by
     90         // the record key.  Wind through the data file, extracting individual
     91         // record operations and building a set of all the updates to apply
     92         // in this update.
     93         BackupDataInput changeSet = new BackupDataInput(data.getFileDescriptor());
     94         try {
     95             int bufSize = 512;
     96             byte[] buf = new byte[bufSize];
     97             while (changeSet.readNextHeader()) {
     98                 String key = changeSet.getKey();
     99                 String base64Key = new String(Base64.encode(key.getBytes()));
    100                 File entityFile = new File(packageDir, base64Key);
    101 
    102                 int dataSize = changeSet.getDataSize();
    103 
    104                 if (DEBUG) Log.v(TAG, "Got change set key=" + key + " size=" + dataSize
    105                         + " key64=" + base64Key);
    106 
    107                 if (dataSize >= 0) {
    108                     if (entityFile.exists()) {
    109                         entityFile.delete();
    110                     }
    111                     FileOutputStream entity = new FileOutputStream(entityFile);
    112 
    113                     if (dataSize > bufSize) {
    114                         bufSize = dataSize;
    115                         buf = new byte[bufSize];
    116                     }
    117                     changeSet.readEntityData(buf, 0, dataSize);
    118                     if (DEBUG) Log.v(TAG, "  data size " + dataSize);
    119 
    120                     try {
    121                         entity.write(buf, 0, dataSize);
    122                     } catch (IOException e) {
    123                         Log.e(TAG, "Unable to update key file " + entityFile.getAbsolutePath());
    124                         return BackupConstants.TRANSPORT_ERROR;
    125                     } finally {
    126                         entity.close();
    127                     }
    128                 } else {
    129                     entityFile.delete();
    130                 }
    131             }
    132             return BackupConstants.TRANSPORT_OK;
    133         } catch (IOException e) {
    134             // oops, something went wrong.  abort the operation and return error.
    135             Log.v(TAG, "Exception reading backup input:", e);
    136             return BackupConstants.TRANSPORT_ERROR;
    137         }
    138     }
    139 
    140     // Deletes the contents but not the given directory
    141     private void deleteContents(File dirname) {
    142         File[] contents = dirname.listFiles();
    143         if (contents != null) {
    144             for (File f : contents) {
    145                 if (f.isDirectory()) {
    146                     // delete the directory's contents then fall through
    147                     // and delete the directory itself.
    148                     deleteContents(f);
    149                 }
    150                 f.delete();
    151             }
    152         }
    153     }
    154 
    155     public int clearBackupData(PackageInfo packageInfo) {
    156         if (DEBUG) Log.v(TAG, "clearBackupData() pkg=" + packageInfo.packageName);
    157 
    158         File packageDir = new File(mDataDir, packageInfo.packageName);
    159         for (File f : packageDir.listFiles()) {
    160             f.delete();
    161         }
    162         packageDir.delete();
    163         return BackupConstants.TRANSPORT_OK;
    164     }
    165 
    166     public int finishBackup() {
    167         if (DEBUG) Log.v(TAG, "finishBackup()");
    168         return BackupConstants.TRANSPORT_OK;
    169     }
    170 
    171     // Restore handling
    172     public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException {
    173         // one hardcoded restore set
    174         RestoreSet set = new RestoreSet("Local disk image", "flash", RESTORE_TOKEN);
    175         RestoreSet[] array = { set };
    176         return array;
    177     }
    178 
    179     public long getCurrentRestoreSet() {
    180         // The hardcoded restore set always has the same token
    181         return RESTORE_TOKEN;
    182     }
    183 
    184     public int startRestore(long token, PackageInfo[] packages) {
    185         if (DEBUG) Log.v(TAG, "start restore " + token);
    186         mRestorePackages = packages;
    187         mRestorePackage = -1;
    188         return BackupConstants.TRANSPORT_OK;
    189     }
    190 
    191     public String nextRestorePackage() {
    192         if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
    193         while (++mRestorePackage < mRestorePackages.length) {
    194             String name = mRestorePackages[mRestorePackage].packageName;
    195             if (new File(mDataDir, name).isDirectory()) {
    196                 if (DEBUG) Log.v(TAG, "  nextRestorePackage() = " + name);
    197                 return name;
    198             }
    199         }
    200 
    201         if (DEBUG) Log.v(TAG, "  no more packages to restore");
    202         return "";
    203     }
    204 
    205     public int getRestoreData(ParcelFileDescriptor outFd) {
    206         if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
    207         if (mRestorePackage < 0) throw new IllegalStateException("nextRestorePackage not called");
    208         File packageDir = new File(mDataDir, mRestorePackages[mRestorePackage].packageName);
    209 
    210         // The restore set is the concatenation of the individual record blobs,
    211         // each of which is a file in the package's directory
    212         File[] blobs = packageDir.listFiles();
    213         if (blobs == null) {  // nextRestorePackage() ensures the dir exists, so this is an error
    214             Log.e(TAG, "Error listing directory: " + packageDir);
    215             return BackupConstants.TRANSPORT_ERROR;
    216         }
    217 
    218         // We expect at least some data if the directory exists in the first place
    219         if (DEBUG) Log.v(TAG, "  getRestoreData() found " + blobs.length + " key files");
    220         BackupDataOutput out = new BackupDataOutput(outFd.getFileDescriptor());
    221         try {
    222             for (File f : blobs) {
    223                 FileInputStream in = new FileInputStream(f);
    224                 try {
    225                     int size = (int) f.length();
    226                     byte[] buf = new byte[size];
    227                     in.read(buf);
    228                     String key = new String(Base64.decode(f.getName()));
    229                     if (DEBUG) Log.v(TAG, "    ... key=" + key + " size=" + size);
    230                     out.writeEntityHeader(key, size);
    231                     out.writeEntityData(buf, size);
    232                 } finally {
    233                     in.close();
    234                 }
    235             }
    236             return BackupConstants.TRANSPORT_OK;
    237         } catch (IOException e) {
    238             Log.e(TAG, "Unable to read backup records", e);
    239             return BackupConstants.TRANSPORT_ERROR;
    240         }
    241     }
    242 
    243     public void finishRestore() {
    244         if (DEBUG) Log.v(TAG, "finishRestore()");
    245     }
    246 }
    247