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.BACKUP_MANIFEST_FILENAME;
  import static com.android.server.backup.UserBackupManagerService.BACKUP_METADATA_FILENAME;
  import static com.android.server.backup.UserBackupManagerService.OP_TYPE_RESTORE_WAIT;
  import static com.android.server.backup.UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
  import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_OPERATION_TIMEOUT;
  
  import android.app.ApplicationThreadConstants;
  import android.app.IBackupAgent;
  import android.app.backup.FullBackup;
  import android.app.backup.IBackupManagerMonitor;
  import android.app.backup.IFullBackupRestoreObserver;
  import android.content.pm.ApplicationInfo;
  import android.content.pm.PackageInfo;
  import android.content.pm.PackageManager.NameNotFoundException;
  import android.content.pm.PackageManagerInternal;
  import android.content.pm.Signature;
  import android.os.ParcelFileDescriptor;
  import android.os.RemoteException;
  import android.provider.Settings;
  import android.text.TextUtils;
  import android.util.Slog;
  
  import com.android.internal.annotations.GuardedBy;
  import com.android.internal.util.Preconditions;
  import com.android.server.LocalServices;
  import com.android.server.backup.BackupAgentTimeoutParameters;
  import com.android.server.backup.BackupRestoreTask;
  import com.android.server.backup.FileMetadata;
  import com.android.server.backup.KeyValueAdbRestoreEngine;
  import com.android.server.backup.UserBackupManagerService;
  import com.android.server.backup.fullbackup.FullBackupObbConnection;
  import com.android.server.backup.utils.BytesReadListener;
  import com.android.server.backup.utils.FullBackupRestoreObserverUtils;
  import com.android.server.backup.utils.RestoreUtils;
  import com.android.server.backup.utils.TarBackupReader;
  
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.util.Arrays;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  
  /**
   * Full restore engine, used by both adb restore and transport-based full restore.
   */
  public class FullRestoreEngine extends RestoreEngine {
  
      private final UserBackupManagerService mBackupManagerService;
      private final int mUserId;
  
      // Task in charge of monitoring timeouts
      private final BackupRestoreTask mMonitorTask;
  
      private final RestoreDeleteObserver mDeleteObserver = new RestoreDeleteObserver();
  
      // Dedicated observer, if any
      private IFullBackupRestoreObserver mObserver;
  
      final IBackupManagerMonitor mMonitor;
  
      // Where we're delivering the file data as we go
      private IBackupAgent mAgent;
  
      // Are we permitted to only deliver a specific package's metadata?
      final PackageInfo mOnlyPackage;
  
      final boolean mAllowApks;
      private final boolean mAllowObbs;
  
      // Which package are we currently handling data for?
      private String mAgentPackage;
  
      // Info for working with the target app process
      private ApplicationInfo mTargetApp;
  
      // Machinery for restoring OBBs
      private FullBackupObbConnection mObbConnection = null;
  
      // possible handling states for a given package in the restore dataset
      private final HashMap<String, RestorePolicy> mPackagePolicies
              = new HashMap<>();
  
      // installer package names for each encountered app, derived from the manifests
      private final HashMap<String, String> mPackageInstallers = new HashMap<>();
  
      // Signatures for a given package found in its manifest file
      private final HashMap<String, Signature[]> mManifestSignatures
              = new HashMap<>();
  
      // Packages we've already wiped data on when restoring their first file
      private final HashSet<String> mClearedPackages = new HashSet<>();
  
      // How much data have we moved?
      private long mBytes;
  
      // Working buffer
      final byte[] mBuffer;
  
      // Pipes for moving data
      private ParcelFileDescriptor[] mPipes = null;
      private final Object mPipesLock = new Object();
  
      // Widget blob to be restored out-of-band
      private byte[] mWidgetData = null;
      private long mAppVersion;
  
      final int mEphemeralOpToken;
  
      private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
      final boolean mIsAdbRestore;
      @GuardedBy("mPipesLock")
      private boolean mPipesClosed;
  
      public FullRestoreEngine(UserBackupManagerService backupManagerService,
              BackupRestoreTask monitorTask, IFullBackupRestoreObserver observer,
              IBackupManagerMonitor monitor, PackageInfo onlyPackage, boolean allowApks,
              boolean allowObbs, int ephemeralOpToken, boolean isAdbRestore) {
          mBackupManagerService = backupManagerService;
          mEphemeralOpToken = ephemeralOpToken;
          mMonitorTask = monitorTask;
          mObserver = observer;
          mMonitor = monitor;
          mOnlyPackage = onlyPackage;
          mAllowApks = allowApks;
          mAllowObbs = allowObbs;
          mBuffer = new byte[32 * 1024];
          mBytes = 0;
          mAgentTimeoutParameters = Preconditions.checkNotNull(
                  backupManagerService.getAgentTimeoutParameters(),
                  "Timeout parameters cannot be null");
          mIsAdbRestore = isAdbRestore;
          mUserId = backupManagerService.getUserId();
      }
  
      public IBackupAgent getAgent() {
          return mAgent;
      }
  
      public byte[] getWidgetData() {
          return mWidgetData;
      }
  
      public boolean restoreOneFile(InputStream instream, boolean mustKillAgent, byte[] buffer,
              PackageInfo onlyPackage, boolean allowApks, int token, IBackupManagerMonitor monitor) {
          if (!isRunning()) {
              Slog.w(TAG, "Restore engine used after halting");
              return false;
          }
  
          BytesReadListener bytesReadListener = new BytesReadListener() {
              @Override
              public void onBytesRead(long bytesRead) {
                  mBytes += bytesRead;
              }
          };
  
          TarBackupReader tarBackupReader = new TarBackupReader(instream,
                  bytesReadListener, monitor);
  
          FileMetadata info;
          try {
              if (MORE_DEBUG) {
                  Slog.v(TAG, "Reading tar header for restoring file");
              }
              info = tarBackupReader.readTarHeaders();
              if (info != null) {
                  if (MORE_DEBUG) {
                      info.dump();
                  }
  
                  final String pkg = info.packageName;
                  if (!pkg.equals(mAgentPackage)) {
                      // In the single-package case, it's a semantic error to expect
                      // one app's data but see a different app's on the wire
                      if (onlyPackage != null) {
                          if (!pkg.equals(onlyPackage.packageName)) {
                              Slog.w(TAG, "Expected data for " + onlyPackage + " but saw " + pkg);
                              setResult(RestoreEngine.TRANSPORT_FAILURE);
                              setRunning(false);
                              return false;
                          }
                      }
  
                      // okay, change in package; set up our various
                      // bookkeeping if we haven't seen it yet
                      if (!mPackagePolicies.containsKey(pkg)) {
                          mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
                      }
  
                      // Clean up the previous agent relationship if necessary,
                      // and let the observer know we're considering a new app.
                      if (mAgent != null) {
                          if (DEBUG) {
                              Slog.d(TAG, "Saw new package; finalizing old one");
                          }
                          // Now we're really done
                          tearDownPipes();
                          tearDownAgent(mTargetApp, mIsAdbRestore);
                          mTargetApp = null;
                          mAgentPackage = null;
                      }
                  }
  
                  if (info.path.equals(BACKUP_MANIFEST_FILENAME)) {
                      Signature[] signatures = tarBackupReader.readAppManifestAndReturnSignatures(
                              info);
                      // readAppManifestAndReturnSignatures() will have extracted the version from
                      // the manifest, so we save it to use in adb key-value restore later.
                      mAppVersion = info.version;
                      PackageManagerInternal pmi = LocalServices.getService(
                              PackageManagerInternal.class);
                      RestorePolicy restorePolicy = tarBackupReader.chooseRestorePolicy(
                              mBackupManagerService.getPackageManager(), allowApks, info, signatures,
                              pmi, mUserId);
                      mManifestSignatures.put(info.packageName, signatures);
                      mPackagePolicies.put(pkg, restorePolicy);
                      mPackageInstallers.put(pkg, info.installerPackageName);
                      // We've read only the manifest content itself at this point,
                      // so consume the footer before looping around to the next
                      // input file
                      tarBackupReader.skipTarPadding(info.size);
                      mObserver = FullBackupRestoreObserverUtils.sendOnRestorePackage(mObserver, pkg);
                  } else if (info.path.equals(BACKUP_METADATA_FILENAME)) {
                      // Metadata blobs!
                      tarBackupReader.readMetadata(info);
  
                      // The following only exist because we want to keep refactoring as safe as
                      // possible, without changing too much.
                      // TODO: Refactor, so that there are no funny things like this.
                      // This is read during TarBackupReader.readMetadata().
                      mWidgetData = tarBackupReader.getWidgetData();
                      // This can be nulled during TarBackupReader.readMetadata().
                      monitor = tarBackupReader.getMonitor();
  
                      tarBackupReader.skipTarPadding(info.size);
                  } else {
                      // Non-manifest, so it's actual file data.  Is this a package
                      // we're ignoring?
                      boolean okay = true;
                      RestorePolicy policy = mPackagePolicies.get(pkg);
                      switch (policy) {
                          case IGNORE:
                              okay = false;
                              break;
  
                          case ACCEPT_IF_APK:
                              // If we're in accept-if-apk state, then the first file we
                              // see MUST be the apk.
                              if (info.domain.equals(FullBackup.APK_TREE_TOKEN)) {
                                  if (DEBUG) {
                                      Slog.d(TAG, "APK file; installing");
                                  }
                                  // Try to install the app.
                                  String installerPackageName = mPackageInstallers.get(pkg);
                                  boolean isSuccessfullyInstalled = RestoreUtils.installApk(
                                          instream, mBackupManagerService.getContext(),
                                          mDeleteObserver, mManifestSignatures,
                                          mPackagePolicies, info, installerPackageName,
                                          bytesReadListener, mUserId);
                                  // good to go; promote to ACCEPT
                                  mPackagePolicies.put(pkg, isSuccessfullyInstalled
                                          ? RestorePolicy.ACCEPT
                                          : RestorePolicy.IGNORE);
                                  // At this point we've consumed this file entry
                                  // ourselves, so just strip the tar footer and
                                  // go on to the next file in the input stream
                                  tarBackupReader.skipTarPadding(info.size);
                                  return true;
                              } else {
                                  // File data before (or without) the apk.  We can't
                                  // handle it coherently in this case so ignore it.
                                  mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
                                  okay = false;
                              }
                              break;
  
                          case ACCEPT:
                              if (info.domain.equals(FullBackup.APK_TREE_TOKEN)) {
                                  if (DEBUG) {
                                      Slog.d(TAG, "apk present but ACCEPT");
                                  }
                                  // we can take the data without the apk, so we
                                  // *want* to do so.  skip the apk by declaring this
                                  // one file not-okay without changing the restore
                                  // policy for the package.
                                  okay = false;
                              }
                              break;
  
                          default:
                              // Something has gone dreadfully wrong when determining
                              // the restore policy from the manifest.  Ignore the
                              // rest of this package's data.
                              Slog.e(TAG, "Invalid policy from manifest");
                              okay = false;
                              mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
                              break;
                      }
  
                      // Is it a *file* we need to drop or is it not a canonical path?
                      if (!isRestorableFile(info) || !isCanonicalFilePath(info.path)) {
                          okay = false;
                      }
  
                      // If the policy is satisfied, go ahead and set up to pipe the
                      // data to the agent.
                      if (MORE_DEBUG && okay && mAgent != null) {
                          Slog.i(TAG, "Reusing existing agent instance");
                      }
                      if (okay && mAgent == null) {
                          if (MORE_DEBUG) {
                              Slog.d(TAG, "Need to launch agent for " + pkg);
                          }
  
                          try {
                              mTargetApp =
                                      mBackupManagerService.getPackageManager()
                                              .getApplicationInfoAsUser(pkg, 0, mUserId);
  
                              // If we haven't sent any data to this app yet, we probably
                              // need to clear it first. Check that.
                              if (!mClearedPackages.contains(pkg)) {
                                  // Apps with their own backup agents are responsible for coherently
                                  // managing a full restore.
                                  // In some rare cases they can't, especially in case of deferred
                                  // restore. In this case check whether this app should be forced to
                                  // clear up.
                                  // TODO: Fix this properly with manifest parameter.
                                  boolean forceClear = shouldForceClearAppDataOnFullRestore(
                                          mTargetApp.packageName);
                                  if (mTargetApp.backupAgentName == null || forceClear) {
                                      if (DEBUG) {
                                          Slog.d(TAG,
                                                  "Clearing app data preparatory to full restore");
                                      }
                                      mBackupManagerService.clearApplicationDataBeforeRestore(pkg);
                                  } else {
                                      if (MORE_DEBUG) {
                                          Slog.d(TAG, "backup agent ("
                                                  + mTargetApp.backupAgentName + ") => no clear");
                                      }
                                  }
                                  mClearedPackages.add(pkg);
                              } else {
                                  if (MORE_DEBUG) {
                                      Slog.d(TAG, "We've initialized this app already; no clear "
                                              + "required");
                                  }
                              }
  
                              // All set; now set up the IPC and launch the agent
                              setUpPipes();
                              mAgent = mBackupManagerService.bindToAgentSynchronous(mTargetApp,
                                      FullBackup.KEY_VALUE_DATA_TOKEN.equals(info.domain)
                                              ? ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL
                                              : ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL);
                              mAgentPackage = pkg;
                          } catch (IOException e) {
                              // fall through to error handling
                          } catch (NameNotFoundException e) {
                              // fall through to error handling
                          }
  
                          if (mAgent == null) {
                              Slog.e(TAG, "Unable to create agent for " + pkg);
                              okay = false;
                              tearDownPipes();
                              mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
                          }
                      }
  
                      // Sanity check: make sure we never give data to the wrong app.  This
                      // should never happen but a little paranoia here won't go amiss.
                      if (okay && !pkg.equals(mAgentPackage)) {
                          Slog.e(TAG, "Restoring data for " + pkg
                                  + " but agent is for " + mAgentPackage);
                          okay = false;
                      }
  
                      // At this point we have an agent ready to handle the full
                      // restore data as well as a pipe for sending data to
                      // that agent.  Tell the agent to start reading from the
                      // pipe.
                      if (okay) {
                          boolean agentSuccess = true;
                          long toCopy = info.size;
                          final boolean isSharedStorage = pkg.equals(SHARED_BACKUP_AGENT_PACKAGE);
                          final long timeout = isSharedStorage ?
                                  mAgentTimeoutParameters.getSharedBackupAgentTimeoutMillis() :
                                  mAgentTimeoutParameters.getRestoreAgentTimeoutMillis();
                          try {
                              mBackupManagerService.prepareOperationTimeout(token,
                                      timeout,
                                      mMonitorTask,
                                      OP_TYPE_RESTORE_WAIT);
  
                              if (FullBackup.OBB_TREE_TOKEN.equals(info.domain)) {
                                  if (DEBUG) {
                                      Slog.d(TAG, "Restoring OBB file for " + pkg
                                              + " : " + info.path);
                                  }
                                  mObbConnection.restoreObbFile(pkg, mPipes[0],
                                          info.size, info.type, info.path, info.mode,
                                          info.mtime, token,
                                          mBackupManagerService.getBackupManagerBinder());
                              } else if (FullBackup.KEY_VALUE_DATA_TOKEN.equals(info.domain)) {
                                  // This is only possible during adb restore.
                                  // TODO: Refactor to clearly separate the flows.
                                  if (DEBUG) {
                                      Slog.d(TAG, "Restoring key-value file for " + pkg
                                              + " : " + info.path);
                                  }
                                  // Set the version saved from manifest entry.
                                  info.version = mAppVersion;
                                  KeyValueAdbRestoreEngine restoreEngine =
                                          new KeyValueAdbRestoreEngine(
                                                  mBackupManagerService,
                                                  mBackupManagerService.getDataDir(), info, mPipes[0],
                                                  mAgent, token);
                                  new Thread(restoreEngine, "restore-key-value-runner").start();
                              } else {
                                  if (MORE_DEBUG) {
                                      Slog.d(TAG, "Invoking agent to restore file " + info.path);
                                  }
                                  // fire up the app's agent listening on the socket.  If
                                  // the agent is running in the system process we can't
                                  // just invoke it asynchronously, so we provide a thread
                                  // for it here.
                                  if (mTargetApp.processName.equals("system")) {
                                      Slog.d(TAG, "system process agent - spinning a thread");
                                      RestoreFileRunnable runner = new RestoreFileRunnable(
                                              mBackupManagerService, mAgent, info, mPipes[0], token);
                                      new Thread(runner, "restore-sys-runner").start();
                                  } else {
                                      mAgent.doRestoreFile(mPipes[0], info.size, info.type,
                                              info.domain, info.path, info.mode, info.mtime,
                                              token, mBackupManagerService.getBackupManagerBinder());
                                  }
                              }
                          } catch (IOException e) {
                              // couldn't dup the socket for a process-local restore
                              Slog.d(TAG, "Couldn't establish restore");
                              agentSuccess = false;
                              okay = false;
                          } catch (RemoteException e) {
                              // whoops, remote entity went away.  We'll eat the content
                              // ourselves, then, and not copy it over.
                              Slog.e(TAG, "Agent crashed during full restore");
                              agentSuccess = false;
                              okay = false;
                          }
  
                          // Copy over the data if the agent is still good
                          if (okay) {
                              if (MORE_DEBUG) {
                                  Slog.v(TAG, "  copying to restore agent: " + toCopy + " bytes");
                              }
                              boolean pipeOkay = true;
                              FileOutputStream pipe = new FileOutputStream(
                                      mPipes[1].getFileDescriptor());
                              while (toCopy > 0) {
                                  int toRead = (toCopy > buffer.length)
                                          ? buffer.length : (int) toCopy;
                                  int nRead = instream.read(buffer, 0, toRead);
                                  if (nRead >= 0) {
                                      mBytes += nRead;
                                  }
                                  if (nRead <= 0) {
                                      break;
                                  }
                                  toCopy -= nRead;
  
                                  // send it to the output pipe as long as things
                                  // are still good
                                  if (pipeOkay) {
                                      try {
                                          pipe.write(buffer, 0, nRead);
                                      } catch (IOException e) {
                                          Slog.e(TAG, "Failed to write to restore pipe: "
                                                  + e.getMessage());
                                          pipeOkay = false;
                                      }
                                  }
                              }
  
                              // done sending that file!  Now we just need to consume
                              // the delta from info.size to the end of block.
                              tarBackupReader.skipTarPadding(info.size);
  
                              // and now that we've sent it all, wait for the remote
                              // side to acknowledge receipt
                              agentSuccess = mBackupManagerService.waitUntilOperationComplete(token);
                          }
  
                          // okay, if the remote end failed at any point, deal with
                          // it by ignoring the rest of the restore on it
                          if (!agentSuccess) {
                              Slog.w(TAG, "Agent failure restoring " + pkg + "; ending restore");
                              mBackupManagerService.getBackupHandler().removeMessages(
                                      MSG_RESTORE_OPERATION_TIMEOUT);
                              tearDownPipes();
                              tearDownAgent(mTargetApp, false);
                              mAgent = null;
                              mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
  
                              // If this was a single-package restore, we halt immediately
                              // with an agent error under these circumstances
                              if (onlyPackage != null) {
                                  setResult(RestoreEngine.TARGET_FAILURE);
                                  setRunning(false);
                                  return false;
                              }
                          }
                      }
  
                      // Problems setting up the agent communication, an explicitly
                      // dropped file, or an already-ignored package: skip to the
                      // next stream entry by reading and discarding this file.
                      if (!okay) {
                          if (MORE_DEBUG) {
                              Slog.d(TAG, "[discarding file content]");
                          }
                          long bytesToConsume = (info.size + 511) & ~511;
                          while (bytesToConsume > 0) {
                              int toRead = (bytesToConsume > buffer.length)
                                      ? buffer.length : (int) bytesToConsume;
                              long nRead = instream.read(buffer, 0, toRead);
                              if (nRead >= 0) {
                                  mBytes += nRead;
                              }
                              if (nRead <= 0) {
                                  break;
                              }
                              bytesToConsume -= nRead;
                          }
                      }
                  }
              }
          } catch (IOException e) {
              if (DEBUG) {
                  Slog.w(TAG, "io exception on restore socket read: " + e.getMessage());
              }
              setResult(RestoreEngine.TRANSPORT_FAILURE);
              info = null;
          }
  
          // If we got here we're either running smoothly or we've finished
          if (info == null) {
              if (MORE_DEBUG) {
                  Slog.i(TAG, "No [more] data for this package; tearing down");
              }
              tearDownPipes();
              setRunning(false);
              if (mustKillAgent) {
                  tearDownAgent(mTargetApp, mIsAdbRestore);
              }
          }
          return (info != null);
      }
  
      private void setUpPipes() throws IOException {
          synchronized (mPipesLock) {
              mPipes = ParcelFileDescriptor.createPipe();
              mPipesClosed = false;
          }
      }
  
      private void tearDownPipes() {
          // Teardown might arise from the inline restore processing or from the asynchronous
          // timeout mechanism, and these might race.  Make sure we don't try to close and
          // null out the pipes twice.
          synchronized (mPipesLock) {
              if (!mPipesClosed && mPipes != null) {
                  try {
                      mPipes[0].close();
                      mPipes[1].close();
  
                      mPipesClosed = true;
                  } catch (IOException e) {
                      Slog.w(TAG, "Couldn't close agent pipes", e);
                  }
              }
          }
      }
  
      private void tearDownAgent(ApplicationInfo app, boolean doRestoreFinished) {
          if (mAgent != null) {
              try {
                  // In the adb restore case, we do restore-finished here
                  if (doRestoreFinished) {
                      final int token = mBackupManagerService.generateRandomIntegerToken();
                      long fullBackupAgentTimeoutMillis =
                              mAgentTimeoutParameters.getFullBackupAgentTimeoutMillis();
                      final AdbRestoreFinishedLatch latch = new AdbRestoreFinishedLatch(
                              mBackupManagerService, token);
                      mBackupManagerService.prepareOperationTimeout(
                              token, fullBackupAgentTimeoutMillis, latch, OP_TYPE_RESTORE_WAIT);
                      if (mTargetApp.processName.equals("system")) {
                          if (MORE_DEBUG) {
                              Slog.d(TAG, "system agent - restoreFinished on thread");
                          }
                          Runnable runner = new AdbRestoreFinishedRunnable(mAgent, token,
                                  mBackupManagerService);
                          new Thread(runner, "restore-sys-finished-runner").start();
                      } else {
                          mAgent.doRestoreFinished(token,
                                  mBackupManagerService.getBackupManagerBinder());
                      }
  
                      latch.await();
                  }
  
                  mBackupManagerService.tearDownAgentAndKill(app);
              } catch (RemoteException e) {
                  Slog.d(TAG, "Lost app trying to shut down");
              }
              mAgent = null;
          }
      }
  
      void handleTimeout() {
          tearDownPipes();
          setResult(RestoreEngine.TARGET_FAILURE);
          setRunning(false);
      }
  
      private static boolean isRestorableFile(FileMetadata info) {
          if (FullBackup.CACHE_TREE_TOKEN.equals(info.domain)) {
              if (MORE_DEBUG) {
                  Slog.i(TAG, "Dropping cache file path " + info.path);
              }
              return false;
          }
  
          if (FullBackup.ROOT_TREE_TOKEN.equals(info.domain)) {
              // It's possible this is "no-backup" dir contents in an archive stream
              // produced on a device running a version of the OS that predates that
              // API.  Respect the no-backup intention and don't let the data get to
              // the app.
              if (info.path.startsWith("no_backup/")) {
                  if (MORE_DEBUG) {
                      Slog.i(TAG, "Dropping no_backup file path " + info.path);
                  }
                  return false;
              }
          }
  
          // Otherwise we think this file is good to go
          return true;
      }
  
      private static boolean isCanonicalFilePath(String path) {
          if (path.contains("..") || path.contains("//")) {
              if (MORE_DEBUG) {
                  Slog.w(TAG, "Dropping invalid path " + path);
              }
              return false;
          }
  
          return true;
      }
  
      /**
       * Returns whether the package is in the list of the packages for which clear app data should
       * be called despite the fact that they have backup agent.
       *
       * <p>The list is read from {@link Settings.Secure#PACKAGES_TO_CLEAR_DATA_BEFORE_FULL_RESTORE}.
       */
      private boolean shouldForceClearAppDataOnFullRestore(String packageName) {
          String packageListString = Settings.Secure.getStringForUser(
                  mBackupManagerService.getContext().getContentResolver(),
                  Settings.Secure.PACKAGES_TO_CLEAR_DATA_BEFORE_FULL_RESTORE,
                  mUserId);
          if (TextUtils.isEmpty(packageListString)) {
              return false;
          }
  
          List<String> packages = Arrays.asList(packageListString.split(";"));
          return packages.contains(packageName);
      }
  
      void sendOnRestorePackage(String name) {
          if (mObserver != null) {
              try {
                  // TODO: use a more user-friendly name string
                  mObserver.onRestorePackage(name);
              } catch (RemoteException e) {
                  Slog.w(TAG, "full restore observer went away: restorePackage");
                  mObserver = null;
              }
          }
      }
  }
  