Loading apct-tests/perftests/core/src/android/content/pm/SystemFeaturesPerfTest.java 0 → 100644 +96 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.content.pm; import android.perftests.utils.BenchmarkState; import android.perftests.utils.PerfStatusReporter; import androidx.test.filters.LargeTest; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import com.android.internal.pm.RoSystemFeatures; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import java.util.Arrays; @RunWith(AndroidJUnit4.class) @LargeTest public class SystemFeaturesPerfTest { // As each query is relatively cheap, add an inner iteration loop to reduce execution noise. private static final int NUM_ITERATIONS = 10; @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); @Test public void hasSystemFeature_PackageManager() { final PackageManager pm = InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager(); final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { for (int i = 0; i < NUM_ITERATIONS; ++i) { pm.hasSystemFeature(PackageManager.FEATURE_WATCH); pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK); pm.hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS); pm.hasSystemFeature(PackageManager.FEATURE_AUTOFILL); pm.hasSystemFeature("com.android.custom.feature.1"); pm.hasSystemFeature("foo"); pm.hasSystemFeature(""); } } } @Test public void hasSystemFeature_SystemFeaturesCache() { final PackageManager pm = InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager(); final SystemFeaturesCache cache = new SystemFeaturesCache(Arrays.asList(pm.getSystemAvailableFeatures())); final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { for (int i = 0; i < NUM_ITERATIONS; ++i) { cache.maybeHasFeature(PackageManager.FEATURE_WATCH, 0); cache.maybeHasFeature(PackageManager.FEATURE_LEANBACK, 0); cache.maybeHasFeature(PackageManager.FEATURE_IPSEC_TUNNELS, 0); cache.maybeHasFeature(PackageManager.FEATURE_AUTOFILL, 0); cache.maybeHasFeature("com.android.custom.feature.1", 0); cache.maybeHasFeature("foo", 0); cache.maybeHasFeature("", 0); } } } @Test public void hasSystemFeature_RoSystemFeatures() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { for (int i = 0; i < NUM_ITERATIONS; ++i) { RoSystemFeatures.maybeHasFeature(PackageManager.FEATURE_WATCH, 0); RoSystemFeatures.maybeHasFeature(PackageManager.FEATURE_LEANBACK, 0); RoSystemFeatures.maybeHasFeature(PackageManager.FEATURE_IPSEC_TUNNELS, 0); RoSystemFeatures.maybeHasFeature(PackageManager.FEATURE_AUTOFILL, 0); RoSystemFeatures.maybeHasFeature("com.android.custom.feature.1", 0); RoSystemFeatures.maybeHasFeature("foo", 0); RoSystemFeatures.maybeHasFeature("", 0); } } } } core/java/android/content/pm/SystemFeaturesCache.aidl 0 → 100644 +19 −0 Original line number Diff line number Diff line /* ** Copyright 2025, The Android Open Source Project ** ** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. ** You may obtain a copy of the License at ** ** http://www.apache.org/licenses/LICENSE-2.0 ** ** Unless required by applicable law or agreed to in writing, software ** distributed under the License is distributed on an "AS IS" BASIS, ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ** See the License for the specific language governing permissions and ** limitations under the License. */ package android.content.pm; parcelable SystemFeaturesCache; core/java/android/content/pm/SystemFeaturesCache.java 0 → 100644 +133 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.content.pm; import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; import android.util.ArrayMap; import com.android.internal.annotations.VisibleForTesting; import java.util.Arrays; import java.util.Collection; /** * A simple cache for SDK-defined system feature versions. * * The dense representation minimizes any per-process memory impact (<1KB). The tradeoff is that * custom, non-SDK defined features are not captured by the cache, for which we can rely on the * usual IPC cache for related queries. * * @hide */ public final class SystemFeaturesCache implements Parcelable { // Sentinel value used for SDK-declared features that are unavailable on the current device. private static final int UNAVAILABLE_FEATURE_VERSION = Integer.MIN_VALUE; // An array of versions for SDK-defined features, from [0, PackageManager.SDK_FEATURE_COUNT). @NonNull private final int[] mSdkFeatureVersions; /** * Populates the cache from the set of all available {@link FeatureInfo} definitions. * * System features declared in {@link PackageManager} will be entered into the cache based on * availability in this feature set. Other custom system features will be ignored. */ public SystemFeaturesCache(@NonNull ArrayMap<String, FeatureInfo> availableFeatures) { this(availableFeatures.values()); } @VisibleForTesting public SystemFeaturesCache(@NonNull Collection<FeatureInfo> availableFeatures) { // First set all SDK-defined features as unavailable. mSdkFeatureVersions = new int[PackageManager.SDK_FEATURE_COUNT]; Arrays.fill(mSdkFeatureVersions, UNAVAILABLE_FEATURE_VERSION); // Then populate SDK-defined feature versions from the full set of runtime features. for (FeatureInfo fi : availableFeatures) { int sdkFeatureIndex = PackageManager.maybeGetSdkFeatureIndex(fi.name); if (sdkFeatureIndex >= 0) { mSdkFeatureVersions[sdkFeatureIndex] = fi.version; } } } /** Only used by @{code CREATOR.createFromParcel(...)} */ private SystemFeaturesCache(@NonNull Parcel parcel) { final int[] featureVersions = parcel.createIntArray(); if (featureVersions == null) { throw new IllegalArgumentException( "Parceled SDK feature versions should never be null"); } if (featureVersions.length != PackageManager.SDK_FEATURE_COUNT) { throw new IllegalArgumentException( String.format( "Unexpected cached SDK feature count: %d (expected %d)", featureVersions.length, PackageManager.SDK_FEATURE_COUNT)); } mSdkFeatureVersions = featureVersions; } /** * @return Whether the given feature is available (for SDK-defined features), otherwise null. */ public Boolean maybeHasFeature(@NonNull String featureName, int version) { // Features defined outside of the SDK aren't cached. int sdkFeatureIndex = PackageManager.maybeGetSdkFeatureIndex(featureName); if (sdkFeatureIndex < 0) { return null; } // As feature versions can in theory collide with our sentinel value, in the (extremely) // unlikely event that the queried version matches the sentinel value, we can't distinguish // between an unavailable feature and a feature with the defined sentinel value. if (version == UNAVAILABLE_FEATURE_VERSION && mSdkFeatureVersions[sdkFeatureIndex] == UNAVAILABLE_FEATURE_VERSION) { return null; } return mSdkFeatureVersions[sdkFeatureIndex] >= version; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(@NonNull Parcel parcel, int flags) { parcel.writeIntArray(mSdkFeatureVersions); } @NonNull public static final Parcelable.Creator<SystemFeaturesCache> CREATOR = new Parcelable.Creator<SystemFeaturesCache>() { @Override public SystemFeaturesCache createFromParcel(Parcel parcel) { return new SystemFeaturesCache(parcel); } @Override public SystemFeaturesCache[] newArray(int size) { return new SystemFeaturesCache[size]; } }; } core/tests/coretests/src/android/content/pm/SystemFeaturesCacheTest.java 0 → 100644 +116 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.content.pm; import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE; import static android.content.pm.PackageManager.FEATURE_WATCH; import static com.google.common.truth.Truth.assertThat; import android.os.Parcel; import android.util.ArrayMap; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) @SmallTest public class SystemFeaturesCacheTest { private SystemFeaturesCache mCache; @Test public void testNoFeatures() throws Exception { SystemFeaturesCache cache = new SystemFeaturesCache(new ArrayMap<String, FeatureInfo>()); assertThat(cache.maybeHasFeature("", 0)).isNull(); assertThat(cache.maybeHasFeature(FEATURE_WATCH, 0)).isFalse(); assertThat(cache.maybeHasFeature(FEATURE_PICTURE_IN_PICTURE, 0)).isFalse(); assertThat(cache.maybeHasFeature("com.missing.feature", 0)).isNull(); } @Test public void testNonSdkFeature() throws Exception { ArrayMap<String, FeatureInfo> features = new ArrayMap<>(); features.put("custom.feature", createFeature("custom.feature", 0)); SystemFeaturesCache cache = new SystemFeaturesCache(features); assertThat(cache.maybeHasFeature("custom.feature", 0)).isNull(); } @Test public void testSdkFeature() throws Exception { ArrayMap<String, FeatureInfo> features = new ArrayMap<>(); features.put(FEATURE_WATCH, createFeature(FEATURE_WATCH, 0)); SystemFeaturesCache cache = new SystemFeaturesCache(features); assertThat(cache.maybeHasFeature(FEATURE_WATCH, 0)).isTrue(); assertThat(cache.maybeHasFeature(FEATURE_WATCH, -1)).isTrue(); assertThat(cache.maybeHasFeature(FEATURE_WATCH, 1)).isFalse(); assertThat(cache.maybeHasFeature(FEATURE_WATCH, Integer.MIN_VALUE)).isTrue(); assertThat(cache.maybeHasFeature(FEATURE_WATCH, Integer.MAX_VALUE)).isFalse(); // Other SDK-declared features should be reported as unavailable. assertThat(cache.maybeHasFeature(FEATURE_PICTURE_IN_PICTURE, 0)).isFalse(); } @Test public void testSdkFeatureHasMinVersion() throws Exception { ArrayMap<String, FeatureInfo> features = new ArrayMap<>(); features.put(FEATURE_WATCH, createFeature(FEATURE_WATCH, Integer.MIN_VALUE)); SystemFeaturesCache cache = new SystemFeaturesCache(features); assertThat(cache.maybeHasFeature(FEATURE_WATCH, 0)).isFalse(); // If both the query and the feature version itself happen to use MIN_VALUE, we can't // reliably indicate availability, so it should report an indeterminate result. assertThat(cache.maybeHasFeature(FEATURE_WATCH, Integer.MIN_VALUE)).isNull(); } @Test public void testParcel() throws Exception { ArrayMap<String, FeatureInfo> features = new ArrayMap<>(); features.put(FEATURE_WATCH, createFeature(FEATURE_WATCH, 0)); SystemFeaturesCache cache = new SystemFeaturesCache(features); Parcel parcel = Parcel.obtain(); SystemFeaturesCache parceledCache; try { parcel.writeParcelable(cache, 0); parcel.setDataPosition(0); parceledCache = parcel.readParcelable(getClass().getClassLoader()); } finally { parcel.recycle(); } assertThat(parceledCache.maybeHasFeature(FEATURE_WATCH, 0)) .isEqualTo(cache.maybeHasFeature(FEATURE_WATCH, 0)); assertThat(parceledCache.maybeHasFeature(FEATURE_PICTURE_IN_PICTURE, 0)) .isEqualTo(cache.maybeHasFeature(FEATURE_PICTURE_IN_PICTURE, 0)); assertThat(parceledCache.maybeHasFeature("custom.feature", 0)) .isEqualTo(cache.maybeHasFeature("custom.feature", 0)); } private static FeatureInfo createFeature(String name, int version) { FeatureInfo fi = new FeatureInfo(); fi.name = name; fi.version = version; return fi; } } Loading
apct-tests/perftests/core/src/android/content/pm/SystemFeaturesPerfTest.java 0 → 100644 +96 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.content.pm; import android.perftests.utils.BenchmarkState; import android.perftests.utils.PerfStatusReporter; import androidx.test.filters.LargeTest; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import com.android.internal.pm.RoSystemFeatures; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import java.util.Arrays; @RunWith(AndroidJUnit4.class) @LargeTest public class SystemFeaturesPerfTest { // As each query is relatively cheap, add an inner iteration loop to reduce execution noise. private static final int NUM_ITERATIONS = 10; @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); @Test public void hasSystemFeature_PackageManager() { final PackageManager pm = InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager(); final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { for (int i = 0; i < NUM_ITERATIONS; ++i) { pm.hasSystemFeature(PackageManager.FEATURE_WATCH); pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK); pm.hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS); pm.hasSystemFeature(PackageManager.FEATURE_AUTOFILL); pm.hasSystemFeature("com.android.custom.feature.1"); pm.hasSystemFeature("foo"); pm.hasSystemFeature(""); } } } @Test public void hasSystemFeature_SystemFeaturesCache() { final PackageManager pm = InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager(); final SystemFeaturesCache cache = new SystemFeaturesCache(Arrays.asList(pm.getSystemAvailableFeatures())); final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { for (int i = 0; i < NUM_ITERATIONS; ++i) { cache.maybeHasFeature(PackageManager.FEATURE_WATCH, 0); cache.maybeHasFeature(PackageManager.FEATURE_LEANBACK, 0); cache.maybeHasFeature(PackageManager.FEATURE_IPSEC_TUNNELS, 0); cache.maybeHasFeature(PackageManager.FEATURE_AUTOFILL, 0); cache.maybeHasFeature("com.android.custom.feature.1", 0); cache.maybeHasFeature("foo", 0); cache.maybeHasFeature("", 0); } } } @Test public void hasSystemFeature_RoSystemFeatures() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { for (int i = 0; i < NUM_ITERATIONS; ++i) { RoSystemFeatures.maybeHasFeature(PackageManager.FEATURE_WATCH, 0); RoSystemFeatures.maybeHasFeature(PackageManager.FEATURE_LEANBACK, 0); RoSystemFeatures.maybeHasFeature(PackageManager.FEATURE_IPSEC_TUNNELS, 0); RoSystemFeatures.maybeHasFeature(PackageManager.FEATURE_AUTOFILL, 0); RoSystemFeatures.maybeHasFeature("com.android.custom.feature.1", 0); RoSystemFeatures.maybeHasFeature("foo", 0); RoSystemFeatures.maybeHasFeature("", 0); } } } }
core/java/android/content/pm/SystemFeaturesCache.aidl 0 → 100644 +19 −0 Original line number Diff line number Diff line /* ** Copyright 2025, The Android Open Source Project ** ** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. ** You may obtain a copy of the License at ** ** http://www.apache.org/licenses/LICENSE-2.0 ** ** Unless required by applicable law or agreed to in writing, software ** distributed under the License is distributed on an "AS IS" BASIS, ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ** See the License for the specific language governing permissions and ** limitations under the License. */ package android.content.pm; parcelable SystemFeaturesCache;
core/java/android/content/pm/SystemFeaturesCache.java 0 → 100644 +133 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.content.pm; import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; import android.util.ArrayMap; import com.android.internal.annotations.VisibleForTesting; import java.util.Arrays; import java.util.Collection; /** * A simple cache for SDK-defined system feature versions. * * The dense representation minimizes any per-process memory impact (<1KB). The tradeoff is that * custom, non-SDK defined features are not captured by the cache, for which we can rely on the * usual IPC cache for related queries. * * @hide */ public final class SystemFeaturesCache implements Parcelable { // Sentinel value used for SDK-declared features that are unavailable on the current device. private static final int UNAVAILABLE_FEATURE_VERSION = Integer.MIN_VALUE; // An array of versions for SDK-defined features, from [0, PackageManager.SDK_FEATURE_COUNT). @NonNull private final int[] mSdkFeatureVersions; /** * Populates the cache from the set of all available {@link FeatureInfo} definitions. * * System features declared in {@link PackageManager} will be entered into the cache based on * availability in this feature set. Other custom system features will be ignored. */ public SystemFeaturesCache(@NonNull ArrayMap<String, FeatureInfo> availableFeatures) { this(availableFeatures.values()); } @VisibleForTesting public SystemFeaturesCache(@NonNull Collection<FeatureInfo> availableFeatures) { // First set all SDK-defined features as unavailable. mSdkFeatureVersions = new int[PackageManager.SDK_FEATURE_COUNT]; Arrays.fill(mSdkFeatureVersions, UNAVAILABLE_FEATURE_VERSION); // Then populate SDK-defined feature versions from the full set of runtime features. for (FeatureInfo fi : availableFeatures) { int sdkFeatureIndex = PackageManager.maybeGetSdkFeatureIndex(fi.name); if (sdkFeatureIndex >= 0) { mSdkFeatureVersions[sdkFeatureIndex] = fi.version; } } } /** Only used by @{code CREATOR.createFromParcel(...)} */ private SystemFeaturesCache(@NonNull Parcel parcel) { final int[] featureVersions = parcel.createIntArray(); if (featureVersions == null) { throw new IllegalArgumentException( "Parceled SDK feature versions should never be null"); } if (featureVersions.length != PackageManager.SDK_FEATURE_COUNT) { throw new IllegalArgumentException( String.format( "Unexpected cached SDK feature count: %d (expected %d)", featureVersions.length, PackageManager.SDK_FEATURE_COUNT)); } mSdkFeatureVersions = featureVersions; } /** * @return Whether the given feature is available (for SDK-defined features), otherwise null. */ public Boolean maybeHasFeature(@NonNull String featureName, int version) { // Features defined outside of the SDK aren't cached. int sdkFeatureIndex = PackageManager.maybeGetSdkFeatureIndex(featureName); if (sdkFeatureIndex < 0) { return null; } // As feature versions can in theory collide with our sentinel value, in the (extremely) // unlikely event that the queried version matches the sentinel value, we can't distinguish // between an unavailable feature and a feature with the defined sentinel value. if (version == UNAVAILABLE_FEATURE_VERSION && mSdkFeatureVersions[sdkFeatureIndex] == UNAVAILABLE_FEATURE_VERSION) { return null; } return mSdkFeatureVersions[sdkFeatureIndex] >= version; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(@NonNull Parcel parcel, int flags) { parcel.writeIntArray(mSdkFeatureVersions); } @NonNull public static final Parcelable.Creator<SystemFeaturesCache> CREATOR = new Parcelable.Creator<SystemFeaturesCache>() { @Override public SystemFeaturesCache createFromParcel(Parcel parcel) { return new SystemFeaturesCache(parcel); } @Override public SystemFeaturesCache[] newArray(int size) { return new SystemFeaturesCache[size]; } }; }
core/tests/coretests/src/android/content/pm/SystemFeaturesCacheTest.java 0 → 100644 +116 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.content.pm; import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE; import static android.content.pm.PackageManager.FEATURE_WATCH; import static com.google.common.truth.Truth.assertThat; import android.os.Parcel; import android.util.ArrayMap; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) @SmallTest public class SystemFeaturesCacheTest { private SystemFeaturesCache mCache; @Test public void testNoFeatures() throws Exception { SystemFeaturesCache cache = new SystemFeaturesCache(new ArrayMap<String, FeatureInfo>()); assertThat(cache.maybeHasFeature("", 0)).isNull(); assertThat(cache.maybeHasFeature(FEATURE_WATCH, 0)).isFalse(); assertThat(cache.maybeHasFeature(FEATURE_PICTURE_IN_PICTURE, 0)).isFalse(); assertThat(cache.maybeHasFeature("com.missing.feature", 0)).isNull(); } @Test public void testNonSdkFeature() throws Exception { ArrayMap<String, FeatureInfo> features = new ArrayMap<>(); features.put("custom.feature", createFeature("custom.feature", 0)); SystemFeaturesCache cache = new SystemFeaturesCache(features); assertThat(cache.maybeHasFeature("custom.feature", 0)).isNull(); } @Test public void testSdkFeature() throws Exception { ArrayMap<String, FeatureInfo> features = new ArrayMap<>(); features.put(FEATURE_WATCH, createFeature(FEATURE_WATCH, 0)); SystemFeaturesCache cache = new SystemFeaturesCache(features); assertThat(cache.maybeHasFeature(FEATURE_WATCH, 0)).isTrue(); assertThat(cache.maybeHasFeature(FEATURE_WATCH, -1)).isTrue(); assertThat(cache.maybeHasFeature(FEATURE_WATCH, 1)).isFalse(); assertThat(cache.maybeHasFeature(FEATURE_WATCH, Integer.MIN_VALUE)).isTrue(); assertThat(cache.maybeHasFeature(FEATURE_WATCH, Integer.MAX_VALUE)).isFalse(); // Other SDK-declared features should be reported as unavailable. assertThat(cache.maybeHasFeature(FEATURE_PICTURE_IN_PICTURE, 0)).isFalse(); } @Test public void testSdkFeatureHasMinVersion() throws Exception { ArrayMap<String, FeatureInfo> features = new ArrayMap<>(); features.put(FEATURE_WATCH, createFeature(FEATURE_WATCH, Integer.MIN_VALUE)); SystemFeaturesCache cache = new SystemFeaturesCache(features); assertThat(cache.maybeHasFeature(FEATURE_WATCH, 0)).isFalse(); // If both the query and the feature version itself happen to use MIN_VALUE, we can't // reliably indicate availability, so it should report an indeterminate result. assertThat(cache.maybeHasFeature(FEATURE_WATCH, Integer.MIN_VALUE)).isNull(); } @Test public void testParcel() throws Exception { ArrayMap<String, FeatureInfo> features = new ArrayMap<>(); features.put(FEATURE_WATCH, createFeature(FEATURE_WATCH, 0)); SystemFeaturesCache cache = new SystemFeaturesCache(features); Parcel parcel = Parcel.obtain(); SystemFeaturesCache parceledCache; try { parcel.writeParcelable(cache, 0); parcel.setDataPosition(0); parceledCache = parcel.readParcelable(getClass().getClassLoader()); } finally { parcel.recycle(); } assertThat(parceledCache.maybeHasFeature(FEATURE_WATCH, 0)) .isEqualTo(cache.maybeHasFeature(FEATURE_WATCH, 0)); assertThat(parceledCache.maybeHasFeature(FEATURE_PICTURE_IN_PICTURE, 0)) .isEqualTo(cache.maybeHasFeature(FEATURE_PICTURE_IN_PICTURE, 0)); assertThat(parceledCache.maybeHasFeature("custom.feature", 0)) .isEqualTo(cache.maybeHasFeature("custom.feature", 0)); } private static FeatureInfo createFeature(String name, int version) { FeatureInfo fi = new FeatureInfo(); fi.name = name; fi.version = version; return fi; } }