content: package com.android.server.backup;
  
  import static android.os.ParcelFileDescriptor.MODE_CREATE;
  import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
  import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
  import static android.os.ParcelFileDescriptor.MODE_TRUNCATE;
  
  import static com.android.server.backup.UserBackupManagerService.BACKUP_MANIFEST_FILENAME;
  import static com.android.server.backup.UserBackupManagerService.OP_TYPE_BACKUP_WAIT;
  
  import android.app.ApplicationThreadConstants;
  import android.app.IBackupAgent;
  import android.app.backup.FullBackup;
  import android.app.backup.FullBackupDataOutput;
  import android.app.backup.IBackupCallback;
  import android.content.pm.ApplicationInfo;
  import android.content.pm.PackageInfo;
  import android.content.pm.PackageManager;
  import android.os.ParcelFileDescriptor;
  import android.os.RemoteException;
  import android.os.SELinux;
  import android.util.Slog;
  
  import com.android.internal.util.Preconditions;
  import com.android.server.backup.fullbackup.AppMetadataBackupWriter;
  import com.android.server.backup.remote.ServiceBackupCallback;
  import com.android.server.backup.utils.FullBackupUtils;
  
  import libcore.io.IoUtils;
  
  import java.io.File;
  import java.io.FileNotFoundException;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.io.OutputStream;
  
  /**
   * Used by BackupManagerService to perform adb backup for key-value packages. At the moment this
   * class resembles what is done in the standard key-value code paths in BackupManagerService, and
   * should be unified later.
   *
   * TODO: We should create unified backup/restore engines that can be used for both transport and
   * adb backup/restore, and for fullbackup and key-value backup.
   */
  public class KeyValueAdbBackupEngine {
      private static final String TAG = "KeyValueAdbBackupEngine";
      private static final boolean DEBUG = false;
  
      private static final String BACKUP_KEY_VALUE_DIRECTORY_NAME = "key_value_dir";
      private static final String BACKUP_KEY_VALUE_BLANK_STATE_FILENAME = "blank_state";
      private static final String BACKUP_KEY_VALUE_BACKUP_DATA_FILENAME_SUFFIX = ".data";
      private static final String BACKUP_KEY_VALUE_NEW_STATE_FILENAME_SUFFIX = ".new";
  
      private UserBackupManagerService mBackupManagerService;
      private final PackageManager mPackageManager;
      private final OutputStream mOutput;
      private final PackageInfo mCurrentPackage;
      private final File mDataDir;
      private final File mStateDir;
      private final File mBlankStateName;
      private final File mBackupDataName;
      private final File mNewStateName;
      private final File mManifestFile;
      private ParcelFileDescriptor mSavedState;
      private ParcelFileDescriptor mBackupData;
      private ParcelFileDescriptor mNewState;
      private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
  
      public KeyValueAdbBackupEngine(OutputStream output, PackageInfo packageInfo,
              UserBackupManagerService backupManagerService, PackageManager packageManager,
              File baseStateDir, File dataDir) {
          mOutput = output;
          mCurrentPackage = packageInfo;
          mBackupManagerService = backupManagerService;
          mPackageManager = packageManager;
  
          mDataDir = dataDir;
          mStateDir = new File(baseStateDir, BACKUP_KEY_VALUE_DIRECTORY_NAME);
          mStateDir.mkdirs();
  
          String pkg = mCurrentPackage.packageName;
  
          mBlankStateName = new File(mStateDir, BACKUP_KEY_VALUE_BLANK_STATE_FILENAME);
          mBackupDataName = new File(mDataDir,
                  pkg + BACKUP_KEY_VALUE_BACKUP_DATA_FILENAME_SUFFIX);
          mNewStateName = new File(mStateDir,
                  pkg + BACKUP_KEY_VALUE_NEW_STATE_FILENAME_SUFFIX);
  
          mManifestFile = new File(mDataDir, BACKUP_MANIFEST_FILENAME);
          mAgentTimeoutParameters = Preconditions.checkNotNull(
                  backupManagerService.getAgentTimeoutParameters(),
                  "Timeout parameters cannot be null");
      }
  
      public void backupOnePackage() throws IOException {
          ApplicationInfo targetApp = mCurrentPackage.applicationInfo;
  
          try {
              prepareBackupFiles(mCurrentPackage.packageName);
  
              IBackupAgent agent = bindToAgent(targetApp);
  
              if (agent == null) {
                  // We failed binding to the agent, so ignore this package
                  Slog.e(TAG, "Failed binding to BackupAgent for package "
                          + mCurrentPackage.packageName);
                  return;
              }
  
              // We are bound to agent, initiate backup.
              if (!invokeAgentForAdbBackup(mCurrentPackage.packageName, agent)) {
                  // Backup failed, skip package.
                  Slog.e(TAG, "Backup Failed for package " + mCurrentPackage.packageName);
                  return;
              }
  
              // Backup finished successfully. Copy the backup data to the output stream.
              writeBackupData();
          } catch (FileNotFoundException e) {
              Slog.e(TAG, "Failed creating files for package " + mCurrentPackage.packageName
                      + " will ignore package. " + e);
          } finally {
              // We are either done, failed or have timed out, so do cleanup and kill the agent.
              cleanup();
          }
      }
  
      private void  prepareBackupFiles(String packageName) throws FileNotFoundException {
  
          // We pass a blank state to make sure we are getting the complete backup, not just an
          // increment
          mSavedState = ParcelFileDescriptor.open(mBlankStateName,
                  MODE_READ_ONLY | MODE_CREATE);  // Make an empty file if necessary
  
          mBackupData = ParcelFileDescriptor.open(mBackupDataName,
                  MODE_READ_WRITE | MODE_CREATE | MODE_TRUNCATE);
  
          if (!SELinux.restorecon(mBackupDataName)) {
              Slog.e(TAG, "SELinux restorecon failed on " + mBackupDataName);
          }
  
          mNewState = ParcelFileDescriptor.open(mNewStateName,
                  MODE_READ_WRITE | MODE_CREATE | MODE_TRUNCATE);
      }
  
      private IBackupAgent bindToAgent(ApplicationInfo targetApp) {
          try {
              return mBackupManagerService.bindToAgentSynchronous(targetApp,
                      ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL);
          } catch (SecurityException e) {
              Slog.e(TAG, "error in binding to agent for package " + targetApp.packageName
                      + ". " + e);
              return null;
          }
      }
  
      // Return true on backup success, false otherwise
      private boolean invokeAgentForAdbBackup(String packageName, IBackupAgent agent) {
          int token = mBackupManagerService.generateRandomIntegerToken();
          long kvBackupAgentTimeoutMillis = mAgentTimeoutParameters.getKvBackupAgentTimeoutMillis();
          try {
              mBackupManagerService.prepareOperationTimeout(token, kvBackupAgentTimeoutMillis, null,
                      OP_TYPE_BACKUP_WAIT);
  
              IBackupCallback callback =
                      new ServiceBackupCallback(
                              mBackupManagerService.getBackupManagerBinder(), token);
              // Start backup and wait for BackupManagerService to get callback for success or timeout
              agent.doBackup(
                      mSavedState,
                      mBackupData,
                      mNewState,
                      /* quotaBytes */ Long.MAX_VALUE,
                      callback,
                      /* transportFlags */ 0);
              if (!mBackupManagerService.waitUntilOperationComplete(token)) {
                  Slog.e(TAG, "Key-value backup failed on package " + packageName);
                  return false;
              }
              if (DEBUG) {
                  Slog.i(TAG, "Key-value backup success for package " + packageName);
              }
              return true;
          } catch (RemoteException e) {
              Slog.e(TAG, "Error invoking agent for backup on " + packageName + ". " + e);
              return false;
          }
      }
  
      class KeyValueAdbBackupDataCopier implements Runnable {
          private final PackageInfo mPackage;
          private final ParcelFileDescriptor mPipe;
          private final int mToken;
  
          KeyValueAdbBackupDataCopier(PackageInfo pack, ParcelFileDescriptor pipe,
                  int token)
                  throws IOException {
              mPackage = pack;
              mPipe = ParcelFileDescriptor.dup(pipe.getFileDescriptor());
              mToken = token;
          }
  
          @Override
          public void run() {
              try {
                  FullBackupDataOutput output = new FullBackupDataOutput(mPipe);
                  AppMetadataBackupWriter writer =
                          new AppMetadataBackupWriter(output, mPackageManager);
  
                  if (DEBUG) {
                      Slog.d(TAG, "Writing manifest for " + mPackage.packageName);
                  }
  
                  writer.backupManifest(
                          mPackage,
                          mManifestFile,
                          mDataDir,
                          FullBackup.KEY_VALUE_DATA_TOKEN,
                          /* linkDomain */ null,
                          /* withApk */ false);
                  mManifestFile.delete();
  
                  if (DEBUG) {
                      Slog.d(TAG, "Writing key-value package payload" + mPackage.packageName);
                  }
                  FullBackup.backupToTar(mPackage.packageName, FullBackup.KEY_VALUE_DATA_TOKEN, null,
                          mDataDir.getAbsolutePath(),
                          mBackupDataName.getAbsolutePath(),
                          output);
  
                  // Write EOD marker
                  try {
                      FileOutputStream out = new FileOutputStream(mPipe.getFileDescriptor());
                      byte[] buf = new byte[4];
                      out.write(buf);
                  } catch (IOException e) {
                      Slog.e(TAG, "Unable to finalize backup stream!");
                  }
  
                  try {
                      mBackupManagerService.getBackupManagerBinder().opComplete(mToken, 0);
                  } catch (RemoteException e) {
                      // we'll time out anyway, so we're safe
                  }
  
              } catch (IOException e) {
                  Slog.e(TAG, "Error running full backup for " + mPackage.packageName + ". " + e);
              } finally {
                  IoUtils.closeQuietly(mPipe);
              }
          }
      }
  
      private void writeBackupData() throws IOException {
          int token = mBackupManagerService.generateRandomIntegerToken();
          long kvBackupAgentTimeoutMillis = mAgentTimeoutParameters.getKvBackupAgentTimeoutMillis();
  
          ParcelFileDescriptor[] pipes = null;
          try {
              pipes = ParcelFileDescriptor.createPipe();
  
              mBackupManagerService.prepareOperationTimeout(token, kvBackupAgentTimeoutMillis, null,
                      OP_TYPE_BACKUP_WAIT);
  
              // We will have to create a runnable that will read the manifest and backup data we
              // created, such that we can pipe the data into mOutput. The reason we do this is that
              // internally FullBackup.backupToTar is used, which will create the necessary file
              // header, but will also chunk the data. The method routeSocketDataToOutput in
              // BackupManagerService will dechunk the data, and append it to the TAR outputstream.
              KeyValueAdbBackupDataCopier runner = new KeyValueAdbBackupDataCopier(mCurrentPackage, pipes[1],
                      token);
              pipes[1].close();   // the runner has dup'd it
              pipes[1] = null;
              Thread t = new Thread(runner, "key-value-app-data-runner");
              t.start();
  
              // Now pull data from the app and stuff it into the output
              FullBackupUtils.routeSocketDataToOutput(pipes[0], mOutput);
  
              if (!mBackupManagerService.waitUntilOperationComplete(token)) {
                  Slog.e(TAG, "Full backup failed on package " + mCurrentPackage.packageName);
              } else {
                  if (DEBUG) {
                      Slog.d(TAG, "Full package backup success: " + mCurrentPackage.packageName);
                  }
              }
          } catch (IOException e) {
              Slog.e(TAG, "Error backing up " + mCurrentPackage.packageName + ": " + e);
          } finally {
              // flush after every package
              mOutput.flush();
              if (pipes != null) {
                  IoUtils.closeQuietly(pipes[0]);
                  IoUtils.closeQuietly(pipes[1]);
              }
          }
      }
  
      private void cleanup() {
          mBackupManagerService.tearDownAgentAndKill(mCurrentPackage.applicationInfo);
          mBlankStateName.delete();
          mNewStateName.delete();
          mBackupDataName.delete();
      }
  }
  