1 /*
2  * Copyright (C) 2023 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 com.android.server.adservices.rollback;
18 
19 import android.annotation.NonNull;
20 import android.app.adservices.AdServicesManager;
21 import android.util.ArrayMap;
22 
23 import com.android.adservices.shared.storage.BooleanFileDatastore;
24 import com.android.internal.annotations.VisibleForTesting;
25 import com.android.server.adservices.LogUtil;
26 
27 import java.io.IOException;
28 import java.io.PrintWriter;
29 import java.util.Objects;
30 
31 /**
32  * Manager to store information on handling a rollback of the AdServices module. We will have one
33  * RollbackHandlingManager instance per user.
34  *
35  * @hide
36  */
37 public final class RollbackHandlingManager {
38     static final String STORAGE_XML_IDENTIFIER = "RollbackHandlingStorageIdentifier.xml";
39 
40     static final String MSMT_FILE_PREFIX = "Measurement";
41 
42     static final String DELETION_OCCURRED_KEY = "deletion_occurred";
43 
44     static final String VERSION_KEY = "adservices_version";
45 
46     @VisibleForTesting static final String DUMP_PREFIX = "  ";
47 
48     private final String mDatastoreDir;
49     private final int mPackageVersion;
50 
51     private final ArrayMap<Integer, BooleanFileDatastore> mBooleanFileDatastoreMap =
52             new ArrayMap<>();
53 
RollbackHandlingManager(@onNull String datastoreDir, int packageVersion)54     private RollbackHandlingManager(@NonNull String datastoreDir, int packageVersion) {
55         Objects.requireNonNull(datastoreDir);
56 
57         mDatastoreDir = datastoreDir;
58         mPackageVersion = packageVersion;
59     }
60 
61     /** Create a RollbackHandlingManager with base directory and for userIdentifier */
62     @NonNull
createRollbackHandlingManager( @onNull String baseDir, int userIdentifier, int packageVersion)63     public static RollbackHandlingManager createRollbackHandlingManager(
64             @NonNull String baseDir, int userIdentifier, int packageVersion) throws IOException {
65         Objects.requireNonNull(baseDir, "Base dir must be provided.");
66 
67         // The data store is in the directore with the following path:
68         // /data/system/adservices/{user_id}/rollback/
69         String rollbackHandlingDataStoreDir =
70                 RollbackHandlingDatastoreLocationHelper.getRollbackHandlingDataStoreDirAndCreateDir(
71                         baseDir, userIdentifier);
72 
73         return new RollbackHandlingManager(rollbackHandlingDataStoreDir, packageVersion);
74     }
75 
76     @VisibleForTesting
getOrCreateBooleanFileDatastore( @dServicesManager.DeletionApiType int deletionApiType)77     BooleanFileDatastore getOrCreateBooleanFileDatastore(
78             @AdServicesManager.DeletionApiType int deletionApiType) throws IOException {
79         synchronized (this) {
80             BooleanFileDatastore datastore = mBooleanFileDatastoreMap.get(deletionApiType);
81             if (datastore == null) {
82                 if (deletionApiType == AdServicesManager.MEASUREMENT_DELETION) {
83                     datastore =
84                             new BooleanFileDatastore(
85                                     mDatastoreDir,
86                                     MSMT_FILE_PREFIX + STORAGE_XML_IDENTIFIER,
87                                     mPackageVersion,
88                                     VERSION_KEY);
89                 }
90                 mBooleanFileDatastoreMap.put(deletionApiType, datastore);
91                 datastore.initialize();
92             }
93             return datastore;
94         }
95     }
96 
97     /** Saves that a deletion of AdServices data occurred to the storage. */
recordAdServicesDataDeletion(@dServicesManager.DeletionApiType int deletionType)98     public void recordAdServicesDataDeletion(@AdServicesManager.DeletionApiType int deletionType)
99             throws IOException {
100         BooleanFileDatastore datastore = getOrCreateBooleanFileDatastore(deletionType);
101         synchronized (this) {
102             try {
103                 datastore.put(DELETION_OCCURRED_KEY, /* value */ true);
104             } catch (IOException e) {
105                 LogUtil.e(e, "Record deletion failed due to IOException thrown by Datastore.");
106             }
107         }
108     }
109 
110     /** Returns information about whether a deletion of AdServices data occurred. */
wasAdServicesDataDeleted(@dServicesManager.DeletionApiType int deletionType)111     public boolean wasAdServicesDataDeleted(@AdServicesManager.DeletionApiType int deletionType)
112             throws IOException {
113         BooleanFileDatastore datastore = getOrCreateBooleanFileDatastore(deletionType);
114         synchronized (this) {
115             return datastore.get(DELETION_OCCURRED_KEY, /* defaultValue */ false);
116         }
117     }
118 
119     /** Returns the previous version number saved in the datastore file. */
getPreviousStoredVersion(@dServicesManager.DeletionApiType int deletionType)120     public int getPreviousStoredVersion(@AdServicesManager.DeletionApiType int deletionType)
121             throws IOException {
122         BooleanFileDatastore datastore = getOrCreateBooleanFileDatastore(deletionType);
123         return datastore.getPreviousStoredVersion();
124     }
125 
126     /**
127      * Deletes the previously stored information about whether a deletion of AdServices data
128      * occurred.
129      */
resetAdServicesDataDeletion(@dServicesManager.DeletionApiType int deletionType)130     public void resetAdServicesDataDeletion(@AdServicesManager.DeletionApiType int deletionType)
131             throws IOException {
132         BooleanFileDatastore datastore = getOrCreateBooleanFileDatastore(deletionType);
133         synchronized (this) {
134             try {
135                 datastore.put(DELETION_OCCURRED_KEY, /* value */ false);
136             } catch (IOException e) {
137                 LogUtil.e(
138                         e, "Reset deletion status failed due to IOException thrown by Datastore.");
139             }
140         }
141     }
142 
143     /** Dumps its internal state. */
dump(PrintWriter writer, String prefix)144     public void dump(PrintWriter writer, String prefix) {
145         writer.printf("%sRollbackHandlingManager:\n", prefix);
146         String prefix2 = prefix + DUMP_PREFIX;
147 
148         writer.printf("%smDatastoreDir: %s\n", prefix2, mDatastoreDir);
149         writer.printf("%smPackageVersion: %s\n", prefix2, mPackageVersion);
150 
151         int mapSize = mBooleanFileDatastoreMap.size();
152         writer.printf("%s%d datastores", prefix2, mapSize);
153         if (mapSize == 0) {
154             writer.println();
155             return;
156         }
157         writer.println(':');
158         String prefix3 = prefix2 + DUMP_PREFIX;
159         String prefix4 = prefix3 + DUMP_PREFIX;
160         for (int i = 0; i < mapSize; i++) {
161             writer.printf("%s deletion API type %d:\n", prefix3, mBooleanFileDatastoreMap.keyAt(i));
162             mBooleanFileDatastoreMap.valueAt(i).dump(writer, prefix4);
163         }
164     }
165 
166     /** tesrDown method used for testing only. */
167     @VisibleForTesting
tearDownForTesting()168     public void tearDownForTesting() {
169         synchronized (this) {
170             mBooleanFileDatastoreMap.clear();
171         }
172     }
173 }
174