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 com.android.server.adservices;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.util.ArrayMap;
22 
23 import com.android.internal.annotations.GuardedBy;
24 import com.android.internal.annotations.VisibleForTesting;
25 import com.android.server.adservices.consent.AppConsentManager;
26 import com.android.server.adservices.consent.ConsentManager;
27 import com.android.server.adservices.data.topics.TopicsDao;
28 import com.android.server.adservices.rollback.RollbackHandlingManager;
29 
30 import java.io.File;
31 import java.io.IOException;
32 import java.io.PrintWriter;
33 import java.nio.file.Files;
34 import java.nio.file.Path;
35 import java.nio.file.Paths;
36 
37 /**
38  * Manager to handle User Instance. This is to ensure that each user profile is isolated.
39  *
40  * @hide
41  */
42 public class UserInstanceManager {
43 
44     private final Object mLock = new Object();
45 
46     // We have 1 ConsentManager per user/user profile. This is to isolate user's data.
47     @GuardedBy("mLock")
48     private final ArrayMap<Integer, ConsentManager> mConsentManagerMapLocked = new ArrayMap<>(0);
49 
50     @GuardedBy("mLock")
51     private final ArrayMap<Integer, AppConsentManager> mAppConsentManagerMapLocked =
52             new ArrayMap<>(0);
53 
54     @GuardedBy("UserInstanceManager.class")
55     private final ArrayMap<Integer, BlockedTopicsManager> mBlockedTopicsManagerMapLocked =
56             new ArrayMap<>(0);
57 
58     // We have 1 RollbackManager per user/user profile, to isolate each user's data.
59     @GuardedBy("mLock")
60     private final ArrayMap<Integer, RollbackHandlingManager> mRollbackHandlingManagerMapLocked =
61             new ArrayMap<>(0);
62 
63     private final String mAdServicesBaseDir;
64 
65     private final TopicsDao mTopicsDao;
66 
UserInstanceManager(@onNull TopicsDao topicsDao, @NonNull String adServicesBaseDir)67     UserInstanceManager(@NonNull TopicsDao topicsDao, @NonNull String adServicesBaseDir) {
68         mTopicsDao = topicsDao;
69         mAdServicesBaseDir = adServicesBaseDir;
70     }
71 
72     @NonNull
getOrCreateUserConsentManagerInstance(int userIdentifier)73     ConsentManager getOrCreateUserConsentManagerInstance(int userIdentifier) throws IOException {
74         synchronized (mLock) {
75             ConsentManager instance = getUserConsentManagerInstance(userIdentifier);
76             if (instance == null) {
77                 instance = ConsentManager.createConsentManager(mAdServicesBaseDir, userIdentifier);
78                 mConsentManagerMapLocked.put(userIdentifier, instance);
79             }
80             return instance;
81         }
82     }
83 
84     @NonNull
getOrCreateUserAppConsentManagerInstance(int userIdentifier)85     AppConsentManager getOrCreateUserAppConsentManagerInstance(int userIdentifier)
86             throws IOException {
87         synchronized (mLock) {
88             AppConsentManager instance = mAppConsentManagerMapLocked.get(userIdentifier);
89             if (instance == null) {
90                 instance =
91                         AppConsentManager.createAppConsentManager(
92                                 mAdServicesBaseDir, userIdentifier);
93                 mAppConsentManagerMapLocked.put(userIdentifier, instance);
94             }
95             return instance;
96         }
97     }
98 
99     @NonNull
getOrCreateUserBlockedTopicsManagerInstance(int userIdentifier)100     BlockedTopicsManager getOrCreateUserBlockedTopicsManagerInstance(int userIdentifier) {
101         synchronized (UserInstanceManager.class) {
102             BlockedTopicsManager instance = mBlockedTopicsManagerMapLocked.get(userIdentifier);
103             if (instance == null) {
104                 instance = new BlockedTopicsManager(mTopicsDao, userIdentifier);
105                 mBlockedTopicsManagerMapLocked.put(userIdentifier, instance);
106             }
107             return instance;
108         }
109     }
110 
111     @NonNull
getOrCreateUserRollbackHandlingManagerInstance( int userIdentifier, int packageVersion)112     RollbackHandlingManager getOrCreateUserRollbackHandlingManagerInstance(
113             int userIdentifier, int packageVersion) throws IOException {
114         synchronized (mLock) {
115             RollbackHandlingManager instance =
116                     mRollbackHandlingManagerMapLocked.get(userIdentifier);
117             if (instance == null) {
118                 instance =
119                         RollbackHandlingManager.createRollbackHandlingManager(
120                                 mAdServicesBaseDir, userIdentifier, packageVersion);
121                 mRollbackHandlingManagerMapLocked.put(userIdentifier, instance);
122             }
123             return instance;
124         }
125     }
126 
127     @VisibleForTesting
getUserConsentManagerInstance(int userIdentifier)128     ConsentManager getUserConsentManagerInstance(int userIdentifier) {
129         synchronized (mLock) {
130             return mConsentManagerMapLocked.get(userIdentifier);
131         }
132     }
133 
134     /**
135      * Deletes the user instance and remove the user consent related data. This will delete the
136      * directory: /data/system/adservices/user_id
137      */
deleteUserInstance(int userIdentifier)138     void deleteUserInstance(int userIdentifier) throws Exception {
139         synchronized (mLock) {
140             ConsentManager instance = mConsentManagerMapLocked.get(userIdentifier);
141             if (instance != null) {
142                 String userDirectoryPath = mAdServicesBaseDir + "/" + userIdentifier;
143                 final Path packageDir = Paths.get(userDirectoryPath);
144                 if (Files.exists(packageDir)) {
145                     if (!instance.deleteUserDirectory(new File(userDirectoryPath))) {
146                         LogUtil.e("Failed to delete " + userDirectoryPath);
147                     }
148                 }
149                 mConsentManagerMapLocked.remove(userIdentifier);
150             }
151 
152             // Delete all data in the database that belongs to this user
153             mTopicsDao.clearAllBlockedTopicsOfUser(userIdentifier);
154         }
155     }
156 
dump(PrintWriter writer, String[] args)157     void dump(PrintWriter writer, String[] args) {
158         writer.println("UserInstanceManager");
159         String prefix = "  ";
160         writer.printf("%smAdServicesBaseDir: %s\n", prefix, mAdServicesBaseDir);
161         dumpPerUserManagers(writer, prefix);
162         mTopicsDao.dump(writer, prefix, args);
163     }
164 
dumpPerUserManagers(PrintWriter writer, String prefix)165     private void dumpPerUserManagers(PrintWriter writer, String prefix) {
166         ArrayMap<Integer, PerUserDumpHelper> perUserDumpHelpers;
167 
168         synchronized (mLock) {
169             perUserDumpHelpers = new ArrayMap<>(mConsentManagerMapLocked.size());
170             for (int i = 0; i < mConsentManagerMapLocked.size(); i++) {
171                 getPerUserDumpHelperForUser(perUserDumpHelpers, mConsentManagerMapLocked.keyAt(0))
172                                 .consentMgr =
173                         mConsentManagerMapLocked.valueAt(i);
174             }
175             for (int i = 0; i < mAppConsentManagerMapLocked.size(); i++) {
176                 getPerUserDumpHelperForUser(
177                                         perUserDumpHelpers, mAppConsentManagerMapLocked.keyAt(0))
178                                 .appConsentMgr =
179                         mAppConsentManagerMapLocked.valueAt(i);
180             }
181             for (int i = 0; i < mRollbackHandlingManagerMapLocked.size(); i++) {
182                 getPerUserDumpHelperForUser(
183                                         perUserDumpHelpers,
184                                         mRollbackHandlingManagerMapLocked.keyAt(0))
185                                 .rollbackHandlingMgr =
186                         mRollbackHandlingManagerMapLocked.valueAt(i);
187             }
188         }
189         synchronized (UserInstanceManager.class) {
190             for (int i = 0; i < mBlockedTopicsManagerMapLocked.size(); i++) {
191                 getPerUserDumpHelperForUser(
192                                         perUserDumpHelpers, mBlockedTopicsManagerMapLocked.keyAt(0))
193                                 .blockedTopicsMgr =
194                         mBlockedTopicsManagerMapLocked.valueAt(i);
195             }
196         }
197 
198         int numberUsers = perUserDumpHelpers.size();
199         if (numberUsers == 0) {
200             writer.printf("%sno per-user data yet\n", prefix);
201         } else {
202             writer.printf("%s%d users:\n", prefix, numberUsers);
203             String prefix2 = prefix + "  ";
204             for (int i = 0; i < numberUsers; i++) {
205                 perUserDumpHelpers.valueAt(i).dump(writer, prefix2, perUserDumpHelpers.keyAt(i));
206             }
207         }
208     }
209 
210     @VisibleForTesting
tearDownForTesting()211     void tearDownForTesting() {
212         synchronized (mLock) {
213             for (ConsentManager consentManager : mConsentManagerMapLocked.values()) {
214                 consentManager.tearDownForTesting();
215             }
216             for (AppConsentManager appConsentManager : mAppConsentManagerMapLocked.values()) {
217                 appConsentManager.tearDownForTesting();
218             }
219             for (RollbackHandlingManager rollbackHandlingManager :
220                     mRollbackHandlingManagerMapLocked.values()) {
221                 rollbackHandlingManager.tearDownForTesting();
222             }
223         }
224     }
225 
getPerUserDumpHelperForUser( ArrayMap<Integer, PerUserDumpHelper> map, Integer userId)226     private PerUserDumpHelper getPerUserDumpHelperForUser(
227             ArrayMap<Integer, PerUserDumpHelper> map, Integer userId) {
228         PerUserDumpHelper dumper = map.get(userId);
229         if (dumper == null) {
230             dumper = new PerUserDumpHelper();
231             map.put(userId, dumper);
232         }
233         return dumper;
234     }
235 
236     /**
237      * Helper class used to group all managers per-user during dump(), as there is no guarantee that
238      * each map of managers will have all managers for a given user.
239      */
240     private static final class PerUserDumpHelper {
241         public @Nullable ConsentManager consentMgr;
242         public @Nullable AppConsentManager appConsentMgr;
243         public @Nullable BlockedTopicsManager blockedTopicsMgr;
244         public @Nullable RollbackHandlingManager rollbackHandlingMgr;
245 
dump(PrintWriter writer, String prefix, Integer userId)246         public void dump(PrintWriter writer, String prefix, Integer userId) {
247             writer.printf("%sUser %d:\n", prefix, userId);
248             String prefix2 = prefix + "  ";
249             if (consentMgr == null) {
250                 writer.printf("%sno consent manager\n", prefix2);
251             } else {
252                 consentMgr.dump(writer, prefix2);
253             }
254             if (appConsentMgr == null) {
255                 writer.printf("%sno app consent manager\n", prefix2);
256             } else {
257                 appConsentMgr.dump(writer, prefix2);
258             }
259             if (blockedTopicsMgr == null) {
260                 writer.printf("%sno blocked topics manager\n", prefix2);
261             } else {
262                 blockedTopicsMgr.dump(writer, prefix2);
263             }
264             if (rollbackHandlingMgr == null) {
265                 writer.printf("%sno rollback handling manager\n", prefix2);
266             } else {
267                 rollbackHandlingMgr.dump(writer, prefix2);
268             }
269         }
270     }
271 }
272