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