content: /*
   * Copyright (C) 2009 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.commands.bmgr;
  
  import android.annotation.IntDef;
  import android.annotation.UserIdInt;
  import android.app.backup.BackupManager;
  import android.app.backup.BackupManagerMonitor;
  import android.app.backup.BackupProgress;
  import android.app.backup.BackupTransport;
  import android.app.backup.IBackupManager;
  import android.app.backup.IBackupManagerMonitor;
  import android.app.backup.IBackupObserver;
  import android.app.backup.IRestoreObserver;
  import android.app.backup.IRestoreSession;
  import android.app.backup.ISelectBackupTransportCallback;
  import android.app.backup.RestoreSet;
  import android.content.ComponentName;
  import android.content.Context;
  import android.content.pm.IPackageManager;
  import android.content.pm.PackageInfo;
  import android.os.Bundle;
  import android.os.RemoteException;
  import android.os.ServiceManager;
  import android.os.SystemClock;
  import android.os.UserHandle;
  import android.util.ArraySet;
  import android.util.Slog;
  
  import com.android.internal.annotations.GuardedBy;
  import com.android.internal.annotations.VisibleForTesting;
  
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Set;
  import java.util.concurrent.CountDownLatch;
  
  /**
   * Adb shell command for {@link android.app.backup.IBackupManager}.
   */
  public class Bmgr {
      public static final String TAG = "Bmgr";
  
      private final IBackupManager mBmgr;
      private IRestoreSession mRestore;
  
      private static final String BMGR_NOT_RUNNING_ERR =
              "Error: Could not access the Backup Manager.  Is the system running?";
      private static final String TRANSPORT_NOT_RUNNING_ERR =
              "Error: Could not access the backup transport.  Is the system running?";
      private static final String PM_NOT_RUNNING_ERR =
              "Error: Could not access the Package Manager.  Is the system running?";
  
      private String[] mArgs;
      private int mNextArg;
  
      @VisibleForTesting
      Bmgr(IBackupManager bmgr) {
          mBmgr = bmgr;
      }
  
      Bmgr() {
          mBmgr = IBackupManager.Stub.asInterface(ServiceManager.getService(Context.BACKUP_SERVICE));
      }
  
      public static void main(String[] args) {
          try {
              new Bmgr().run(args);
          } catch (Exception e) {
              System.err.println("Exception caught:");
              e.printStackTrace();
          }
      }
  
      public void run(String[] args) {
          if (args.length < 1) {
              showUsage();
              return;
          }
  
          mArgs = args;
          mNextArg = 0;
          int userId = parseUserId();
          String op = nextArg();
          Slog.v(TAG, "Running " + op + " for user:" + userId);
  
          if (mBmgr == null) {
              System.err.println(BMGR_NOT_RUNNING_ERR);
              return;
          }
  
          if ("activate".equals(op)) {
              doActivateService(userId);
              return;
          }
  
          if ("activated".equals(op)) {
              doActivated(userId);
              return;
          }
  
          if (!isBackupActive(userId)) {
              return;
          }
  
          if ("enabled".equals(op)) {
              doEnabled(userId);
              return;
          }
  
          if ("enable".equals(op)) {
              doEnable(userId);
              return;
          }
  
          if ("run".equals(op)) {
              doRun(userId);
              return;
          }
  
          if ("backup".equals(op)) {
              doBackup(userId);
              return;
          }
  
          if ("init".equals(op)) {
              doInit(userId);
              return;
          }
  
          if ("list".equals(op)) {
              doList(userId);
              return;
          }
  
          if ("restore".equals(op)) {
              doRestore(userId);
              return;
          }
  
          if ("transport".equals(op)) {
              doTransport(userId);
              return;
          }
  
          if ("wipe".equals(op)) {
              doWipe(userId);
              return;
          }
  
          if ("fullbackup".equals(op)) {
              doFullTransportBackup(userId);
              return;
          }
  
          if ("backupnow".equals(op)) {
              doBackupNow(userId);
              return;
          }
  
          if ("cancel".equals(op)) {
              doCancel(userId);
              return;
          }
  
          if ("whitelist".equals(op)) {
              doPrintWhitelist();
              return;
          }
  
          System.err.println("Unknown command");
          showUsage();
      }
  
      boolean isBackupActive(@UserIdInt int userId) {
          try {
              if (!mBmgr.isBackupServiceActive(userId)) {
                  System.err.println(BMGR_NOT_RUNNING_ERR);
                  return false;
              }
          } catch (RemoteException e) {
              System.err.println(e.toString());
              System.err.println(BMGR_NOT_RUNNING_ERR);
              return false;
          }
  
          return true;
      }
  
      private String activatedToString(boolean activated) {
          return activated ? "activated" : "deactivated";
      }
  
      private void doActivated(@UserIdInt int userId) {
          try {
              System.out.println("Backup Manager currently "
                      + activatedToString(mBmgr.isBackupServiceActive(userId)));
          } catch (RemoteException e) {
              System.err.println(e.toString());
              System.err.println(BMGR_NOT_RUNNING_ERR);
          }
  
      }
  
      private String enableToString(boolean enabled) {
          return enabled ? "enabled" : "disabled";
      }
  
      private void doEnabled(@UserIdInt int userId) {
          try {
              boolean isEnabled = mBmgr.isBackupEnabledForUser(userId);
              System.out.println("Backup Manager currently "
                      + enableToString(isEnabled));
          } catch (RemoteException e) {
              System.err.println(e.toString());
              System.err.println(BMGR_NOT_RUNNING_ERR);
          }
      }
  
      private void doEnable(@UserIdInt int userId) {
          String arg = nextArg();
          if (arg == null) {
              showUsage();
              return;
          }
  
          try {
              boolean enable = Boolean.parseBoolean(arg);
              mBmgr.setBackupEnabledForUser(userId, enable);
              System.out.println("Backup Manager now " + enableToString(enable));
          } catch (NumberFormatException e) {
              showUsage();
              return;
          } catch (RemoteException e) {
              System.err.println(e.toString());
              System.err.println(BMGR_NOT_RUNNING_ERR);
          }
      }
  
      void doRun(@UserIdInt int userId) {
          try {
              mBmgr.backupNowForUser(userId);
          } catch (RemoteException e) {
              System.err.println(e.toString());
              System.err.println(BMGR_NOT_RUNNING_ERR);
          }
      }
  
      private void doBackup(@UserIdInt int userId) {
          String pkg = nextArg();
          if (pkg == null) {
              showUsage();
              return;
          }
  
          try {
              mBmgr.dataChangedForUser(userId, pkg);
          } catch (RemoteException e) {
              System.err.println(e.toString());
              System.err.println(BMGR_NOT_RUNNING_ERR);
          }
      }
  
      private void doFullTransportBackup(@UserIdInt int userId) {
          System.out.println("Performing full transport backup");
  
          String pkg;
          ArraySet<String> allPkgs = new ArraySet<String>();
          while ((pkg = nextArg()) != null) {
              allPkgs.add(pkg);
          }
          if (allPkgs.size() > 0) {
              try {
                  mBmgr.fullTransportBackupForUser(
                          userId, allPkgs.toArray(new String[allPkgs.size()]));
              } catch (RemoteException e) {
                  System.err.println(e.toString());
                  System.err.println(BMGR_NOT_RUNNING_ERR);
              }
          }
      }
  
      // IBackupObserver generically usable for any backup/init operation
      private static abstract class Observer extends IBackupObserver.Stub {
          private final Object trigger = new Object();
  
          @GuardedBy("trigger")
          private volatile boolean done = false;
  
          @Override
          public void onUpdate(String currentPackage, BackupProgress backupProgress) {
          }
  
          @Override
          public void onResult(String currentPackage, int status) {
          }
  
          @Override
          public void backupFinished(int status) {
              synchronized (trigger) {
                  done = true;
                  trigger.notify();
              }
          }
  
          public boolean done() {
              return this.done;
          }
  
          // Wait forever
          public void waitForCompletion() {
              waitForCompletion(0);
          }
  
          // Wait for a given time and then give up
          public void waitForCompletion(long timeout) {
              // The backupFinished() callback will throw the 'done' flag; we
              // just sit and wait on that notification.
              final long targetTime = SystemClock.elapsedRealtime() + timeout;
              synchronized (trigger) {
                  // Wait until either we're done, or we've reached a stated positive timeout
                  while (!done && (timeout <= 0 || SystemClock.elapsedRealtime() < targetTime)) {
                      try {
                          trigger.wait(1000L);
                      } catch (InterruptedException ex) {
                      }
                  }
              }
          }
      }
  
      private static class BackupObserver extends Observer {
          @Override
          public void onUpdate(String currentPackage, BackupProgress backupProgress) {
              super.onUpdate(currentPackage, backupProgress);
              System.out.println(
                  "Package " + currentPackage + " with progress: " + backupProgress.bytesTransferred
                      + "/" + backupProgress.bytesExpected);
          }
  
          @Override
          public void onResult(String currentPackage, int status) {
              super.onResult(currentPackage, status);
              System.out.println("Package " + currentPackage + " with result: "
                      + convertBackupStatusToString(status));
          }
  
          @Override
          public void backupFinished(int status) {
              super.backupFinished(status);
              System.out.println("Backup finished with result: "
                      + convertBackupStatusToString(status));
              if (status == BackupManager.ERROR_BACKUP_CANCELLED) {
                  System.out.println("Backups can be cancelled if a backup is already running, check "
                                  + "backup dumpsys");
              }
          }
      }
  
      private static String convertBackupStatusToString(int errorCode) {
          switch (errorCode) {
              case BackupManager.SUCCESS:
                  return "Success";
              case BackupManager.ERROR_BACKUP_NOT_ALLOWED:
                  return "Backup is not allowed";
              case BackupManager.ERROR_PACKAGE_NOT_FOUND:
                  return "Package not found";
              case BackupManager.ERROR_TRANSPORT_ABORTED:
                  return "Transport error";
              case BackupManager.ERROR_TRANSPORT_PACKAGE_REJECTED:
                  return "Transport rejected package because it wasn't able to process it"
                          + " at the time";
              case BackupManager.ERROR_AGENT_FAILURE:
                  return "Agent error";
              case BackupManager.ERROR_TRANSPORT_QUOTA_EXCEEDED:
                  return "Size quota exceeded";
              case BackupManager.ERROR_BACKUP_CANCELLED:
                  return "Backup cancelled";
              default:
                  return "Unknown error";
          }
      }
  
      private void backupNowAllPackages(@UserIdInt int userId, boolean nonIncrementalBackup,
              @Monitor int monitorState) {
          IPackageManager mPm =
                  IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
          if (mPm == null) {
              System.err.println(PM_NOT_RUNNING_ERR);
              return;
          }
          List<PackageInfo> installedPackages = null;
          try {
              installedPackages =  mPm.getInstalledPackages(0, userId).getList();
          } catch (RemoteException e) {
              System.err.println(e.toString());
              System.err.println(PM_NOT_RUNNING_ERR);
          }
          if (installedPackages != null) {
              String[] packages =
                      installedPackages.stream().map(p -> p.packageName).toArray(String[]::new);
              String[] filteredPackages = {};
              try {
                  filteredPackages = mBmgr.filterAppsEligibleForBackupForUser(userId, packages);
              } catch (RemoteException e) {
                  System.err.println(e.toString());
                  System.err.println(BMGR_NOT_RUNNING_ERR);
              }
              backupNowPackages(userId, Arrays.asList(filteredPackages), nonIncrementalBackup,
                      monitorState);
          }
      }
  
      private void backupNowPackages(
              @UserIdInt int userId,
              List<String> packages, boolean nonIncrementalBackup, @Monitor int monitorState) {
          int flags = 0;
          if (nonIncrementalBackup) {
              flags |= BackupManager.FLAG_NON_INCREMENTAL_BACKUP;
          }
          try {
              BackupObserver observer = new BackupObserver();
              BackupMonitor monitor =
                      (monitorState != Monitor.OFF)
                              ? new BackupMonitor(monitorState == Monitor.VERBOSE)
                              : null;
              int err = mBmgr.requestBackupForUser(
                      userId,
                      packages.toArray(new String[packages.size()]),
                      observer,
                      monitor,
                      flags);
              if (err == 0) {
                  // Off and running -- wait for the backup to complete
                  observer.waitForCompletion();
              } else {
                  System.err.println("Unable to run backup");
              }
          } catch (RemoteException e) {
              System.err.println(e.toString());
              System.err.println(BMGR_NOT_RUNNING_ERR);
          }
      }
  
      private void doBackupNow(@UserIdInt int userId) {
          String pkg;
          boolean backupAll = false;
          boolean nonIncrementalBackup = false;
          @Monitor int monitor = Monitor.OFF;
          ArrayList<String> allPkgs = new ArrayList<String>();
          while ((pkg = nextArg()) != null) {
              if (pkg.equals("--all")) {
                  backupAll = true;
              } else if (pkg.equals("--non-incremental")) {
                  nonIncrementalBackup = true;
              } else if (pkg.equals("--incremental")) {
                  nonIncrementalBackup = false;
              } else if (pkg.equals("--monitor")) {
                  monitor = Monitor.NORMAL;
              } else if (pkg.equals("--monitor-verbose")) {
                  monitor = Monitor.VERBOSE;
              } else {
                  if (!allPkgs.contains(pkg)) {
                      allPkgs.add(pkg);
                  }
              }
          }
          if (backupAll) {
              if (allPkgs.size() == 0) {
                  System.out.println("Running " + (nonIncrementalBackup ? "non-" : "") +
                          "incremental backup for all packages.");
                  backupNowAllPackages(userId, nonIncrementalBackup, monitor);
              } else {
                  System.err.println("Provide only '--all' flag or list of packages.");
              }
          } else if (allPkgs.size() > 0) {
              System.out.println("Running " + (nonIncrementalBackup ? "non-" : "") +
                      "incremental backup for " + allPkgs.size() +" requested packages.");
              backupNowPackages(userId, allPkgs, nonIncrementalBackup, monitor);
          } else {
              System.err.println("Provide '--all' flag or list of packages.");
          }
      }
  
      private void doCancel(@UserIdInt int userId) {
          String arg = nextArg();
          if ("backups".equals(arg)) {
              try {
                  mBmgr.cancelBackupsForUser(userId);
              } catch (RemoteException e) {
                  System.err.println(e.toString());
                  System.err.println(BMGR_NOT_RUNNING_ERR);
              }
              return;
          }
  
          System.err.println("Unknown command.");
      }
  
      private void doTransport(@UserIdInt int userId) {
          try {
              String which = nextArg();
              if (which == null) {
                  showUsage();
                  return;
              }
  
              if ("-c".equals(which)) {
                  doTransportByComponent(userId);
                  return;
              }
  
              String old = mBmgr.selectBackupTransportForUser(userId, which);
              if (old == null) {
                  System.out.println("Unknown transport '" + which
                          + "' specified; no changes made.");
              } else {
                  System.out.println("Selected transport " + which + " (formerly " + old + ")");
              }
  
          } catch (RemoteException e) {
              System.err.println(e.toString());
              System.err.println(BMGR_NOT_RUNNING_ERR);
          }
      }
  
      private void doTransportByComponent(@UserIdInt int userId) {
          String which = nextArg();
          if (which == null) {
              showUsage();
              return;
          }
  
          final CountDownLatch latch = new CountDownLatch(1);
  
          try {
              mBmgr.selectBackupTransportAsyncForUser(
                      userId,
                      ComponentName.unflattenFromString(which),
                      new ISelectBackupTransportCallback.Stub() {
                          @Override
                          public void onSuccess(String transportName) {
                              System.out.println("Success. Selected transport: " + transportName);
                              latch.countDown();
                          }
  
                          @Override
                          public void onFailure(int reason) {
                              System.err.println("Failure. error=" + reason);
                              latch.countDown();
                          }
                      });
          } catch (RemoteException e) {
              System.err.println(e.toString());
              System.err.println(BMGR_NOT_RUNNING_ERR);
              return;
          }
  
          try {
              latch.await();
          } catch (InterruptedException e) {
              System.err.println("Operation interrupted.");
          }
      }
  
      private void doWipe(@UserIdInt int userId) {
          String transport = nextArg();
          if (transport == null) {
              showUsage();
              return;
          }
  
          String pkg = nextArg();
          if (pkg == null) {
              showUsage();
              return;
          }
  
          try {
              mBmgr.clearBackupDataForUser(userId, transport, pkg);
              System.out.println("Wiped backup data for " + pkg + " on " + transport);
          } catch (RemoteException e) {
              System.err.println(e.toString());
              System.err.println(BMGR_NOT_RUNNING_ERR);
          }
      }
  
      class InitObserver extends Observer {
          public int result = BackupTransport.TRANSPORT_ERROR;
  
          @Override
          public void backupFinished(int status) {
              super.backupFinished(status);
              result = status;
          }
      }
  
      private void doInit(@UserIdInt int userId) {
          ArraySet<String> transports = new ArraySet<>();
          String transport;
          while ((transport = nextArg()) != null) {
              transports.add(transport);
          }
          if (transports.size() == 0) {
              showUsage();
              return;
          }
  
          InitObserver observer = new InitObserver();
          try {
              System.out.println("Initializing transports: " + transports);
              mBmgr.initializeTransportsForUser(
                      userId, transports.toArray(new String[transports.size()]), observer);
              observer.waitForCompletion(30*1000L);
              System.out.println("Initialization result: " + observer.result);
          } catch (RemoteException e) {
              System.err.println(e.toString());
              System.err.println(BMGR_NOT_RUNNING_ERR);
          }
      }
  
      private void doList(@UserIdInt int userId) {
          String arg = nextArg();     // sets, transports, packages set#
          if ("transports".equals(arg)) {
              doListTransports(userId);
              return;
          }
  
          // The rest of the 'list' options work with a restore session on the current transport
          try {
              mRestore = mBmgr.beginRestoreSessionForUser(userId, null, null);
              if (mRestore == null) {
                  System.err.println(BMGR_NOT_RUNNING_ERR);
                  return;
              }
  
              if ("sets".equals(arg)) {
                  doListRestoreSets();
              }
  
              mRestore.endRestoreSession();
          } catch (RemoteException e) {
              System.err.println(e.toString());
              System.err.println(BMGR_NOT_RUNNING_ERR);
          }
      }
  
      private void doListTransports(@UserIdInt int userId) {
          String arg = nextArg();
  
          try {
              if ("-c".equals(arg)) {
                  for (ComponentName transport : mBmgr.listAllTransportComponentsForUser(userId)) {
                      System.out.println(transport.flattenToShortString());
                  }
                  return;
              }
  
              String current = mBmgr.getCurrentTransportForUser(userId);
              String[] transports = mBmgr.listAllTransportsForUser(userId);
              if (transports == null || transports.length == 0) {
                  System.out.println("No transports available.");
                  return;
              }
  
              for (String t : transports) {
                  String pad = (t.equals(current)) ? "  * " : "    ";
                  System.out.println(pad + t);
              }
          } catch (RemoteException e) {
              System.err.println(e.toString());
              System.err.println(BMGR_NOT_RUNNING_ERR);
          }
      }
  
      private void doListRestoreSets() {
          try {
              RestoreObserver observer = new RestoreObserver();
              // TODO implement monitor here
              int err = mRestore.getAvailableRestoreSets(observer, null);
              if (err != 0) {
                  System.out.println("Unable to request restore sets");
              } else {
                  observer.waitForCompletion();
                  printRestoreSets(observer.sets);
              }
          } catch (RemoteException e) {
              System.err.println(e.toString());
              System.err.println(TRANSPORT_NOT_RUNNING_ERR);
          }
      }
  
      private void printRestoreSets(RestoreSet[] sets) {
          if (sets == null || sets.length == 0) {
              System.out.println("No restore sets");
              return;
          }
          for (RestoreSet s : sets) {
              System.out.println("  " + Long.toHexString(s.token) + " : " + s.name);
          }
      }
  
      class RestoreObserver extends IRestoreObserver.Stub {
          boolean done;
          RestoreSet[] sets = null;
  
          public void restoreSetsAvailable(RestoreSet[] result) {
              synchronized (this) {
                  sets = result;
                  done = true;
                  this.notify();
              }
          }
  
          public void restoreStarting(int numPackages) {
              System.out.println("restoreStarting: " + numPackages + " packages");
          }
  
          public void onUpdate(int nowBeingRestored, String currentPackage) {
              System.out.println("onUpdate: " + nowBeingRestored + " = " + currentPackage);
          }
  
          public void restoreFinished(int error) {
              System.out.println("restoreFinished: " + error);
              synchronized (this) {
                  done = true;
                  this.notify();
              }
          }
  
          /**
           * Wait until either {@link #restoreFinished} or {@link #restoreStarting} is called.
           * Once one is called, it clears the internal flag again, so that the same observer intance
           * can be reused for a next operation.
           */
          public void waitForCompletion() {
              // The restoreFinished() callback will throw the 'done' flag; we
              // just sit and wait on that notification.
              synchronized (this) {
                  while (!this.done) {
                      try {
                          this.wait();
                      } catch (InterruptedException ex) {
                      }
                  }
                  done = false;
              }
          }
      }
  
      private void doRestore(@UserIdInt int userId) {
          String arg = nextArg();
          if (arg == null) {
              showUsage();
              return;
          }
  
          if (arg.indexOf('.') >= 0 || arg.equals("android")) {
              // it's a package name
              doRestorePackage(arg);
          } else {
              try {
                  long token = Long.parseLong(arg, 16);
                  HashSet<String> filter = null;
                  while ((arg = nextArg()) != null) {
                      if (filter == null) filter = new HashSet<String>();
                      filter.add(arg);
                  }
  
                  doRestoreAll(userId, token, filter);
              } catch (NumberFormatException e) {
                  showUsage();
                  return;
              }
          }
      }
  
      private void doRestorePackage(String pkg) {
          System.err.println("The syntax 'restore <package>' is no longer supported, please use ");
          System.err.println("'restore <token> <package>'.");
      }
  
      private void doRestoreAll(@UserIdInt int userId, long token, HashSet<String> filter) {
          RestoreObserver observer = new RestoreObserver();
  
          try {
              boolean didRestore = false;
              mRestore = mBmgr.beginRestoreSessionForUser(userId, null, null);
              if (mRestore == null) {
                  System.err.println(BMGR_NOT_RUNNING_ERR);
                  return;
              }
              RestoreSet[] sets = null;
              // TODO implement monitor here
              int err = mRestore.getAvailableRestoreSets(observer, null);
              if (err == 0) {
                  observer.waitForCompletion();
                  sets = observer.sets;
                  if (sets != null) {
                      for (RestoreSet s : sets) {
                          if (s.token == token) {
                              System.out.println("Scheduling restore: " + s.name);
                              if (filter == null) {
                                  didRestore = (mRestore.restoreAll(token, observer, null) == 0);
                              } else {
                                  String[] names = new String[filter.size()];
                                  filter.toArray(names);
                                  didRestore = (mRestore.restorePackages(token, observer, names,
                                          null) == 0);
                              }
                              break;
                          }
                      }
                  }
              }
              if (!didRestore) {
                  if (sets == null || sets.length == 0) {
                      System.out.println("No available restore sets; no restore performed");
                  } else {
                      System.out.println("No matching restore set token.  Available sets:");
                      printRestoreSets(sets);
                  }
              }
  
              // if we kicked off a restore successfully, we have to wait for it
              // to complete before we can shut down the restore session safely
              if (didRestore) {
                  observer.waitForCompletion();
              }
  
              // once the restore has finished, close down the session and we're done
              mRestore.endRestoreSession();
  
              System.out.println("done");
          } catch (RemoteException e) {
              System.err.println(e.toString());
              System.err.println(BMGR_NOT_RUNNING_ERR);
          }
      }
  
      private void doPrintWhitelist() {
          try {
              final String[] whitelist = mBmgr.getTransportWhitelist();
              if (whitelist != null) {
                  for (String transport : whitelist) {
                      System.out.println(transport);
                  }
              }
          } catch (RemoteException e) {
              System.err.println(e.toString());
              System.err.println(BMGR_NOT_RUNNING_ERR);
          }
      }
  
      private void doActivateService(int userId) {
          String arg = nextArg();
          if (arg == null) {
              showUsage();
              return;
          }
  
          try {
              boolean activate = Boolean.parseBoolean(arg);
              mBmgr.setBackupServiceActive(userId, activate);
              System.out.println(
                      "Backup service now "
                              + (activate ? "activated" : "deactivated")
                              + " for user "
                              + userId);
          } catch (RemoteException e) {
              System.err.println(e.toString());
              System.err.println(BMGR_NOT_RUNNING_ERR);
          }
      }
  
      private String nextArg() {
          if (mNextArg >= mArgs.length) {
              return null;
          }
          String arg = mArgs[mNextArg];
          mNextArg++;
          return arg;
      }
  
      private int parseUserId() {
          String arg = nextArg();
          if ("--user".equals(arg)) {
              return UserHandle.parseUserArg(nextArg());
          } else {
              mNextArg--;
              return UserHandle.USER_SYSTEM;
          }
      }
  
      private static void showUsage() {
          System.err.println("usage: bmgr [--user <userId>] [backup|restore|list|transport|run]");
          System.err.println("       bmgr backup PACKAGE");
          System.err.println("       bmgr enable BOOL");
          System.err.println("       bmgr enabled");
          System.err.println("       bmgr list transports [-c]");
          System.err.println("       bmgr list sets");
          System.err.println("       bmgr transport WHICH|-c WHICH_COMPONENT");
          System.err.println("       bmgr restore TOKEN");
          System.err.println("       bmgr restore TOKEN PACKAGE...");
          System.err.println("       bmgr run");
          System.err.println("       bmgr wipe TRANSPORT PACKAGE");
          System.err.println("       bmgr fullbackup PACKAGE...");
          System.err.println("       bmgr backupnow [--monitor|--monitor-verbose] --all|PACKAGE...");
          System.err.println("       bmgr cancel backups");
          System.err.println("       bmgr init TRANSPORT...");
          System.err.println("       bmgr activate BOOL");
          System.err.println("       bmgr activated");
          System.err.println("");
          System.err.println("The '--user' option specifies the user on which the operation is run.");
          System.err.println("It must be the first argument before the operation.");
          System.err.println("The default value is 0 which is the system user.");
          System.err.println("");
          System.err.println("The 'backup' command schedules a backup pass for the named package.");
          System.err.println("Note that the backup pass will effectively be a no-op if the package");
          System.err.println("does not actually have changed data to store.");
          System.err.println("");
          System.err.println("The 'enable' command enables or disables the entire backup mechanism.");
          System.err.println("If the argument is 'true' it will be enabled, otherwise it will be");
          System.err.println("disabled.  When disabled, neither backup or restore operations will");
          System.err.println("be performed.");
          System.err.println("");
          System.err.println("The 'enabled' command reports the current enabled/disabled state of");
          System.err.println("the backup mechanism.");
          System.err.println("");
          System.err.println("The 'list transports' command reports the names of the backup transports");
          System.err.println("BackupManager is currently bound to. These names can be passed as arguments");
          System.err.println("to the 'transport' and 'wipe' commands.  The currently active transport");
          System.err.println("is indicated with a '*' character. If -c flag is used, all available");
          System.err.println("transport components on the device are listed. These can be used with");
          System.err.println("the component variant of 'transport' command.");
          System.err.println("");
          System.err.println("The 'list sets' command reports the token and name of each restore set");
          System.err.println("available to the device via the currently active transport.");
          System.err.println("");
          System.err.println("The 'transport' command designates the named transport as the currently");
          System.err.println("active one.  This setting is persistent across reboots. If -c flag is");
          System.err.println("specified, the following string is treated as a component name.");
          System.err.println("");
          System.err.println("The 'restore' command when given just a restore token initiates a full-system");
          System.err.println("restore operation from the currently active transport.  It will deliver");
          System.err.println("the restore set designated by the TOKEN argument to each application");
          System.err.println("that had contributed data to that restore set.");
          System.err.println("");
          System.err.println("The 'restore' command when given a token and one or more package names");
          System.err.println("initiates a restore operation of just those given packages from the restore");
          System.err.println("set designated by the TOKEN argument.  It is effectively the same as the");
          System.err.println("'restore' operation supplying only a token, but applies a filter to the");
          System.err.println("set of applications to be restored.");
          System.err.println("");
          System.err.println("The 'run' command causes any scheduled backup operation to be initiated");
          System.err.println("immediately, without the usual waiting period for batching together");
          System.err.println("data changes.");
          System.err.println("");
          System.err.println("The 'wipe' command causes all backed-up data for the given package to be");
          System.err.println("erased from the given transport's storage.  The next backup operation");
          System.err.println("that the given application performs will rewrite its entire data set.");
          System.err.println("Transport names to use here are those reported by 'list transports'.");
          System.err.println("");
          System.err.println("The 'fullbackup' command induces a full-data stream backup for one or more");
          System.err.println("packages.  The data is sent via the currently active transport.");
          System.err.println("");
          System.err.println("The 'backupnow' command runs an immediate backup for one or more packages.");
          System.err.println("    --all flag runs backup for all eligible packages.");
          System.err.println("    --monitor flag prints monitor events.");
          System.err.println("    --monitor-verbose flag prints monitor events with all keys.");
          System.err.println("For each package it will run key/value or full data backup ");
          System.err.println("depending on the package's manifest declarations.");
          System.err.println("The data is sent via the currently active transport.");
          System.err.println("");
          System.err.println("The 'cancel backups' command cancels all running backups.");
          System.err.println("");
          System.err.println("The 'init' command initializes the given transports, wiping all data");
          System.err.println("from their backing data stores.");
          System.err.println("");
          System.err.println("The 'activate' command activates or deactivates the backup service.");
          System.err.println("If the argument is 'true' it will be activated, otherwise it will be");
          System.err.println("deactivated. When deactivated, the service will not be running and no");
          System.err.println("operations can be performed until activation.");
          System.err.println("");
          System.err.println("The 'activated' command reports the current activated/deactivated");
          System.err.println("state of the backup mechanism.");
      }
  
      private static class BackupMonitor extends IBackupManagerMonitor.Stub {
          private final boolean mVerbose;
  
          private BackupMonitor(boolean verbose) {
              mVerbose = verbose;
          }
  
          @Override
          public void onEvent(Bundle event) throws RemoteException {
              StringBuilder out = new StringBuilder();
              int id = event.getInt(BackupManagerMonitor.EXTRA_LOG_EVENT_ID);
              int category = event.getInt(BackupManagerMonitor.EXTRA_LOG_EVENT_CATEGORY);
              out.append("=> Event{").append(eventCategoryToString(category));
              out.append(" / ").append(eventIdToString(id));
              String packageName = event.getString(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_NAME);
              if (packageName != null) {
                  out.append(" : package = ").append(packageName);
                  if (event.containsKey(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_LONG_VERSION)) {
                      long version =
                              event.getLong(
                                      BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_LONG_VERSION);
                      out.append("(v").append(version).append(")");
                  }
              }
              if (mVerbose) {
                  Set<String> remainingKeys = new ArraySet<>(event.keySet());
                  remainingKeys.remove(BackupManagerMonitor.EXTRA_LOG_EVENT_ID);
                  remainingKeys.remove(BackupManagerMonitor.EXTRA_LOG_EVENT_CATEGORY);
                  remainingKeys.remove(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_NAME);
                  remainingKeys.remove(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_LONG_VERSION);
                  remainingKeys.remove(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_VERSION);
                  if (!remainingKeys.isEmpty()) {
                      out.append(", other keys =");
                      for (String key : remainingKeys) {
                          out.append(" ").append(key);
                      }
                  }
              }
              out.append("}");
              System.out.println(out.toString());
          }
      }
  
      private static String eventCategoryToString(int eventCategory) {
          switch (eventCategory) {
              case BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT:
                  return "TRANSPORT";
              case BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT:
                  return "AGENT";
              case BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY:
                  return "BACKUP_MANAGER_POLICY";
              default:
                  return "UNKNOWN_CATEGORY";
          }
      }
  
      private static String eventIdToString(int eventId) {
          switch (eventId) {
              case BackupManagerMonitor.LOG_EVENT_ID_FULL_BACKUP_CANCEL:
                  return "FULL_BACKUP_CANCEL";
              case BackupManagerMonitor.LOG_EVENT_ID_ILLEGAL_KEY:
                  return "ILLEGAL_KEY";
              case BackupManagerMonitor.LOG_EVENT_ID_NO_DATA_TO_SEND:
                  return "NO_DATA_TO_SEND";
              case BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_INELIGIBLE:
                  return "PACKAGE_INELIGIBLE";
              case BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_KEY_VALUE_PARTICIPANT:
                  return "PACKAGE_KEY_VALUE_PARTICIPANT";
              case BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_STOPPED:
                  return "PACKAGE_STOPPED";
              case BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_NOT_FOUND:
                  return "PACKAGE_NOT_FOUND";
              case BackupManagerMonitor.LOG_EVENT_ID_BACKUP_DISABLED:
                  return "BACKUP_DISABLED";
              case BackupManagerMonitor.LOG_EVENT_ID_DEVICE_NOT_PROVISIONED:
                  return "DEVICE_NOT_PROVISIONED";
              case BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_TRANSPORT_NOT_PRESENT:
                  return "PACKAGE_TRANSPORT_NOT_PRESENT";
              case BackupManagerMonitor.LOG_EVENT_ID_ERROR_PREFLIGHT:
                  return "ERROR_PREFLIGHT";
              case BackupManagerMonitor.LOG_EVENT_ID_QUOTA_HIT_PREFLIGHT:
                  return "QUOTA_HIT_PREFLIGHT";
              case BackupManagerMonitor.LOG_EVENT_ID_EXCEPTION_FULL_BACKUP:
                  return "EXCEPTION_FULL_BACKUP";
              case BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_BACKUP_CANCEL:
                  return "KEY_VALUE_BACKUP_CANCEL";
              case BackupManagerMonitor.LOG_EVENT_ID_NO_RESTORE_METADATA_AVAILABLE:
                  return "NO_RESTORE_METADATA_AVAILABLE";
              case BackupManagerMonitor.LOG_EVENT_ID_NO_PM_METADATA_RECEIVED:
                  return "NO_PM_METADATA_RECEIVED";
              case BackupManagerMonitor.LOG_EVENT_ID_PM_AGENT_HAS_NO_METADATA:
                  return "PM_AGENT_HAS_NO_METADATA";
              case BackupManagerMonitor.LOG_EVENT_ID_LOST_TRANSPORT:
                  return "LOST_TRANSPORT";
              case BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_NOT_PRESENT:
                  return "PACKAGE_NOT_PRESENT";
              case BackupManagerMonitor.LOG_EVENT_ID_RESTORE_VERSION_HIGHER:
                  return "RESTORE_VERSION_HIGHER";
              case BackupManagerMonitor.LOG_EVENT_ID_APP_HAS_NO_AGENT:
                  return "APP_HAS_NO_AGENT";
              case BackupManagerMonitor.LOG_EVENT_ID_SIGNATURE_MISMATCH:
                  return "SIGNATURE_MISMATCH";
              case BackupManagerMonitor.LOG_EVENT_ID_CANT_FIND_AGENT:
                  return "CANT_FIND_AGENT";
              case BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_RESTORE_TIMEOUT:
                  return "KEY_VALUE_RESTORE_TIMEOUT";
              case BackupManagerMonitor.LOG_EVENT_ID_RESTORE_ANY_VERSION:
                  return "RESTORE_ANY_VERSION";
              case BackupManagerMonitor.LOG_EVENT_ID_VERSIONS_MATCH:
                  return "VERSIONS_MATCH";
              case BackupManagerMonitor.LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER:
                  return "VERSION_OF_BACKUP_OLDER";
              case BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE_SIGNATURE_MISMATCH:
                  return "FULL_RESTORE_SIGNATURE_MISMATCH";
              case BackupManagerMonitor.LOG_EVENT_ID_SYSTEM_APP_NO_AGENT:
                  return "SYSTEM_APP_NO_AGENT";
              case BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE_ALLOW_BACKUP_FALSE:
                  return "FULL_RESTORE_ALLOW_BACKUP_FALSE";
              case BackupManagerMonitor.LOG_EVENT_ID_APK_NOT_INSTALLED:
                  return "APK_NOT_INSTALLED";
              case BackupManagerMonitor.LOG_EVENT_ID_CANNOT_RESTORE_WITHOUT_APK:
                  return "CANNOT_RESTORE_WITHOUT_APK";
              case BackupManagerMonitor.LOG_EVENT_ID_MISSING_SIGNATURE:
                  return "MISSING_SIGNATURE";
              case BackupManagerMonitor.LOG_EVENT_ID_EXPECTED_DIFFERENT_PACKAGE:
                  return "EXPECTED_DIFFERENT_PACKAGE";
              case BackupManagerMonitor.LOG_EVENT_ID_UNKNOWN_VERSION:
                  return "UNKNOWN_VERSION";
              case BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE_TIMEOUT:
                  return "FULL_RESTORE_TIMEOUT";
              case BackupManagerMonitor.LOG_EVENT_ID_CORRUPT_MANIFEST:
                  return "CORRUPT_MANIFEST";
              case BackupManagerMonitor.LOG_EVENT_ID_WIDGET_METADATA_MISMATCH:
                  return "WIDGET_METADATA_MISMATCH";
              case BackupManagerMonitor.LOG_EVENT_ID_WIDGET_UNKNOWN_VERSION:
                  return "WIDGET_UNKNOWN_VERSION";
              case BackupManagerMonitor.LOG_EVENT_ID_NO_PACKAGES:
                  return "NO_PACKAGES";
              case BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_IS_NULL:
                  return "TRANSPORT_IS_NULL";
              default:
                  return "UNKNOWN_ID";
          }
      }
  
      @IntDef({Monitor.OFF, Monitor.NORMAL, Monitor.VERBOSE})
      @Retention(RetentionPolicy.SOURCE)
      private @interface Monitor {
          int OFF = 0;
          int NORMAL = 1;
          int VERBOSE = 2;
      }
  }
  