Merge "Integrate BackupAgent with B&REventLogger"

This commit is contained in:
Sarp Misoglu
2022-11-17 16:29:00 +00:00
committed by Android (Google) Code Review
6 changed files with 213 additions and 13 deletions

View File

@@ -16,9 +16,12 @@
package android.app;
import android.app.backup.BackupRestoreEventLogger;
import android.app.backup.IBackupCallback;
import android.app.backup.IBackupManager;
import android.os.ParcelFileDescriptor;
import com.android.internal.infra.AndroidFuture;
/**
* Interface presented by applications being asked to participate in the
@@ -193,4 +196,14 @@ oneway interface IBackupAgent {
* @param message The message to be passed to the agent's application in an exception.
*/
void fail(String message);
/**
* Provides the logging results that were accumulated in the BackupAgent during a backup or
* restore operation. This method should be called after the agent completes its backup or
* restore.
*
* @param resultsFuture a future that is completed with the logging results.
*/
void getLoggerResults(
in AndroidFuture<List<BackupRestoreEventLogger.DataTypeResult>> resultsFuture);
}

View File

@@ -41,6 +41,7 @@ import android.util.ArraySet;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.infra.AndroidFuture;
import libcore.io.IoUtils;
@@ -202,6 +203,7 @@ public abstract class BackupAgent extends ContextWrapper {
Handler mHandler = null;
@Nullable private volatile BackupRestoreEventLogger mLogger = null;
@Nullable private UserHandle mUser;
// This field is written from the main thread (in onCreate), and read in a Binder thread (in
// onFullBackup that is called from system_server via Binder).
@@ -234,6 +236,20 @@ public abstract class BackupAgent extends ContextWrapper {
} catch (InterruptedException e) { /* ignored */ }
}
/**
* Get a logger to record app-specific backup and restore events that are happening during a
* backup or restore operation.
*
* <p>The logger instance had been created by the system with the correct {@link
* BackupRestoreEventLogger.OperationType} that corresponds to the operation the {@code
* BackupAgent} is currently handling.
*
* @hide
*/
@Nullable
public BackupRestoreEventLogger getBackupRestoreEventLogger() {
return mLogger;
}
public BackupAgent() {
super(null);
@@ -264,6 +280,9 @@ public abstract class BackupAgent extends ContextWrapper {
* @hide
*/
public void onCreate(UserHandle user, @OperationType int operationType) {
// TODO: Instantiate with the correct type using a parameter.
mLogger = new BackupRestoreEventLogger(BackupRestoreEventLogger.OperationType.BACKUP);
onCreate();
mUser = user;
@@ -1305,6 +1324,16 @@ public abstract class BackupAgent extends ContextWrapper {
}
}
}
@Override
public void getLoggerResults(
AndroidFuture<List<BackupRestoreEventLogger.DataTypeResult>> in) {
if (mLogger != null) {
in.complete(mLogger.getLoggingResults());
} else {
in.complete(Collections.emptyList());
}
}
}
static class FailRunnable implements Runnable {

View File

@@ -0,0 +1,19 @@
/*
* 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.
* 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 android.app.backup;
parcelable BackupRestoreEventLogger.DataTypeResult;

View File

@@ -19,6 +19,10 @@ package android.app.backup;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArrayMap;
import android.util.Slog;
import java.lang.annotation.Retention;
@@ -312,7 +316,7 @@ public class BackupRestoreEventLogger {
/**
* Encapsulate logging results for a single data type.
*/
public static class DataTypeResult {
public static class DataTypeResult implements Parcelable {
@BackupRestoreDataType
private final String mDataType;
private int mSuccessCount;
@@ -362,5 +366,57 @@ public class BackupRestoreEventLogger {
public byte[] getMetadataHash() {
return mMetadataHash;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mDataType);
dest.writeInt(mSuccessCount);
dest.writeInt(mFailCount);
Bundle errorsBundle = new Bundle();
for (Map.Entry<String, Integer> e : mErrors.entrySet()) {
errorsBundle.putInt(e.getKey(), e.getValue());
}
dest.writeBundle(errorsBundle);
dest.writeByteArray(mMetadataHash);
}
public static final Parcelable.Creator<DataTypeResult> CREATOR =
new Parcelable.Creator<>() {
public DataTypeResult createFromParcel(Parcel in) {
String dataType = in.readString();
int successCount = in.readInt();
int failCount = in.readInt();
Map<String, Integer> errors = new ArrayMap<>();
Bundle errorsBundle = in.readBundle(getClass().getClassLoader());
for (String key : errorsBundle.keySet()) {
errors.put(key, errorsBundle.getInt(key));
}
byte[] metadataHash = in.createByteArray();
DataTypeResult result = new DataTypeResult(dataType);
result.mSuccessCount = successCount;
result.mFailCount = failCount;
result.mErrors.putAll(errors);
result.mMetadataHash = metadataHash;
return result;
}
public DataTypeResult[] newArray(int size) {
return new DataTypeResult[size];
}
};
}
}

View File

@@ -46,6 +46,7 @@ import java.util.Set;
public class BackupAgentTest {
// An arbitrary user.
private static final UserHandle USER_HANDLE = new UserHandle(15);
private static final String DATA_TYPE_BACKED_UP = "test data type";
@Mock FullBackup.BackupScheme mBackupScheme;
@@ -73,6 +74,42 @@ public class BackupAgentTest {
assertThat(rules).isEqualTo(expectedRules);
}
@Test
public void getBackupRestoreEventLogger_beforeOnCreate_isNull() {
BackupAgent agent = new TestFullBackupAgent();
assertThat(agent.getBackupRestoreEventLogger()).isNull();
}
@Test
public void getBackupRestoreEventLogger_afterOnCreateForBackup_initializedForBackup() {
BackupAgent agent = new TestFullBackupAgent();
agent.onCreate(USER_HANDLE, OperationType.BACKUP); // TODO: pass in new operation type
assertThat(agent.getBackupRestoreEventLogger().getOperationType()).isEqualTo(1);
}
@Test
public void getBackupRestoreEventLogger_afterOnCreateForRestore_initializedForRestore() {
BackupAgent agent = new TestFullBackupAgent();
agent.onCreate(USER_HANDLE, OperationType.BACKUP); // TODO: pass in new operation type
assertThat(agent.getBackupRestoreEventLogger().getOperationType()).isEqualTo(1);
}
@Test
public void getBackupRestoreEventLogger_afterBackup_containsLogsLoggedByAgent()
throws Exception {
BackupAgent agent = new TestFullBackupAgent();
agent.onCreate(USER_HANDLE, OperationType.BACKUP); // TODO: pass in new operation type
// TestFullBackupAgent logs DATA_TYPE_BACKED_UP when onFullBackup is called.
agent.onFullBackup(new FullBackupDataOutput(/* quota = */ 0));
assertThat(agent.getBackupRestoreEventLogger().getLoggingResults().get(0).getDataType())
.isEqualTo(DATA_TYPE_BACKED_UP);
}
private BackupAgent getAgentForOperationType(@OperationType int operationType) {
BackupAgent agent = new TestFullBackupAgent();
agent.onCreate(USER_HANDLE, operationType);
@@ -87,6 +124,11 @@ public class BackupAgentTest {
// Left empty as this is a full backup agent.
}
@Override
public void onFullBackup(FullBackupDataOutput data) {
getBackupRestoreEventLogger().logItemsBackedUp(DATA_TYPE_BACKED_UP, 1);
}
@Override
public void onRestore(BackupDataInput data, int appVersionCode,
ParcelFileDescriptor newState) throws IOException {

View File

@@ -25,6 +25,7 @@ import static junit.framework.Assert.fail;
import android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType;
import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
import androidx.test.runner.AndroidJUnit4;
@@ -35,6 +36,7 @@ import org.junit.runner.RunWith;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
@@ -236,10 +238,10 @@ public class BackupRestoreEventLoggerTest {
mLogger.logItemsBackupFailed(DATA_TYPE_1, firstCount, ERROR_1);
mLogger.logItemsBackupFailed(DATA_TYPE_1, secondCount, ERROR_2);
int firstErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1)
.getErrors().get(ERROR_1);
int secondErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1)
.getErrors().get(ERROR_2);
int firstErrorTypeCount =
getResultForDataType(mLogger, DATA_TYPE_1).getErrors().get(ERROR_1);
int secondErrorTypeCount =
getResultForDataType(mLogger, DATA_TYPE_1).getErrors().get(ERROR_2);
assertThat(firstErrorTypeCount).isEqualTo(firstCount);
assertThat(secondErrorTypeCount).isEqualTo(secondCount);
}
@@ -253,16 +255,54 @@ public class BackupRestoreEventLoggerTest {
mLogger.logItemsRestoreFailed(DATA_TYPE_1, firstCount, ERROR_1);
mLogger.logItemsRestoreFailed(DATA_TYPE_1, secondCount, ERROR_2);
int firstErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1)
.getErrors().get(ERROR_1);
int secondErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1)
.getErrors().get(ERROR_2);
int firstErrorTypeCount =
getResultForDataType(mLogger, DATA_TYPE_1).getErrors().get(ERROR_1);
int secondErrorTypeCount =
getResultForDataType(mLogger, DATA_TYPE_1).getErrors().get(ERROR_2);
assertThat(firstErrorTypeCount).isEqualTo(firstCount);
assertThat(secondErrorTypeCount).isEqualTo(secondCount);
}
private static DataTypeResult getResultForDataType(BackupRestoreEventLogger logger,
@BackupRestoreDataType String dataType) {
@Test
public void testGetLoggingResults_resultsParceledAndUnparceled_recreatedCorrectly() {
mLogger = new BackupRestoreEventLogger(RESTORE);
int firstTypeSuccessCount = 1;
int firstTypeErrorOneCount = 2;
int firstTypeErrorTwoCount = 3;
mLogger.logItemsRestored(DATA_TYPE_1, firstTypeSuccessCount);
mLogger.logItemsRestoreFailed(DATA_TYPE_1, firstTypeErrorOneCount, ERROR_1);
mLogger.logItemsRestoreFailed(DATA_TYPE_1, firstTypeErrorTwoCount, ERROR_2);
mLogger.logRestoreMetadata(DATA_TYPE_1, METADATA_1);
int secondTypeSuccessCount = 4;
int secondTypeErrorOneCount = 5;
mLogger.logItemsRestored(DATA_TYPE_2, secondTypeSuccessCount);
mLogger.logItemsRestoreFailed(DATA_TYPE_2, secondTypeErrorOneCount, ERROR_1);
List<DataTypeResult> resultsList = mLogger.getLoggingResults();
Parcel parcel = Parcel.obtain();
parcel.writeParcelableList(resultsList, /* flags= */ 0);
parcel.setDataPosition(0);
List<DataTypeResult> recreatedList = new ArrayList<>();
parcel.readParcelableList(
recreatedList, DataTypeResult.class.getClassLoader(), DataTypeResult.class);
assertThat(recreatedList.get(0).getDataType()).isEqualTo(DATA_TYPE_1);
assertThat(recreatedList.get(0).getSuccessCount()).isEqualTo(firstTypeSuccessCount);
assertThat(recreatedList.get(0).getFailCount())
.isEqualTo(firstTypeErrorOneCount + firstTypeErrorTwoCount);
assertThat(recreatedList.get(0).getErrors().get(ERROR_1)).isEqualTo(firstTypeErrorOneCount);
assertThat(recreatedList.get(0).getErrors().get(ERROR_2)).isEqualTo(firstTypeErrorTwoCount);
assertThat(recreatedList.get(1).getDataType()).isEqualTo(DATA_TYPE_2);
assertThat(recreatedList.get(1).getSuccessCount()).isEqualTo(secondTypeSuccessCount);
assertThat(recreatedList.get(1).getFailCount()).isEqualTo(secondTypeErrorOneCount);
assertThat(recreatedList.get(1).getErrors().get(ERROR_1))
.isEqualTo(secondTypeErrorOneCount);
}
private static DataTypeResult getResultForDataType(
BackupRestoreEventLogger logger, @BackupRestoreDataType String dataType) {
Optional<DataTypeResult> result = getResultForDataTypeIfPresent(logger, dataType);
if (result.isEmpty()) {
fail("Failed to find result for data type: " + dataType);
@@ -273,8 +313,9 @@ public class BackupRestoreEventLoggerTest {
private static Optional<DataTypeResult> getResultForDataTypeIfPresent(
BackupRestoreEventLogger logger, @BackupRestoreDataType String dataType) {
List<DataTypeResult> resultList = logger.getLoggingResults();
return resultList.stream().filter(
dataTypeResult -> dataTypeResult.getDataType().equals(dataType)).findAny();
return resultList.stream()
.filter(dataTypeResult -> dataTypeResult.getDataType().equals(dataType))
.findAny();
}
private byte[] getMetaDataHash(String metaData) {