diff --git a/core/api/system-current.txt b/core/api/system-current.txt index bd64f4db3a7bd..80673123d84a7 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -1496,11 +1496,13 @@ package android.app.backup { method @RequiresPermission(android.Manifest.permission.BACKUP) public android.content.Intent getDataManagementIntent(String); method @Nullable @RequiresPermission(android.Manifest.permission.BACKUP) public CharSequence getDataManagementIntentLabel(@NonNull String); method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.BACKUP) public String getDataManagementLabel(@NonNull String); + method @NonNull public android.app.backup.BackupRestoreEventLogger getDelayedRestoreLogger(); method @RequiresPermission(android.Manifest.permission.BACKUP) public String getDestinationString(String); method @RequiresPermission(android.Manifest.permission.BACKUP) public boolean isAppEligibleForBackup(String); method @RequiresPermission(android.Manifest.permission.BACKUP) public boolean isBackupEnabled(); method @RequiresPermission(android.Manifest.permission.BACKUP) public boolean isBackupServiceActive(android.os.UserHandle); method @RequiresPermission(android.Manifest.permission.BACKUP) public String[] listAllTransports(); + method @NonNull public void reportDelayedRestoreResult(@NonNull android.app.backup.BackupRestoreEventLogger); method @RequiresPermission(android.Manifest.permission.BACKUP) public int requestBackup(String[], android.app.backup.BackupObserver); method @RequiresPermission(android.Manifest.permission.BACKUP) public int requestBackup(String[], android.app.backup.BackupObserver, android.app.backup.BackupManagerMonitor, int); method @Deprecated public int requestRestore(android.app.backup.RestoreObserver, android.app.backup.BackupManagerMonitor); diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java index 7255c3e0813f1..bad282ead3d3a 100644 --- a/core/java/android/app/backup/BackupManager.java +++ b/core/java/android/app/backup/BackupManager.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; +import android.app.backup.BackupAnnotations.OperationType; import android.app.compat.CompatChanges; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledAfter; @@ -1041,6 +1042,42 @@ public class BackupManager { return backupAgent.getBackupRestoreEventLogger(); } + /** + * Get an instance of {@link BackupRestoreEventLogger} to report B&R related events during a + * delayed restore operation. + * + * @return an instance of {@link BackupRestoreEventLogger}. + * + * @hide + */ + @NonNull + @SystemApi + public BackupRestoreEventLogger getDelayedRestoreLogger() { + return new BackupRestoreEventLogger(OperationType.RESTORE); + } + + /** + * Report B&R related events following a delayed restore operation. + * + * @param logger an instance of {@link BackupRestoreEventLogger} to which the corresponding + * events have been logged. + * + * @hide + */ + @NonNull + @SystemApi + public void reportDelayedRestoreResult(@NonNull BackupRestoreEventLogger logger) { + checkServiceBinder(); + if (sService != null) { + try { + sService.reportDelayedRestoreResult(mContext.getPackageName(), + logger.getLoggingResults()); + } catch (RemoteException e) { + Log.w(TAG, "reportDelayedRestoreResult() couldn't connect"); + } + } + } + /* * We wrap incoming binder calls with a private class implementation that * redirects them into main-thread actions. This serializes the backup diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl index bf5be95c4ab06..aeb498721fb69 100644 --- a/core/java/android/app/backup/IBackupManager.aidl +++ b/core/java/android/app/backup/IBackupManager.aidl @@ -16,6 +16,7 @@ package android.app.backup; +import android.app.backup.BackupRestoreEventLogger.DataTypeResult; import android.app.backup.IBackupObserver; import android.app.backup.IBackupManagerMonitor; import android.app.backup.IFullBackupRestoreObserver; @@ -722,4 +723,6 @@ interface IBackupManager { * that have been excluded will be passed to the agent to make it aware of the exclusions. */ void excludeKeysFromRestore(String packageName, in List keys); + + void reportDelayedRestoreResult(in String packageName, in List results); } diff --git a/core/tests/coretests/src/android/app/backup/BackupManagerTest.java b/core/tests/coretests/src/android/app/backup/BackupManagerTest.java index cbf167c49f951..27ee82e99376b 100644 --- a/core/tests/coretests/src/android/app/backup/BackupManagerTest.java +++ b/core/tests/coretests/src/android/app/backup/BackupManagerTest.java @@ -83,6 +83,13 @@ public class BackupManagerTest { () -> mBackupManager.getBackupRestoreEventLogger(agent)); } + @Test + public void testGetDelayedRestoreLogger_returnsRestoreLogger() { + BackupRestoreEventLogger logger = mBackupManager.getDelayedRestoreLogger(); + + assertThat(logger.getOperationType()).isEqualTo(OperationType.RESTORE); + } + private static BackupAgent getTestAgent() { return new BackupAgent() { @Override diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index 1c571a7036ad6..53f5fe10d3e97 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -25,6 +25,8 @@ import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.admin.DevicePolicyManager; import android.app.backup.BackupManager; +import android.app.backup.BackupRestoreEventLogger; +import android.app.backup.BackupRestoreEventLogger.DataTypeResult; import android.app.backup.IBackupManager; import android.app.backup.IBackupManagerMonitor; import android.app.backup.IBackupObserver; @@ -1556,6 +1558,22 @@ public class BackupManagerService extends IBackupManager.Stub { } } + public void reportDelayedRestoreResult(String packageName, List results) { + int userId = Binder.getCallingUserHandle().getIdentifier(); + if (!isUserReadyForBackup(userId)) { + Slog.w(TAG, "Returning from reportDelayedRestoreResult as backup for user" + userId + + " is not initialized yet"); + return; + } + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(userId, + /* caller */ "reportDelayedRestoreResult()"); + + if (userBackupManagerService != null) { + userBackupManagerService.reportDelayedRestoreResult(packageName, results); + } + } + /** * Returns the {@link UserBackupManagerService} instance for the specified user {@code userId}. * If the user is not registered with the service (either the user is locked or not eligible for diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index ce3e6289d40ea..6ba01d712e92d 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -49,6 +49,7 @@ import android.app.backup.BackupAgent; import android.app.backup.BackupAnnotations.BackupDestination; import android.app.backup.BackupManager; import android.app.backup.BackupManagerMonitor; +import android.app.backup.BackupRestoreEventLogger; import android.app.backup.FullBackup; import android.app.backup.IBackupManager; import android.app.backup.IBackupManagerMonitor; @@ -505,13 +506,14 @@ public class UserBackupManagerService { @VisibleForTesting UserBackupManagerService(Context context, PackageManager packageManager, - LifecycleOperationStorage operationStorage) { + LifecycleOperationStorage operationStorage, TransportManager transportManager) { mContext = context; mUserId = 0; mRegisterTransportsRequestedTime = 0; mPackageManager = packageManager; mOperationStorage = operationStorage; + mTransportManager = transportManager; mBaseStateDir = null; mDataDir = null; @@ -521,7 +523,6 @@ public class UserBackupManagerService { mRunInitReceiver = null; mRunInitIntent = null; mAgentTimeoutParameters = null; - mTransportManager = null; mActivityManagerInternal = null; mAlarmManager = null; mConstants = null; @@ -3038,6 +3039,37 @@ public class UserBackupManagerService { mBackupPreferences.addExcludedKeys(packageName, keys); } + public void reportDelayedRestoreResult(String packageName, + List results) { + String transport = mTransportManager.getCurrentTransportName(); + if (transport == null) { + Slog.w(TAG, "Failed to send delayed restore logs as no transport selected"); + return; + } + + TransportConnection transportConnection = null; + try { + PackageInfo packageInfo = getPackageManager().getPackageInfoAsUser(packageName, + PackageManager.PackageInfoFlags.of(/* value */ 0), getUserId()); + + transportConnection = mTransportManager.getTransportClientOrThrow( + transport, /* caller */"BMS.reportDelayedRestoreResult"); + BackupTransportClient transportClient = transportConnection.connectOrThrow( + /* caller */ "BMS.reportDelayedRestoreResult"); + + IBackupManagerMonitor monitor = transportClient.getBackupManagerMonitor(); + BackupManagerMonitorUtils.sendAgentLoggingResults(monitor, packageInfo, results); + } catch (NameNotFoundException | TransportNotAvailableException + | TransportNotRegisteredException | RemoteException e) { + Slog.w(TAG, "Failed to send delayed restore logs: " + e); + } finally { + if (transportConnection != null) { + mTransportManager.disposeOfTransportClient(transportConnection, + /* caller */"BMS.reportDelayedRestoreResult"); + } + } + } + private boolean startConfirmationUi(int token, String action) { try { Intent confIntent = new Intent(action); diff --git a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorUtils.java b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorUtils.java index 8eda5b9a32190..57ad89b0a482b 100644 --- a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorUtils.java +++ b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorUtils.java @@ -24,10 +24,11 @@ import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_AGENT_LOGGING import static com.android.server.backup.BackupManagerService.DEBUG; import static com.android.server.backup.BackupManagerService.TAG; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.IBackupAgent; import android.app.backup.BackupManagerMonitor; -import android.app.backup.BackupRestoreEventLogger; +import android.app.backup.BackupRestoreEventLogger.DataTypeResult; import android.app.backup.IBackupManagerMonitor; import android.content.pm.PackageInfo; import android.os.Bundle; @@ -119,19 +120,11 @@ public class BackupManagerMonitorUtils { } try { - AndroidFuture> resultsFuture = + AndroidFuture> resultsFuture = new AndroidFuture<>(); agent.getLoggerResults(resultsFuture); - Bundle loggerResultsBundle = new Bundle(); - loggerResultsBundle.putParcelableList( - EXTRA_LOG_AGENT_LOGGING_RESULTS, + return sendAgentLoggingResults(monitor, pkg, resultsFuture.get(AGENT_LOGGER_RESULTS_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - return BackupManagerMonitorUtils.monitorEvent( - monitor, - LOG_EVENT_ID_AGENT_LOGGING_RESULTS, - pkg, - LOG_EVENT_CATEGORY_AGENT, - loggerResultsBundle); } catch (TimeoutException e) { Slog.w(TAG, "Timeout while waiting to retrieve logging results from agent", e); } catch (Exception e) { @@ -140,6 +133,19 @@ public class BackupManagerMonitorUtils { return monitor; } + public static IBackupManagerMonitor sendAgentLoggingResults( + @NonNull IBackupManagerMonitor monitor, PackageInfo pkg, List results) { + Bundle loggerResultsBundle = new Bundle(); + loggerResultsBundle.putParcelableList( + EXTRA_LOG_AGENT_LOGGING_RESULTS, results); + return monitorEvent( + monitor, + LOG_EVENT_ID_AGENT_LOGGING_RESULTS, + pkg, + LOG_EVENT_CATEGORY_AGENT, + loggerResultsBundle); + } + /** * Adds given key-value pair in the bundle and returns the bundle. If bundle was null it will * be created. diff --git a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java similarity index 80% rename from services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java rename to services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java index cd2f205f55529..3480af62e6c4e 100644 --- a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2022 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. @@ -18,6 +18,9 @@ package com.android.server.backup; import static com.google.common.truth.Truth.assertThat; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; + import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -25,11 +28,11 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.backup.BackupAgent; import android.app.backup.BackupAnnotations.BackupDestination; +import android.app.backup.BackupRestoreEventLogger.DataTypeResult; import android.app.backup.IBackupManagerMonitor; import android.app.backup.IBackupObserver; import android.content.Context; @@ -37,6 +40,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.platform.test.annotations.Presubmit; +import android.util.FeatureFlagUtils; import androidx.test.filters.FlakyTest; import androidx.test.runner.AndroidJUnit4; @@ -47,15 +51,21 @@ import com.android.server.backup.params.BackupParams; import com.android.server.backup.transport.BackupTransportClient; import com.android.server.backup.transport.TransportConnection; import com.android.server.backup.utils.BackupEligibilityRules; +import com.android.server.backup.utils.BackupManagerMonitorUtils; import com.google.common.collect.ImmutableSet; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; +import java.util.Arrays; +import java.util.List; import java.util.function.IntConsumer; @Presubmit @@ -63,6 +73,7 @@ import java.util.function.IntConsumer; public class UserBackupManagerServiceTest { private static final String TEST_PACKAGE = "package1"; private static final String[] TEST_PACKAGES = new String[] { TEST_PACKAGE }; + private static final String TEST_TRANSPORT = "transport"; private static final int WORKER_THREAD_TIMEOUT_MILLISECONDS = 1; @Mock Context mContext; @@ -70,21 +81,38 @@ public class UserBackupManagerServiceTest { @Mock IBackupObserver mBackupObserver; @Mock PackageManager mPackageManager; @Mock TransportConnection mTransportConnection; + @Mock TransportManager mTransportManager; @Mock BackupTransportClient mBackupTransport; @Mock BackupEligibilityRules mBackupEligibilityRules; @Mock LifecycleOperationStorage mOperationStorage; + private MockitoSession mSession; private TestBackupService mService; @Before public void setUp() throws Exception { + mSession = mockitoSession() + .initMocks(this) + .mockStatic(BackupManagerMonitorUtils.class) + .mockStatic(FeatureFlagUtils.class) + // TODO(b/263239775): Remove unnecessary stubbing. + .strictness(Strictness.LENIENT) + .startMocking(); MockitoAnnotations.initMocks(this); - mService = new TestBackupService(mContext, mPackageManager, mOperationStorage); + mService = new TestBackupService(mContext, mPackageManager, mOperationStorage, + mTransportManager); mService.setEnabled(true); mService.setSetupComplete(true); } + @After + public void tearDown() { + if (mSession != null) { + mSession.finishMocking(); + } + } + @Test public void initializeBackupEnableState_doesntWriteStateToDisk() { mService.initializeBackupEnableState(); @@ -201,6 +229,26 @@ public class UserBackupManagerServiceTest { .cancelOperation(anyInt(), anyBoolean(), any(IntConsumer.class)); } + @Test + public void testReportDelayedRestoreResult_sendsLogsToMonitor() throws Exception { + PackageInfo packageInfo = getPackageInfo(TEST_PACKAGE); + when(mPackageManager.getPackageInfoAsUser(anyString(), + any(PackageManager.PackageInfoFlags.class), anyInt())).thenReturn(packageInfo); + when(mTransportManager.getCurrentTransportName()).thenReturn(TEST_TRANSPORT); + when(mTransportManager.getTransportClientOrThrow(eq(TEST_TRANSPORT), anyString())) + .thenReturn(mTransportConnection); + when(mTransportConnection.connectOrThrow(any())).thenReturn(mBackupTransport); + when(mBackupTransport.getBackupManagerMonitor()).thenReturn(mBackupManagerMonitor); + + + List results = Arrays.asList(new DataTypeResult(/* dataType */ "type_1"), + new DataTypeResult(/* dataType */ "type_2")); + mService.reportDelayedRestoreResult(TEST_PACKAGE, results); + + verify(() -> BackupManagerMonitorUtils.sendAgentLoggingResults( + eq(mBackupManagerMonitor), eq(packageInfo), eq(results))); + } + private static PackageInfo getPackageInfo(String packageName) { PackageInfo packageInfo = new PackageInfo(); packageInfo.applicationInfo = new ApplicationInfo(); @@ -215,8 +263,8 @@ public class UserBackupManagerServiceTest { private volatile Thread mWorkerThread = null; TestBackupService(Context context, PackageManager packageManager, - LifecycleOperationStorage operationStorage) { - super(context, packageManager, operationStorage); + LifecycleOperationStorage operationStorage, TransportManager transportManager) { + super(context, packageManager, operationStorage, transportManager); } @Override diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/BackupManagerMonitorUtilsTest.java b/services/tests/servicestests/src/com/android/server/backup/utils/BackupManagerMonitorUtilsTest.java index cadc890f64fdd..87ade963e121a 100644 --- a/services/tests/servicestests/src/com/android/server/backup/utils/BackupManagerMonitorUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/backup/utils/BackupManagerMonitorUtilsTest.java @@ -172,11 +172,30 @@ public class BackupManagerMonitorUtilsTest { .when(agent) .getLoggerResults(any()); - IBackupManagerMonitor result = + IBackupManagerMonitor monitor = BackupManagerMonitorUtils.monitorAgentLoggingResults( mMonitorMock, packageInfo, agent); - assertThat(result).isEqualTo(mMonitorMock); + assertCorrectBundleSentToMonitor(monitor); + } + + @Test + public void sendAgentLoggingResults_fillsBundleCorrectly() throws Exception { + PackageInfo packageInfo = new PackageInfo(); + packageInfo.packageName = "test.package"; + List loggingResults = new ArrayList<>(); + loggingResults.add(new BackupRestoreEventLogger.DataTypeResult("testLoggingResult")); + + IBackupManagerMonitor monitor = BackupManagerMonitorUtils.sendAgentLoggingResults( + mMonitorMock, packageInfo, loggingResults); + + assertCorrectBundleSentToMonitor(monitor); + } + + private void assertCorrectBundleSentToMonitor(IBackupManagerMonitor monitor) throws Exception { + + + assertThat(monitor).isEqualTo(mMonitorMock); ArgumentCaptor bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(mMonitorMock).onEvent(bundleCaptor.capture()); Bundle eventBundle = bundleCaptor.getValue();