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