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 android.app.admin.DevicePolicyManager;
23 import android.content.Context;
24 import android.content.pm.ApplicationInfo;
25 import android.content.pm.PackageManager;
26 import android.os.Process;
27 import android.os.UserHandle;
28 import android.os.UserManager;
29 import android.util.LongSparseArray;
30 
31 import androidx.annotation.GuardedBy;
32 import androidx.annotation.NonNull;
33 
34 import com.android.modules.utils.build.SdkLevel;
35 
36 import java.io.PrintWriter;
37 import java.util.ArrayList;
38 import java.util.List;
39 
40 /**
41  * UserCache is a class that keeps track of all users that the current MediaProvider
42  * instance is responsible for. By default, it handles storage for the user it is running as,
43  * but as of Android API 31, it will also handle storage for profiles that share media
44  * with their parent - profiles for which @link{UserManager#isMediaSharedWithParent} is set.
45  *
46  * It also keeps a cache of user contexts, for improving these lookups.
47  *
48  * Note that we don't use the USER_ broadcasts for keeping this state up to date, because they
49  * aren't guaranteed to be received before the volume events for a user.
50  */
51 public class UserCache {
52     final Object mLock = new Object();
53     final Context mContext;
54     final UserManager mUserManager;
55 
56     @GuardedBy("mLock")
57     final LongSparseArray<Context> mUserContexts = new LongSparseArray<>();
58     @GuardedBy("mLock")
59     final LongSparseArray<Boolean> mUserIsWorkProfile = new LongSparseArray<>();
60 
61     @GuardedBy("mLock")
62     final ArrayList<UserHandle> mUsers = new ArrayList<>();
63 
UserCache(Context context)64     public UserCache(Context context) {
65         mContext = context;
66         mUserManager = context.getSystemService(UserManager.class);
67 
68         update();
69     }
70 
update()71     private void update() {
72         List<UserHandle> profiles = mUserManager.getEnabledProfiles();
73         synchronized (mLock) {
74             mUsers.clear();
75             // Add the user we're running as by default
76             mUsers.add(Process.myUserHandle());
77             if (!SdkLevel.isAtLeastS()) {
78                 // Before S, we only handle the owner user
79                 return;
80             }
81             // And find all profiles that share media with us
82             for (UserHandle profile : profiles) {
83                 if (!profile.equals(mContext.getUser())) {
84                     // Check if it's a profile that shares media with us
85                     Context userContext = getContextForUser(profile);
86                     if (userContext.getSystemService(UserManager.class).isMediaSharedWithParent()) {
87                         mUsers.add(profile);
88                     }
89                 }
90             }
91         }
92     }
93 
updateAndGetUsers()94     public @NonNull List<UserHandle> updateAndGetUsers() {
95         update();
96         synchronized (mLock) {
97             return (List<UserHandle>) mUsers.clone();
98         }
99     }
100 
getUsersCached()101     public @NonNull List<UserHandle> getUsersCached() {
102         synchronized (mLock) {
103             return (List<UserHandle>) mUsers.clone();
104         }
105     }
106 
isWorkProfile(int userId)107     public boolean isWorkProfile(int userId) {
108         if (userId == 0) {
109             // Owner user can not have a work profile
110             return false;
111         }
112         synchronized (mLock) {
113             int index = mUserIsWorkProfile.indexOfKey(userId);
114             if (index >= 0) {
115                 return mUserIsWorkProfile.valueAt(index);
116             }
117         }
118 
119         Context userContext = getContextForUser(UserHandle.of(userId));
120         PackageManager packageManager = userContext.getPackageManager();
121         DevicePolicyManager policyManager = userContext.getSystemService(
122                 DevicePolicyManager.class);
123         boolean isWorkProfile = false;
124         for (ApplicationInfo ai : packageManager.getInstalledApplications(
125                 MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE)) {
126             if (policyManager.isProfileOwnerApp(ai.packageName)) {
127                 isWorkProfile = true;
128             }
129         }
130 
131         synchronized (mLock) {
132             mUserIsWorkProfile.put(userId, isWorkProfile);
133         }
134 
135         return isWorkProfile;
136     }
137 
getContextForUser(@onNull UserHandle user)138     public @NonNull Context getContextForUser(@NonNull UserHandle user) {
139         Context userContext;
140         synchronized (mLock) {
141             userContext = mUserContexts.get(user.getIdentifier());
142             if (userContext != null) {
143                 return userContext;
144             }
145         }
146         try {
147             userContext = mContext.createPackageContextAsUser("system", 0, user);
148             synchronized (mLock) {
149                 mUserContexts.put(user.getIdentifier(), userContext);
150             }
151             return userContext;
152         } catch (PackageManager.NameNotFoundException e) {
153             throw new RuntimeException("Failed to create context for user " + user, e);
154         }
155     }
156 
157     /**
158      *  Returns whether the passed in user shares media with its parent (or peer).
159      *
160      * @param user user to check
161      * @return whether the user shares media with its parent
162      */
userSharesMediaWithParent(@onNull UserHandle user)163     public boolean userSharesMediaWithParent(@NonNull UserHandle user) {
164         if (Process.myUserHandle().equals(user)) {
165             // Early return path - the owner user doesn't have a parent
166             return false;
167         }
168         boolean found = userSharesMediaWithParentCached(user);
169         if (!found) {
170             // Update the cache and try again
171             update();
172             found = userSharesMediaWithParentCached(user);
173         }
174         return found;
175     }
176 
177     /**
178      *  Returns whether the passed in user shares media with its parent (or peer).
179      *  Note that the value returned here is based on cached data; it relies on
180      *  other callers to keep the user cache up-to-date.
181      *
182      * @param user user to check
183      * @return whether the user shares media with its parent
184      */
userSharesMediaWithParentCached(@onNull UserHandle user)185     public boolean userSharesMediaWithParentCached(@NonNull UserHandle user) {
186         synchronized (mLock) {
187             // It must be a user that we manage, and not equal to the main user that we run as
188             return !Process.myUserHandle().equals(user) && mUsers.contains(user);
189         }
190     }
191 
dump(PrintWriter writer)192     public void dump(PrintWriter writer) {
193         writer.println("User cache state:");
194         synchronized (mLock) {
195             for (UserHandle user : mUsers) {
196                 writer.println("  user: " + user);
197             }
198         }
199     }
200 }
201