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