• 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.BackupTransport;
22 import android.app.backup.RestoreDescription;
23 import android.app.backup.RestoreSet;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.pm.PackageInfo;
28 import android.os.Environment;
29 import android.os.ParcelFileDescriptor;
30 import android.system.ErrnoException;
31 import android.system.Os;
32 import android.system.StructStat;
33 import android.util.Log;
34 
35 import com.android.org.bouncycastle.util.encoders.Base64;
36 
37 import libcore.io.IoUtils;
38 
39 import java.io.BufferedOutputStream;
40 import java.io.File;
41 import java.io.FileInputStream;
42 import java.io.FileNotFoundException;
43 import java.io.FileOutputStream;
44 import java.io.IOException;
45 import java.util.ArrayList;
46 import java.util.Collections;
47 import static android.system.OsConstants.SEEK_CUR;
48 
49 /**
50  * Backup transport for stashing stuff into a known location on disk, and
51  * later restoring from there.  For testing only.
52  */
53 
54 public class LocalTransport extends BackupTransport {
55     private static final String TAG = "LocalTransport";
56     private static final boolean DEBUG = false;
57 
58     private static final String TRANSPORT_DIR_NAME
59             = "com.android.internal.backup.LocalTransport";
60 
61     private static final String TRANSPORT_DESTINATION_STRING
62             = "Backing up to debug-only private cache";
63 
64     private static final String TRANSPORT_DATA_MANAGEMENT_LABEL
65             = "";
66 
67     private static final String INCREMENTAL_DIR = "_delta";
68     private static final String FULL_DATA_DIR = "_full";
69 
70     // The currently-active restore set always has the same (nonzero!) token
71     private static final long CURRENT_SET_TOKEN = 1;
72 
73     // Full backup size quota is set to reasonable value.
74     private static final long FULL_BACKUP_SIZE_QUOTA = 25 * 1024 * 1024;
75 
76     private Context mContext;
77     private File mDataDir = new File(Environment.getDownloadCacheDirectory(), "backup");
78     private File mCurrentSetDir = new File(mDataDir, Long.toString(CURRENT_SET_TOKEN));
79     private File mCurrentSetIncrementalDir = new File(mCurrentSetDir, INCREMENTAL_DIR);
80     private File mCurrentSetFullDir = new File(mCurrentSetDir, FULL_DATA_DIR);
81 
82     private PackageInfo[] mRestorePackages = null;
83     private int mRestorePackage = -1;  // Index into mRestorePackages
84     private int mRestoreType;
85     private File mRestoreSetDir;
86     private File mRestoreSetIncrementalDir;
87     private File mRestoreSetFullDir;
88 
89     // Additional bookkeeping for full backup
90     private String mFullTargetPackage;
91     private ParcelFileDescriptor mSocket;
92     private FileInputStream mSocketInputStream;
93     private BufferedOutputStream mFullBackupOutputStream;
94     private byte[] mFullBackupBuffer;
95     private long mFullBackupSize;
96 
97     private FileInputStream mCurFullRestoreStream;
98     private FileOutputStream mFullRestoreSocketStream;
99     private byte[] mFullRestoreBuffer;
100 
makeDataDirs()101     private void makeDataDirs() {
102         mCurrentSetDir.mkdirs();
103         mCurrentSetFullDir.mkdir();
104         mCurrentSetIncrementalDir.mkdir();
105     }
106 
LocalTransport(Context context)107     public LocalTransport(Context context) {
108         mContext = context;
109         makeDataDirs();
110     }
111 
112     @Override
name()113     public String name() {
114         return new ComponentName(mContext, this.getClass()).flattenToShortString();
115     }
116 
117     @Override
configurationIntent()118     public Intent configurationIntent() {
119         // The local transport is not user-configurable
120         return null;
121     }
122 
123     @Override
currentDestinationString()124     public String currentDestinationString() {
125         return TRANSPORT_DESTINATION_STRING;
126     }
127 
dataManagementIntent()128     public Intent dataManagementIntent() {
129         // The local transport does not present a data-management UI
130         // TODO: consider adding simple UI to wipe the archives entirely,
131         // for cleaning up the cache partition.
132         return null;
133     }
134 
dataManagementLabel()135     public String dataManagementLabel() {
136         return TRANSPORT_DATA_MANAGEMENT_LABEL;
137     }
138 
139     @Override
transportDirName()140     public String transportDirName() {
141         return TRANSPORT_DIR_NAME;
142     }
143 
144     @Override
requestBackupTime()145     public long requestBackupTime() {
146         // any time is a good time for local backup
147         return 0;
148     }
149 
150     @Override
initializeDevice()151     public int initializeDevice() {
152         if (DEBUG) Log.v(TAG, "wiping all data");
153         deleteContents(mCurrentSetDir);
154         makeDataDirs();
155         return TRANSPORT_OK;
156     }
157 
158     @Override
performBackup(PackageInfo packageInfo, ParcelFileDescriptor data)159     public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data) {
160         if (DEBUG) {
161             try {
162             StructStat ss = Os.fstat(data.getFileDescriptor());
163             Log.v(TAG, "performBackup() pkg=" + packageInfo.packageName
164                     + " size=" + ss.st_size);
165             } catch (ErrnoException e) {
166                 Log.w(TAG, "Unable to stat input file in performBackup() on "
167                         + packageInfo.packageName);
168             }
169         }
170 
171         File packageDir = new File(mCurrentSetIncrementalDir, packageInfo.packageName);
172         packageDir.mkdirs();
173 
174         // Each 'record' in the restore set is kept in its own file, named by
175         // the record key.  Wind through the data file, extracting individual
176         // record operations and building a set of all the updates to apply
177         // in this update.
178         BackupDataInput changeSet = new BackupDataInput(data.getFileDescriptor());
179         try {
180             int bufSize = 512;
181             byte[] buf = new byte[bufSize];
182             while (changeSet.readNextHeader()) {
183                 String key = changeSet.getKey();
184                 String base64Key = new String(Base64.encode(key.getBytes()));
185                 File entityFile = new File(packageDir, base64Key);
186 
187                 int dataSize = changeSet.getDataSize();
188 
189                 if (DEBUG) Log.v(TAG, "Got change set key=" + key + " size=" + dataSize
190                         + " key64=" + base64Key);
191 
192                 if (dataSize >= 0) {
193                     if (entityFile.exists()) {
194                         entityFile.delete();
195                     }
196                     FileOutputStream entity = new FileOutputStream(entityFile);
197 
198                     if (dataSize > bufSize) {
199                         bufSize = dataSize;
200                         buf = new byte[bufSize];
201                     }
202                     changeSet.readEntityData(buf, 0, dataSize);
203                     if (DEBUG) {
204                         try {
205                             long cur = Os.lseek(data.getFileDescriptor(), 0, SEEK_CUR);
206                             Log.v(TAG, "  read entity data; new pos=" + cur);
207                         }
208                         catch (ErrnoException e) {
209                             Log.w(TAG, "Unable to stat input file in performBackup() on "
210                                     + packageInfo.packageName);
211                         }
212                     }
213 
214                     try {
215                         entity.write(buf, 0, dataSize);
216                     } catch (IOException e) {
217                         Log.e(TAG, "Unable to update key file " + entityFile.getAbsolutePath());
218                         return TRANSPORT_ERROR;
219                     } finally {
220                         entity.close();
221                     }
222                 } else {
223                     entityFile.delete();
224                 }
225             }
226             return TRANSPORT_OK;
227         } catch (IOException e) {
228             // oops, something went wrong.  abort the operation and return error.
229             Log.v(TAG, "Exception reading backup input:", e);
230             return TRANSPORT_ERROR;
231         }
232     }
233 
234     // Deletes the contents but not the given directory
deleteContents(File dirname)235     private void deleteContents(File dirname) {
236         File[] contents = dirname.listFiles();
237         if (contents != null) {
238             for (File f : contents) {
239                 if (f.isDirectory()) {
240                     // delete the directory's contents then fall through
241                     // and delete the directory itself.
242                     deleteContents(f);
243                 }
244                 f.delete();
245             }
246         }
247     }
248 
249     @Override
clearBackupData(PackageInfo packageInfo)250     public int clearBackupData(PackageInfo packageInfo) {
251         if (DEBUG) Log.v(TAG, "clearBackupData() pkg=" + packageInfo.packageName);
252 
253         File packageDir = new File(mCurrentSetIncrementalDir, packageInfo.packageName);
254         final File[] fileset = packageDir.listFiles();
255         if (fileset != null) {
256             for (File f : fileset) {
257                 f.delete();
258             }
259             packageDir.delete();
260         }
261 
262         packageDir = new File(mCurrentSetFullDir, packageInfo.packageName);
263         final File[] tarballs = packageDir.listFiles();
264         if (tarballs != null) {
265             for (File f : tarballs) {
266                 f.delete();
267             }
268             packageDir.delete();
269         }
270 
271         return TRANSPORT_OK;
272     }
273 
274     @Override
finishBackup()275     public int finishBackup() {
276         if (DEBUG) Log.v(TAG, "finishBackup() of " + mFullTargetPackage);
277         return tearDownFullBackup();
278     }
279 
280     // ------------------------------------------------------------------------------------
281     // Full backup handling
282 
tearDownFullBackup()283     private int tearDownFullBackup() {
284         if (mSocket != null) {
285             try {
286                 if (mFullBackupOutputStream != null) {
287                     mFullBackupOutputStream.flush();
288                     mFullBackupOutputStream.close();
289                 }
290                 mSocketInputStream = null;
291                 mFullTargetPackage = null;
292                 mSocket.close();
293             } catch (IOException e) {
294                 if (DEBUG) {
295                     Log.w(TAG, "Exception caught in tearDownFullBackup()", e);
296                 }
297                 return TRANSPORT_ERROR;
298             } finally {
299                 mSocket = null;
300                 mFullBackupOutputStream = null;
301             }
302         }
303         return TRANSPORT_OK;
304     }
305 
tarballFile(String pkgName)306     private File tarballFile(String pkgName) {
307         return new File(mCurrentSetFullDir, pkgName);
308     }
309 
310     @Override
requestFullBackupTime()311     public long requestFullBackupTime() {
312         return 0;
313     }
314 
315     @Override
checkFullBackupSize(long size)316     public int checkFullBackupSize(long size) {
317         int result = TRANSPORT_OK;
318         // Decline zero-size "backups"
319         if (size <= 0) {
320             result = TRANSPORT_PACKAGE_REJECTED;
321         } else if (size > FULL_BACKUP_SIZE_QUOTA) {
322             result = TRANSPORT_QUOTA_EXCEEDED;
323         }
324         if (result != TRANSPORT_OK) {
325             if (DEBUG) {
326                 Log.v(TAG, "Declining backup of size " + size);
327             }
328         }
329         return result;
330     }
331 
332     @Override
performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket)333     public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket) {
334         if (mSocket != null) {
335             Log.e(TAG, "Attempt to initiate full backup while one is in progress");
336             return TRANSPORT_ERROR;
337         }
338 
339         if (DEBUG) {
340             Log.i(TAG, "performFullBackup : " + targetPackage);
341         }
342 
343         // We know a priori that we run in the system process, so we need to make
344         // sure to dup() our own copy of the socket fd.  Transports which run in
345         // their own processes must not do this.
346         try {
347             mFullBackupSize = 0;
348             mSocket = ParcelFileDescriptor.dup(socket.getFileDescriptor());
349             mSocketInputStream = new FileInputStream(mSocket.getFileDescriptor());
350         } catch (IOException e) {
351             Log.e(TAG, "Unable to process socket for full backup");
352             return TRANSPORT_ERROR;
353         }
354 
355         mFullTargetPackage = targetPackage.packageName;
356         mFullBackupBuffer = new byte[4096];
357 
358         return TRANSPORT_OK;
359     }
360 
361     @Override
sendBackupData(final int numBytes)362     public int sendBackupData(final int numBytes) {
363         if (mSocket == null) {
364             Log.w(TAG, "Attempted sendBackupData before performFullBackup");
365             return TRANSPORT_ERROR;
366         }
367 
368         mFullBackupSize += numBytes;
369         if (mFullBackupSize > FULL_BACKUP_SIZE_QUOTA) {
370             return TRANSPORT_QUOTA_EXCEEDED;
371         }
372 
373         if (numBytes > mFullBackupBuffer.length) {
374             mFullBackupBuffer = new byte[numBytes];
375         }
376 
377         if (mFullBackupOutputStream == null) {
378             FileOutputStream tarstream;
379             try {
380                 File tarball = tarballFile(mFullTargetPackage);
381                 tarstream = new FileOutputStream(tarball);
382             } catch (FileNotFoundException e) {
383                 return TRANSPORT_ERROR;
384             }
385             mFullBackupOutputStream = new BufferedOutputStream(tarstream);
386         }
387 
388         int bytesLeft = numBytes;
389         while (bytesLeft > 0) {
390             try {
391             int nRead = mSocketInputStream.read(mFullBackupBuffer, 0, bytesLeft);
392             if (nRead < 0) {
393                 // Something went wrong if we expect data but saw EOD
394                 Log.w(TAG, "Unexpected EOD; failing backup");
395                 return TRANSPORT_ERROR;
396             }
397             mFullBackupOutputStream.write(mFullBackupBuffer, 0, nRead);
398             bytesLeft -= nRead;
399             } catch (IOException e) {
400                 Log.e(TAG, "Error handling backup data for " + mFullTargetPackage);
401                 return TRANSPORT_ERROR;
402             }
403         }
404         if (DEBUG) {
405             Log.v(TAG, "   stored " + numBytes + " of data");
406         }
407         return TRANSPORT_OK;
408     }
409 
410     // For now we can't roll back, so just tear everything down.
411     @Override
cancelFullBackup()412     public void cancelFullBackup() {
413         if (DEBUG) {
414             Log.i(TAG, "Canceling full backup of " + mFullTargetPackage);
415         }
416         File archive = tarballFile(mFullTargetPackage);
417         tearDownFullBackup();
418         if (archive.exists()) {
419             archive.delete();
420         }
421     }
422 
423     // ------------------------------------------------------------------------------------
424     // Restore handling
425     static final long[] POSSIBLE_SETS = { 2, 3, 4, 5, 6, 7, 8, 9 };
426 
427     @Override
getAvailableRestoreSets()428     public RestoreSet[] getAvailableRestoreSets() {
429         long[] existing = new long[POSSIBLE_SETS.length + 1];
430         int num = 0;
431 
432         // see which possible non-current sets exist...
433         for (long token : POSSIBLE_SETS) {
434             if ((new File(mDataDir, Long.toString(token))).exists()) {
435                 existing[num++] = token;
436             }
437         }
438         // ...and always the currently-active set last
439         existing[num++] = CURRENT_SET_TOKEN;
440 
441         RestoreSet[] available = new RestoreSet[num];
442         for (int i = 0; i < available.length; i++) {
443             available[i] = new RestoreSet("Local disk image", "flash", existing[i]);
444         }
445         return available;
446     }
447 
448     @Override
getCurrentRestoreSet()449     public long getCurrentRestoreSet() {
450         // The current restore set always has the same token
451         return CURRENT_SET_TOKEN;
452     }
453 
454     @Override
startRestore(long token, PackageInfo[] packages)455     public int startRestore(long token, PackageInfo[] packages) {
456         if (DEBUG) Log.v(TAG, "start restore " + token + " : " + packages.length
457                 + " matching packages");
458         mRestorePackages = packages;
459         mRestorePackage = -1;
460         mRestoreSetDir = new File(mDataDir, Long.toString(token));
461         mRestoreSetIncrementalDir = new File(mRestoreSetDir, INCREMENTAL_DIR);
462         mRestoreSetFullDir = new File(mRestoreSetDir, FULL_DATA_DIR);
463         return TRANSPORT_OK;
464     }
465 
466     @Override
nextRestorePackage()467     public RestoreDescription nextRestorePackage() {
468         if (DEBUG) {
469             Log.v(TAG, "nextRestorePackage() : mRestorePackage=" + mRestorePackage
470                     + " length=" + mRestorePackages.length);
471         }
472         if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
473 
474         boolean found = false;
475         while (++mRestorePackage < mRestorePackages.length) {
476             String name = mRestorePackages[mRestorePackage].packageName;
477 
478             // If we have key/value data for this package, deliver that
479             // skip packages where we have a data dir but no actual contents
480             String[] contents = (new File(mRestoreSetIncrementalDir, name)).list();
481             if (contents != null && contents.length > 0) {
482                 if (DEBUG) {
483                     Log.v(TAG, "  nextRestorePackage(TYPE_KEY_VALUE) @ "
484                         + mRestorePackage + " = " + name);
485                 }
486                 mRestoreType = RestoreDescription.TYPE_KEY_VALUE;
487                 found = true;
488             }
489 
490             if (!found) {
491                 // No key/value data; check for [non-empty] full data
492                 File maybeFullData = new File(mRestoreSetFullDir, name);
493                 if (maybeFullData.length() > 0) {
494                     if (DEBUG) {
495                         Log.v(TAG, "  nextRestorePackage(TYPE_FULL_STREAM) @ "
496                                 + mRestorePackage + " = " + name);
497                     }
498                     mRestoreType = RestoreDescription.TYPE_FULL_STREAM;
499                     mCurFullRestoreStream = null;   // ensure starting from the ground state
500                     found = true;
501                 }
502             }
503 
504             if (found) {
505                 return new RestoreDescription(name, mRestoreType);
506             }
507 
508             if (DEBUG) {
509                 Log.v(TAG, "  ... package @ " + mRestorePackage + " = " + name
510                         + " has no data; skipping");
511             }
512         }
513 
514         if (DEBUG) Log.v(TAG, "  no more packages to restore");
515         return RestoreDescription.NO_MORE_PACKAGES;
516     }
517 
518     @Override
getRestoreData(ParcelFileDescriptor outFd)519     public int getRestoreData(ParcelFileDescriptor outFd) {
520         if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
521         if (mRestorePackage < 0) throw new IllegalStateException("nextRestorePackage not called");
522         if (mRestoreType != RestoreDescription.TYPE_KEY_VALUE) {
523             throw new IllegalStateException("getRestoreData(fd) for non-key/value dataset");
524         }
525         File packageDir = new File(mRestoreSetIncrementalDir,
526                 mRestorePackages[mRestorePackage].packageName);
527 
528         // The restore set is the concatenation of the individual record blobs,
529         // each of which is a file in the package's directory.  We return the
530         // data in lexical order sorted by key, so that apps which use synthetic
531         // keys like BLOB_1, BLOB_2, etc will see the date in the most obvious
532         // order.
533         ArrayList<DecodedFilename> blobs = contentsByKey(packageDir);
534         if (blobs == null) {  // nextRestorePackage() ensures the dir exists, so this is an error
535             Log.e(TAG, "No keys for package: " + packageDir);
536             return TRANSPORT_ERROR;
537         }
538 
539         // We expect at least some data if the directory exists in the first place
540         if (DEBUG) Log.v(TAG, "  getRestoreData() found " + blobs.size() + " key files");
541         BackupDataOutput out = new BackupDataOutput(outFd.getFileDescriptor());
542         try {
543             for (DecodedFilename keyEntry : blobs) {
544                 File f = keyEntry.file;
545                 FileInputStream in = new FileInputStream(f);
546                 try {
547                     int size = (int) f.length();
548                     byte[] buf = new byte[size];
549                     in.read(buf);
550                     if (DEBUG) Log.v(TAG, "    ... key=" + keyEntry.key + " size=" + size);
551                     out.writeEntityHeader(keyEntry.key, size);
552                     out.writeEntityData(buf, size);
553                 } finally {
554                     in.close();
555                 }
556             }
557             return TRANSPORT_OK;
558         } catch (IOException e) {
559             Log.e(TAG, "Unable to read backup records", e);
560             return TRANSPORT_ERROR;
561         }
562     }
563 
564     static class DecodedFilename implements Comparable<DecodedFilename> {
565         public File file;
566         public String key;
567 
DecodedFilename(File f)568         public DecodedFilename(File f) {
569             file = f;
570             key = new String(Base64.decode(f.getName()));
571         }
572 
573         @Override
compareTo(DecodedFilename other)574         public int compareTo(DecodedFilename other) {
575             // sorts into ascending lexical order by decoded key
576             return key.compareTo(other.key);
577         }
578     }
579 
580     // Return a list of the files in the given directory, sorted lexically by
581     // the Base64-decoded file name, not by the on-disk filename
contentsByKey(File dir)582     private ArrayList<DecodedFilename> contentsByKey(File dir) {
583         File[] allFiles = dir.listFiles();
584         if (allFiles == null || allFiles.length == 0) {
585             return null;
586         }
587 
588         // Decode the filenames into keys then sort lexically by key
589         ArrayList<DecodedFilename> contents = new ArrayList<DecodedFilename>();
590         for (File f : allFiles) {
591             contents.add(new DecodedFilename(f));
592         }
593         Collections.sort(contents);
594         return contents;
595     }
596 
597     @Override
finishRestore()598     public void finishRestore() {
599         if (DEBUG) Log.v(TAG, "finishRestore()");
600         if (mRestoreType == RestoreDescription.TYPE_FULL_STREAM) {
601             resetFullRestoreState();
602         }
603         mRestoreType = 0;
604     }
605 
606     // ------------------------------------------------------------------------------------
607     // Full restore handling
608 
resetFullRestoreState()609     private void resetFullRestoreState() {
610         IoUtils.closeQuietly(mCurFullRestoreStream);
611         mCurFullRestoreStream = null;
612         mFullRestoreSocketStream = null;
613         mFullRestoreBuffer = null;
614     }
615 
616     /**
617      * Ask the transport to provide data for the "current" package being restored.  The
618      * transport then writes some data to the socket supplied to this call, and returns
619      * the number of bytes written.  The system will then read that many bytes and
620      * stream them to the application's agent for restore, then will call this method again
621      * to receive the next chunk of the archive.  This sequence will be repeated until the
622      * transport returns zero indicating that all of the package's data has been delivered
623      * (or returns a negative value indicating some sort of hard error condition at the
624      * transport level).
625      *
626      * <p>After this method returns zero, the system will then call
627      * {@link #getNextFullRestorePackage()} to begin the restore process for the next
628      * application, and the sequence begins again.
629      *
630      * @param socket The file descriptor that the transport will use for delivering the
631      *    streamed archive.
632      * @return 0 when no more data for the current package is available.  A positive value
633      *    indicates the presence of that much data to be delivered to the app.  A negative
634      *    return value is treated as equivalent to {@link BackupTransport#TRANSPORT_ERROR},
635      *    indicating a fatal error condition that precludes further restore operations
636      *    on the current dataset.
637      */
638     @Override
getNextFullRestoreDataChunk(ParcelFileDescriptor socket)639     public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) {
640         if (mRestoreType != RestoreDescription.TYPE_FULL_STREAM) {
641             throw new IllegalStateException("Asked for full restore data for non-stream package");
642         }
643 
644         // first chunk?
645         if (mCurFullRestoreStream == null) {
646             final String name = mRestorePackages[mRestorePackage].packageName;
647             if (DEBUG) Log.i(TAG, "Starting full restore of " + name);
648             File dataset = new File(mRestoreSetFullDir, name);
649             try {
650                 mCurFullRestoreStream = new FileInputStream(dataset);
651             } catch (IOException e) {
652                 // If we can't open the target package's tarball, we return the single-package
653                 // error code and let the caller go on to the next package.
654                 Log.e(TAG, "Unable to read archive for " + name);
655                 return TRANSPORT_PACKAGE_REJECTED;
656             }
657             mFullRestoreSocketStream = new FileOutputStream(socket.getFileDescriptor());
658             mFullRestoreBuffer = new byte[2*1024];
659         }
660 
661         int nRead;
662         try {
663             nRead = mCurFullRestoreStream.read(mFullRestoreBuffer);
664             if (nRead < 0) {
665                 // EOF: tell the caller we're done
666                 nRead = NO_MORE_DATA;
667             } else if (nRead == 0) {
668                 // This shouldn't happen when reading a FileInputStream; we should always
669                 // get either a positive nonzero byte count or -1.  Log the situation and
670                 // treat it as EOF.
671                 Log.w(TAG, "read() of archive file returned 0; treating as EOF");
672                 nRead = NO_MORE_DATA;
673             } else {
674                 if (DEBUG) {
675                     Log.i(TAG, "   delivering restore chunk: " + nRead);
676                 }
677                 mFullRestoreSocketStream.write(mFullRestoreBuffer, 0, nRead);
678             }
679         } catch (IOException e) {
680             return TRANSPORT_ERROR;  // Hard error accessing the file; shouldn't happen
681         } finally {
682             // Most transports will need to explicitly close 'socket' here, but this transport
683             // is in the same process as the caller so it can leave it up to the backup manager
684             // to manage both socket fds.
685         }
686 
687         return nRead;
688     }
689 
690     /**
691      * If the OS encounters an error while processing {@link RestoreDescription#TYPE_FULL_STREAM}
692      * data for restore, it will invoke this method to tell the transport that it should
693      * abandon the data download for the current package.  The OS will then either call
694      * {@link #nextRestorePackage()} again to move on to restoring the next package in the
695      * set being iterated over, or will call {@link #finishRestore()} to shut down the restore
696      * operation.
697      *
698      * @return {@link #TRANSPORT_OK} if the transport was successful in shutting down the
699      *    current stream cleanly, or {@link #TRANSPORT_ERROR} to indicate a serious
700      *    transport-level failure.  If the transport reports an error here, the entire restore
701      *    operation will immediately be finished with no further attempts to restore app data.
702      */
703     @Override
abortFullRestore()704     public int abortFullRestore() {
705         if (mRestoreType != RestoreDescription.TYPE_FULL_STREAM) {
706             throw new IllegalStateException("abortFullRestore() but not currently restoring");
707         }
708         resetFullRestoreState();
709         mRestoreType = 0;
710         return TRANSPORT_OK;
711     }
712 
713     @Override
getBackupQuota(String packageName, boolean isFullBackup)714     public long getBackupQuota(String packageName, boolean isFullBackup) {
715         return isFullBackup ? FULL_BACKUP_SIZE_QUOTA : Long.MAX_VALUE;
716     }
717 }
718