1 /*
2  * Copyright (C) 2021 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.providers.media.util;
18 
19 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
20 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
21 
22 import static com.android.providers.media.util.Logging.TAG;
23 
24 import android.annotation.SuppressLint;
25 import android.app.admin.DevicePolicyManager;
26 import android.content.Context;
27 import android.content.pm.ApplicationInfo;
28 import android.content.pm.PackageManager;
29 import android.os.Process;
30 import android.os.UserHandle;
31 import android.os.UserManager;
32 import android.util.Log;
33 import android.util.LongSparseArray;
34 import android.util.SparseArray;
35 
36 import androidx.annotation.GuardedBy;
37 import androidx.annotation.NonNull;
38 
39 import com.android.modules.utils.build.SdkLevel;
40 
41 import java.io.PrintWriter;
42 import java.util.ArrayList;
43 import java.util.List;
44 
45 /**
46  * UserCache is a class that keeps track of all users that the current MediaProvider
47  * instance is responsible for. By default, it handles storage for the user it is running as,
48  * but as of Android API 31, it will also handle storage for profiles that share media
49  * with their parent - profiles for which @link{UserManager#isMediaSharedWithParent} is set.
50  *
51  * It also keeps a cache of user contexts, for improving these lookups.
52  *
53  * Note that we don't use the USER_ broadcasts for keeping this state up to date, because they
54  * aren't guaranteed to be received before the volume events for a user.
55  */
56 public class UserCache {
57     // This is being used for non work profile users. It is introduced to remove the necessity of
58     // second cache i.e. mUserIsWorkProfile
59     private static final String NO_WORK_PROFILE_OWNER_APP = "No Work Profile Owner App";
60 
61     final Object mLock = new Object();
62     final Context mContext;
63     final UserManager mUserManager;
64 
65     @GuardedBy("mLock")
66     final LongSparseArray<Context> mUserContexts = new LongSparseArray<>();
67 
68     // This contains a mapping from userId to packageName of the Profile Owner App
69     // or NO_WORK_PROFILE_OWNER_APP
70     @GuardedBy("mLock")
71     final SparseArray<String> mWorkProfileOwnerApps = new SparseArray<>();
72 
73     @GuardedBy("mLock")
74     final ArrayList<UserHandle> mUsers = new ArrayList<>();
75 
UserCache(Context context)76     public UserCache(Context context) {
77         mContext = context;
78         mUserManager = context.getSystemService(UserManager.class);
79 
80         update();
81     }
82 
83     @SuppressLint("NewApi")
update()84     private void update() {
85         List<UserHandle> profiles = mUserManager.getEnabledProfiles();
86         synchronized (mLock) {
87             mUsers.clear();
88             // Add the user we're running as by default
89             mUsers.add(Process.myUserHandle());
90             if (!SdkLevel.isAtLeastS()) {
91                 // Before S, we only handle the owner user
92                 return;
93             }
94 
95             // App cloning is not supported for profile users like AFW.
96             if (mUserManager.isProfile()) {
97                 return;
98             }
99 
100             // And find all profiles that share media with us
101             for (UserHandle profile : profiles) {
102                 if (!profile.equals(mContext.getUser())) {
103                     // Check if it's unlocked, and it's a profile that shares media with us
104                     if (isUnlockedAndMediaSharedWithParent(profile)) {
105                         mUsers.add(profile);
106                     }
107                 }
108             }
109         }
110     }
111 
isUnlockedAndMediaSharedWithParent(@onNull UserHandle profile)112     private boolean isUnlockedAndMediaSharedWithParent(@NonNull UserHandle profile) {
113         Context userContext = getContextForUser(profile);
114         UserManager userManager = userContext.getSystemService(UserManager.class);
115         return userManager.isUserUnlockingOrUnlocked(profile)
116                 && userManager.isMediaSharedWithParent();
117     }
118 
updateAndGetUsers()119     public @NonNull List<UserHandle> updateAndGetUsers() {
120         update();
121         synchronized (mLock) {
122             return (List<UserHandle>) mUsers.clone();
123         }
124     }
125 
getUsersCached()126     public @NonNull List<UserHandle> getUsersCached() {
127         synchronized (mLock) {
128             return (List<UserHandle>) mUsers.clone();
129         }
130     }
131 
isWorkProfile(int userId)132     public boolean isWorkProfile(int userId) {
133         if (userId == 0) {
134             // Owner user can not have a work profile
135             return false;
136         }
137 
138         synchronized (mLock) {
139             int index = mWorkProfileOwnerApps.indexOfKey(userId);
140             if (index >= 0) {
141                 return !NO_WORK_PROFILE_OWNER_APP.equals(mWorkProfileOwnerApps.valueAt(index));
142             }
143         }
144 
145         Context userContext = getContextForUser(UserHandle.of(userId));
146         PackageManager packageManager = userContext.getPackageManager();
147         DevicePolicyManager policyManager = userContext.getSystemService(
148                 DevicePolicyManager.class);
149         boolean isWorkProfile = false;
150         for (ApplicationInfo ai : packageManager.getInstalledApplications(
151                 MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE)) {
152             if (policyManager.isProfileOwnerApp(ai.packageName)) {
153                 synchronized (mLock) {
154                     mWorkProfileOwnerApps.put(userId, ai.packageName);
155                 }
156                 isWorkProfile = true;
157                 break;
158             }
159         }
160 
161         if(!isWorkProfile) {
162             synchronized (mLock) {
163                 // NO_WORK_PROFILE_OWNER_APP is being used for all the non work profile users
164                 mWorkProfileOwnerApps.put(userId, NO_WORK_PROFILE_OWNER_APP);
165             }
166         }
167 
168         return isWorkProfile;
169     }
170 
getContextForUser(@onNull UserHandle user)171     public @NonNull Context getContextForUser(@NonNull UserHandle user) {
172         Context userContext;
173         synchronized (mLock) {
174             userContext = mUserContexts.get(user.getIdentifier());
175             if (userContext != null) {
176                 return userContext;
177             }
178         }
179         try {
180             userContext = mContext.createPackageContextAsUser("system", 0, user);
181             synchronized (mLock) {
182                 mUserContexts.put(user.getIdentifier(), userContext);
183             }
184             return userContext;
185         } catch (PackageManager.NameNotFoundException e) {
186             throw new RuntimeException("Failed to create context for user " + user, e);
187         }
188     }
189 
190     /**
191      *  Returns whether the passed in user shares media with its parent (or peer).
192      *
193      * @param user user to check
194      * @return whether the user shares media with its parent
195      */
userSharesMediaWithParent(@onNull UserHandle user)196     public boolean userSharesMediaWithParent(@NonNull UserHandle user) {
197         if (Process.myUserHandle().equals(user)) {
198             // Early return path - the owner user doesn't have a parent
199             return false;
200         }
201         boolean found = userSharesMediaWithParentCached(user);
202         if (!found) {
203             // Update the cache and try again
204             update();
205             found = userSharesMediaWithParentCached(user);
206         }
207         return found;
208     }
209 
210     /**
211      *  Returns whether the passed in user shares media with its parent (or peer).
212      *  Note that the value returned here is based on cached data; it relies on
213      *  other callers to keep the user cache up-to-date.
214      *
215      * @param user user to check
216      * @return whether the user shares media with its parent
217      */
userSharesMediaWithParentCached(@onNull UserHandle user)218     public boolean userSharesMediaWithParentCached(@NonNull UserHandle user) {
219         synchronized (mLock) {
220             // It must be a user that we manage, and not equal to the main user that we run as
221             return !Process.myUserHandle().equals(user) && mUsers.contains(user);
222         }
223     }
224 
dump(PrintWriter writer)225     public void dump(PrintWriter writer) {
226         writer.println("User cache state:");
227         synchronized (mLock) {
228             for (UserHandle user : mUsers) {
229                 writer.println("  user: " + user);
230             }
231         }
232     }
233 
invalidateWorkProfileOwnerApps(@onNull String packageName)234     public void invalidateWorkProfileOwnerApps(@NonNull String packageName) {
235         synchronized (mLock) {
236             if (mWorkProfileOwnerApps.size() == 0) {
237                 Log.w(TAG, "WorkProfileOwnerApps cache is empty");
238                 return;
239             }
240 
241             boolean cacheMissForGivenPackage = true;
242             for (int i = 0; i < mWorkProfileOwnerApps.size(); i++) {
243                 final int userId = mWorkProfileOwnerApps.keyAt(i);
244                 if (packageName.equals(mWorkProfileOwnerApps.get(userId))) {
245                     Log.i(TAG, "Invalidating WorkProfileOwnerApps cache for package " + packageName
246                             + ". UserId: " + userId);
247                     mWorkProfileOwnerApps.remove(userId);
248                     cacheMissForGivenPackage = false;
249                 }
250             }
251 
252             if(cacheMissForGivenPackage) {
253                 Log.w(TAG, "WorkProfileOwnerApps cache miss for package " + packageName);
254             }
255         }
256     }
257 }
258