1 /* 2 * Copyright (C) 2022 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 android.content.pm; 18 19 import android.annotation.NonNull; 20 import android.annotation.UserIdInt; 21 import android.util.SparseArrayMap; 22 23 import com.android.internal.annotations.GuardedBy; 24 import com.android.internal.annotations.VisibleForTesting; 25 import com.android.internal.util.ArrayUtils; 26 27 import libcore.util.EmptyArray; 28 29 import java.util.Objects; 30 import java.util.Random; 31 32 /** 33 * POJO to represent a package for a specific user ID. 34 * 35 * @hide 36 */ 37 public final class UserPackage { 38 private static final boolean ENABLE_CACHING = true; 39 /** 40 * The maximum number of entries to keep in the cache per user ID. 41 * The value should ideally be high enough to cover all packages on an end-user device, 42 * but low enough that stale or invalid packages would eventually (probably) get removed. 43 * This should benefit components that loop through all packages on a device and use this class, 44 * since being able to cache the objects for all packages on the device 45 * means we don't have to keep recreating the objects. 46 */ 47 @VisibleForTesting 48 static final int MAX_NUM_CACHED_ENTRIES_PER_USER = 1000; 49 50 @UserIdInt 51 public final int userId; 52 public final String packageName; 53 54 private static final Object sCacheLock = new Object(); 55 @GuardedBy("sCacheLock") 56 private static final SparseArrayMap<String, UserPackage> sCache = new SparseArrayMap<>(); 57 58 /** 59 * Set of userIDs to cache objects for. We start off with an empty set, so there's no caching 60 * by default. The system will override with a valid set of userIDs in its process so that 61 * caching becomes active in the system process. 62 */ 63 @GuardedBy("sCacheLock") 64 private static int[] sUserIds = EmptyArray.INT; 65 UserPackage(int userId, String packageName)66 private UserPackage(int userId, String packageName) { 67 this.userId = userId; 68 this.packageName = packageName; 69 } 70 71 @Override toString()72 public String toString() { 73 return "<" + userId + ">" + packageName; 74 } 75 76 @Override equals(Object obj)77 public boolean equals(Object obj) { 78 if (this == obj) { 79 return true; 80 } 81 if (obj instanceof UserPackage) { 82 UserPackage other = (UserPackage) obj; 83 return userId == other.userId && Objects.equals(packageName, other.packageName); 84 } 85 return false; 86 } 87 88 @Override hashCode()89 public int hashCode() { 90 int result = 0; 91 result = 31 * result + userId; 92 result = 31 * result + packageName.hashCode(); 93 return result; 94 } 95 96 /** Return an instance of this class representing the given userId + packageName combination. */ 97 @NonNull of(@serIdInt int userId, @NonNull String packageName)98 public static UserPackage of(@UserIdInt int userId, @NonNull String packageName) { 99 if (!ENABLE_CACHING) { 100 return new UserPackage(userId, packageName); 101 } 102 103 synchronized (sCacheLock) { 104 if (!ArrayUtils.contains(sUserIds, userId)) { 105 // Don't cache objects for invalid userIds. 106 return new UserPackage(userId, packageName); 107 } 108 109 UserPackage up = sCache.get(userId, packageName); 110 if (up == null) { 111 maybePurgeRandomEntriesLocked(userId); 112 packageName = packageName.intern(); 113 up = new UserPackage(userId, packageName); 114 sCache.add(userId, packageName, up); 115 } 116 return up; 117 } 118 } 119 120 /** Remove the specified app from the cache. */ removeFromCache(@serIdInt int userId, @NonNull String packageName)121 public static void removeFromCache(@UserIdInt int userId, @NonNull String packageName) { 122 if (!ENABLE_CACHING) { 123 return; 124 } 125 126 synchronized (sCacheLock) { 127 sCache.delete(userId, packageName); 128 } 129 } 130 131 /** Indicate the list of valid user IDs on the device. */ setValidUserIds(@onNull int[] userIds)132 public static void setValidUserIds(@NonNull int[] userIds) { 133 if (!ENABLE_CACHING) { 134 return; 135 } 136 137 userIds = userIds.clone(); 138 synchronized (sCacheLock) { 139 sUserIds = userIds; 140 141 for (int u = sCache.numMaps() - 1; u >= 0; --u) { 142 final int userId = sCache.keyAt(u); 143 if (!ArrayUtils.contains(userIds, userId)) { 144 sCache.deleteAt(u); 145 } 146 } 147 } 148 } 149 150 @VisibleForTesting numEntriesForUser(int userId)151 public static int numEntriesForUser(int userId) { 152 synchronized (sCacheLock) { 153 return sCache.numElementsForKey(userId); 154 } 155 } 156 157 /** Purge a random set of entries if the cache size is too large. */ 158 @GuardedBy("sCacheLock") maybePurgeRandomEntriesLocked(int userId)159 private static void maybePurgeRandomEntriesLocked(int userId) { 160 final int uIdx = sCache.indexOfKey(userId); 161 if (uIdx < 0) { 162 return; 163 } 164 int numCached = sCache.numElementsForKeyAt(uIdx); 165 if (numCached < MAX_NUM_CACHED_ENTRIES_PER_USER) { 166 return; 167 } 168 // Purge a random set of 1% of cached elements for the userId. We don't want to use a 169 // deterministic system of purging because that may cause us to repeatedly remove elements 170 // that are frequently added and queried more than others. Choosing a random set 171 // means we will probably eventually remove less useful elements. 172 // An LRU cache is too expensive for this commonly used utility class. 173 final Random rand = new Random(); 174 final int numToPurge = Math.max(1, MAX_NUM_CACHED_ENTRIES_PER_USER / 100); 175 for (int i = 0; i < numToPurge && numCached > 0; ++i) { 176 final int removeIdx = rand.nextInt(numCached--); 177 sCache.deleteAt(uIdx, removeIdx); 178 } 179 } 180 } 181