content: /*
   * Copyright (C) 2018 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.keyvalue;
  
  import android.annotation.Nullable;
  import android.app.backup.BackupManager;
  import android.app.backup.BackupManagerMonitor;
  import android.app.backup.BackupTransport;
  import android.app.backup.IBackupManagerMonitor;
  import android.app.backup.IBackupObserver;
  import android.content.pm.PackageInfo;
  import android.util.EventLog;
  import android.util.Slog;
  
  import com.android.internal.annotations.VisibleForTesting;
  import com.android.server.EventLogTags;
  import com.android.server.backup.BackupManagerService;
  import com.android.server.backup.DataChangedJournal;
  import com.android.server.backup.UserBackupManagerService;
  import com.android.server.backup.remote.RemoteResult;
  import com.android.server.backup.utils.BackupManagerMonitorUtils;
  import com.android.server.backup.utils.BackupObserverUtils;
  
  import java.io.File;
  import java.io.IOException;
  import java.security.NoSuchAlgorithmException;
  import java.util.List;
  
  /**
   * Reports events that happen during a key-value backup task to:
   *
   * <ul>
   *   <li>Logcat (main and event buffers).
   *   <li>Backup observer (see {@link IBackupObserver}).
   *   <li>Backup manager monitor (see {@link IBackupManagerMonitor}).
   * </ul>
   */
  // TODO: In KeyValueBackupTaskTest, remove direct assertions on logcat, observer or monitor and
  //       verify calls to this object. Add these and more assertions to the test of this class.
  @VisibleForTesting
  public class KeyValueBackupReporter {
      @VisibleForTesting static final String TAG = "KeyValueBackupTask";
      private static final boolean DEBUG = BackupManagerService.DEBUG;
      @VisibleForTesting static final boolean MORE_DEBUG = BackupManagerService.MORE_DEBUG;
  
      static void onNewThread(String threadName) {
          if (DEBUG) {
              Slog.d(TAG, "Spinning thread " + threadName);
          }
      }
  
      private final UserBackupManagerService mBackupManagerService;
      private final IBackupObserver mObserver;
      @Nullable private IBackupManagerMonitor mMonitor;
  
      KeyValueBackupReporter(
              UserBackupManagerService backupManagerService,
              IBackupObserver observer,
              @Nullable IBackupManagerMonitor monitor) {
          mBackupManagerService = backupManagerService;
          mObserver = observer;
          mMonitor = monitor;
      }
  
      /** Returns the monitor or {@code null} if we lost connection to it. */
      @Nullable
      IBackupManagerMonitor getMonitor() {
          return mMonitor;
      }
  
      IBackupObserver getObserver() {
          return mObserver;
      }
  
      void onSkipBackup() {
          if (DEBUG) {
              Slog.d(TAG, "Skipping backup since one is already in progress");
          }
      }
  
      void onEmptyQueueAtStart() {
          Slog.w(TAG, "Backup begun with an empty queue, nothing to do");
      }
  
      void onQueueReady(List<String> queue) {
          if (DEBUG) {
              Slog.v(TAG, "Beginning backup of " + queue.size() + " targets");
          }
      }
  
      void onTransportReady(String transportName) {
          EventLog.writeEvent(EventLogTags.BACKUP_START, transportName);
      }
  
      void onInitializeTransport(String transportName) {
          Slog.i(TAG, "Initializing transport and resetting backup state");
      }
  
      void onTransportInitialized(int status) {
          if (status == BackupTransport.TRANSPORT_OK) {
              EventLog.writeEvent(EventLogTags.BACKUP_INITIALIZE);
          } else {
              EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, "(initialize)");
              Slog.e(TAG, "Transport error in initializeDevice()");
          }
      }
  
      void onInitializeTransportError(Exception e) {
          Slog.e(TAG, "Error during initialization", e);
      }
  
      void onSkipPm() {
          Slog.d(TAG, "Skipping backup of PM metadata");
      }
  
      void onExtractPmAgentDataError(Exception e) {
          Slog.e(TAG, "Error during PM metadata backup", e);
      }
  
      void onStartPackageBackup(String packageName) {
          Slog.d(TAG, "Starting key-value backup of " + packageName);
      }
  
      void onPackageNotEligibleForBackup(String packageName) {
          Slog.i(TAG, "Package " + packageName + " no longer supports backup, skipping");
          BackupObserverUtils.sendBackupOnPackageResult(
                  mObserver, packageName, BackupManager.ERROR_BACKUP_NOT_ALLOWED);
      }
  
      void onPackageEligibleForFullBackup(String packageName) {
          Slog.i(
                  TAG,
                  "Package " + packageName + " performs full-backup rather than key-value, skipping");
          BackupObserverUtils.sendBackupOnPackageResult(
                  mObserver, packageName, BackupManager.ERROR_BACKUP_NOT_ALLOWED);
      }
  
      void onPackageStopped(String packageName) {
          BackupObserverUtils.sendBackupOnPackageResult(
                  mObserver, packageName, BackupManager.ERROR_BACKUP_NOT_ALLOWED);
      }
  
      void onAgentUnknown(String packageName) {
          Slog.d(TAG, "Package does not exist, skipping");
          BackupObserverUtils.sendBackupOnPackageResult(
                  mObserver, packageName, BackupManager.ERROR_PACKAGE_NOT_FOUND);
      }
  
      void onBindAgentError(String packageName, SecurityException e) {
          Slog.d(TAG, "Error in bind/backup", e);
          BackupObserverUtils.sendBackupOnPackageResult(
                  mObserver, packageName, BackupManager.ERROR_AGENT_FAILURE);
      }
  
      void onAgentError(String packageName) {
          if (MORE_DEBUG) {
              Slog.i(TAG, "Agent failure for " + packageName + ", re-staging");
          }
          BackupObserverUtils.sendBackupOnPackageResult(
                  mObserver, packageName, BackupManager.ERROR_AGENT_FAILURE);
      }
  
      void onExtractAgentData(String packageName) {
          if (DEBUG) {
              Slog.d(TAG, "Invoking agent on " + packageName);
          }
      }
  
      void onAgentFilesReady(File backupDataFile) {
          if (MORE_DEBUG) {
              Slog.d(TAG, "Data file: " + backupDataFile);
          }
      }
  
      void onRestoreconFailed(File backupDataFile) {
          Slog.e(TAG, "SELinux restorecon failed on " + backupDataFile);
      }
  
      void onCallAgentDoBackupError(String packageName, boolean callingAgent, Exception e) {
          if (callingAgent) {
              Slog.e(TAG, "Error invoking agent on " + packageName + ": " + e);
              BackupObserverUtils.sendBackupOnPackageResult(
                      mObserver, packageName, BackupManager.ERROR_AGENT_FAILURE);
          } else {
              Slog.e(TAG, "Error before invoking agent on " + packageName + ": " + e);
          }
          EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, packageName, e.toString());
      }
  
      void onFailAgentError(String packageName) {
          Slog.w(TAG, "Error conveying failure to " + packageName);
      }
  
      void onAgentIllegalKey(PackageInfo packageInfo, String key) {
          String packageName = packageInfo.packageName;
          EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, packageName, "bad key");
          mMonitor =
                  BackupManagerMonitorUtils.monitorEvent(
                          mMonitor,
                          BackupManagerMonitor.LOG_EVENT_ID_ILLEGAL_KEY,
                          packageInfo,
                          BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
                          BackupManagerMonitorUtils.putMonitoringExtra(
                                  null, BackupManagerMonitor.EXTRA_LOG_ILLEGAL_KEY, key));
          BackupObserverUtils.sendBackupOnPackageResult(
                  mObserver, packageName, BackupManager.ERROR_AGENT_FAILURE);
          if (MORE_DEBUG) {
              Slog.i(
                      TAG,
                      "Agent failure for " + packageName + " with illegal key " + key + ", dropped");
          }
      }
  
      void onAgentDataError(String packageName, IOException e) {
          Slog.w(TAG, "Unable to read/write agent data for " + packageName + ": " + e);
      }
  
      void onDigestError(NoSuchAlgorithmException e) {
          Slog.e(TAG, "Unable to use SHA-1!");
      }
  
      void onWriteWidgetData(boolean priorStateExists, @Nullable byte[] widgetState) {
          if (MORE_DEBUG) {
              Slog.i(
                      TAG,
                      "Checking widget update: state="
                              + (widgetState != null)
                              + " prior="
                              + priorStateExists);
          }
      }
  
      void onTransportPerformBackup(String packageName) {
          if (MORE_DEBUG) {
              Slog.v(TAG, "Sending non-empty data to transport for " + packageName);
          }
      }
  
      void onEmptyData(PackageInfo packageInfo) {
          if (MORE_DEBUG) {
              Slog.i(TAG, "No backup data written, not calling transport");
          }
          mMonitor =
                  BackupManagerMonitorUtils.monitorEvent(
                          mMonitor,
                          BackupManagerMonitor.LOG_EVENT_ID_NO_DATA_TO_SEND,
                          packageInfo,
                          BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
                          null);
      }
  
      void onPackageBackupComplete(String packageName, long size) {
          BackupObserverUtils.sendBackupOnPackageResult(
                  mObserver, packageName, BackupManager.SUCCESS);
          EventLog.writeEvent(EventLogTags.BACKUP_PACKAGE, packageName, size);
          mBackupManagerService.logBackupComplete(packageName);
      }
  
      void onPackageBackupRejected(String packageName) {
          BackupObserverUtils.sendBackupOnPackageResult(
                  mObserver, packageName, BackupManager.ERROR_TRANSPORT_PACKAGE_REJECTED);
          EventLogTags.writeBackupAgentFailure(packageName, "Transport rejected");
      }
  
      void onPackageBackupQuotaExceeded(String packageName) {
          if (MORE_DEBUG) {
              Slog.d(TAG, "Package " + packageName + " hit quota limit on key-value backup");
          }
          BackupObserverUtils.sendBackupOnPackageResult(
                  mObserver, packageName, BackupManager.ERROR_TRANSPORT_QUOTA_EXCEEDED);
          EventLog.writeEvent(EventLogTags.BACKUP_QUOTA_EXCEEDED, packageName);
      }
  
      void onAgentDoQuotaExceededError(Exception e) {
          Slog.e(TAG, "Unable to notify about quota exceeded: " + e);
      }
  
      void onPackageBackupNonIncrementalRequired(PackageInfo packageInfo) {
          Slog.i(TAG, "Transport lost data, retrying package");
          BackupManagerMonitorUtils.monitorEvent(
                  mMonitor,
                  BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED,
                  packageInfo,
                  BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT,
                  /* extras */ null);
      }
  
      void onPackageBackupNonIncrementalAndNonIncrementalRequired(String packageName) {
          Slog.e(TAG, "Transport requested non-incremental but already the case");
          BackupObserverUtils.sendBackupOnPackageResult(
                  mObserver, packageName, BackupManager.ERROR_TRANSPORT_ABORTED);
          EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, packageName);
      }
  
      void onPackageBackupTransportFailure(String packageName) {
          BackupObserverUtils.sendBackupOnPackageResult(
                  mObserver, packageName, BackupManager.ERROR_TRANSPORT_ABORTED);
          EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, packageName);
      }
  
      void onPackageBackupTransportError(String packageName, Exception e) {
          Slog.e(TAG, "Transport error backing up " + packageName, e);
          BackupObserverUtils.sendBackupOnPackageResult(
                  mObserver, packageName, BackupManager.ERROR_TRANSPORT_ABORTED);
          EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, packageName);
      }
  
      void onCloseFileDescriptorError(String logName) {
          Slog.w(TAG, "Error closing " + logName + " file-descriptor");
      }
  
      void onCancel() {
          if (MORE_DEBUG) {
              Slog.v(TAG, "Cancel received");
          }
      }
  
      void onAgentTimedOut(@Nullable PackageInfo packageInfo) {
          String packageName = getPackageName(packageInfo);
          Slog.i(TAG, "Agent " + packageName + " timed out");
          EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, packageName);
          // Time-out used to be implemented as cancel w/ cancelAll = false.
          // TODO: Change monitoring event to reflect time-out as an event itself.
          mMonitor =
                  BackupManagerMonitorUtils.monitorEvent(
                          mMonitor,
                          BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_BACKUP_CANCEL,
                          packageInfo,
                          BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT,
                          BackupManagerMonitorUtils.putMonitoringExtra(
                                  null, BackupManagerMonitor.EXTRA_LOG_CANCEL_ALL, false));
      }
  
      void onAgentCancelled(@Nullable PackageInfo packageInfo) {
          String packageName = getPackageName(packageInfo);
          Slog.i(TAG, "Cancel backing up " + packageName);
          EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, packageName);
          mMonitor =
                  BackupManagerMonitorUtils.monitorEvent(
                          mMonitor,
                          BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_BACKUP_CANCEL,
                          packageInfo,
                          BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT,
                          BackupManagerMonitorUtils.putMonitoringExtra(
                                  null, BackupManagerMonitor.EXTRA_LOG_CANCEL_ALL, true));
      }
  
      void onAgentResultError(@Nullable PackageInfo packageInfo) {
          String packageName = getPackageName(packageInfo);
          BackupObserverUtils.sendBackupOnPackageResult(
                  mObserver, packageName, BackupManager.ERROR_AGENT_FAILURE);
          EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, packageName, "result error");
          Slog.w(TAG, "Agent " + packageName + " error in onBackup()");
      }
  
      private String getPackageName(@Nullable PackageInfo packageInfo) {
          return (packageInfo != null) ? packageInfo.packageName : "no_package_yet";
      }
  
      void onRevertTask() {
          if (MORE_DEBUG) {
              Slog.i(TAG, "Reverting backup queue, re-staging everything");
          }
      }
  
      void onTransportRequestBackupTimeError(Exception e) {
          Slog.w(TAG, "Unable to contact transport for recommended backoff: " + e);
      }
  
      void onRemoteCallReturned(RemoteResult result, String logIdentifier) {
          if (MORE_DEBUG) {
              Slog.v(TAG, "Agent call " + logIdentifier + " returned " + result);
          }
      }
  
      void onJournalDeleteFailed(DataChangedJournal journal) {
          Slog.e(TAG, "Unable to remove backup journal file " + journal);
      }
  
      void onSetCurrentTokenError(Exception e) {
          Slog.e(TAG, "Transport threw reporting restore set: " + e);
      }
  
      void onTransportNotInitialized() {
          if (MORE_DEBUG) {
              Slog.d(TAG, "Transport requires initialization, rerunning");
          }
      }
  
      void onPendingInitializeTransportError(Exception e) {
          Slog.w(TAG, "Failed to query transport name for pending init: " + e);
      }
  
      /**
       * This is a bit different from {@link #onTaskFinished()}, it's only called if there is no
       * full-backup requests associated with the key-value task.
       */
      void onBackupFinished(int status) {
          BackupObserverUtils.sendBackupFinished(mObserver, status);
      }
  
      void onStartFullBackup(List<String> pendingFullBackups) {
          Slog.d(TAG, "Starting full backups for: " + pendingFullBackups);
      }
  
      void onTaskFinished() {
          Slog.i(TAG, "K/V backup pass finished");
      }
  }
  