Refactored LocalTransport to log DataTypeResult as a String.

You can check by running:

$ adb logcat LocalTransport *:s &
$ adb shell 'settings put secure backup_local_transport_parameters "dump_agent_results=true"'
$ adb shell bmgr transport com.android.localtransport/.LocalTransport
$ adb shell bmgr backupnow android
$ adb shell bmgr run

Test: atest FrameworksCoreTests --test-filter=".*DataTypeResult.*"
Test: manual verification with steps above

Bug: 394173116
Flag: EXEMPT changes on debugging components only

Change-Id: Ife98467d8169984c9e4f212260e3d18442812666
diff --git a/core/java/android/app/backup/BackupRestoreEventLogger.java b/core/java/android/app/backup/BackupRestoreEventLogger.java
index 112c5fd..8bde3a5 100644
--- a/core/java/android/app/backup/BackupRestoreEventLogger.java
+++ b/core/java/android/app/backup/BackupRestoreEventLogger.java
@@ -34,9 +34,11 @@
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * Class to log B&R stats for each data type that is backed up and restored by the calling app.
@@ -325,6 +327,21 @@
         }
     }
 
+    /** @hide */
+    public static String toString(DataTypeResult result) {
+        Objects.requireNonNull(result, "result cannot be null");
+        StringBuilder string = new StringBuilder("type=").append(result.mDataType)
+                .append(", successCount=").append(result.mSuccessCount)
+                .append(", failCount=").append(result.mFailCount);
+        if (!result.mErrors.isEmpty()) {
+            string.append(", errors=").append(result.mErrors);
+        }
+        if (result.mMetadataHash != null) {
+            string.append(", metadataHash=").append(Arrays.toString(result.mMetadataHash));
+        }
+        return string.toString();
+    }
+
     /**
      * Encapsulate logging results for a single data type.
      */
diff --git a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
index 7b41217..ab2e77e 100644
--- a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
+++ b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
@@ -23,6 +23,8 @@
 
 import static junit.framework.Assert.fail;
 
+import static org.junit.Assert.assertThrows;
+
 import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
 import android.os.Parcel;
 import android.platform.test.annotations.Presubmit;
@@ -32,6 +34,8 @@
 
 import com.android.server.backup.Flags;
 
+import com.google.common.truth.Expect;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -42,6 +46,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 
 @Presubmit
@@ -64,6 +69,9 @@
     @Rule
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
+    @Rule
+    public final Expect expect = Expect.create();
+
     @Before
     public void setUp() throws Exception {
         mHashDigest = MessageDigest.getInstance("SHA-256");
@@ -366,6 +374,32 @@
         assertThat(mLogger.getLoggingResults()).isEmpty();
     }
 
