diff options
13 files changed, 908 insertions, 65 deletions
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java index a39cf84a4ebe..038dcdb7efc9 100644 --- a/core/java/android/app/PropertyInvalidatedCache.java +++ b/core/java/android/app/PropertyInvalidatedCache.java @@ -17,6 +17,7 @@ package android.app; import static android.text.TextUtils.formatSimple; +import static com.android.internal.util.Preconditions.checkArgumentPositive; import android.annotation.NonNull; import android.annotation.Nullable; @@ -40,6 +41,7 @@ import com.android.internal.os.BackgroundThread; import dalvik.annotation.optimization.CriticalNative; import dalvik.annotation.optimization.FastNative; +import dalvik.annotation.optimization.NeverCompile; import java.io.ByteArrayOutputStream; import java.io.FileOutputStream; @@ -201,6 +203,23 @@ public class PropertyInvalidatedCache<Query, Result> { } /** + * The list of known and legal modules. The list is not sorted. + */ + private static final String[] sValidModule = { + MODULE_SYSTEM, MODULE_BLUETOOTH, MODULE_TELEPHONY, MODULE_TEST, + }; + + /** + * Verify that the module string is in the legal list. Throw if it is not. + */ + private static void throwIfInvalidModule(@NonNull String name) { + for (int i = 0; i < sValidModule.length; i++) { + if (sValidModule[i].equals(name)) return; + } + throw new IllegalArgumentException("invalid module: " + name); + } + + /** * All legal keys start with one of the following strings. */ private static final String[] sValidKeyPrefix = { @@ -254,8 +273,11 @@ public class PropertyInvalidatedCache<Query, Result> { // written to global store. private static final int NONCE_BYPASS = 3; + // The largest reserved nonce value. Update this whenever a reserved nonce is added. + private static final int MAX_RESERVED_NONCE = NONCE_BYPASS; + private static boolean isReservedNonce(long n) { - return n >= NONCE_UNSET && n <= NONCE_BYPASS; + return n >= NONCE_UNSET && n <= MAX_RESERVED_NONCE; } /** @@ -293,7 +315,7 @@ public class PropertyInvalidatedCache<Query, Result> { private long mMisses = 0; @GuardedBy("mLock") - private long[] mSkips = new long[]{ 0, 0, 0, 0 }; + private long[] mSkips = new long[MAX_RESERVED_NONCE + 1]; @GuardedBy("mLock") private long mMissOverflow = 0; @@ -854,6 +876,73 @@ public class PropertyInvalidatedCache<Query, Result> { } /** + * A public argument builder to configure cache behavior. The root instance requires a + * module; this is immutable. New instances are created with member methods. It is important + * to note that the member methods create new instances: they do not modify 'this'. The api + * is allowed to be null in the record constructor to facility reuse of Args instances. + * @hide + */ + public static record Args(@NonNull String mModule, @Nullable String mApi, int mMaxEntries) { + + // Validation: the module must be one of the known module strings and the maxEntries must + // be positive. + public Args { + throwIfInvalidModule(mModule); + checkArgumentPositive(mMaxEntries, "max cache size must be positive"); + } + + // The base constructor must include the module. Modules do not change in a source file, + // so even if the Args is reused, the module will not/should not change. The api is null, + // which is not legal, but there is no reasonable default. Clients must call the api + // method to set the field properly. + public Args(@NonNull String module) { + this(module, /* api */ null, /* maxEntries */ 32); + } + + public Args api(@NonNull String api) { + return new Args(mModule, api, mMaxEntries); + } + + public Args maxEntries(int val) { + return new Args(mModule, mApi, val); + } + } + + /** + * Make a new property invalidated cache. The key is computed from the module and api + * parameters. + * + * @param args The cache configuration. + * @param cacheName Name of this cache in debug and dumpsys + * @param computer The code to compute values that are not in the cache. + * @hide + */ + public PropertyInvalidatedCache(@NonNull Args args, @NonNull String cacheName, + @Nullable QueryHandler<Query, Result> computer) { + mPropertyName = createPropertyName(args.mModule, args.mApi); + mCacheName = cacheName; + mNonce = getNonceHandler(mPropertyName); + mMaxEntries = args.mMaxEntries; + mCache = createMap(); + mComputer = (computer != null) ? computer : new DefaultComputer<>(this); + registerCache(); + } + + /** + * Burst a property name into module and api. Throw if the key is invalid. This method is + * used in to transition legacy cache constructors to the args constructor. + */ + private static Args parseProperty(@NonNull String name) { + throwIfInvalidCacheKey(name); + // Strip off the leading well-known prefix. + String base = name.substring(CACHE_KEY_PREFIX.length() + 1); + int dot = base.indexOf("."); + String module = base.substring(0, dot); + String api = base.substring(dot + 1); + return new Args(module).api(api); + } + + /** * Make a new property invalidated cache. This constructor names the cache after the * property name. New clients should prefer the constructor that takes an explicit * cache name. @@ -867,7 +956,7 @@ public class PropertyInvalidatedCache<Query, Result> { * @hide */ public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName) { - this(maxEntries, propertyName, propertyName); + this(parseProperty(propertyName).maxEntries(maxEntries), propertyName, null); } /** @@ -883,13 +972,7 @@ public class PropertyInvalidatedCache<Query, Result> { */ public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName, @NonNull String cacheName) { - mPropertyName = propertyName; - mCacheName = cacheName; - mNonce = getNonceHandler(mPropertyName); - mMaxEntries = maxEntries; - mComputer = new DefaultComputer<>(this); - mCache = createMap(); - registerCache(); + this(parseProperty(propertyName).maxEntries(maxEntries), cacheName, null); } /** @@ -907,13 +990,7 @@ public class PropertyInvalidatedCache<Query, Result> { @TestApi public PropertyInvalidatedCache(int maxEntries, @NonNull String module, @NonNull String api, @NonNull String cacheName, @NonNull QueryHandler<Query, Result> computer) { - mPropertyName = createPropertyName(module, api); - mCacheName = cacheName; - mNonce = getNonceHandler(mPropertyName); - mMaxEntries = maxEntries; - mComputer = computer; - mCache = createMap(); - registerCache(); + this(new Args(module).maxEntries(maxEntries).api(api), cacheName, computer); } // Create a map. This should be called only from the constructor. @@ -1181,7 +1258,8 @@ public class PropertyInvalidatedCache<Query, Result> { public @Nullable Result query(@NonNull Query query) { // Let access to mDisabled race: it's atomic anyway. long currentNonce = (!isDisabled()) ? getCurrentNonce() : NONCE_DISABLED; - if (bypass(query)) { + if (!isReservedNonce(currentNonce) + && bypass(query)) { currentNonce = NONCE_BYPASS; } for (;;) { @@ -1649,54 +1727,62 @@ public class PropertyInvalidatedCache<Query, Result> { return false; } - /** - * helper method to check if dump should be skipped due to zero values - * @param args takes command arguments to check if -brief is present - * @return True if dump should be skipped - */ - private boolean skipDump(String[] args) { - for (String a : args) { - if (a.equals(BRIEF)) { - return (mSkips[NONCE_CORKED] + mSkips[NONCE_UNSET] + mSkips[NONCE_DISABLED] - + mSkips[NONCE_BYPASS] + mHits + mMisses) == 0; - } + @GuardedBy("mLock") + private long getSkipsLocked() { + int sum = 0; + for (int i = 0; i < mSkips.length; i++) { + sum += mSkips[i]; } - return false; + return sum; } + // Return true if this cache has had any activity. If the hits, misses, and skips are all + // zero then the client never tried to use the cache. + private boolean isActive() { + synchronized (mLock) { + return mHits + mMisses + getSkipsLocked() > 0; + } + } + + @NeverCompile private void dumpContents(PrintWriter pw, boolean detailed, String[] args) { // If the user has requested specific caches and this is not one of them, return // immediately. if (detailed && !showDetailed(args)) { return; } + // Does the user want brief output? + boolean brief = false; + for (String a : args) brief |= a.equals(BRIEF); NonceHandler.Stats stats = mNonce.getStats(); synchronized (mLock) { - if (!skipDump(args)) { - pw.println(formatSimple(" Cache Name: %s", cacheName())); - pw.println(formatSimple(" Property: %s", mPropertyName)); - final long skips = - mSkips[NONCE_CORKED] + mSkips[NONCE_UNSET] + mSkips[NONCE_DISABLED] - + mSkips[NONCE_BYPASS]; - pw.println(formatSimple( - " Hits: %d, Misses: %d, Skips: %d, Clears: %d", - mHits, mMisses, skips, mClears)); - pw.println(formatSimple( - " Skip-corked: %d, Skip-unset: %d, Skip-bypass: %d, Skip-other: %d", - mSkips[NONCE_CORKED], mSkips[NONCE_UNSET], - mSkips[NONCE_BYPASS], mSkips[NONCE_DISABLED])); - pw.println(formatSimple( - " Nonce: 0x%016x, Invalidates: %d, CorkedInvalidates: %d", - mLastSeenNonce, stats.invalidated, stats.corkedInvalidates)); - pw.println(formatSimple( - " Current Size: %d, Max Size: %d, HW Mark: %d, Overflows: %d", - mCache.size(), mMaxEntries, mHighWaterMark, mMissOverflow)); - pw.println(formatSimple(" Enabled: %s", mDisabled ? "false" : "true")); - pw.println(""); + if (brief && !isActive()) { + return; } + pw.println(formatSimple(" Cache Name: %s", cacheName())); + pw.println(formatSimple(" Property: %s", mPropertyName)); + pw.println(formatSimple( + " Hits: %d, Misses: %d, Skips: %d, Clears: %d", + mHits, mMisses, getSkipsLocked(), mClears)); + + // Print all the skip reasons. + pw.format(" Skip-%s: %d", sNonceName[0], mSkips[0]); + for (int i = 1; i < mSkips.length; i++) { + pw.format(", Skip-%s: %d", sNonceName[i], mSkips[i]); + } + pw.println(); + + pw.println(formatSimple( + " Nonce: 0x%016x, Invalidates: %d, CorkedInvalidates: %d", + mLastSeenNonce, stats.invalidated, stats.corkedInvalidates)); + pw.println(formatSimple( + " Current Size: %d, Max Size: %d, HW Mark: %d, Overflows: %d", + mCache.size(), mMaxEntries, mHighWaterMark, mMissOverflow)); + pw.println(formatSimple(" Enabled: %s", mDisabled ? "false" : "true")); + // No specific cache was requested. This is the default, and no details // should be dumped. if (!detailed) { @@ -1723,6 +1809,7 @@ public class PropertyInvalidatedCache<Query, Result> { * specific caches (selection is by cache name or property name); if these switches * are used then the output includes both cache statistics and cache entries. */ + @NeverCompile private static void dumpCacheInfo(@NonNull PrintWriter pw, @NonNull String[] args) { if (!sEnabled) { pw.println(" Caching is disabled in this process."); @@ -1755,6 +1842,7 @@ public class PropertyInvalidatedCache<Query, Result> { * are used then the output includes both cache statistics and cache entries. * @hide */ + @NeverCompile public static void dumpCacheInfo(@NonNull ParcelFileDescriptor pfd, @NonNull String[] args) { // Create a PrintWriter that uses a byte array. The code can safely write to // this array without fear of blocking. The completed byte array will be sent diff --git a/core/java/android/os/IpcDataCache.java b/core/java/android/os/IpcDataCache.java index 7875c23be038..8db1567336d3 100644 --- a/core/java/android/os/IpcDataCache.java +++ b/core/java/android/os/IpcDataCache.java @@ -23,6 +23,7 @@ import android.annotation.StringDef; import android.annotation.SystemApi; import android.annotation.TestApi; import android.app.PropertyInvalidatedCache; +import android.app.PropertyInvalidatedCache.Args; import android.text.TextUtils; import android.util.ArraySet; @@ -341,7 +342,7 @@ public class IpcDataCache<Query, Result> extends PropertyInvalidatedCache<Query, public IpcDataCache(int maxEntries, @NonNull @IpcDataCacheModule String module, @NonNull String api, @NonNull String cacheName, @NonNull QueryHandler<Query, Result> computer) { - super(maxEntries, module, api, cacheName, computer); + super(new Args(module).maxEntries(maxEntries).api(api), cacheName, computer); } /** @@ -563,7 +564,8 @@ public class IpcDataCache<Query, Result> extends PropertyInvalidatedCache<Query, * @hide */ public IpcDataCache(@NonNull Config config, @NonNull QueryHandler<Query, Result> computer) { - super(config.maxEntries(), config.module(), config.api(), config.name(), computer); + super(new Args(config.module()).maxEntries(config.maxEntries()).api(config.api()), + config.name(), computer); } /** diff --git a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java index c2d8f9129e2c..da1fffa0fac4 100644 --- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java +++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java @@ -17,6 +17,9 @@ package android.app; import static android.app.PropertyInvalidatedCache.NONCE_UNSET; +import static android.app.PropertyInvalidatedCache.MODULE_BLUETOOTH; +import static android.app.PropertyInvalidatedCache.MODULE_SYSTEM; +import static android.app.PropertyInvalidatedCache.MODULE_TEST; import static android.app.PropertyInvalidatedCache.NonceStore.INVALID_NONCE_INDEX; import static com.android.internal.os.Flags.FLAG_APPLICATION_SHARED_MEMORY_ENABLED; @@ -27,6 +30,8 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import android.app.PropertyInvalidatedCache.Args; +import android.annotation.SuppressLint; import com.android.internal.os.ApplicationSharedMemory; import android.platform.test.annotations.IgnoreUnderRavenwood; @@ -57,7 +62,7 @@ public class PropertyInvalidatedCacheTests { DeviceFlagsValueProvider.createCheckFlagsRule(); // Configuration for creating caches - private static final String MODULE = PropertyInvalidatedCache.MODULE_TEST; + private static final String MODULE = MODULE_TEST; private static final String API = "testApi"; // This class is a proxy for binder calls. It contains a counter that increments @@ -245,6 +250,12 @@ public class PropertyInvalidatedCacheTests { mQuery = query; } + // Create a cache from the args. The name of the cache is the api. + TestCache(Args args, TestQuery query) { + super(args, args.mApi(), query); + mQuery = query; + } + public int getRecomputeCount() { return mQuery.getRecomputeCount(); } @@ -374,14 +385,11 @@ public class PropertyInvalidatedCacheTests { @Test public void testPropertyNames() { String n1; - n1 = PropertyInvalidatedCache.createPropertyName( - PropertyInvalidatedCache.MODULE_SYSTEM, "getPackageInfo"); + n1 = PropertyInvalidatedCache.createPropertyName(MODULE_SYSTEM, "getPackageInfo"); assertEquals(n1, "cache_key.system_server.get_package_info"); - n1 = PropertyInvalidatedCache.createPropertyName( - PropertyInvalidatedCache.MODULE_SYSTEM, "get_package_info"); + n1 = PropertyInvalidatedCache.createPropertyName(MODULE_SYSTEM, "get_package_info"); assertEquals(n1, "cache_key.system_server.get_package_info"); - n1 = PropertyInvalidatedCache.createPropertyName( - PropertyInvalidatedCache.MODULE_BLUETOOTH, "getState"); + n1 = PropertyInvalidatedCache.createPropertyName(MODULE_BLUETOOTH, "getState"); assertEquals(n1, "cache_key.bluetooth.get_state"); } @@ -391,7 +399,7 @@ public class PropertyInvalidatedCacheTests { reason = "SystemProperties doesn't have permission check") public void testPermissionFailure() { // Create a cache that will write a system nonce. - TestCache sysCache = new TestCache(PropertyInvalidatedCache.MODULE_SYSTEM, "mode1"); + TestCache sysCache = new TestCache(MODULE_SYSTEM, "mode1"); try { // Invalidate the cache, which writes the system property. There must be a permission // failure. @@ -407,7 +415,7 @@ public class PropertyInvalidatedCacheTests { @Test public void testTestMode() { // Create a cache that will write a system nonce. - TestCache sysCache = new TestCache(PropertyInvalidatedCache.MODULE_SYSTEM, "mode1"); + TestCache sysCache = new TestCache(MODULE_SYSTEM, "mode1"); sysCache.testPropertyName(); // Invalidate the cache. This must succeed because the property has been marked for @@ -416,7 +424,7 @@ public class PropertyInvalidatedCacheTests { // Create a cache that uses MODULE_TEST. Invalidation succeeds whether or not the // property is tagged as being tested. - TestCache testCache = new TestCache(PropertyInvalidatedCache.MODULE_TEST, "mode2"); + TestCache testCache = new TestCache(MODULE_TEST, "mode2"); testCache.invalidateCache(); testCache.testPropertyName(); testCache.invalidateCache(); @@ -432,7 +440,7 @@ public class PropertyInvalidatedCacheTests { // The expected exception. } // Configuring a property for testing must fail if test mode is false. - TestCache cache2 = new TestCache(PropertyInvalidatedCache.MODULE_SYSTEM, "mode3"); + TestCache cache2 = new TestCache(MODULE_SYSTEM, "mode3"); try { cache2.testPropertyName(); fail("expected an IllegalStateException"); @@ -444,6 +452,34 @@ public class PropertyInvalidatedCacheTests { PropertyInvalidatedCache.setTestMode(true); } + // Test the Args-style constructor. + @Test + public void testArgsConstructor() { + // Create a cache with a maximum of four entries. + TestCache cache = new TestCache(new Args(MODULE_TEST).api("init1").maxEntries(4), + new TestQuery()); + + cache.invalidateCache(); + for (int i = 1; i <= 4; i++) { + assertEquals("foo" + i, cache.query(i)); + assertEquals(i, cache.getRecomputeCount()); + } + // Everything is in the cache. The recompute count must not increase. + for (int i = 1; i <= 4; i++) { + assertEquals("foo" + i, cache.query(i)); + assertEquals(4, cache.getRecomputeCount()); + } + // Overflow the max entries. The recompute count increases by one. + assertEquals("foo5", cache.query(5)); + assertEquals(5, cache.getRecomputeCount()); + // The oldest entry (1) has been evicted. Iterating through the first four entries will + // sequentially evict them all because the loop is proceeding oldest to newest. + for (int i = 1; i <= 4; i++) { + assertEquals("foo" + i, cache.query(i)); + assertEquals(5+i, cache.getRecomputeCount()); + } + } + // Verify the behavior of shared memory nonce storage. This does not directly test the cache // storing nonces in shared memory. @RequiresFlagsEnabled(FLAG_APPLICATION_SHARED_MEMORY_ENABLED) @@ -495,4 +531,43 @@ public class PropertyInvalidatedCacheTests { shmem.close(); } + + // Verify that an invalid module causes an exception. + private void testInvalidModule(String module) { + try { + @SuppressLint("UnusedVariable") + Args arg = new Args(module); + fail("expected an invalid module exception: module=" + module); + } catch (IllegalArgumentException e) { + // Expected exception. + } + } + + // Test various instantiation errors. The good path is tested in other methods. + @Test + public void testArgumentErrors() { + // Verify that an illegal module throws an exception. + testInvalidModule(MODULE_SYSTEM.substring(0, MODULE_SYSTEM.length() - 1)); + testInvalidModule(MODULE_SYSTEM + "x"); + testInvalidModule("mymodule"); + + // Verify that a negative max entries throws. + Args arg = new Args(MODULE_SYSTEM); + try { + arg.maxEntries(0); + fail("expected an invalid maxEntries exception"); + } catch (IllegalArgumentException e) { + // Expected exception. + } + + // Verify that creating a cache with an invalid property string throws. + try { + final String badKey = "cache_key.volume_list"; + @SuppressLint("UnusedVariable") + var cache = new PropertyInvalidatedCache<Integer, Void>(4, badKey); + fail("expected bad property exception: prop=" + badKey); + } catch (IllegalArgumentException e) { + // Expected exception. + } + } } diff --git a/media/java/android/media/quality/AmbientBacklightEvent.aidl b/media/java/android/media/quality/AmbientBacklightEvent.aidl new file mode 100644 index 000000000000..174cd461e846 --- /dev/null +++ b/media/java/android/media/quality/AmbientBacklightEvent.aidl @@ -0,0 +1,19 @@ +/* + * 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.media.quality; + +parcelable AmbientBacklightEvent; diff --git a/media/java/android/media/quality/AmbientBacklightEvent.java b/media/java/android/media/quality/AmbientBacklightEvent.java new file mode 100644 index 000000000000..3bc6b86c0615 --- /dev/null +++ b/media/java/android/media/quality/AmbientBacklightEvent.java @@ -0,0 +1,136 @@ +/* + * 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.media.quality; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * @hide + */ +public final class AmbientBacklightEvent implements Parcelable { + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({AMBIENT_BACKLIGHT_EVENT_ENABLED, AMBIENT_BACKLIGHT_EVENT_DISABLED, + AMBIENT_BACKLIGHT_EVENT_METADATA, + AMBIENT_BACKLIGHT_EVENT_INTERRUPTED}) + public @interface AmbientBacklightEventTypes {} + + /** + * Event type for ambient backlight events. The ambient backlight is enabled. + */ + public static final int AMBIENT_BACKLIGHT_EVENT_ENABLED = 1; + + /** + * Event type for ambient backlight events. The ambient backlight is disabled. + */ + public static final int AMBIENT_BACKLIGHT_EVENT_DISABLED = 2; + + /** + * Event type for ambient backlight events. The ambient backlight metadata is + * available. + */ + public static final int AMBIENT_BACKLIGHT_EVENT_METADATA = 3; + + /** + * Event type for ambient backlight events. The ambient backlight event is preempted by another + * application. + */ + public static final int AMBIENT_BACKLIGHT_EVENT_INTERRUPTED = 4; + + private final int mEventType; + @Nullable + private final AmbientBacklightMetadata mMetadata; + + public AmbientBacklightEvent(int eventType, + @Nullable AmbientBacklightMetadata metadata) { + mEventType = eventType; + mMetadata = metadata; + } + + private AmbientBacklightEvent(Parcel in) { + mEventType = in.readInt(); + mMetadata = in.readParcelable(AmbientBacklightMetadata.class.getClassLoader()); + } + + public int getEventType() { + return mEventType; + } + + @Nullable + public AmbientBacklightMetadata getMetadata() { + return mMetadata; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mEventType); + dest.writeParcelable(mMetadata, flags); + } + + @Override + public int describeContents() { + return 0; + } + + public static final @NonNull Parcelable.Creator<AmbientBacklightEvent> CREATOR = + new Parcelable.Creator<AmbientBacklightEvent>() { + public AmbientBacklightEvent createFromParcel(Parcel in) { + return new AmbientBacklightEvent(in); + } + + public AmbientBacklightEvent[] newArray(int size) { + return new AmbientBacklightEvent[size]; + } + }; + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (!(obj instanceof AmbientBacklightEvent)) { + return false; + } + + AmbientBacklightEvent other = (AmbientBacklightEvent) obj; + return mEventType == other.mEventType + && Objects.equals(mMetadata, other.mMetadata); + } + + @Override + public int hashCode() { + return mEventType * 31 + (mMetadata != null ? mMetadata.hashCode() : 0); + } + + @Override + public String toString() { + return "AmbientBacklightEvent{" + + "mEventType=" + mEventType + + ", mMetadata=" + mMetadata + + '}'; + } +} diff --git a/media/java/android/media/quality/AmbientBacklightMetadata.aidl b/media/java/android/media/quality/AmbientBacklightMetadata.aidl new file mode 100644 index 000000000000..b95a474fbf90 --- /dev/null +++ b/media/java/android/media/quality/AmbientBacklightMetadata.aidl @@ -0,0 +1,19 @@ +/* + * 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.media.quality; + +parcelable AmbientBacklightMetadata;
\ No newline at end of file diff --git a/media/java/android/media/quality/AmbientBacklightMetadata.java b/media/java/android/media/quality/AmbientBacklightMetadata.java new file mode 100644 index 000000000000..fc779348de11 --- /dev/null +++ b/media/java/android/media/quality/AmbientBacklightMetadata.java @@ -0,0 +1,127 @@ +/* + * 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.media.quality; + +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; + +import java.util.Arrays; + +/** + * @hide + */ +public class AmbientBacklightMetadata implements Parcelable { + @NonNull + private final String mPackageName; + private final int mCompressAlgorithm; + private final int mSource; + private final int mColorFormat; + private final int mHorizontalZonesNumber; + private final int mVerticalZonesNumber; + @NonNull + private final int[] mZonesColors; + + public AmbientBacklightMetadata(@NonNull String packageName, int compressAlgorithm, + int source, int colorFormat, int horizontalZonesNumber, int verticalZonesNumber, + @NonNull int[] zonesColors) { + mPackageName = packageName; + mCompressAlgorithm = compressAlgorithm; + mSource = source; + mColorFormat = colorFormat; + mHorizontalZonesNumber = horizontalZonesNumber; + mVerticalZonesNumber = verticalZonesNumber; + mZonesColors = zonesColors; + } + + private AmbientBacklightMetadata(Parcel in) { + mPackageName = in.readString(); + mCompressAlgorithm = in.readInt(); + mSource = in.readInt(); + mColorFormat = in.readInt(); + mHorizontalZonesNumber = in.readInt(); + mVerticalZonesNumber = in.readInt(); + mZonesColors = in.createIntArray(); + } + + @NonNull + public String getPackageName() { + return mPackageName; + } + + public int getCompressAlgorithm() { + return mCompressAlgorithm; + } + + public int getSource() { + return mSource; + } + + public int getColorFormat() { + return mColorFormat; + } + + public int getHorizontalZonesNumber() { + return mHorizontalZonesNumber; + } + + public int getVerticalZonesNumber() { + return mVerticalZonesNumber; + } + + @NonNull + public int[] getZonesColors() { + return mZonesColors; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(mPackageName); + dest.writeInt(mCompressAlgorithm); + dest.writeInt(mSource); + dest.writeInt(mColorFormat); + dest.writeInt(mHorizontalZonesNumber); + dest.writeInt(mVerticalZonesNumber); + dest.writeIntArray(mZonesColors); + } + + @Override + public int describeContents() { + return 0; + } + + public static final @NonNull Parcelable.Creator<AmbientBacklightMetadata> CREATOR = + new Parcelable.Creator<AmbientBacklightMetadata>() { + public AmbientBacklightMetadata createFromParcel(Parcel in) { + return new AmbientBacklightMetadata(in); + } + + public AmbientBacklightMetadata[] newArray(int size) { + return new AmbientBacklightMetadata[size]; + } + }; + + @Override + public String toString() { + return "AmbientBacklightMetadata{packageName=" + mPackageName + + ", compressAlgorithm=" + mCompressAlgorithm + ", source=" + mSource + + ", colorFormat=" + mColorFormat + ", horizontalZonesNumber=" + + mHorizontalZonesNumber + ", verticalZonesNumber=" + mVerticalZonesNumber + + ", zonesColors=" + Arrays.toString(mZonesColors) + "}"; + } +} diff --git a/media/java/android/media/quality/AmbientBacklightSettings.aidl b/media/java/android/media/quality/AmbientBacklightSettings.aidl new file mode 100644 index 000000000000..e2cdd03194cd --- /dev/null +++ b/media/java/android/media/quality/AmbientBacklightSettings.aidl @@ -0,0 +1,19 @@ +/* + * 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.media.quality; + +parcelable AmbientBacklightSettings; diff --git a/media/java/android/media/quality/AmbientBacklightSettings.java b/media/java/android/media/quality/AmbientBacklightSettings.java new file mode 100644 index 000000000000..391eb225bcab --- /dev/null +++ b/media/java/android/media/quality/AmbientBacklightSettings.java @@ -0,0 +1,203 @@ +/* + * 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.media.quality; + +import android.annotation.IntDef; +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * @hide + */ +public class AmbientBacklightSettings implements Parcelable { + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({SOURCE_NONE, SOURCE_AUDIO, SOURCE_VIDEO, SOURCE_AUDIO_VIDEO}) + public @interface Source {} + + /** + * The detection is disabled. + */ + public static final int SOURCE_NONE = 0; + + /** + * The detection is enabled for audio. + */ + public static final int SOURCE_AUDIO = 1; + + /** + * The detection is enabled for video. + */ + public static final int SOURCE_VIDEO = 2; + + /** + * The detection is enabled for audio and video. + */ + public static final int SOURCE_AUDIO_VIDEO = 3; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({COLOR_FORMAT_RGB888}) + public @interface ColorFormat {} + + /** + * The color format is RGB888. + */ + public static final int COLOR_FORMAT_RGB888 = 1; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ALGORITHM_NONE, ALGORITHM_RLE}) + public @interface CompressAlgorithm {} + + /** + * The compress algorithm is disabled. + */ + public static final int ALGORITHM_NONE = 0; + + /** + * The compress algorithm is RLE. + */ + public static final int ALGORITHM_RLE = 1; + + /** + * The source of the ambient backlight. + */ + private final int mSource; + + /** + * The maximum framerate for the ambient backlight. + */ + private final int mMaxFps; + + /** + * The color format for the ambient backlight. + */ + private final int mColorFormat; + + /** + * The number of zones in horizontal direction. + */ + private final int mHorizontalZonesNumber; + + /** + * The number of zones in vertical direction. + */ + private final int mVerticalZonesNumber; + + /** + * The flag to indicate whether the letterbox is omitted. + */ + private final boolean mIsLetterboxOmitted; + + /** + * The color threshold for the ambient backlight. + */ + private final int mThreshold; + + public AmbientBacklightSettings(int source, int maxFps, int colorFormat, + int horizontalZonesNumber, int verticalZonesNumber, boolean isLetterboxOmitted, + int threshold) { + mSource = source; + mMaxFps = maxFps; + mColorFormat = colorFormat; + mHorizontalZonesNumber = horizontalZonesNumber; + mVerticalZonesNumber = verticalZonesNumber; + mIsLetterboxOmitted = isLetterboxOmitted; + mThreshold = threshold; + } + + private AmbientBacklightSettings(Parcel in) { + mSource = in.readInt(); + mMaxFps = in.readInt(); + mColorFormat = in.readInt(); + mHorizontalZonesNumber = in.readInt(); + mVerticalZonesNumber = in.readInt(); + mIsLetterboxOmitted = in.readBoolean(); + mThreshold = in.readInt(); + } + + @Source + public int getSource() { + return mSource; + } + + public int getMaxFps() { + return mMaxFps; + } + + @ColorFormat + public int getColorFormat() { + return mColorFormat; + } + + public int getHorizontalZonesNumber() { + return mHorizontalZonesNumber; + } + + public int getVerticalZonesNumber() { + return mVerticalZonesNumber; + } + + public boolean isLetterboxOmitted() { + return mIsLetterboxOmitted; + } + + public int getThreshold() { + return mThreshold; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mSource); + dest.writeInt(mMaxFps); + dest.writeInt(mColorFormat); + dest.writeInt(mHorizontalZonesNumber); + dest.writeInt(mVerticalZonesNumber); + dest.writeBoolean(mIsLetterboxOmitted); + dest.writeInt(mThreshold); + } + + @Override + public int describeContents() { + return 0; + } + + public static final @NonNull Parcelable.Creator<AmbientBacklightSettings> CREATOR = + new Parcelable.Creator<AmbientBacklightSettings>() { + public AmbientBacklightSettings createFromParcel(Parcel in) { + return new AmbientBacklightSettings(in); + } + + public AmbientBacklightSettings[] newArray(int size) { + return new AmbientBacklightSettings[size]; + } + }; + + @Override + public String toString() { + return "AmbientBacklightSettings{Source=" + mSource + ", MaxFps=" + mMaxFps + + ", ColorFormat=" + mColorFormat + ", HorizontalZonesNumber=" + + mHorizontalZonesNumber + ", VerticalZonesNumber=" + mVerticalZonesNumber + + ", IsLetterboxOmitted=" + mIsLetterboxOmitted + ", Threshold=" + mThreshold + "}"; + } +} diff --git a/media/java/android/media/quality/IAmbientBacklightCallback.aidl b/media/java/android/media/quality/IAmbientBacklightCallback.aidl new file mode 100644 index 000000000000..159f5b7b5e71 --- /dev/null +++ b/media/java/android/media/quality/IAmbientBacklightCallback.aidl @@ -0,0 +1,24 @@ +/* + * 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.media.quality; + +import android.media.quality.AmbientBacklightEvent; + +/** @hide */ +oneway interface IAmbientBacklightCallback { + void onAmbientBacklightEvent(in AmbientBacklightEvent event); +} diff --git a/media/java/android/media/quality/IMediaQualityManager.aidl b/media/java/android/media/quality/IMediaQualityManager.aidl index 83f8e795c138..e6c79dd9681f 100644 --- a/media/java/android/media/quality/IMediaQualityManager.aidl +++ b/media/java/android/media/quality/IMediaQualityManager.aidl @@ -16,6 +16,8 @@ package android.media.quality; +import android.media.quality.AmbientBacklightSettings; +import android.media.quality.IAmbientBacklightCallback; import android.media.quality.IPictureProfileCallback; import android.media.quality.ISoundProfileCallback; import android.media.quality.ParamCapability; @@ -45,6 +47,7 @@ interface IMediaQualityManager { void registerPictureProfileCallback(in IPictureProfileCallback cb); void registerSoundProfileCallback(in ISoundProfileCallback cb); + void registerAmbientBacklightCallback(in IAmbientBacklightCallback cb); List<ParamCapability> getParamCapabilities(in List<String> names); @@ -55,4 +58,7 @@ interface IMediaQualityManager { boolean isSuperResolutionEnabled(); void setAutoSoundQualityEnabled(in boolean enabled); boolean isAutoSoundQualityEnabled(); -}
\ No newline at end of file + + void setAmbientBacklightSettings(in AmbientBacklightSettings settings); + void setAmbientBacklightEnabled(in boolean enabled); +} diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java index 23f3b59faaed..1237d673162c 100644 --- a/media/java/android/media/quality/MediaQualityManager.java +++ b/media/java/android/media/quality/MediaQualityManager.java @@ -50,6 +50,9 @@ public final class MediaQualityManager { private final List<PictureProfileCallbackRecord> mPpCallbackRecords = new ArrayList<>(); // @GuardedBy("mLock") private final List<SoundProfileCallbackRecord> mSpCallbackRecords = new ArrayList<>(); + // @GuardedBy("mLock") + private final List<AmbientBacklightCallbackRecord> mAbCallbackRecords = new ArrayList<>(); + /** * @hide @@ -115,10 +118,22 @@ public final class MediaQualityManager { } } }; + IAmbientBacklightCallback abCallback = new IAmbientBacklightCallback.Stub() { + @Override + public void onAmbientBacklightEvent(AmbientBacklightEvent event) { + synchronized (mLock) { + for (AmbientBacklightCallbackRecord record : mAbCallbackRecords) { + record.postAmbientBacklightEvent(event); + } + } + } + }; + try { if (mService != null) { mService.registerPictureProfileCallback(ppCallback); mService.registerSoundProfileCallback(spCallback); + mService.registerAmbientBacklightCallback(abCallback); } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -455,6 +470,68 @@ public final class MediaQualityManager { } } + /** + * Registers a {@link AmbientBacklightCallback}. + * @hide + */ + public void registerAmbientBacklightCallback( + @NonNull @CallbackExecutor Executor executor, + @NonNull AmbientBacklightCallback callback) { + Preconditions.checkNotNull(callback); + Preconditions.checkNotNull(executor); + synchronized (mLock) { + mAbCallbackRecords.add(new AmbientBacklightCallbackRecord(callback, executor)); + } + } + + /** + * Unregisters the existing {@link AmbientBacklightCallback}. + * @hide + */ + public void unregisterAmbientBacklightCallback( + @NonNull final AmbientBacklightCallback callback) { + Preconditions.checkNotNull(callback); + synchronized (mLock) { + for (Iterator<AmbientBacklightCallbackRecord> it = mAbCallbackRecords.iterator(); + it.hasNext(); ) { + AmbientBacklightCallbackRecord record = it.next(); + if (record.getCallback() == callback) { + it.remove(); + break; + } + } + } + } + + /** + * Set the ambient backlight settings. + * + * @param settings The settings to use for the backlight detector. + */ + public void setAmbientBacklightSettings( + @NonNull AmbientBacklightSettings settings) { + Preconditions.checkNotNull(settings); + try { + mService.setAmbientBacklightSettings(settings); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Enables or disables the ambient backlight detection. + * + * @param enabled {@code true} to enable, {@code false} to disable. + */ + public void setAmbientBacklightEnabled(boolean enabled) { + try { + mService.setAmbientBacklightEnabled(enabled); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private static final class PictureProfileCallbackRecord { private final PictureProfileCallback mCallback; private final Executor mExecutor; @@ -539,6 +616,29 @@ public final class MediaQualityManager { } } + private static final class AmbientBacklightCallbackRecord { + private final AmbientBacklightCallback mCallback; + private final Executor mExecutor; + + AmbientBacklightCallbackRecord(AmbientBacklightCallback callback, Executor executor) { + mCallback = callback; + mExecutor = executor; + } + + public AmbientBacklightCallback getCallback() { + return mCallback; + } + + public void postAmbientBacklightEvent(AmbientBacklightEvent event) { + mExecutor.execute(new Runnable() { + @Override + public void run() { + mCallback.onAmbientBacklightEvent(event); + } + }); + } + } + /** * Callback used to monitor status of picture profiles. * @hide @@ -592,4 +692,16 @@ public final class MediaQualityManager { public void onError(int errorCode) { } } + + /** + * Callback used to monitor status of ambient backlight. + * @hide + */ + public abstract static class AmbientBacklightCallback { + /** + * Called when new ambient backlight event is emitted. + */ + public void onAmbientBacklightEvent(AmbientBacklightEvent event) { + } + } } diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java index e15d41497968..a45ea1d8369d 100644 --- a/services/core/java/com/android/server/media/quality/MediaQualityService.java +++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java @@ -17,6 +17,8 @@ package com.android.server.media.quality; import android.content.Context; +import android.media.quality.AmbientBacklightSettings; +import android.media.quality.IAmbientBacklightCallback; import android.media.quality.IMediaQualityManager; import android.media.quality.IPictureProfileCallback; import android.media.quality.ISoundProfileCallback; @@ -119,6 +121,17 @@ public class MediaQualityService extends SystemService { public void registerSoundProfileCallback(final ISoundProfileCallback callback) { } + @Override + public void registerAmbientBacklightCallback(IAmbientBacklightCallback callback) { + } + + @Override + public void setAmbientBacklightSettings(AmbientBacklightSettings settings) { + } + + @Override + public void setAmbientBacklightEnabled(boolean enabled) { + } @Override public List<ParamCapability> getParamCapabilities(List<String> names) { |