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