1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.car.audio; 18 19 import static com.android.car.audio.CarAudioService.SystemClockWrapper; 20 import static com.android.car.audio.CarAudioUtils.hasExpired; 21 22 import android.annotation.NonNull; 23 import android.media.AudioAttributes; 24 import android.media.AudioManager; 25 import android.media.AudioPlaybackConfiguration; 26 import android.util.ArrayMap; 27 28 import com.android.internal.annotations.GuardedBy; 29 import com.android.internal.util.Preconditions; 30 31 import java.util.ArrayList; 32 import java.util.List; 33 import java.util.Map; 34 import java.util.Objects; 35 36 final class CarAudioPlaybackCallback extends AudioManager.AudioPlaybackCallback { 37 private final Object mLock = new Object(); 38 @GuardedBy("mLock") 39 private final ArrayMap<AudioAttributes, Long> mAudioAttributesStartTime = new ArrayMap<>(); 40 @GuardedBy("mLock") 41 private final ArrayMap<String, AudioPlaybackConfiguration> mLastActiveConfigs = 42 new ArrayMap<>(); 43 private final CarAudioZone mCarPrimaryAudioZone; 44 private final SystemClockWrapper mClock; 45 private final int mVolumeKeyEventTimeoutMs; 46 CarAudioPlaybackCallback(@onNull CarAudioZone carPrimaryAudioZone, @NonNull SystemClockWrapper clock, int volumeKeyEventTimeoutMs)47 CarAudioPlaybackCallback(@NonNull CarAudioZone carPrimaryAudioZone, 48 @NonNull SystemClockWrapper clock, 49 int volumeKeyEventTimeoutMs) { 50 mCarPrimaryAudioZone = Objects.requireNonNull(carPrimaryAudioZone); 51 mClock = Objects.requireNonNull(clock); 52 mVolumeKeyEventTimeoutMs = Preconditions.checkArgumentNonnegative(volumeKeyEventTimeoutMs); 53 } 54 55 @Override onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configurations)56 public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configurations) { 57 ArrayMap<String, AudioPlaybackConfiguration> newActiveConfigs = 58 filterNewActiveConfiguration(configurations); 59 60 synchronized (mLock) { 61 List<AudioPlaybackConfiguration> newlyInactiveConfigurations = 62 getNewlyInactiveConfigurationsLocked(newActiveConfigs); 63 64 mLastActiveConfigs.clear(); 65 mLastActiveConfigs.putAll(newActiveConfigs); 66 67 startTimersForContextThatBecameInactiveLocked(newlyInactiveConfigurations); 68 } 69 } 70 71 /** 72 * Returns all active contexts for the primary zone 73 * @return all active audio contexts, including those that recently became inactive but are 74 * considered active due to the audio playback timeout. 75 */ getAllActiveAudioAttributesForPrimaryZone()76 public List<AudioAttributes> getAllActiveAudioAttributesForPrimaryZone() { 77 synchronized (mLock) { 78 List<AudioAttributes> activeContexts = getCurrentlyActiveAttributesLocked(); 79 activeContexts 80 .addAll(getStillActiveContextAndRemoveExpiredContextsLocked()); 81 return activeContexts; 82 } 83 } 84 85 @GuardedBy("mLock") startTimersForContextThatBecameInactiveLocked( List<AudioPlaybackConfiguration> inactiveConfigs)86 private void startTimersForContextThatBecameInactiveLocked( 87 List<AudioPlaybackConfiguration> inactiveConfigs) { 88 List<AudioAttributes> activeAttributes = mCarPrimaryAudioZone 89 .findActiveAudioAttributesFromPlaybackConfigurations(inactiveConfigs); 90 91 for (int index = 0; index < inactiveConfigs.size(); index++) { 92 mAudioAttributesStartTime.put(activeAttributes.get(index), mClock.uptimeMillis()); 93 } 94 } 95 96 @GuardedBy("mLock") getNewlyInactiveConfigurationsLocked( Map<String, AudioPlaybackConfiguration> newActiveConfigurations)97 private List<AudioPlaybackConfiguration> getNewlyInactiveConfigurationsLocked( 98 Map<String, AudioPlaybackConfiguration> newActiveConfigurations) { 99 List<AudioPlaybackConfiguration> newlyInactiveConfigurations = new ArrayList<>(); 100 for (int index = 0; index < mLastActiveConfigs.size(); index++) { 101 if (newActiveConfigurations 102 .containsKey(mLastActiveConfigs.keyAt(index))) { 103 continue; 104 } 105 newlyInactiveConfigurations.add(mLastActiveConfigs.valueAt(index)); 106 } 107 return newlyInactiveConfigurations; 108 } 109 filterNewActiveConfiguration( List<AudioPlaybackConfiguration> configurations)110 private ArrayMap<String, AudioPlaybackConfiguration> filterNewActiveConfiguration( 111 List<AudioPlaybackConfiguration> configurations) { 112 ArrayMap<String, AudioPlaybackConfiguration> newActiveConfigs = new ArrayMap<>(); 113 for (int index = 0; index < configurations.size(); index++) { 114 AudioPlaybackConfiguration configuration = configurations.get(index); 115 if (!configuration.isActive()) { 116 continue; 117 } 118 if (mCarPrimaryAudioZone 119 .isAudioDeviceInfoValidForZone(configuration.getAudioDeviceInfo())) { 120 newActiveConfigs.put( 121 configuration.getAudioDeviceInfo().getAddress(), configuration); 122 } 123 } 124 return newActiveConfigs; 125 } 126 127 @GuardedBy("mLock") getCurrentlyActiveAttributesLocked()128 private List<AudioAttributes> getCurrentlyActiveAttributesLocked() { 129 return mCarPrimaryAudioZone.findActiveAudioAttributesFromPlaybackConfigurations( 130 new ArrayList<>(mLastActiveConfigs.values())); 131 } 132 133 @GuardedBy("mLock") getStillActiveContextAndRemoveExpiredContextsLocked()134 private List<AudioAttributes> getStillActiveContextAndRemoveExpiredContextsLocked() { 135 List<AudioAttributes> attributesToRemove = new ArrayList<>(); 136 List<AudioAttributes> activeAttributes = new ArrayList<>(); 137 for (int index = 0; index < mAudioAttributesStartTime.size(); index++) { 138 long startTime = mAudioAttributesStartTime.valueAt(index); 139 if (hasExpired(startTime, mClock.uptimeMillis(), mVolumeKeyEventTimeoutMs)) { 140 attributesToRemove.add(mAudioAttributesStartTime.keyAt(index)); 141 continue; 142 } 143 activeAttributes.add(mAudioAttributesStartTime.keyAt(index)); 144 } 145 146 for (int indexToRemove = 0; indexToRemove < attributesToRemove.size(); indexToRemove++) { 147 mAudioAttributesStartTime.remove(attributesToRemove.get(indexToRemove)); 148 } 149 return activeAttributes; 150 } 151 resetStillActiveContexts()152 void resetStillActiveContexts() { 153 synchronized (mLock) { 154 mAudioAttributesStartTime.clear(); 155 } 156 } 157 } 158