• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.CarAudioContext.AudioContext;
20 import static com.android.car.audio.CarAudioService.SystemClockWrapper;
21 import static com.android.car.audio.CarAudioUtils.hasExpired;
22 
23 import android.annotation.NonNull;
24 import android.media.AudioManager;
25 import android.media.AudioPlaybackConfiguration;
26 import android.util.SparseLongArray;
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.HashMap;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Objects;
36 
37 final class CarAudioPlaybackCallback extends AudioManager.AudioPlaybackCallback {
38     private final Object mLock = new Object();
39     @GuardedBy("mLock")
40     private final SparseLongArray mContextStartTime = new SparseLongArray();
41     @GuardedBy("mLock")
42     private final Map<String, AudioPlaybackConfiguration> mLastActiveConfigs = new HashMap<>();
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         Map<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      */
getAllActiveContextsForPrimaryZone()76     public List<Integer> getAllActiveContextsForPrimaryZone() {
77         synchronized (mLock) {
78             List<Integer> activeContexts = getCurrentlyActiveContextsLocked();
79             activeContexts
80                     .addAll(getStillActiveContextAndRemoveExpiredContextsLocked());
81             return activeContexts;
82         }
83     }
84 
startTimersForContextThatBecameInactiveLocked( List<AudioPlaybackConfiguration> inactiveConfigs)85     private void startTimersForContextThatBecameInactiveLocked(
86             List<AudioPlaybackConfiguration> inactiveConfigs) {
87         List<Integer> activeContexts = mCarPrimaryAudioZone
88                 .findActiveContextsFromPlaybackConfigurations(inactiveConfigs);
89 
90         for (int activeContext : activeContexts) {
91             mContextStartTime.put(activeContext, mClock.uptimeMillis());
92         }
93     }
94 
getNewlyInactiveConfigurationsLocked( Map<String, AudioPlaybackConfiguration> newActiveConfigurations)95     private List<AudioPlaybackConfiguration> getNewlyInactiveConfigurationsLocked(
96             Map<String, AudioPlaybackConfiguration> newActiveConfigurations) {
97         List<AudioPlaybackConfiguration> newlyInactiveConfigurations = new ArrayList<>();
98         for (String address : mLastActiveConfigs.keySet()) {
99             if (newActiveConfigurations.containsKey(address)) {
100                 continue;
101             }
102             newlyInactiveConfigurations.add(mLastActiveConfigs.get(address));
103         }
104         return newlyInactiveConfigurations;
105     }
106 
filterNewActiveConfiguration( List<AudioPlaybackConfiguration> configurations)107     private Map<String, AudioPlaybackConfiguration> filterNewActiveConfiguration(
108             List<AudioPlaybackConfiguration> configurations) {
109         Map<String, AudioPlaybackConfiguration> newActiveConfigs = new HashMap<>();
110         for (int index = 0; index < configurations.size(); index++) {
111             AudioPlaybackConfiguration configuration = configurations.get(index);
112             if (!configuration.isActive()) {
113                 continue;
114             }
115             if (mCarPrimaryAudioZone
116                     .isAudioDeviceInfoValidForZone(configuration.getAudioDeviceInfo())) {
117                 newActiveConfigs.put(
118                         configuration.getAudioDeviceInfo().getAddress(), configuration);
119             }
120         }
121         return newActiveConfigs;
122     }
123 
getCurrentlyActiveContextsLocked()124     private List<Integer> getCurrentlyActiveContextsLocked() {
125         return mCarPrimaryAudioZone.findActiveContextsFromPlaybackConfigurations(
126                 new ArrayList<>(mLastActiveConfigs.values()));
127     }
128 
getStillActiveContextAndRemoveExpiredContextsLocked()129     private List<Integer> getStillActiveContextAndRemoveExpiredContextsLocked() {
130         List<Integer> contextsToRemove = new ArrayList<>();
131         List<Integer> stillActiveContexts = new ArrayList<>();
132         for (int index = 0; index < mContextStartTime.size(); index++) {
133             @AudioContext int context = mContextStartTime.keyAt(index);
134             if (hasExpired(mContextStartTime.valueAt(index),
135                     mClock.uptimeMillis(), mVolumeKeyEventTimeoutMs)) {
136                 contextsToRemove.add(context);
137                 continue;
138             }
139             stillActiveContexts.add(context);
140         }
141 
142         for (int indexToRemove = 0; indexToRemove < contextsToRemove.size(); indexToRemove++) {
143             mContextStartTime.delete(contextsToRemove.get(indexToRemove));
144         }
145         return stillActiveContexts;
146     }
147 
resetStillActiveContexts()148     void resetStillActiveContexts() {
149         synchronized (mLock) {
150             mContextStartTime.clear();
151         }
152     }
153 }
154