1 /* 2 * Copyright (C) 2025 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.MainThread; 20 import android.annotation.NonNull; 21 import android.util.ArrayMap; 22 23 import com.android.internal.annotations.VisibleForTesting; 24 25 import java.util.Arrays; 26 import java.util.Collection; 27 28 /** 29 * A simple cache for SDK-defined system feature versions. 30 * 31 * The dense representation minimizes any per-process memory impact (<1KB). The tradeoff is that 32 * custom, non-SDK defined features are not captured by the cache, for which we can rely on the 33 * usual IPC cache for related queries. 34 * 35 * @hide 36 */ 37 public final class SystemFeaturesCache { 38 39 // Sentinel value used for SDK-declared features that are unavailable on the current device. 40 private static final int UNAVAILABLE_FEATURE_VERSION = Integer.MIN_VALUE; 41 42 // This will be initialized just once, from the process main thread, but ready from any thread. 43 private static volatile SystemFeaturesCache sInstance; 44 45 // An array of versions for SDK-defined features, from [0, PackageManager.SDK_FEATURE_COUNT). 46 @NonNull 47 private final int[] mSdkFeatureVersions; 48 49 /** 50 * Installs the process-global cache instance. 51 * 52 * <p>Note: Usage should be gated on android.content.pm.Flags.cacheSdkSystemFeature(). In 53 * practice, this should only be called from 1) SystemServer init, or 2) bindApplication. 54 */ 55 @MainThread setInstance(SystemFeaturesCache instance)56 public static void setInstance(SystemFeaturesCache instance) { 57 if (sInstance != null) { 58 throw new IllegalStateException("SystemFeaturesCache instance already initialized."); 59 } 60 sInstance = instance; 61 } 62 63 /** 64 * Gets the process-global cache instance. 65 * 66 * Note: Usage should be gated on android.content.pm.Flags.cacheSdkSystemFeature(), and should 67 * always occur after the instance has been installed early in the process lifecycle. 68 */ getInstance()69 public static @NonNull SystemFeaturesCache getInstance() { 70 SystemFeaturesCache instance = sInstance; 71 if (instance == null) { 72 throw new IllegalStateException("SystemFeaturesCache not initialized"); 73 } 74 return instance; 75 } 76 77 /** Checks for existence of the process-global instance. */ hasInstance()78 public static boolean hasInstance() { 79 return sInstance != null; 80 } 81 82 /** Clears the process-global cache instance for testing. */ 83 @VisibleForTesting clearInstance()84 public static void clearInstance() { 85 sInstance = null; 86 } 87 88 /** 89 * Populates the cache from the set of all available {@link FeatureInfo} definitions. 90 * 91 * System features declared in {@link PackageManager} will be entered into the cache based on 92 * availability in this feature set. Other custom system features will be ignored. 93 */ SystemFeaturesCache(@onNull ArrayMap<String, FeatureInfo> availableFeatures)94 public SystemFeaturesCache(@NonNull ArrayMap<String, FeatureInfo> availableFeatures) { 95 this(availableFeatures.values()); 96 } 97 98 @VisibleForTesting SystemFeaturesCache(@onNull Collection<FeatureInfo> availableFeatures)99 public SystemFeaturesCache(@NonNull Collection<FeatureInfo> availableFeatures) { 100 // First set all SDK-defined features as unavailable. 101 mSdkFeatureVersions = new int[PackageManager.SDK_FEATURE_COUNT]; 102 Arrays.fill(mSdkFeatureVersions, UNAVAILABLE_FEATURE_VERSION); 103 104 // Then populate SDK-defined feature versions from the full set of runtime features. 105 for (FeatureInfo fi : availableFeatures) { 106 int sdkFeatureIndex = PackageManager.maybeGetSdkFeatureIndex(fi.name); 107 if (sdkFeatureIndex >= 0) { 108 mSdkFeatureVersions[sdkFeatureIndex] = fi.version; 109 } 110 } 111 } 112 113 /** 114 * Populates the cache from an array of SDK feature versions originally obtained via {@link 115 * #getSdkFeatureVersions()} from another instance. 116 */ SystemFeaturesCache(@onNull int[] sdkFeatureVersions)117 public SystemFeaturesCache(@NonNull int[] sdkFeatureVersions) { 118 if (sdkFeatureVersions.length != PackageManager.SDK_FEATURE_COUNT) { 119 throw new IllegalArgumentException( 120 String.format( 121 "Unexpected cached SDK feature count: %d (expected %d)", 122 sdkFeatureVersions.length, PackageManager.SDK_FEATURE_COUNT)); 123 } 124 mSdkFeatureVersions = sdkFeatureVersions; 125 } 126 127 /** 128 * Gets the raw cached feature versions. 129 * 130 * <p>Note: This should generally only be neded for (de)serialization purposes. 131 */ 132 // TODO(b/375000483): Consider reusing the ApplicationSharedMemory mapping for version lookup. getSdkFeatureVersions()133 public int[] getSdkFeatureVersions() { 134 return mSdkFeatureVersions; 135 } 136 137 /** 138 * @return Whether the given feature is available (for SDK-defined features), otherwise null. 139 */ maybeHasFeature(@onNull String featureName, int version)140 public Boolean maybeHasFeature(@NonNull String featureName, int version) { 141 // Features defined outside of the SDK aren't cached. 142 int sdkFeatureIndex = PackageManager.maybeGetSdkFeatureIndex(featureName); 143 if (sdkFeatureIndex < 0) { 144 return null; 145 } 146 147 // As feature versions can in theory collide with our sentinel value, in the (extremely) 148 // unlikely event that the queried version matches the sentinel value, we can't distinguish 149 // between an unavailable feature and a feature with the defined sentinel value. 150 if (version == UNAVAILABLE_FEATURE_VERSION 151 && mSdkFeatureVersions[sdkFeatureIndex] == UNAVAILABLE_FEATURE_VERSION) { 152 return null; 153 } 154 155 return mSdkFeatureVersions[sdkFeatureIndex] >= version; 156 } 157 } 158