diff options
author | 2024-11-25 19:21:56 -0800 | |
---|---|---|
committer | 2024-12-03 18:45:13 +0000 | |
commit | 80fd09f1e89f621134161c6ce3bcee5f8cdf7b7c (patch) | |
tree | db789c969f1b5e312ae42fa1fcf9ad5f41edf7c8 | |
parent | b41cf3061550e983a9858cddc43f3aa46ad1d858 (diff) |
Separate permission notifications from cache updates
If the feature is enabled, the following behavior changes:
1. Caches that used CACHE_KEY_PACKAGE_INFO as a key now use
CACHE_KEY_PACKAGE_INFO_CACHE. This has a different value from the
previous key, so it will be stored in shared memory, not in a
sysprop.
2. CACHE_KEY_PACKAGE_INFO_CACHE caches do not use auto-corking.
3. AudioServer listens to changes on CACHE_KEY_PACKAGE_INFO_NOTIFY,
sysprop, which has the same value as the legacy
CACHE_KEY_PACKAGE_INFO.
4. A new Thread in AudioServer spins, waiting for changes on the
CACHE_KEY_PACKAGE_INFO_CACHE nonce. When the nonce changes, the
CACHE_KEY_PACKAGE_INFO_NOTIFY sysprop is updated. Updating is
throttled using the same logic as auto-corking.
The net effect is that all cache operations take place through shared
memory. PackageManger cache invalidation never stalls behind a sysprop
set operation. An adapter in AudioServer generates the legacy sysprop
changes.
Tested with the flag enabled:
* Verified that no cache uses "package_info" as its key
* Verified that caches use "package_info_cache" as their key
* Verified no regressions on UserLifecyceTest#switchUser
Flag: android.app.pic_separate_permission_notifications
Bug: 379699402
Bug: 360897450
Test: atest
* FrameworksCoreTests:PropertyInvalidatedCacheTests
* FrameworksCoreTests:IpcDataCacheTest
* CtsOsTestCases:IpcDataCacheTest
* ServiceBluetoothTests
* android.app.appops.cts.AppOpsLoggingTest
* android.media.audio.cts.AudioFocusTest
* CtsMediaAudioPermissionTestCases
* CtsMediaAudioTestCases:AudioRecordTest
Change-Id: I314e4ea32765ca22b8ecd23a1bde93ab2c828111
6 files changed, 205 insertions, 24 deletions
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java index 3973c58c0708..9bb0ba4cf8fa 100644 --- a/core/java/android/app/PropertyInvalidatedCache.java +++ b/core/java/android/app/PropertyInvalidatedCache.java @@ -80,6 +80,17 @@ import java.util.concurrent.atomic.AtomicLong; @android.ravenwood.annotation.RavenwoodKeepWholeClass public class PropertyInvalidatedCache<Query, Result> { /** + * A method to report if the PermissionManager notifications can be separated from cache + * invalidation. The feature relies on a series of flags; the dependency is captured in this + * method. + * @hide + */ + public static boolean separatePermissionNotificationsEnabled() { + return isSharedMemoryAvailable() + && Flags.picSeparatePermissionNotifications(); + } + + /** * This is a configuration class that customizes a cache instance. * @hide */ @@ -1921,6 +1932,10 @@ public class PropertyInvalidatedCache<Query, Result> { } public AutoCorker(@NonNull String propertyName, int autoCorkDelayMs) { + if (separatePermissionNotificationsEnabled()) { + throw new IllegalStateException("AutoCorking is unavailable"); + } + mPropertyName = propertyName; mAutoCorkDelayMs = autoCorkDelayMs; // We can't initialize mHandler here: when we're created, the main loop might not diff --git a/core/java/android/app/performance.aconfig b/core/java/android/app/performance.aconfig index 359c84eeb559..82c7617746e6 100644 --- a/core/java/android/app/performance.aconfig +++ b/core/java/android/app/performance.aconfig @@ -37,6 +37,14 @@ flag { flag { namespace: "system_performance" + name: "pic_separate_permission_notifications" + is_fixed_read_only: true + description: "Seperate PermissionManager notifications from cache udpates" + bug: "373752556" +} + +flag { + namespace: "system_performance" name: "pic_cache_nulls" is_fixed_read_only: true description: "Cache null returns from binder calls" diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index a06eb1c5b4ad..7e0805137d0b 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -11651,7 +11651,7 @@ public abstract class PackageManager { private static final PropertyInvalidatedCache<ApplicationInfoQuery, ApplicationInfo> sApplicationInfoCache = new PropertyInvalidatedCache<ApplicationInfoQuery, ApplicationInfo>( - 2048, PermissionManager.CACHE_KEY_PACKAGE_INFO, + 2048, PermissionManager.CACHE_KEY_PACKAGE_INFO_CACHE, "getApplicationInfo") { @Override public ApplicationInfo recompute(ApplicationInfoQuery query) { @@ -11682,18 +11682,6 @@ public abstract class PackageManager { sApplicationInfoCache.disableLocal(); } - private static final PropertyInvalidatedCache.AutoCorker sCacheAutoCorker = - new PropertyInvalidatedCache.AutoCorker(PermissionManager.CACHE_KEY_PACKAGE_INFO); - - /** - * Invalidate caches of package and permission information system-wide. - * - * @hide - */ - public static void invalidatePackageInfoCache() { - sCacheAutoCorker.autoCork(); - } - // Some of the flags don't affect the query result, but let's be conservative and cache // each combination of flags separately. @@ -11752,7 +11740,7 @@ public abstract class PackageManager { private static final PropertyInvalidatedCache<PackageInfoQuery, PackageInfo> sPackageInfoCache = new PropertyInvalidatedCache<PackageInfoQuery, PackageInfo>( - 2048, PermissionManager.CACHE_KEY_PACKAGE_INFO, + 2048, PermissionManager.CACHE_KEY_PACKAGE_INFO_CACHE, "getPackageInfo") { @Override public PackageInfo recompute(PackageInfoQuery query) { @@ -11784,17 +11772,40 @@ public abstract class PackageManager { /** * Inhibit package info cache invalidations when correct. * - * @hide */ + * @hide + */ public static void corkPackageInfoCache() { - PropertyInvalidatedCache.corkInvalidations(PermissionManager.CACHE_KEY_PACKAGE_INFO); + sPackageInfoCache.corkInvalidations(); } /** * Enable package info cache invalidations. * - * @hide */ + * @hide + */ public static void uncorkPackageInfoCache() { - PropertyInvalidatedCache.uncorkInvalidations(PermissionManager.CACHE_KEY_PACKAGE_INFO); + sPackageInfoCache.uncorkInvalidations(); + } + + // This auto-corker is obsolete once the separate permission notifications feature is + // committed. + private static final PropertyInvalidatedCache.AutoCorker sCacheAutoCorker = + PropertyInvalidatedCache.separatePermissionNotificationsEnabled() + ? null + : new PropertyInvalidatedCache + .AutoCorker(PermissionManager.CACHE_KEY_PACKAGE_INFO_CACHE); + + /** + * Invalidate caches of package and permission information system-wide. + * + * @hide + */ + public static void invalidatePackageInfoCache() { + if (PropertyInvalidatedCache.separatePermissionNotificationsEnabled()) { + sPackageInfoCache.invalidateCache(); + } else { + sCacheAutoCorker.autoCork(); + } } /** diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index e98397d104d6..2473de4ff6d7 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -1795,14 +1795,45 @@ public final class PermissionManager { } } - /** @hide */ - public static final String CACHE_KEY_PACKAGE_INFO = + // The legacy system property "package_info" had two purposes: to invalidate PIC caches and to + // signal that package information, and therefore permissions, might have changed. + // AudioSystem is the only client of the signaling behavior. The "separate permissions + // notification" feature splits the two behaviors into two system property names. + // + // If the feature is disabled (legacy behavior) then the two system property names have the + // same value. This means there is only one system property in use. + // + // If the feature is enabled, then the two system property names have different values, which + // means there is a system property used by PIC and a system property used for signaling. The + // legacy value is hard-coded in native code that relies on the signaling behavior, so the + // system property name for signaling is the legacy property name, and the system property + // name for PIC is new. + private static String getPackageInfoCacheKey() { + if (PropertyInvalidatedCache.separatePermissionNotificationsEnabled()) { + return PropertyInvalidatedCache.createSystemCacheKey("package_info_cache"); + } else { + return CACHE_KEY_PACKAGE_INFO_NOTIFY; + } + } + + /** + * The system property that is used to notify clients that package information, and therefore + * permissions, may have changed. + * @hide + */ + public static final String CACHE_KEY_PACKAGE_INFO_NOTIFY = PropertyInvalidatedCache.createSystemCacheKey("package_info"); + /** + * The system property that is used to invalidate PIC caches. + * @hide + */ + public static final String CACHE_KEY_PACKAGE_INFO_CACHE = getPackageInfoCacheKey(); + /** @hide */ private static final PropertyInvalidatedCache<PermissionQuery, Integer> sPermissionCache = new PropertyInvalidatedCache<PermissionQuery, Integer>( - 2048, CACHE_KEY_PACKAGE_INFO, "checkPermission") { + 2048, CACHE_KEY_PACKAGE_INFO_CACHE, "checkPermission") { @Override public Integer recompute(PermissionQuery query) { return checkPermissionUncached(query.permission, query.pid, query.uid, @@ -1920,7 +1951,7 @@ public final class PermissionManager { private static PropertyInvalidatedCache<PackageNamePermissionQuery, Integer> sPackageNamePermissionCache = new PropertyInvalidatedCache<PackageNamePermissionQuery, Integer>( - 16, CACHE_KEY_PACKAGE_INFO, "checkPackageNamePermission") { + 16, CACHE_KEY_PACKAGE_INFO_CACHE, "checkPackageNamePermission") { @Override public Integer recompute(PackageNamePermissionQuery query) { return checkPackageNamePermissionUncached( diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 2f7a54daed27..e388673791fd 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -95,6 +95,7 @@ import android.app.AppOpsManager; import android.app.BroadcastOptions; import android.app.IUidObserver; import android.app.NotificationManager; +import android.app.PropertyInvalidatedCache; import android.app.UidObserver; import android.app.role.OnRoleHoldersChangedListener; import android.app.role.RoleManager; @@ -248,6 +249,7 @@ import android.widget.Toast; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.BackgroundThread; import com.android.internal.os.SomeArgs; import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; @@ -10941,14 +10943,122 @@ public class AudioService extends IAudioService.Stub } }; mSysPropListenerNativeHandle = mAudioSystem.listenForSystemPropertyChange( - PermissionManager.CACHE_KEY_PACKAGE_INFO, + PermissionManager.CACHE_KEY_PACKAGE_INFO_NOTIFY, task); } else { mAudioSystem.listenForSystemPropertyChange( - PermissionManager.CACHE_KEY_PACKAGE_INFO, + PermissionManager.CACHE_KEY_PACKAGE_INFO_NOTIFY, () -> mAudioServerLifecycleExecutor.execute( mPermissionProvider::onPermissionStateChanged)); } + + if (PropertyInvalidatedCache.separatePermissionNotificationsEnabled()) { + new PackageInfoTransducer().start(); + } + } + + /** + * A transducer that converts high-speed changes in the CACHE_KEY_PACKAGE_INFO_CACHE + * PropertyInvalidatedCache into low-speed changes in the CACHE_KEY_PACKAGE_INFO_NOTIFY system + * property. This operates on the popcorn principle: changes in the source are done when the + * source has been quiet for the soak interval. + * + * TODO(b/381097912) This is a temporary measure to support migration away from sysprop + * sniffing. It should be cleaned up. + */ + private static class PackageInfoTransducer extends Thread { + + // The run/stop signal. + private final AtomicBoolean mRunning = new AtomicBoolean(false); + + // The source of change information. + private final PropertyInvalidatedCache.NonceWatcher mWatcher; + + // The handler for scheduling delayed reactions to changes. + private final Handler mHandler; + + // How long to soak changes: 50ms is the legacy choice. + private final static long SOAK_TIME_MS = 50; + + // The ubiquitous lock. + private final Object mLock = new Object(); + + // If positive, this is the soak expiration time. + @GuardedBy("mLock") + private long mSoakDeadlineMs = -1; + + // A source of unique long values. + @GuardedBy("mLock") + private long mToken = 0; + + PackageInfoTransducer() { + mWatcher = PropertyInvalidatedCache + .getNonceWatcher(PermissionManager.CACHE_KEY_PACKAGE_INFO_CACHE); + mHandler = new Handler(BackgroundThread.getHandler().getLooper()) { + @Override + public void handleMessage(Message msg) { + PackageInfoTransducer.this.handleMessage(msg); + }}; + } + + public void run() { + mRunning.set(true); + while (mRunning.get()) { + try { + final int changes = mWatcher.waitForChange(); + if (changes == 0 || !mRunning.get()) { + continue; + } + } catch (InterruptedException e) { + // We don't know why the exception occurred but keep running until told to + // stop. + continue; + } + trigger(); + } + } + + @GuardedBy("mLock") + private void updateLocked() { + String n = Long.toString(mToken++); + SystemProperties.set(PermissionManager.CACHE_KEY_PACKAGE_INFO_NOTIFY, n); + } + + private void trigger() { + synchronized (mLock) { + boolean alreadyQueued = mSoakDeadlineMs >= 0; + final long nowMs = SystemClock.uptimeMillis(); + mSoakDeadlineMs = nowMs + SOAK_TIME_MS; + if (!alreadyQueued) { + mHandler.sendEmptyMessageAtTime(0, mSoakDeadlineMs); + updateLocked(); + } + } + } + + private void handleMessage(Message msg) { + synchronized (mLock) { + if (mSoakDeadlineMs < 0) { + return; // ??? + } + final long nowMs = SystemClock.uptimeMillis(); + if (mSoakDeadlineMs > nowMs) { + mSoakDeadlineMs = nowMs + SOAK_TIME_MS; + mHandler.sendEmptyMessageAtTime(0, mSoakDeadlineMs); + return; + } + mSoakDeadlineMs = -1; + updateLocked(); + } + } + + /** + * Cause the thread to exit. Running is set to false and the watcher is awakened. + */ + public void done() { + mRunning.set(false); + mWatcher.wakeUp(); + } } //========================================================================================== diff --git a/tools/processors/property_cache/src/java/android/processor/property_cache/CachedPropertyProcessor.java b/tools/processors/property_cache/src/java/android/processor/property_cache/CachedPropertyProcessor.java index 03610128d269..c438163948fc 100644 --- a/tools/processors/property_cache/src/java/android/processor/property_cache/CachedPropertyProcessor.java +++ b/tools/processors/property_cache/src/java/android/processor/property_cache/CachedPropertyProcessor.java @@ -30,6 +30,7 @@ import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Filer; import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; @@ -42,6 +43,11 @@ public class CachedPropertyProcessor extends AbstractProcessor { new IpcDataCacheComposer(); @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + @Override public Set<String> getSupportedAnnotationTypes() { return new HashSet<String>( ImmutableSet.of(CachedPropertyDefaults.class.getCanonicalName())); |