+    @Test
+    public void testDataTypeResultToString_nullArgs() {
+        assertThrows(NullPointerException.class, () -> BackupRestoreEventLogger.toString(null));
+    }
+
+    @Test
+    public void testDataTypeResultToString_typeOnly() {
+        DataTypeResult result = new DataTypeResult("The Type is Bond, James Bond!");
+
+        expect.withMessage("toString()")
+                .that(BackupRestoreEventLogger.toString(result)).isEqualTo(
+                        "type=The Type is Bond, James Bond!, successCount=0, failCount=0");
+    }
+
+    @Test
+    public void testDataTypeResultToString_allFields() {
+        DataTypeResult result = DataTypeResultTest.createDataTypeResult(
+                "The Type is Bond, James Bond!", /* successCount= */ 42, /* failCount= */ 108,
+                Map.of("D'OH!", 666, "", 0), new byte[] { 4, 8, 15, 16, 23, 42 });
+
+        expect.withMessage("toString()")
+                .that(BackupRestoreEventLogger.toString(result)).isEqualTo(
+                        "type=The Type is Bond, James Bond!, successCount=42, failCount=108, "
+                        + "errors={=0, D'OH!=666}, metadataHash=[4, 8, 15, 16, 23, 42]");
+    }
+
     private static DataTypeResult getResultForDataType(
             BackupRestoreEventLogger logger, String dataType) {
         Optional<DataTypeResult> result = getResultForDataTypeIfPresent(logger, dataType);
diff --git a/core/tests/coretests/src/android/app/backup/DataTypeResultTest.java b/core/tests/coretests/src/android/app/backup/DataTypeResultTest.java
new file mode 100644
index 0000000..cf9e9c6
--- /dev/null
+++ b/core/tests/coretests/src/android/app/backup/DataTypeResultTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2025 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;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
+import android.os.Bundle;
+import android.os.Parcel;
+
+import com.google.common.truth.Expect;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.Map;
+
+public final class DataTypeResultTest {
+
+    @Rule
+    public final Expect expect = Expect.create();
+
+    @Test
+    public void testGetters_defaultConstructorFields() {
+        var result = new DataTypeResult("The Type is Bond, James Bond!");
+
+        expect.withMessage("getDataType()").that(result.getDataType())
+                .isEqualTo("The Type is Bond, James Bond!");
+        expect.withMessage("getSuccessCount()").that(result.getSuccessCount()).isEqualTo(0);
+        expect.withMessage("getFailCount()").that(result.getFailCount()).isEqualTo(0);
+        expect.withMessage("getErrorsCount()").that(result.getErrors()).isEmpty();
+        expect.withMessage("getMetadataHash()").that(result.getMetadataHash()).isNull();
+        expect.withMessage("describeContents()").that(result.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    public void testGetters_allFields() {
+        DataTypeResult result = createDataTypeResult("The Type is Bond, James Bond!",
+                /* successCount= */ 42, /* failCount= */ 108, Map.of("D'OH!", 666),
+                new byte[] { 4, 8, 15, 16, 23, 42 });
+
+        expect.withMessage("getDataType()").that(result.getDataType())
+                .isEqualTo("The Type is Bond, James Bond!");
+        expect.withMessage("getSuccessCount()").that(result.getSuccessCount()).isEqualTo(42);
+        expect.withMessage("getFailCount()").that(result.getFailCount()).isEqualTo(108);
+        expect.withMessage("getErrorsCount()").that(result.getErrors()).containsExactly("D'OH!",
+                666);
+        expect.withMessage("getMetadataHash()").that(result.getMetadataHash()).asList()
+                .containsExactly((byte) 4, (byte) 8, (byte) 15, (byte) 16, (byte) 23, (byte) 42)
+                .inOrder();
+        expect.withMessage("describeContents()").that(result.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    public void testParcelMethods() {
+        DataTypeResult original = createDataTypeResult("The Type is Bond, James Bond!",
+                /* successCount= */ 42, /* failCount= */ 108, Map.of("D'OH!", 666),
+                new byte[] { 4, 8, 15, 16, 23, 42 });
+        Parcel parcel = Parcel.obtain();
+        try {
+            original.writeToParcel(parcel, /* flags= */ 0);
+
+            parcel.setDataPosition(0);
+            var clone = DataTypeResult.CREATOR.createFromParcel(parcel);
+            assertWithMessage("createFromParcel()").that(clone).isNotNull();
+
+            expect.withMessage("getDataType()").that(clone.getDataType())
+                    .isEqualTo(original.getDataType());
+            expect.withMessage("getSuccessCount()").that(clone.getSuccessCount())
+                    .isEqualTo(original.getSuccessCount());
+            expect.withMessage("getFailCount()").that(clone.getFailCount())
+                    .isEqualTo(original.getFailCount());
+            expect.withMessage("getErrorsCount()").that(clone.getErrors())
+                    .containsExactlyEntriesIn(original.getErrors()).inOrder();
+            expect.withMessage("getMetadataHash()").that(clone.getMetadataHash())
+                    .isEqualTo(original.getMetadataHash());
+            expect.withMessage("describeContents()").that(clone.describeContents()).isEqualTo(0);
+        } finally {
+            parcel.recycle();
+        }
+    }
+
+    static DataTypeResult createDataTypeResult(String dataType, int successCount, int failCount,
+            Map<String, Integer> errors, byte... metadataHash) {
+        Parcel parcel = Parcel.obtain();
+        try {
+            parcel.writeString(dataType);
+            parcel.writeInt(successCount);
+            parcel.writeInt(failCount);
+            Bundle errorsBundle = new Bundle();
+            errors.entrySet()
+                    .forEach(entry -> errorsBundle.putInt(entry.getKey(), entry.getValue()));
+            parcel.writeBundle(errorsBundle);
+            parcel.writeByteArray(metadataHash);
+
+            parcel.setDataPosition(0);
+            var result = DataTypeResult.CREATOR.createFromParcel(parcel);
+            assertWithMessage("createFromParcel()").that(result).isNotNull();
+            return result;
+        } finally {
+            parcel.recycle();
+        }
+    }
+}
diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
index a3b06e8..1af3bc8 100644
--- a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
+++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
@@ -23,6 +23,7 @@
 import android.app.backup.BackupDataInput;
 import android.app.backup.BackupDataOutput;
 import android.app.backup.BackupManagerMonitor;
+import android.app.backup.BackupRestoreEventLogger;
 import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
 import android.app.backup.BackupTransport;
 import android.app.backup.RestoreDescription;
@@ -52,7 +53,6 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
@@ -924,21 +924,9 @@
                         BackupManagerMonitor.EXTRA_LOG_AGENT_LOGGING_RESULTS,
                         DataTypeResult.class);
                 for (DataTypeResult result : results) {
-                    Log.i(TAG, "\tdataType: " + result.getDataType());
-                    Log.i(TAG, "\tsuccessCount: " + result.getSuccessCount());
-                    Log.i(TAG, "\tfailCount: " + result.getFailCount());
-                    Log.i(TAG, "\tmetadataHash: " + Arrays.toString(result.getMetadataHash()));
-
-                    if (!result.getErrors().isEmpty()) {
-                        Log.i(TAG, "\terrors {");
-                        for (String error : result.getErrors().keySet()) {
-                            Log.i(TAG, "\t\t" + error + ": " + result.getErrors().get(error));
-                        }
-                        Log.i(TAG, "\t}");
-                    }
-
-                    Log.i(TAG, "}");
+                    Log.i(TAG, "\t" + BackupRestoreEventLogger.toString(result));
                 }
+                Log.i(TAG, "}");
             }
         }
     }