Loading services/core/java/com/android/server/audio/AudioService.java +45 −38 Original line number Original line Diff line number Diff line Loading @@ -790,8 +790,7 @@ public class AudioService extends IAudioService.Stub private final BroadcastReceiver mReceiver = new AudioServiceBroadcastReceiver(); private final BroadcastReceiver mReceiver = new AudioServiceBroadcastReceiver(); private final Executor mAudioServerLifecycleExecutor; private final Executor mAudioServerLifecycleExecutor; private final ConcurrentLinkedQueue<Future> mScheduledPermissionTasks = private final List<Future> mScheduledPermissionTasks = new ArrayList(); new ConcurrentLinkedQueue(); private IMediaProjectionManager mProjectionService; // to validate projection token private IMediaProjectionManager mProjectionService; // to validate projection token Loading Loading @@ -10600,20 +10599,24 @@ public class AudioService extends IAudioService.Stub // instanceof to simplify the construction requirements of AudioService for testing: no // instanceof to simplify the construction requirements of AudioService for testing: no // delayed execution during unit tests. // delayed execution during unit tests. if (mAudioServerLifecycleExecutor instanceof ScheduledExecutorService exec) { if (mAudioServerLifecycleExecutor instanceof ScheduledExecutorService exec) { // We schedule and add from a this callback thread only (serially), so the task order on // The order on the task list is an embedding on the scheduling order of the executor, // the serial executor matches the order on the task list. This list should almost // since we synchronously add the scheduled task to our local queue. This list should // always have only two elements, except in cases of serious system contention. // almost always have only two elements, except in cases of serious system contention. Runnable task = () -> mScheduledPermissionTasks.add(exec.schedule(() -> { Runnable task = () -> { synchronized (mScheduledPermissionTasks) { mScheduledPermissionTasks.add(exec.schedule(() -> { try { try { // Clean up completed tasks before us to bound the queue length. Cancel any // Our goal is to remove all tasks which don't correspond to ourselves // pending permission refresh tasks, after our own, since we are about to // on this queue. Either they are already done (ahead of us), or we // fulfill all of them. We must be the first non-completed task in the // should cancel them (behind us), since their work is redundant after // queue, since the execution order matches the queue order. Note, this // we fire. // task is the only writer on elements in the queue, and the task is // We must be the first non-completed task in the queue, since the // serialized, so // execution order matches the queue order. Note, this task is the only // writer on elements in the queue, and the task is serialized, so // => no in-flight cancellation // => no in-flight cancellation // => exists at least one non-completed task (ourselves) // => exists at least one non-completed task (ourselves) // => the queue is non-empty (only completed tasks removed) // => the queue is non-empty (only completed tasks removed) synchronized (mScheduledPermissionTasks) { final var iter = mScheduledPermissionTasks.iterator(); final var iter = mScheduledPermissionTasks.iterator(); while (iter.next().isDone()) { while (iter.next().isDone()) { iter.remove(); iter.remove(); Loading @@ -10627,19 +10630,19 @@ public class AudioService extends IAudioService.Stub } } iter.remove(); iter.remove(); } } } mPermissionProvider.onPermissionStateChanged(); mPermissionProvider.onPermissionStateChanged(); } catch (Exception e) { } catch (Exception e) { // Handle executor routing exceptions to nowhere // Handle executor routing exceptions to nowhere Thread.getDefaultUncaughtExceptionHandler() Thread.getDefaultUncaughtExceptionHandler() .uncaughtException(Thread.currentThread(), e); .uncaughtException(Thread.currentThread(), e); } } }, }, UPDATE_DELAY_MS, TimeUnit.MILLISECONDS)); UPDATE_DELAY_MS, } TimeUnit.MILLISECONDS)); }; mAudioSystem.listenForSystemPropertyChange( mAudioSystem.listenForSystemPropertyChange( PermissionManager.CACHE_KEY_PACKAGE_INFO, PermissionManager.CACHE_KEY_PACKAGE_INFO, task); task); task.run(); } else { } else { mAudioSystem.listenForSystemPropertyChange( mAudioSystem.listenForSystemPropertyChange( PermissionManager.CACHE_KEY_PACKAGE_INFO, PermissionManager.CACHE_KEY_PACKAGE_INFO, Loading Loading @@ -14710,7 +14713,11 @@ public class AudioService extends IAudioService.Stub @Override @Override /** @see AudioManager#permissionUpdateBarrier() */ /** @see AudioManager#permissionUpdateBarrier() */ public void permissionUpdateBarrier() { public void permissionUpdateBarrier() { for (var x : List.copyOf(mScheduledPermissionTasks)) { List<Future> snapshot; synchronized (mScheduledPermissionTasks) { snapshot = List.copyOf(mScheduledPermissionTasks); } for (var x : snapshot) { try { try { x.get(); x.get(); } catch (CancellationException e) { } catch (CancellationException e) { Loading
services/core/java/com/android/server/audio/AudioService.java +45 −38 Original line number Original line Diff line number Diff line Loading @@ -790,8 +790,7 @@ public class AudioService extends IAudioService.Stub private final BroadcastReceiver mReceiver = new AudioServiceBroadcastReceiver(); private final BroadcastReceiver mReceiver = new AudioServiceBroadcastReceiver(); private final Executor mAudioServerLifecycleExecutor; private final Executor mAudioServerLifecycleExecutor; private final ConcurrentLinkedQueue<Future> mScheduledPermissionTasks = private final List<Future> mScheduledPermissionTasks = new ArrayList(); new ConcurrentLinkedQueue(); private IMediaProjectionManager mProjectionService; // to validate projection token private IMediaProjectionManager mProjectionService; // to validate projection token Loading Loading @@ -10600,20 +10599,24 @@ public class AudioService extends IAudioService.Stub // instanceof to simplify the construction requirements of AudioService for testing: no // instanceof to simplify the construction requirements of AudioService for testing: no // delayed execution during unit tests. // delayed execution during unit tests. if (mAudioServerLifecycleExecutor instanceof ScheduledExecutorService exec) { if (mAudioServerLifecycleExecutor instanceof ScheduledExecutorService exec) { // We schedule and add from a this callback thread only (serially), so the task order on // The order on the task list is an embedding on the scheduling order of the executor, // the serial executor matches the order on the task list. This list should almost // since we synchronously add the scheduled task to our local queue. This list should // always have only two elements, except in cases of serious system contention. // almost always have only two elements, except in cases of serious system contention. Runnable task = () -> mScheduledPermissionTasks.add(exec.schedule(() -> { Runnable task = () -> { synchronized (mScheduledPermissionTasks) { mScheduledPermissionTasks.add(exec.schedule(() -> { try { try { // Clean up completed tasks before us to bound the queue length. Cancel any // Our goal is to remove all tasks which don't correspond to ourselves // pending permission refresh tasks, after our own, since we are about to // on this queue. Either they are already done (ahead of us), or we // fulfill all of them. We must be the first non-completed task in the // should cancel them (behind us), since their work is redundant after // queue, since the execution order matches the queue order. Note, this // we fire. // task is the only writer on elements in the queue, and the task is // We must be the first non-completed task in the queue, since the // serialized, so // execution order matches the queue order. Note, this task is the only // writer on elements in the queue, and the task is serialized, so // => no in-flight cancellation // => no in-flight cancellation // => exists at least one non-completed task (ourselves) // => exists at least one non-completed task (ourselves) // => the queue is non-empty (only completed tasks removed) // => the queue is non-empty (only completed tasks removed) synchronized (mScheduledPermissionTasks) { final var iter = mScheduledPermissionTasks.iterator(); final var iter = mScheduledPermissionTasks.iterator(); while (iter.next().isDone()) { while (iter.next().isDone()) { iter.remove(); iter.remove(); Loading @@ -10627,19 +10630,19 @@ public class AudioService extends IAudioService.Stub } } iter.remove(); iter.remove(); } } } mPermissionProvider.onPermissionStateChanged(); mPermissionProvider.onPermissionStateChanged(); } catch (Exception e) { } catch (Exception e) { // Handle executor routing exceptions to nowhere // Handle executor routing exceptions to nowhere Thread.getDefaultUncaughtExceptionHandler() Thread.getDefaultUncaughtExceptionHandler() .uncaughtException(Thread.currentThread(), e); .uncaughtException(Thread.currentThread(), e); } } }, }, UPDATE_DELAY_MS, TimeUnit.MILLISECONDS)); UPDATE_DELAY_MS, } TimeUnit.MILLISECONDS)); }; mAudioSystem.listenForSystemPropertyChange( mAudioSystem.listenForSystemPropertyChange( PermissionManager.CACHE_KEY_PACKAGE_INFO, PermissionManager.CACHE_KEY_PACKAGE_INFO, task); task); task.run(); } else { } else { mAudioSystem.listenForSystemPropertyChange( mAudioSystem.listenForSystemPropertyChange( PermissionManager.CACHE_KEY_PACKAGE_INFO, PermissionManager.CACHE_KEY_PACKAGE_INFO, Loading Loading @@ -14710,7 +14713,11 @@ public class AudioService extends IAudioService.Stub @Override @Override /** @see AudioManager#permissionUpdateBarrier() */ /** @see AudioManager#permissionUpdateBarrier() */ public void permissionUpdateBarrier() { public void permissionUpdateBarrier() { for (var x : List.copyOf(mScheduledPermissionTasks)) { List<Future> snapshot; synchronized (mScheduledPermissionTasks) { snapshot = List.copyOf(mScheduledPermissionTasks); } for (var x : snapshot) { try { try { x.get(); x.get(); } catch (CancellationException e) { } catch (CancellationException e) {