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