content: /*
   * Copyright (C) 2017 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License
   */
  
  package com.android.server.backup.restore;
  
  import static com.android.server.backup.BackupManagerService.DEBUG;
  import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
  import static com.android.server.backup.BackupManagerService.TAG;
  import static com.android.server.backup.UserBackupManagerService.KEY_WIDGET_STATE;
  import static com.android.server.backup.UserBackupManagerService.OP_TYPE_RESTORE_WAIT;
  import static com.android.server.backup.UserBackupManagerService.PACKAGE_MANAGER_SENTINEL;
  import static com.android.server.backup.UserBackupManagerService.SETTINGS_PACKAGE;
  import static com.android.server.backup.internal.BackupHandler.MSG_BACKUP_RESTORE_STEP;
  import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_OPERATION_TIMEOUT;
  import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_SESSION_TIMEOUT;
  
  import android.annotation.Nullable;
  import android.app.ApplicationThreadConstants;
  import android.app.IBackupAgent;
  import android.app.backup.BackupDataInput;
  import android.app.backup.BackupDataOutput;
  import android.app.backup.BackupManagerMonitor;
  import android.app.backup.BackupTransport;
  import android.app.backup.IBackupManagerMonitor;
  import android.app.backup.IRestoreObserver;
  import android.app.backup.RestoreDescription;
  import android.content.pm.ApplicationInfo;
  import android.content.pm.PackageInfo;
  import android.content.pm.PackageManager;
  import android.content.pm.PackageManager.NameNotFoundException;
  import android.content.pm.PackageManagerInternal;
  import android.os.Bundle;
  import android.os.Message;
  import android.os.ParcelFileDescriptor;
  import android.os.RemoteException;
  import android.os.SystemClock;
  import android.os.UserHandle;
  import android.util.EventLog;
  import android.util.Slog;
  
  import com.android.internal.backup.IBackupTransport;
  import com.android.internal.util.Preconditions;
  import com.android.server.AppWidgetBackupBridge;
  import com.android.server.EventLogTags;
  import com.android.server.LocalServices;
  import com.android.server.backup.BackupAgentTimeoutParameters;
  import com.android.server.backup.BackupRestoreTask;
  import com.android.server.backup.BackupUtils;
  import com.android.server.backup.PackageManagerBackupAgent;
  import com.android.server.backup.PackageManagerBackupAgent.Metadata;
  import com.android.server.backup.TransportManager;
  import com.android.server.backup.UserBackupManagerService;
  import com.android.server.backup.internal.OnTaskFinishedListener;
  import com.android.server.backup.transport.TransportClient;
  import com.android.server.backup.utils.AppBackupUtils;
  import com.android.server.backup.utils.BackupManagerMonitorUtils;
  
  import libcore.io.IoUtils;
  
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.List;
  
  public class PerformUnifiedRestoreTask implements BackupRestoreTask {
  
      private UserBackupManagerService backupManagerService;
      private final int mUserId;
      private final TransportManager mTransportManager;
      // Transport client we're working with to do the restore
      private final TransportClient mTransportClient;
  
      // Where per-transport saved state goes
      File mStateDir;
  
      // Restore observer; may be null
      private IRestoreObserver mObserver;
  
      // BackuoManagerMonitor; may be null
      private IBackupManagerMonitor mMonitor;
  
      // Token identifying the dataset to the transport
      private long mToken;
  
      // When this is a restore-during-install, this is the token identifying the
      // operation to the Package Manager, and we must ensure that we let it know
      // when we're finished.
      private int mPmToken;
  
      // When this is restore-during-install, we need to tell the package manager
      // whether we actually launched the app, because this affects notifications
      // around externally-visible state transitions.
      private boolean mDidLaunch;
  
      // Is this a whole-system restore, i.e. are we establishing a new ancestral
      // dataset to base future restore-at-install operations from?
      private boolean mIsSystemRestore;
  
      // If this is a single-package restore, what package are we interested in?
      private PackageInfo mTargetPackage;
  
      // In all cases, the calculated list of packages that we are trying to restore
      private List<PackageInfo> mAcceptSet;
  
      // Our bookkeeping about the ancestral dataset
      private PackageManagerBackupAgent mPmAgent;
  
      // Currently-bound backup agent for restore + restoreFinished purposes
      private IBackupAgent mAgent;
  
      // What sort of restore we're doing now
      private RestoreDescription mRestoreDescription;
  
      // The package we're currently restoring
      private PackageInfo mCurrentPackage;
  
      // Widget-related data handled as part of this restore operation
      private byte[] mWidgetData;
  
      // Number of apps restored in this pass
      private int mCount;
  
      // When did we start?
      private long mStartRealtime;
  
      // State machine progress
      private UnifiedRestoreState mState;
  
      // How are things going?
      private int mStatus;
  
      // Done?
      private boolean mFinished;
  
      // When finished call listener
      private final OnTaskFinishedListener mListener;
  
      // Key/value: bookkeeping about staged data and files for agent access
      private File mBackupDataName;
      private File mStageName;
      private File mSavedStateName;
      private File mNewStateName;
      ParcelFileDescriptor mBackupData;
      ParcelFileDescriptor mNewState;
  
      private final int mEphemeralOpToken;
      private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
  
      // This task can assume that the wakelock is properly held for it and doesn't have to worry
      // about releasing it.
      public PerformUnifiedRestoreTask(
              UserBackupManagerService backupManagerService,
              TransportClient transportClient,
              IRestoreObserver observer,
              IBackupManagerMonitor monitor,
              long restoreSetToken,
              @Nullable PackageInfo targetPackage,
              int pmToken,
              boolean isFullSystemRestore,
              @Nullable String[] filterSet,
              OnTaskFinishedListener listener) {
          this.backupManagerService = backupManagerService;
          mUserId = backupManagerService.getUserId();
          mTransportManager = backupManagerService.getTransportManager();
          mEphemeralOpToken = backupManagerService.generateRandomIntegerToken();
          mState = UnifiedRestoreState.INITIAL;
          mStartRealtime = SystemClock.elapsedRealtime();
  
          mTransportClient = transportClient;
          mObserver = observer;
          mMonitor = monitor;
          mToken = restoreSetToken;
          mPmToken = pmToken;
          mTargetPackage = targetPackage;
          mIsSystemRestore = isFullSystemRestore;
          mFinished = false;
          mDidLaunch = false;
          mListener = listener;
          mAgentTimeoutParameters = Preconditions.checkNotNull(
                  backupManagerService.getAgentTimeoutParameters(),
                  "Timeout parameters cannot be null");
  
          if (targetPackage != null) {
              // Single package restore
              mAcceptSet = new ArrayList<>();
              mAcceptSet.add(targetPackage);
          } else {
              // Everything possible, or a target set
              if (filterSet == null) {
                  // We want everything and a pony
                  List<PackageInfo> apps =
                          PackageManagerBackupAgent.getStorableApplications(
                                  backupManagerService.getPackageManager(), mUserId);
                  filterSet = packagesToNames(apps);
                  if (DEBUG) {
                      Slog.i(TAG, "Full restore; asking about " + filterSet.length + " apps");
                  }
              }
  
              mAcceptSet = new ArrayList<>(filterSet.length);
  
              // Pro tem, we insist on moving the settings provider package to last place.
              // Keep track of whether it's in the list, and bump it down if so.  We also
              // want to do the system package itself first if it's called for.
              boolean hasSystem = false;
              boolean hasSettings = false;
              for (int i = 0; i < filterSet.length; i++) {
                  try {
                      PackageManager pm = backupManagerService.getPackageManager();
                      PackageInfo info = pm.getPackageInfoAsUser(filterSet[i], 0, mUserId);
                      if ("android".equals(info.packageName)) {
                          hasSystem = true;
                          continue;
                      }
                      if (SETTINGS_PACKAGE.equals(info.packageName)) {
                          hasSettings = true;
                          continue;
                      }
  
                      if (AppBackupUtils.appIsEligibleForBackup(info.applicationInfo, mUserId)) {
                          mAcceptSet.add(info);
                      }
                  } catch (NameNotFoundException e) {
                      // requested package name doesn't exist; ignore it
                  }
              }
              if (hasSystem) {
                  try {
                      mAcceptSet.add(0, backupManagerService.getPackageManager().getPackageInfoAsUser(
                                      "android", 0, mUserId));
                  } catch (NameNotFoundException e) {
                      // won't happen; we know a priori that it's valid
                  }
              }
              if (hasSettings) {
                  try {
                      mAcceptSet.add(backupManagerService.getPackageManager().getPackageInfoAsUser(
                              SETTINGS_PACKAGE, 0, mUserId));
                  } catch (NameNotFoundException e) {
                      // this one is always valid too
                  }
              }
          }
  
          if (MORE_DEBUG) {
              Slog.v(TAG, "Restore; accept set size is " + mAcceptSet.size());
              for (PackageInfo info : mAcceptSet) {
                  Slog.v(TAG, "   " + info.packageName);
              }
          }
      }
  
      private String[] packagesToNames(List<PackageInfo> apps) {
          final int N = apps.size();
          String[] names = new String[N];
          for (int i = 0; i < N; i++) {
              names[i] = apps.get(i).packageName;
          }
          return names;
      }
  
      // Execute one tick of whatever state machine the task implements
      @Override
      public void execute() {
          if (MORE_DEBUG) {
              Slog.v(TAG, "*** Executing restore step " + mState);
          }
          switch (mState) {
              case INITIAL:
                  startRestore();
                  break;
  
              case RUNNING_QUEUE:
                  dispatchNextRestore();
                  break;
  
              case RESTORE_KEYVALUE:
                  restoreKeyValue();
                  break;
  
              case RESTORE_FULL:
                  restoreFull();
                  break;
  
              case RESTORE_FINISHED:
                  restoreFinished();
                  break;
  
              case FINAL:
                  if (!mFinished) {
                      finalizeRestore();
                  } else {
                      Slog.e(TAG, "Duplicate finish");
                  }
                  mFinished = true;
                  break;
          }
      }
  
      /*
       * SKETCH OF OPERATION
       *
       * create one of these PerformUnifiedRestoreTask objects, telling it which
       * dataset & transport to address, and then parameters within the restore
       * operation: single target package vs many, etc.
       *
       * 1. transport.startRestore(token, list-of-packages).  If we need @pm@  it is
       * always placed first and the settings provider always placed last [for now].
       *
       * 1a [if we needed @pm@ then nextRestorePackage() and restore the PMBA inline]
       *
       *   [ state change => RUNNING_QUEUE ]
       *
       * NOW ITERATE:
       *
       * { 3. t.nextRestorePackage()
       *   4. does the metadata for this package allow us to restore it?
       *      does the on-disk app permit us to restore it? [re-check allowBackup etc]
       *   5. is this a key/value dataset?  => key/value agent restore
       *       [ state change => RESTORE_KEYVALUE ]
       *       5a. spin up agent
       *       5b. t.getRestoreData() to stage it properly
       *       5c. call into agent to perform restore
       *       5d. tear down agent
       *       [ state change => RUNNING_QUEUE ]
       *
       *   6. else it's a stream dataset:
       *       [ state change => RESTORE_FULL ]
       *       6a. instantiate the engine for a stream restore: engine handles agent lifecycles
       *       6b. spin off engine runner on separate thread
       *       6c. ITERATE getNextFullRestoreDataChunk() and copy data to engine runner socket
       *       [ state change => RUNNING_QUEUE ]
       * }
       *
       *   [ state change => FINAL ]
       *
       * 7. t.finishRestore(), call listeners, etc.
       *
       *
       */
  
      // state INITIAL : set up for the restore and read the metadata if necessary
      private void startRestore() {
          sendStartRestore(mAcceptSet.size());
  
          // If we're starting a full-system restore, set up to begin widget ID remapping
          if (mIsSystemRestore) {
              AppWidgetBackupBridge.restoreStarting(mUserId);
          }
  
          try {
              String transportDirName =
                      mTransportManager.getTransportDirName(mTransportClient.getTransportComponent());
              mStateDir = new File(backupManagerService.getBaseStateDir(), transportDirName);
  
              // Fetch the current metadata from the dataset first
              PackageInfo pmPackage = new PackageInfo();
              pmPackage.packageName = PACKAGE_MANAGER_SENTINEL;
              mAcceptSet.add(0, pmPackage);
  
              PackageInfo[] packages = mAcceptSet.toArray(new PackageInfo[0]);
  
              IBackupTransport transport =
                      mTransportClient.connectOrThrow("PerformUnifiedRestoreTask.startRestore()");
  
              mStatus = transport.startRestore(mToken, packages);
              if (mStatus != BackupTransport.TRANSPORT_OK) {
                  Slog.e(TAG, "Transport error " + mStatus + "; no restore possible");
                  mStatus = BackupTransport.TRANSPORT_ERROR;
                  executeNextState(UnifiedRestoreState.FINAL);
                  return;
              }
  
              RestoreDescription desc = transport.nextRestorePackage();
              if (desc == null) {
                  Slog.e(TAG, "No restore metadata available; halting");
                  mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
                          BackupManagerMonitor.LOG_EVENT_ID_NO_RESTORE_METADATA_AVAILABLE,
                          mCurrentPackage,
                          BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, null);
                  mStatus = BackupTransport.TRANSPORT_ERROR;
                  executeNextState(UnifiedRestoreState.FINAL);
                  return;
              }
              if (!PACKAGE_MANAGER_SENTINEL.equals(
                      desc.getPackageName())) {
                  Slog.e(TAG, "Required package metadata but got "
                          + desc.getPackageName());
                  mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
                          BackupManagerMonitor.LOG_EVENT_ID_NO_PM_METADATA_RECEIVED,
                          mCurrentPackage,
                          BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, null);
                  mStatus = BackupTransport.TRANSPORT_ERROR;
                  executeNextState(UnifiedRestoreState.FINAL);
                  return;
              }
  
              // Pull the Package Manager metadata from the restore set first
              mCurrentPackage = new PackageInfo();
              mCurrentPackage.packageName = PACKAGE_MANAGER_SENTINEL;
              mPmAgent = backupManagerService.makeMetadataAgent(null);
              mAgent = IBackupAgent.Stub.asInterface(mPmAgent.onBind());
              if (MORE_DEBUG) {
                  Slog.v(TAG, "initiating restore for PMBA");
              }
              initiateOneRestore(mCurrentPackage, 0);
              // The PM agent called operationComplete() already, because our invocation
              // of it is process-local and therefore synchronous.  That means that the
              // next-state message (RUNNING_QUEUE) is already enqueued.  Only if we're
              // unable to proceed with running the queue do we remove that pending
              // message and jump straight to the FINAL state.  Because this was
              // synchronous we also know that we should cancel the pending timeout
              // message.
              backupManagerService.getBackupHandler().removeMessages(
                      MSG_RESTORE_OPERATION_TIMEOUT);
  
              // Verify that the backup set includes metadata.  If not, we can't do
              // signature/version verification etc, so we simply do not proceed with
              // the restore operation.
              if (!mPmAgent.hasMetadata()) {
                  Slog.e(TAG, "PM agent has no metadata, so not restoring");
                  mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
                          BackupManagerMonitor.LOG_EVENT_ID_PM_AGENT_HAS_NO_METADATA,
                          mCurrentPackage,
                          BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, null);
                  EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
                          PACKAGE_MANAGER_SENTINEL,
                          "Package manager restore metadata missing");
                  mStatus = BackupTransport.TRANSPORT_ERROR;
                  backupManagerService.getBackupHandler().removeMessages(
                          MSG_BACKUP_RESTORE_STEP, this);
                  executeNextState(UnifiedRestoreState.FINAL);
                  return;
              }
  
              // Success; cache the metadata and continue as expected with the
              // next state already enqueued
  
          } catch (Exception e) {
              // If we lost the transport at any time, halt
              Slog.e(TAG, "Unable to contact transport for restore: " + e.getMessage());
              mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
                      BackupManagerMonitor.LOG_EVENT_ID_LOST_TRANSPORT,
                      null,
                      BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT, null);
              mStatus = BackupTransport.TRANSPORT_ERROR;
              backupManagerService.getBackupHandler().removeMessages(
                      MSG_BACKUP_RESTORE_STEP, this);
              executeNextState(UnifiedRestoreState.FINAL);
              return;
          }
      }
  
      // state RUNNING_QUEUE : figure out what the next thing to be restored is,
      // and fire the appropriate next step
      private void dispatchNextRestore() {
          UnifiedRestoreState nextState = UnifiedRestoreState.FINAL;
          try {
              IBackupTransport transport =
                      mTransportClient.connectOrThrow(
                              "PerformUnifiedRestoreTask.dispatchNextRestore()");
              mRestoreDescription = transport.nextRestorePackage();
              final String pkgName = (mRestoreDescription != null)
                      ? mRestoreDescription.getPackageName() : null;
              if (pkgName == null) {
                  Slog.e(TAG, "Failure getting next package name");
                  EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
                  nextState = UnifiedRestoreState.FINAL;
                  return;
              } else if (mRestoreDescription == RestoreDescription.NO_MORE_PACKAGES) {
                  // Yay we've reached the end cleanly
                  if (DEBUG) {
                      Slog.v(TAG, "No more packages; finishing restore");
                  }
                  int millis = (int) (SystemClock.elapsedRealtime() - mStartRealtime);
                  EventLog.writeEvent(EventLogTags.RESTORE_SUCCESS, mCount, millis);
                  nextState = UnifiedRestoreState.FINAL;
                  return;
              }
  
              if (DEBUG) {
                  Slog.i(TAG, "Next restore package: " + mRestoreDescription);
              }
              sendOnRestorePackage(pkgName);
  
              Metadata metaInfo = mPmAgent.getRestoredMetadata(pkgName);
              if (metaInfo == null) {
                  Slog.e(TAG, "No metadata for " + pkgName);
                  EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, pkgName,
                          "Package metadata missing");
                  nextState = UnifiedRestoreState.RUNNING_QUEUE;
                  return;
              }
  
              try {
                  mCurrentPackage = backupManagerService.getPackageManager().getPackageInfoAsUser(
                          pkgName, PackageManager.GET_SIGNING_CERTIFICATES, mUserId);
              } catch (NameNotFoundException e) {
                  // Whoops, we thought we could restore this package but it
                  // turns out not to be present.  Skip it.
                  Slog.e(TAG, "Package not present: " + pkgName);
                  mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
                          BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_NOT_PRESENT,
                          mCurrentPackage,
                          BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
                          null);
                  EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, pkgName,
                          "Package missing on device");
                  nextState = UnifiedRestoreState.RUNNING_QUEUE;
                  return;
              }
  
              if (metaInfo.versionCode > mCurrentPackage.getLongVersionCode()) {
                  // Data is from a "newer" version of the app than we have currently
                  // installed.  If the app has not declared that it is prepared to
                  // handle this case, we do not attempt the restore.
                  if ((mCurrentPackage.applicationInfo.flags
                          & ApplicationInfo.FLAG_RESTORE_ANY_VERSION) == 0) {
                      String message = "Source version " + metaInfo.versionCode
                              + " > installed version " + mCurrentPackage.getLongVersionCode();
                      Slog.w(TAG, "Package " + pkgName + ": " + message);
                      Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(null,
                              BackupManagerMonitor.EXTRA_LOG_RESTORE_VERSION,
                              metaInfo.versionCode);
                      monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(
                              monitoringExtras,
                              BackupManagerMonitor.EXTRA_LOG_RESTORE_ANYWAY, false);
                      mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
                              BackupManagerMonitor.LOG_EVENT_ID_RESTORE_VERSION_HIGHER,
                              mCurrentPackage,
                              BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
                              monitoringExtras);
                      EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
                              pkgName, message);
                      nextState = UnifiedRestoreState.RUNNING_QUEUE;
                      return;
                  } else {
                      if (DEBUG) {
                          Slog.v(TAG, "Source version " + metaInfo.versionCode
                                  + " > installed version " + mCurrentPackage.getLongVersionCode()
                                  + " but restoreAnyVersion");
                      }
                      Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(null,
                              BackupManagerMonitor.EXTRA_LOG_RESTORE_VERSION,
                              metaInfo.versionCode);
                      monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(
                              monitoringExtras,
                              BackupManagerMonitor.EXTRA_LOG_RESTORE_ANYWAY, true);
                      mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
                              BackupManagerMonitor.LOG_EVENT_ID_RESTORE_VERSION_HIGHER,
                              mCurrentPackage,
                              BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
                              monitoringExtras);
                  }
              }
  
              if (MORE_DEBUG) {
                  Slog.v(TAG, "Package " + pkgName
                          + " restore version [" + metaInfo.versionCode
                          + "] is compatible with installed version ["
                          + mCurrentPackage.getLongVersionCode() + "]");
              }
  
              // Reset per-package preconditions and fire the appropriate next state
              mWidgetData = null;
              final int type = mRestoreDescription.getDataType();
              if (type == RestoreDescription.TYPE_KEY_VALUE) {
                  nextState = UnifiedRestoreState.RESTORE_KEYVALUE;
              } else if (type == RestoreDescription.TYPE_FULL_STREAM) {
                  nextState = UnifiedRestoreState.RESTORE_FULL;
              } else {
                  // Unknown restore type; ignore this package and move on
                  Slog.e(TAG, "Unrecognized restore type " + type);
                  nextState = UnifiedRestoreState.RUNNING_QUEUE;
                  return;
              }
          } catch (Exception e) {
              Slog.e(TAG, "Can't get next restore target from transport; halting: "
                      + e.getMessage());
              EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
              nextState = UnifiedRestoreState.FINAL;
              return;
          } finally {
              executeNextState(nextState);
          }
      }
  
      // state RESTORE_KEYVALUE : restore one package via key/value API set
      private void restoreKeyValue() {
          // Initiating the restore will pass responsibility for the state machine's
          // progress to the agent callback, so we do not always execute the
          // next state here.
          final String packageName = mCurrentPackage.packageName;
          // Validate some semantic requirements that apply in this way
          // only to the key/value restore API flow
          if (mCurrentPackage.applicationInfo.backupAgentName == null
                  || "".equals(mCurrentPackage.applicationInfo.backupAgentName)) {
              if (MORE_DEBUG) {
                  Slog.i(TAG, "Data exists for package " + packageName
                          + " but app has no agent; skipping");
              }
              mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
                      BackupManagerMonitor.LOG_EVENT_ID_APP_HAS_NO_AGENT, mCurrentPackage,
                      BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, null);
              EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
                      "Package has no agent");
              executeNextState(UnifiedRestoreState.RUNNING_QUEUE);
              return;
          }
  
          Metadata metaInfo = mPmAgent.getRestoredMetadata(packageName);
          PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
          if (!BackupUtils.signaturesMatch(metaInfo.sigHashes, mCurrentPackage, pmi)) {
              Slog.w(TAG, "Signature mismatch restoring " + packageName);
              mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
                      BackupManagerMonitor.LOG_EVENT_ID_SIGNATURE_MISMATCH, mCurrentPackage,
                      BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, null);
              EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
                      "Signature mismatch");
              executeNextState(UnifiedRestoreState.RUNNING_QUEUE);
              return;
          }
  
          // Good to go!  Set up and bind the agent...
          mAgent = backupManagerService.bindToAgentSynchronous(
                  mCurrentPackage.applicationInfo,
                  ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL);
          if (mAgent == null) {
              Slog.w(TAG, "Can't find backup agent for " + packageName);
              mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
                      BackupManagerMonitor.LOG_EVENT_ID_CANT_FIND_AGENT, mCurrentPackage,
                      BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, null);
              EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
                      "Restore agent missing");
              executeNextState(UnifiedRestoreState.RUNNING_QUEUE);
              return;
          }
  
          // Whatever happens next, we've launched the target app now; remember that.
          mDidLaunch = true;
  
          // And then finally start the restore on this agent
          try {
              initiateOneRestore(mCurrentPackage, metaInfo.versionCode);
              ++mCount;
          } catch (Exception e) {
              Slog.e(TAG, "Error when attempting restore: " + e.toString());
              keyValueAgentErrorCleanup(false);
              executeNextState(UnifiedRestoreState.RUNNING_QUEUE);
          }
      }
  
      // Guts of a key/value restore operation
      void initiateOneRestore(PackageInfo app, long appVersionCode) {
          final String packageName = app.packageName;
  
          if (DEBUG) {
              Slog.d(TAG, "initiateOneRestore packageName=" + packageName);
          }
  
          // !!! TODO: get the dirs from the transport
          mBackupDataName = new File(backupManagerService.getDataDir(), packageName + ".restore");
          mStageName = new File(backupManagerService.getDataDir(), packageName + ".stage");
          mNewStateName = new File(mStateDir, packageName + ".new");
          mSavedStateName = new File(mStateDir, packageName);
  
          // don't stage the 'android' package where the wallpaper data lives.  this is
          // an optimization: we know there's no widget data hosted/published by that
          // package, and this way we avoid doing a spurious copy of MB-sized wallpaper
          // data following the download.
          boolean staging = !packageName.equals("android");
          ParcelFileDescriptor stage;
          File downloadFile = (staging) ? mStageName : mBackupDataName;
          boolean startedAgentRestore = false;
  
          try {
              IBackupTransport transport =
                      mTransportClient.connectOrThrow(
                              "PerformUnifiedRestoreTask.initiateOneRestore()");
  
              // Run the transport's restore pass
              stage = ParcelFileDescriptor.open(downloadFile,
                      ParcelFileDescriptor.MODE_READ_WRITE |
                              ParcelFileDescriptor.MODE_CREATE |
                              ParcelFileDescriptor.MODE_TRUNCATE);
  
              if (transport.getRestoreData(stage) != BackupTransport.TRANSPORT_OK) {
                  // Transport-level failure, so we wind everything up and
                  // terminate the restore operation.
                  Slog.e(TAG, "Error getting restore data for " + packageName);
                  EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
                  stage.close();
                  downloadFile.delete();
                  executeNextState(UnifiedRestoreState.FINAL);
                  return;
              }
  
              // We have the data from the transport. Now we extract and strip
              // any per-package metadata (typically widget-related information)
              // if appropriate
              if (staging) {
                  stage.close();
                  stage = ParcelFileDescriptor.open(downloadFile,
                          ParcelFileDescriptor.MODE_READ_ONLY);
  
                  mBackupData = ParcelFileDescriptor.open(mBackupDataName,
                          ParcelFileDescriptor.MODE_READ_WRITE |
                                  ParcelFileDescriptor.MODE_CREATE |
                                  ParcelFileDescriptor.MODE_TRUNCATE);
  
                  BackupDataInput in = new BackupDataInput(stage.getFileDescriptor());
                  BackupDataOutput out = new BackupDataOutput(mBackupData.getFileDescriptor());
                  byte[] buffer = new byte[8192]; // will grow when needed
                  while (in.readNextHeader()) {
                      final String key = in.getKey();
                      final int size = in.getDataSize();
  
                      // is this a special key?
                      if (key.equals(KEY_WIDGET_STATE)) {
                          if (DEBUG) {
                              Slog.i(TAG, "Restoring widget state for " + packageName);
                          }
                          mWidgetData = new byte[size];
                          in.readEntityData(mWidgetData, 0, size);
                      } else {
                          if (size > buffer.length) {
                              buffer = new byte[size];
                          }
                          in.readEntityData(buffer, 0, size);
                          out.writeEntityHeader(key, size);
                          out.writeEntityData(buffer, size);
                      }
                  }
  
                  mBackupData.close();
              }
  
              // Okay, we have the data.  Now have the agent do the restore.
              stage.close();
  
              mBackupData = ParcelFileDescriptor.open(mBackupDataName,
                      ParcelFileDescriptor.MODE_READ_ONLY);
  
              mNewState = ParcelFileDescriptor.open(mNewStateName,
                      ParcelFileDescriptor.MODE_READ_WRITE |
                              ParcelFileDescriptor.MODE_CREATE |
                              ParcelFileDescriptor.MODE_TRUNCATE);
  
              // Kick off the restore, checking for hung agents.  The timeout or
              // the operationComplete() callback will schedule the next step,
              // so we do not do that here.
              long restoreAgentTimeoutMillis = mAgentTimeoutParameters.getRestoreAgentTimeoutMillis();
              backupManagerService.prepareOperationTimeout(
                      mEphemeralOpToken, restoreAgentTimeoutMillis, this, OP_TYPE_RESTORE_WAIT);
              startedAgentRestore = true;
              mAgent.doRestore(mBackupData, appVersionCode, mNewState,
                      mEphemeralOpToken, backupManagerService.getBackupManagerBinder());
          } catch (Exception e) {
              Slog.e(TAG, "Unable to call app for restore: " + packageName, e);
              EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
                      packageName, e.toString());
              // Clears any pending timeout messages as well.
              keyValueAgentErrorCleanup(startedAgentRestore);
  
              // After a restore failure we go back to running the queue.  If there
              // are no more packages to be restored that will be handled by the
              // next step.
              executeNextState(UnifiedRestoreState.RUNNING_QUEUE);
          }
      }
  
      // state RESTORE_FULL : restore one package via streaming engine
      private void restoreFull() {
          // None of this can run on the work looper here, so we spin asynchronous
          // work like this:
          //
          //   StreamFeederThread: read data from transport.getNextFullRestoreDataChunk()
          //                       write it into the pipe to the engine
          //   EngineThread: FullRestoreEngine thread communicating with the target app
          //
          // When finished, StreamFeederThread executes next state as appropriate on the
          // backup looper, and the overall unified restore task resumes
          try {
              StreamFeederThread feeder = new StreamFeederThread();
              if (MORE_DEBUG) {
                  Slog.i(TAG, "Spinning threads for stream restore of "
                          + mCurrentPackage.packageName);
              }
              new Thread(feeder, "unified-stream-feeder").start();
  
              // At this point the feeder is responsible for advancing the restore
              // state, so we're done here.
          } catch (IOException e) {
              // Unable to instantiate the feeder thread -- we need to bail on the
              // current target.  We haven't asked the transport for data yet, though,
              // so we can do that simply by going back to running the restore queue.
              Slog.e(TAG, "Unable to construct pipes for stream restore!");
              executeNextState(UnifiedRestoreState.RUNNING_QUEUE);
          }
      }
  
      // state RESTORE_FINISHED : provide the "no more data" signpost callback at the end
      private void restoreFinished() {
          if (DEBUG) {
              Slog.d(TAG, "restoreFinished packageName=" + mCurrentPackage.packageName);
          }
          try {
              long restoreAgentFinishedTimeoutMillis =
                      mAgentTimeoutParameters.getRestoreAgentFinishedTimeoutMillis();
              backupManagerService
                      .prepareOperationTimeout(mEphemeralOpToken,
                              restoreAgentFinishedTimeoutMillis, this,
                              OP_TYPE_RESTORE_WAIT);
              mAgent.doRestoreFinished(mEphemeralOpToken,
                      backupManagerService.getBackupManagerBinder());
              // If we get this far, the callback or timeout will schedule the
              // next restore state, so we're done
          } catch (Exception e) {
              final String packageName = mCurrentPackage.packageName;
              Slog.e(TAG, "Unable to finalize restore of " + packageName);
              EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
                      packageName, e.toString());
              keyValueAgentErrorCleanup(true);
              executeNextState(UnifiedRestoreState.RUNNING_QUEUE);
          }
      }
  
      class StreamFeederThread extends RestoreEngine implements Runnable, BackupRestoreTask {
  
          final String TAG = "StreamFeederThread";
          FullRestoreEngine mEngine;
          FullRestoreEngineThread mEngineThread;
  
          // pipe through which we read data from the transport. [0] read, [1] write
          ParcelFileDescriptor[] mTransportPipes;
  
          // pipe through which the engine will read data.  [0] read, [1] write
          ParcelFileDescriptor[] mEnginePipes;
  
          private final int mEphemeralOpToken;
  
          public StreamFeederThread() throws IOException {
              mEphemeralOpToken = backupManagerService.generateRandomIntegerToken();
              mTransportPipes = ParcelFileDescriptor.createPipe();
              mEnginePipes = ParcelFileDescriptor.createPipe();
              setRunning(true);
          }
  
          @Override
          public void run() {
              UnifiedRestoreState nextState = UnifiedRestoreState.RUNNING_QUEUE;
              int status = BackupTransport.TRANSPORT_OK;
  
              EventLog.writeEvent(EventLogTags.FULL_RESTORE_PACKAGE,
                      mCurrentPackage.packageName);
  
              mEngine = new FullRestoreEngine(backupManagerService, this, null,
                      mMonitor, mCurrentPackage, false, false, mEphemeralOpToken, false);
              mEngineThread = new FullRestoreEngineThread(mEngine, mEnginePipes[0]);
  
              ParcelFileDescriptor eWriteEnd = mEnginePipes[1];
              ParcelFileDescriptor tReadEnd = mTransportPipes[0];
              ParcelFileDescriptor tWriteEnd = mTransportPipes[1];
  
              int bufferSize = 32 * 1024;
              byte[] buffer = new byte[bufferSize];
              FileOutputStream engineOut = new FileOutputStream(eWriteEnd.getFileDescriptor());
              FileInputStream transportIn = new FileInputStream(tReadEnd.getFileDescriptor());
  
              // spin up the engine and start moving data to it
              new Thread(mEngineThread, "unified-restore-engine").start();
  
              String callerLogString = "PerformUnifiedRestoreTask$StreamFeederThread.run()";
              try {
                  IBackupTransport transport = mTransportClient.connectOrThrow(callerLogString);
                  while (status == BackupTransport.TRANSPORT_OK) {
                      // have the transport write some of the restoring data to us
                      int result = transport.getNextFullRestoreDataChunk(tWriteEnd);
                      if (result > 0) {
                          // The transport wrote this many bytes of restore data to the
                          // pipe, so pass it along to the engine.
                          if (MORE_DEBUG) {
                              Slog.v(TAG, "  <- transport provided chunk size " + result);
                          }
                          if (result > bufferSize) {
                              bufferSize = result;
                              buffer = new byte[bufferSize];
                          }
                          int toCopy = result;
                          while (toCopy > 0) {
                              int n = transportIn.read(buffer, 0, toCopy);
                              engineOut.write(buffer, 0, n);
                              toCopy -= n;
                              if (MORE_DEBUG) {
                                  Slog.v(TAG, "  -> wrote " + n + " to engine, left=" + toCopy);
                              }
                          }
                      } else if (result == BackupTransport.NO_MORE_DATA) {
                          // Clean finish.  Wind up and we're done!
                          if (MORE_DEBUG) {
                              Slog.i(TAG, "Got clean full-restore EOF for "
                                      + mCurrentPackage.packageName);
                          }
                          status = BackupTransport.TRANSPORT_OK;
                          break;
                      } else {
                          // Transport reported some sort of failure; the fall-through
                          // handling will deal properly with that.
                          Slog.e(TAG, "Error " + result + " streaming restore for "
                                  + mCurrentPackage.packageName);
                          EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
                          status = result;
                      }
                  }
                  if (MORE_DEBUG) {
                      Slog.v(TAG, "Done copying to engine, falling through");
                  }
              } catch (IOException e) {
                  // We lost our ability to communicate via the pipes.  That's worrying
                  // but potentially recoverable; abandon this package's restore but
                  // carry on with the next restore target.
                  Slog.e(TAG, "Unable to route data for restore");
                  EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
                          mCurrentPackage.packageName, "I/O error on pipes");
                  status = BackupTransport.AGENT_ERROR;
              } catch (Exception e) {
                  // The transport threw; terminate the whole operation.  Closing
                  // the sockets will wake up the engine and it will then tidy up the
                  // remote end.
                  Slog.e(TAG, "Transport failed during restore: " + e.getMessage());
                  EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
                  status = BackupTransport.TRANSPORT_ERROR;
              } finally {
                  // Close the transport pipes and *our* end of the engine pipe,
                  // but leave the engine thread's end open so that it properly
                  // hits EOF and winds up its operations.
                  IoUtils.closeQuietly(mEnginePipes[1]);
                  IoUtils.closeQuietly(mTransportPipes[0]);
                  IoUtils.closeQuietly(mTransportPipes[1]);
  
                  // Don't proceed until the engine has wound up operations
                  mEngineThread.waitForResult();
  
                  // Now we're really done with this one too
                  IoUtils.closeQuietly(mEnginePipes[0]);
  
                  // In all cases we want to remember whether we launched
                  // the target app as part of our work so far.
                  mDidLaunch = (mEngine.getAgent() != null);
  
                  // If we hit a transport-level error, we are done with everything;
                  // if we hit an agent error we just go back to running the queue.
                  if (status == BackupTransport.TRANSPORT_OK) {
                      // Clean finish means we issue the restore-finished callback
                      nextState = UnifiedRestoreState.RESTORE_FINISHED;
  
                      // the engine bound the target's agent, so recover that binding
                      // to use for the callback.
                      mAgent = mEngine.getAgent();
  
                      // and the restored widget data, if any
                      mWidgetData = mEngine.getWidgetData();
                  } else {
                      // Something went wrong somewhere.  Whether it was at the transport
                      // level is immaterial; we need to tell the transport to bail
                      try {
                          IBackupTransport transport =
                                  mTransportClient.connectOrThrow(callerLogString);
                          transport.abortFullRestore();
                      } catch (Exception e) {
                          // transport itself is dead; make sure we handle this as a
                          // fatal error
                          Slog.e(TAG, "Transport threw from abortFullRestore: " + e.getMessage());
                          status = BackupTransport.TRANSPORT_ERROR;
                      }
  
                      // We also need to wipe the current target's data, as it's probably
                      // in an incoherent state.
                      backupManagerService.clearApplicationDataAfterRestoreFailure(
                              mCurrentPackage.packageName);
  
                      // Schedule the next state based on the nature of our failure
                      if (status == BackupTransport.TRANSPORT_ERROR) {
                          nextState = UnifiedRestoreState.FINAL;
                      } else {
                          nextState = UnifiedRestoreState.RUNNING_QUEUE;
                      }
                  }
                  executeNextState(nextState);
                  setRunning(false);
              }
          }
  
          // BackupRestoreTask interface, specifically for timeout handling
  
          @Override
          public void execute() { /* intentionally empty */ }
  
          @Override
          public void operationComplete(long result) { /* intentionally empty */ }
  
          // The app has timed out handling a restoring file
          @Override
          public void handleCancel(boolean cancelAll) {
              backupManagerService.removeOperation(mEphemeralOpToken);
              if (DEBUG) {
                  Slog.w(TAG, "Full-data restore target timed out; shutting down");
              }
  
              mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
                      BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE_TIMEOUT,
                      mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, null);
              mEngineThread.handleTimeout();
  
              IoUtils.closeQuietly(mEnginePipes[1]);
              mEnginePipes[1] = null;
              IoUtils.closeQuietly(mEnginePipes[0]);
              mEnginePipes[0] = null;
          }
      }
  
      // state FINAL : tear everything down and we're done.
      private void finalizeRestore() {
          if (MORE_DEBUG) {
              Slog.d(TAG, "finishing restore mObserver=" + mObserver);
          }
  
          String callerLogString = "PerformUnifiedRestoreTask.finalizeRestore()";
          try {
              IBackupTransport transport =
                      mTransportClient.connectOrThrow(callerLogString);
              transport.finishRestore();
          } catch (Exception e) {
              Slog.e(TAG, "Error finishing restore", e);
          }
  
          // Tell the observer we're done
          if (mObserver != null) {
              try {
                  mObserver.restoreFinished(mStatus);
              } catch (RemoteException e) {
                  Slog.d(TAG, "Restore observer died at restoreFinished");
              }
          }
  
          // Clear any ongoing session timeout.
          backupManagerService.getBackupHandler().removeMessages(MSG_RESTORE_SESSION_TIMEOUT);
  
          // If we have a PM token, we must under all circumstances be sure to
          // handshake when we've finished.
          if (mPmToken > 0) {
              if (MORE_DEBUG) {
                  Slog.v(TAG, "finishing PM token " + mPmToken);
              }
              try {
                  backupManagerService.getPackageManagerBinder().finishPackageInstall(mPmToken,
                          mDidLaunch);
              } catch (RemoteException e) { /* can't happen */ }
          } else {
              // We were invoked via an active restore session, not by the Package
              // Manager, so start up the session timeout again.
              long restoreAgentTimeoutMillis = mAgentTimeoutParameters.getRestoreAgentTimeoutMillis();
              backupManagerService.getBackupHandler().sendEmptyMessageDelayed(
                      MSG_RESTORE_SESSION_TIMEOUT,
                      restoreAgentTimeoutMillis);
          }
  
          // Kick off any work that may be needed regarding app widget restores
          AppWidgetBackupBridge.restoreFinished(mUserId);
  
          // If this was a full-system restore, record the ancestral
          // dataset information
          if (mIsSystemRestore && mPmAgent != null) {
              backupManagerService.setAncestralPackages(mPmAgent.getRestoredPackages());
              backupManagerService.setAncestralToken(mToken);
              backupManagerService.writeRestoreTokens();
          }
  
          synchronized (backupManagerService.getPendingRestores()) {
              if (backupManagerService.getPendingRestores().size() > 0) {
                  if (DEBUG) {
                      Slog.d(TAG, "Starting next pending restore.");
                  }
                  PerformUnifiedRestoreTask task = backupManagerService.getPendingRestores().remove();
                  backupManagerService.getBackupHandler().sendMessage(
                          backupManagerService.getBackupHandler().obtainMessage(
                                  MSG_BACKUP_RESTORE_STEP, task));
  
              } else {
                  backupManagerService.setRestoreInProgress(false);
                  if (MORE_DEBUG) {
                      Slog.d(TAG, "No pending restores.");
                  }
              }
          }
  
          Slog.i(TAG, "Restore complete.");
          mListener.onFinished(callerLogString);
      }
  
      /**
       * @param clearAppData - set to {@code true} if the backup agent had already been invoked when
       *     restore faied. So the app data may be in corrupted state and has to be cleared.
       */
      void keyValueAgentErrorCleanup(boolean clearAppData) {
          if (clearAppData) {
              // If the agent fails restore, it might have put the app's data
              // into an incoherent state.  For consistency we wipe its data
              // again in this case before continuing with normal teardown
              backupManagerService.clearApplicationDataAfterRestoreFailure(
                      mCurrentPackage.packageName);
          }
          keyValueAgentCleanup();
      }
  
      // TODO: clean up naming; this is now used at finish by both k/v and stream restores
      void keyValueAgentCleanup() {
          mBackupDataName.delete();
          mStageName.delete();
          try {
              if (mBackupData != null) {
                  mBackupData.close();
              }
          } catch (IOException e) {
          }
          try {
              if (mNewState != null) {
                  mNewState.close();
              }
          } catch (IOException e) {
          }
          mBackupData = mNewState = null;
  
          // if everything went okay, remember the recorded state now
          //
          // !!! TODO: the restored data could be migrated on the server
          // side into the current dataset.  In that case the new state file
          // we just created would reflect the data already extant in the
          // backend, so there'd be nothing more to do.  Until that happens,
          // however, we need to make sure that we record the data to the
          // current backend dataset.  (Yes, this means shipping the data over
          // the wire in both directions.  That's bad, but consistency comes
          // first, then efficiency.)  Once we introduce server-side data
          // migration to the newly-restored device's dataset, we will change
          // the following from a discard of the newly-written state to the
          // "correct" operation of renaming into the canonical state blob.
          mNewStateName.delete();                      // TODO: remove; see above comment
          //mNewStateName.renameTo(mSavedStateName);   // TODO: replace with this
  
          // If this wasn't the PM pseudopackage, tear down the agent side
          if (mCurrentPackage.applicationInfo != null) {
              // unbind and tidy up even on timeout or failure
              try {
                  backupManagerService.getActivityManager().unbindBackupAgent(
                          mCurrentPackage.applicationInfo);
  
                  // The agent was probably running with a stub Application object,
                  // which isn't a valid run mode for the main app logic.  Shut
                  // down the app so that next time it's launched, it gets the
                  // usual full initialization.  Note that this is only done for
                  // full-system restores: when a single app has requested a restore,
                  // it is explicitly not killed following that operation.
                  //
                  // We execute this kill when these conditions hold:
                  //    1. it's not a system-uid process,
                  //    2. the app did not request its own restore (mTargetPackage == null), and
                  // either
                  //    3a. the app is a full-data target (TYPE_FULL_STREAM) or
                  //     b. the app does not state android:killAfterRestore="false" in its manifest
                  final int appFlags = mCurrentPackage.applicationInfo.flags;
                  final boolean killAfterRestore =
                          !UserHandle.isCore(mCurrentPackage.applicationInfo.uid)
                                  && ((mRestoreDescription.getDataType()
                                  == RestoreDescription.TYPE_FULL_STREAM)
                                  || ((appFlags & ApplicationInfo.FLAG_KILL_AFTER_RESTORE) != 0));
  
                  if (mTargetPackage == null && killAfterRestore) {
                      if (DEBUG) {
                          Slog.d(TAG, "Restore complete, killing host process of "
                                  + mCurrentPackage.applicationInfo.processName);
                      }
                      backupManagerService.getActivityManager().killApplicationProcess(
                              mCurrentPackage.applicationInfo.processName,
                              mCurrentPackage.applicationInfo.uid);
                  }
              } catch (RemoteException e) {
                  // can't happen; we run in the same process as the activity manager
              }
          }
  
          // The caller is responsible for reestablishing the state machine; our
          // responsibility here is to clear the decks for whatever comes next.
          backupManagerService.getBackupHandler().removeMessages(MSG_RESTORE_OPERATION_TIMEOUT, this);
      }
  
      @Override
      public void operationComplete(long unusedResult) {
          backupManagerService.removeOperation(mEphemeralOpToken);
          if (MORE_DEBUG) {
              Slog.i(TAG, "operationComplete() during restore: target="
                      + mCurrentPackage.packageName
                      + " state=" + mState);
          }
  
          final UnifiedRestoreState nextState;
          switch (mState) {
              case INITIAL:
                  // We've just (manually) restored the PMBA.  It doesn't need the
                  // additional restore-finished callback so we bypass that and go
                  // directly to running the queue.
                  nextState = UnifiedRestoreState.RUNNING_QUEUE;
                  break;
  
              case RESTORE_KEYVALUE:
              case RESTORE_FULL: {
                  // Okay, we've just heard back from the agent that it's done with
                  // the restore itself.  We now have to send the same agent its
                  // doRestoreFinished() callback, so roll into that state.
                  nextState = UnifiedRestoreState.RESTORE_FINISHED;
                  break;
              }
  
              case RESTORE_FINISHED: {
                  // Okay, we're done with this package.  Tidy up and go on to the next
                  // app in the queue.
                  int size = (int) mBackupDataName.length();
                  EventLog.writeEvent(EventLogTags.RESTORE_PACKAGE,
                          mCurrentPackage.packageName, size);
  
                  // Just go back to running the restore queue
                  keyValueAgentCleanup();
  
                  // If there was widget state associated with this app, get the OS to
                  // incorporate it into current bookeeping and then pass that along to
                  // the app as part of the restore-time work.
                  if (mWidgetData != null) {
                      backupManagerService.restoreWidgetData(mCurrentPackage.packageName,
                              mWidgetData);
                  }
  
                  nextState = UnifiedRestoreState.RUNNING_QUEUE;
                  break;
              }
  
              default: {
                  // Some kind of horrible semantic error; we're in an unexpected state.
                  // Back off hard and wind up.
                  Slog.e(TAG, "Unexpected restore callback into state " + mState);
                  keyValueAgentErrorCleanup(true);
                  nextState = UnifiedRestoreState.FINAL;
                  break;
              }
          }
  
          executeNextState(nextState);
      }
  
      // A call to agent.doRestore() or agent.doRestoreFinished() has timed out
      @Override
      public void handleCancel(boolean cancelAll) {
          backupManagerService.removeOperation(mEphemeralOpToken);
          Slog.e(TAG, "Timeout restoring application " + mCurrentPackage.packageName);
          mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
                  BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_RESTORE_TIMEOUT,
                  mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, null);
          EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
                  mCurrentPackage.packageName, "restore timeout");
          // Handle like an agent that threw on invocation: wipe it and go on to the next
          keyValueAgentErrorCleanup(true);
          executeNextState(UnifiedRestoreState.RUNNING_QUEUE);
      }
  
      void executeNextState(UnifiedRestoreState nextState) {
          if (MORE_DEBUG) {
              Slog.i(TAG, " => executing next step on "
                      + this + " nextState=" + nextState);
          }
          mState = nextState;
          Message msg = backupManagerService.getBackupHandler().obtainMessage(
                  MSG_BACKUP_RESTORE_STEP, this);
          backupManagerService.getBackupHandler().sendMessage(msg);
      }
  
      // restore observer support
      void sendStartRestore(int numPackages) {
          if (mObserver != null) {
              try {
                  mObserver.restoreStarting(numPackages);
              } catch (RemoteException e) {
                  Slog.w(TAG, "Restore observer went away: startRestore");
                  mObserver = null;
              }
          }
      }
  
      void sendOnRestorePackage(String name) {
          if (mObserver != null) {
              try {
                  mObserver.onUpdate(mCount, name);
              } catch (RemoteException e) {
                  Slog.d(TAG, "Restore observer died in onUpdate");
                  mObserver = null;
              }
          }
      }
  
      void sendEndRestore() {
          if (mObserver != null) {
              try {
                  mObserver.restoreFinished(mStatus);
              } catch (RemoteException e) {
                  Slog.w(TAG, "Restore observer went away: endRestore");
                  mObserver = null;
              }
          }
      }
  }
  