Merge "Integrate BackupAgent with B&REventLogger"
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
19
core/java/android/app/backup/BackupRestoreEventLogger.aidl
Normal file
19
core/java/android/app/backup/BackupRestoreEventLogger.aidl
Normal 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;
|
||||
@@ -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];
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user