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