• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
LocalTransport(Context context)65     public LocalTransport(Context context) {
66         mContext = context;
67     }
68 
configurationIntent()69     public Intent configurationIntent() {
70         // The local transport is not user-configurable
71         return null;
72     }
73 
currentDestinationString()74     public String currentDestinationString() {
75         return TRANSPORT_DESTINATION_STRING;
76     }
77 
transportDirName()78     public String transportDirName() {
79         return TRANSPORT_DIR_NAME;
80     }
81 
requestBackupTime()82     public long requestBackupTime() {
83         // any time is a good time for local backup
84         return 0;
85     }
86 
initializeDevice()87     public int initializeDevice() {
88         if (DEBUG) Log.v(TAG, "wiping all data");
89         deleteContents(mDataDir);
90         return BackupConstants.TRANSPORT_OK;
91     }
92 
performBackup(PackageInfo packageInfo, ParcelFileDescriptor data)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
deleteContents(File dirname)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 
clearBackupData(PackageInfo packageInfo)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 
finishBackup()179     public int finishBackup() {
180         if (DEBUG) Log.v(TAG, "finishBackup()");
181         return BackupConstants.TRANSPORT_OK;
182     }
183 
184     // Restore handling
getAvailableRestoreSets()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 
getCurrentRestoreSet()192     public long getCurrentRestoreSet() {
193         // The hardcoded restore set always has the same token
194         return RESTORE_TOKEN;
195     }
196 
startRestore(long token, PackageInfo[] packages)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 
nextRestorePackage()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 
getRestoreData(ParcelFileDescriptor outFd)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 
finishRestore()256     public void finishRestore() {
257         if (DEBUG) Log.v(TAG, "finishRestore()");
258     }
259 }
260