• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.app.backup;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.SystemApi;
22 import android.app.backup.BackupAnnotations.OperationType;
23 import android.os.Bundle;
24 import android.os.Parcel;
25 import android.os.Parcelable;
26 import android.util.ArrayMap;
27 import android.util.Slog;
28 
29 import com.android.server.backup.Flags;
30 
31 import java.lang.annotation.Retention;
32 import java.lang.annotation.RetentionPolicy;
33 import java.nio.charset.StandardCharsets;
34 import java.security.MessageDigest;
35 import java.security.NoSuchAlgorithmException;
36 import java.util.ArrayList;
37 import java.util.Arrays;
38 import java.util.HashMap;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Objects;
42 
43 /**
44  * Class to log B&R stats for each data type that is backed up and restored by the calling app.
45  *
46  * The logger instance is designed to accept a limited number of unique
47  * {link @BackupRestoreDataType} values, as determined by the underlying implementation. Apps are
48  * expected to have a small pre-defined set of data type values they use. Attempts to log too many
49  * unique values will be rejected.
50  *
51  * @hide
52  */
53 @SystemApi
54 public final class BackupRestoreEventLogger {
55     private static final String TAG = "BackupRestoreEventLogger";
56 
57     /**
58      * Max number of unique data types for which an instance of this logger can store info. Attempts
59      * to use more distinct data type values will be rejected.
60      *
61      * @hide
62      */
63     public static final int DATA_TYPES_ALLOWED = 150;
64 
65     /**
66      * Denotes that the annotated element identifies a data type as required by the logging methods
67      * of {@code BackupRestoreEventLogger}
68      *
69      * @hide
70      */
71     @Retention(RetentionPolicy.SOURCE)
72     public @interface BackupRestoreDataType {}
73 
74     /**
75      * Denotes that the annotated element identifies an error type as required by the logging
76      * methods of {@code BackupRestoreEventLogger}
77      *
78      * @hide
79      */
80     @Retention(RetentionPolicy.SOURCE)
81     public @interface BackupRestoreError {}
82 
83     private final int mOperationType;
84     private final Map<String, DataTypeResult> mResults = new HashMap<>();
85     private final MessageDigest mHashDigest;
86 
87     /**
88      * @param operationType type of the operation for which logging will be performed. See
89      *                      {@link OperationType}. Attempts to use logging methods that don't match
90      *                      the specified operation type will be rejected (e.g. use backup methods
91      *                      for a restore logger and vice versa).
92      *
93      * @hide
94      */
BackupRestoreEventLogger(@perationType int operationType)95     public BackupRestoreEventLogger(@OperationType int operationType) {
96         mOperationType = operationType;
97 
98         MessageDigest hashDigest = null;
99         try {
100             hashDigest = MessageDigest.getInstance("SHA-256");
101         } catch (NoSuchAlgorithmException e) {
102             Slog.w("Couldn't create MessageDigest for hash computation", e);
103         }
104         mHashDigest = hashDigest;
105     }
106 
107     /**
108      * Report progress during a backup operation. Call this method for each distinct data type that
109      * your {@code BackupAgent} implementation handles for any items of that type that have been
110      * successfully backed up. Repeated calls to this method with the same {@code dataType} will
111      * increase the total count of items associated with this data type by {@code count}.
112      *
113      * This method should be called from a {@link BackupAgent} implementation during an ongoing
114      * backup operation.
115      *
116      * @param dataType the type of data being backed.
117      * @param count number of items of the given type that have been successfully backed up.
118      */
logItemsBackedUp(@onNull @ackupRestoreDataType String dataType, int count)119     public void logItemsBackedUp(@NonNull @BackupRestoreDataType String dataType, int count) {
120         logSuccess(OperationType.BACKUP, dataType, count);
121     }
122 
123     /**
124      * Report errors during a backup operation. Call this method whenever items of a certain data
125      * type failed to back up. Repeated calls to this method with the same {@code dataType} /
126      * {@code error} will increase the total count of items associated with this data type / error
127      * by {@code count}.
128      *
129      * This method should be called from a {@link BackupAgent} implementation during an ongoing
130      * backup operation.
131      *
132      * @param dataType the type of data being backed.
133      * @param count number of items of the given type that have failed to back up.
134      * @param error optional, the error that has caused the failure.
135      */
logItemsBackupFailed(@onNull @ackupRestoreDataType String dataType, int count, @Nullable @BackupRestoreError String error)136     public void logItemsBackupFailed(@NonNull @BackupRestoreDataType String dataType, int count,
137             @Nullable @BackupRestoreError String error) {
138         logFailure(OperationType.BACKUP, dataType, count, error);
139     }
140 
141     /**
142      * Report metadata associated with a data type that is currently being backed up, e.g. name of
143      * the selected wallpaper file / package. Repeated calls to this method with the same {@code
144      * dataType} will overwrite the previously supplied {@code metaData} value.
145      *
146      * The logger does not store or transmit the provided metadata value. Instead, it’s replaced
147      * with the SHA-256 hash of the provided string.
148      *
149      * This method should be called from a {@link BackupAgent} implementation during an ongoing
150      * backup operation.
151      *
152      * @param dataType the type of data being backed up.
153      * @param metaData the metadata associated with the data type.
154      */
logBackupMetadata(@onNull @ackupRestoreDataType String dataType, @NonNull String metaData)155     public void logBackupMetadata(@NonNull @BackupRestoreDataType String dataType,
156             @NonNull String metaData) {
157         logMetaData(OperationType.BACKUP, dataType, metaData);
158     }
159 
160     /**
161      * Report progress during a restore operation. Call this method for each distinct data type that
162      * your {@code BackupAgent} implementation handles if any items of that type have been
163      * successfully restored. Repeated calls to this method with the same {@code dataType} will
164      * increase the total count of items associated with this data type by {@code count}.
165      *
166      * This method should either be called from a {@link BackupAgent} implementation during an
167      * ongoing restore operation or during any delayed restore actions the package had scheduled
168      * earlier (e.g. complete the restore once a certain dependency becomes available on the
169      * device).
170      *
171      * @param dataType the type of data being restored.
172      * @param count number of items of the given type that have been successfully restored.
173      */
logItemsRestored(@onNull @ackupRestoreDataType String dataType, int count)174     public void logItemsRestored(@NonNull @BackupRestoreDataType String dataType, int count) {
175         logSuccess(OperationType.RESTORE, dataType, count);
176     }
177 
178     /**
179      * Report errors during a restore operation. Call this method whenever items of a certain data
180      * type failed to restore. Repeated calls to this method with the same {@code dataType} /
181      * {@code error} will increase the total count of items associated with this data type / error
182      * by {@code count}.
183      *
184      * This method should either be called from a {@link BackupAgent} implementation during an
185      * ongoing restore operation or during any delayed restore actions the package had scheduled
186      * earlier (e.g. complete the restore once a certain dependency becomes available on the
187      * device).
188      *
189      * @param dataType the type of data being restored.
190      * @param count number of items of the given type that have failed to restore.
191      * @param error optional, the error that has caused the failure.
192      */
logItemsRestoreFailed(@onNull @ackupRestoreDataType String dataType, int count, @Nullable @BackupRestoreError String error)193     public void logItemsRestoreFailed(@NonNull @BackupRestoreDataType String dataType, int count,
194             @Nullable @BackupRestoreError String error) {
195         logFailure(OperationType.RESTORE, dataType, count, error);
196     }
197 
198     /**
199      * Report metadata associated with a data type that is currently being restored, e.g. name of
200      * the selected wallpaper file / package. Repeated calls to this method with the same
201      * {@code dataType} will overwrite the previously supplied {@code metaData} value.
202      *
203      * The logger does not store or transmit the provided metadata value. Instead, it’s replaced
204      * with the SHA-256 hash of the provided string.
205      *
206      * This method should either be called from a {@link BackupAgent} implementation during an
207      * ongoing restore operation or during any delayed restore actions the package had scheduled
208      * earlier (e.g. complete the restore once a certain dependency becomes available on the
209      * device).
210      *
211      * @param dataType the type of data being restored.
212      * @param metadata the metadata associated with the data type.
213      */
logRestoreMetadata(@onNull @ackupRestoreDataType String dataType, @NonNull String metadata)214     public void logRestoreMetadata(@NonNull @BackupRestoreDataType String dataType,
215             @NonNull  String metadata) {
216         logMetaData(OperationType.RESTORE, dataType, metadata);
217     }
218 
219     /**
220      * Get the contents of this logger. This method should only be used by B&R code in Android
221      * Framework.
222      *
223      * @hide
224      */
getLoggingResults()225     public List<DataTypeResult> getLoggingResults() {
226         return new ArrayList<>(mResults.values());
227     }
228 
229     /**
230      * Get the operation type for which this logger was created. This method should only be used
231      * by B&R code in Android Framework.
232      *
233      * @hide
234      */
235     @OperationType
getOperationType()236     public int getOperationType() {
237         return mOperationType;
238     }
239 
240     /**
241      * Clears data logged. This method should only be used by B&R code in Android Framework.
242      *
243      * @hide
244      */
clearData()245     public void clearData() {
246         mResults.clear();
247 
248     }
249 
logSuccess(@perationType int operationType, @BackupRestoreDataType String dataType, int count)250     private void logSuccess(@OperationType int operationType,
251             @BackupRestoreDataType String dataType, int count) {
252         DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType);
253         if (dataTypeResult == null) {
254             return;
255         }
256 
257         dataTypeResult.mSuccessCount += count;
258         mResults.put(dataType, dataTypeResult);
259     }
260 
logFailure(@perationType int operationType, @NonNull @BackupRestoreDataType String dataType, int count, @Nullable @BackupRestoreError String error)261     private void logFailure(@OperationType int operationType,
262             @NonNull @BackupRestoreDataType String dataType, int count,
263             @Nullable @BackupRestoreError String error) {
264         DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType);
265         if (dataTypeResult == null) {
266             return;
267         }
268 
269         dataTypeResult.mFailCount += count;
270         if (error != null) {
271             dataTypeResult.mErrors.merge(error, count, Integer::sum);
272         }
273     }
274 
logMetaData(@perationType int operationType, @NonNull @BackupRestoreDataType String dataType, @NonNull String metaData)275     private void logMetaData(@OperationType int operationType,
276             @NonNull @BackupRestoreDataType String dataType, @NonNull String metaData) {
277         if (mHashDigest == null) {
278             return;
279         }
280         DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType);
281         if (dataTypeResult == null) {
282             return;
283         }
284 
285         dataTypeResult.mMetadataHash = getMetaDataHash(metaData);
286     }
287 
288     /**
289      * Get the result container for the given data type.
290      *
291      * @return {@code DataTypeResult} object corresponding to the given {@code dataType} or
292      *         {@code null} if the logger can't accept logs for the given data type.
293      */
294     @Nullable
getDataTypeResult(@perationType int operationType, @BackupRestoreDataType String dataType)295     private DataTypeResult getDataTypeResult(@OperationType int operationType,
296             @BackupRestoreDataType String dataType) {
297         if (operationType != mOperationType) {
298             // Operation type for which we're trying to record logs doesn't match the operation
299             // type for which this logger instance was created.
300             Slog.d(TAG, "Operation type mismatch: logger created for " + mOperationType
301                     + ", trying to log for " + operationType);
302             return null;
303         }
304 
305         if (!mResults.containsKey(dataType)) {
306             if (mResults.keySet().size() == getDataTypesAllowed()) {
307                 // This is a new data type and we're already at capacity.
308                 Slog.d(TAG, "Logger is full, ignoring new data type");
309                 return null;
310             }
311 
312             mResults.put(dataType,  new DataTypeResult(dataType));
313         }
314 
315         return mResults.get(dataType);
316     }
317 
getMetaDataHash(String metaData)318     private byte[] getMetaDataHash(String metaData) {
319         return mHashDigest.digest(metaData.getBytes(StandardCharsets.UTF_8));
320     }
321 
getDataTypesAllowed()322     private int getDataTypesAllowed(){
323         if (Flags.enableIncreaseDatatypesForAgentLogging()) {
324             return DATA_TYPES_ALLOWED;
325         } else {
326             return 15;
327         }
328     }
329 
330     /** @hide */
toString(DataTypeResult result)331     public static String toString(DataTypeResult result) {
332         Objects.requireNonNull(result, "result cannot be null");
333         StringBuilder string = new StringBuilder("type=").append(result.mDataType)
334                 .append(", successCount=").append(result.mSuccessCount)
335                 .append(", failCount=").append(result.mFailCount);
336         if (!result.mErrors.isEmpty()) {
337             string.append(", errors=").append(result.mErrors);
338         }
339         if (result.mMetadataHash != null) {
340             string.append(", metadataHash=").append(Arrays.toString(result.mMetadataHash));
341         }
342         return string.toString();
343     }
344 
345     /**
346      * Encapsulate logging results for a single data type.
347      */
348     public static final class DataTypeResult implements Parcelable {
349         @BackupRestoreDataType
350         private final String mDataType;
351         private int mSuccessCount;
352         private int mFailCount;
353         private final Map<String, Integer> mErrors = new HashMap<>();
354         private byte[] mMetadataHash;
355 
DataTypeResult(@onNull String dataType)356         public DataTypeResult(@NonNull String dataType) {
357             mDataType = dataType;
358         }
359 
360         @NonNull
361         @BackupRestoreDataType
getDataType()362         public String getDataType() {
363             return mDataType;
364         }
365 
366         /**
367          * @return number of items of the given data type that have been successfully backed up or
368          *         restored.
369          */
getSuccessCount()370         public int getSuccessCount() {
371             return mSuccessCount;
372         }
373 
374         /**
375          * @return number of items of the given data type that have failed to back up or restore.
376          */
getFailCount()377         public int getFailCount() {
378             return mFailCount;
379         }
380 
381         /**
382          * @return mapping of {@link BackupRestoreError} to the count of items that are affected by
383          *         the error.
384          */
385         @NonNull
getErrors()386         public Map<String, Integer> getErrors() {
387             return mErrors;
388         }
389 
390         /**
391          * @return SHA-256 hash of the metadata or {@code null} of no metadata has been logged for
392          *         this data type.
393          */
394         @Nullable
getMetadataHash()395         public byte[] getMetadataHash() {
396             return mMetadataHash;
397         }
398 
399         @Override
describeContents()400         public int describeContents() {
401             return 0;
402         }
403 
404         @Override
writeToParcel(@onNull Parcel dest, int flags)405         public void writeToParcel(@NonNull Parcel dest, int flags) {
406             dest.writeString(mDataType);
407 
408             dest.writeInt(mSuccessCount);
409 
410             dest.writeInt(mFailCount);
411 
412             Bundle errorsBundle = new Bundle();
413             for (Map.Entry<String, Integer> e : mErrors.entrySet()) {
414                 errorsBundle.putInt(e.getKey(), e.getValue());
415             }
416             dest.writeBundle(errorsBundle);
417 
418             dest.writeByteArray(mMetadataHash);
419         }
420 
421         @NonNull
422         public static final Parcelable.Creator<DataTypeResult> CREATOR =
423                 new Parcelable.Creator<>() {
424                     public DataTypeResult createFromParcel(Parcel in) {
425                         String dataType = in.readString();
426 
427                         int successCount = in.readInt();
428 
429                         int failCount = in.readInt();
430 
431                         Map<String, Integer> errors = new ArrayMap<>();
432                         Bundle errorsBundle = in.readBundle(getClass().getClassLoader());
433                         for (String key : errorsBundle.keySet()) {
434                             errors.put(key, errorsBundle.getInt(key));
435                         }
436 
437                         byte[] metadataHash = in.createByteArray();
438 
439                         DataTypeResult result = new DataTypeResult(dataType);
440                         result.mSuccessCount = successCount;
441                         result.mFailCount = failCount;
442                         result.mErrors.putAll(errors);
443                         result.mMetadataHash = metadataHash;
444                         return result;
445                     }
446 
447                     public DataTypeResult[] newArray(int size) {
448                         return new DataTypeResult[size];
449                     }
450                 };
451     }
452 }
453