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