1 /* 2 * Copyright (C) 2018 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 android.annotation.NonNull; 20 import android.annotation.UserIdInt; 21 import android.car.media.CarAudioManager; 22 import android.content.pm.PackageManager; 23 import android.media.AudioAttributes; 24 import android.media.AudioFocusInfo; 25 import android.media.AudioManager; 26 import android.media.audiopolicy.AudioPolicy; 27 import android.os.Bundle; 28 import android.util.Log; 29 30 import com.android.car.CarLog; 31 import com.android.internal.util.Preconditions; 32 33 import java.io.PrintWriter; 34 import java.util.ArrayList; 35 import java.util.HashMap; 36 import java.util.Map; 37 import java.util.Objects; 38 39 /** 40 * Implements {@link AudioPolicy.AudioPolicyFocusListener} 41 * 42 * <p><b>Note:</b> Manages audio focus on a per zone basis. 43 */ 44 class CarZonesAudioFocus extends AudioPolicy.AudioPolicyFocusListener { 45 46 private final boolean mDelayedFocusEnabled; 47 private CarAudioService mCarAudioService; // Dynamically assigned just after construction 48 private AudioPolicy mAudioPolicy; // Dynamically assigned just after construction 49 50 private final Map<Integer, CarAudioFocus> mFocusZones = new HashMap<>(); 51 CarZonesAudioFocus(@onNull AudioManager audioManager, @NonNull PackageManager packageManager, @NonNull CarAudioZone[] carAudioZones, @NonNull CarAudioSettings carAudioSettings, boolean enableDelayedAudioFocus)52 CarZonesAudioFocus(@NonNull AudioManager audioManager, 53 @NonNull PackageManager packageManager, 54 @NonNull CarAudioZone[] carAudioZones, 55 @NonNull CarAudioSettings carAudioSettings, 56 boolean enableDelayedAudioFocus) { 57 //Create the zones here, the policy will be set setOwningPolicy, 58 // which is called right after this constructor. 59 Objects.requireNonNull(audioManager); 60 Objects.requireNonNull(packageManager); 61 Objects.requireNonNull(carAudioZones); 62 Objects.requireNonNull(carAudioSettings); 63 Preconditions.checkArgument(carAudioZones.length != 0, 64 "There must be a minimum of one audio zone"); 65 66 //Create focus for all the zones 67 for (CarAudioZone audioZone : carAudioZones) { 68 int audioZoneId = audioZone.getId(); 69 if (Log.isLoggable(CarLog.TAG_AUDIO, Log.DEBUG)) { 70 Log.d(CarLog.TAG_AUDIO, 71 "CarZonesAudioFocus adding new zone " + audioZoneId); 72 } 73 CarAudioFocus zoneFocusListener = 74 new CarAudioFocus(audioManager, packageManager, 75 new FocusInteraction(carAudioSettings), enableDelayedAudioFocus); 76 mFocusZones.put(audioZoneId, zoneFocusListener); 77 } 78 mDelayedFocusEnabled = enableDelayedAudioFocus; 79 } 80 81 82 /** 83 * Query the current list of focus loser in zoneId for uid 84 * @param uid uid to query for current focus losers 85 * @param zoneId zone id to query for info 86 * @return list of current focus losers for uid 87 */ getAudioFocusLosersForUid(int uid, int zoneId)88 ArrayList<AudioFocusInfo> getAudioFocusLosersForUid(int uid, int zoneId) { 89 CarAudioFocus focus = mFocusZones.get(zoneId); 90 return focus.getAudioFocusLosersForUid(uid); 91 } 92 93 /** 94 * Query the current list of focus holders in zoneId for uid 95 * @param uid uid to query for current focus holders 96 * @param zoneId zone id to query 97 * @return list of current focus holders that for uid 98 */ getAudioFocusHoldersForUid(int uid, int zoneId)99 ArrayList<AudioFocusInfo> getAudioFocusHoldersForUid(int uid, int zoneId) { 100 CarAudioFocus focus = mFocusZones.get(zoneId); 101 return focus.getAudioFocusHoldersForUid(uid); 102 } 103 104 /** 105 * For each entry in list, transiently lose focus 106 * @param afiList list of audio focus entries 107 * @param zoneId zone id where focus should should be lost 108 */ transientlyLoseInFocusInZone(@onNull ArrayList<AudioFocusInfo> afiList, int zoneId)109 void transientlyLoseInFocusInZone(@NonNull ArrayList<AudioFocusInfo> afiList, 110 int zoneId) { 111 CarAudioFocus focus = mFocusZones.get(zoneId); 112 113 for (AudioFocusInfo info : afiList) { 114 focus.removeAudioFocusInfoAndTransientlyLoseFocus(info); 115 } 116 } 117 reevaluateAndRegainAudioFocus(AudioFocusInfo afi)118 int reevaluateAndRegainAudioFocus(AudioFocusInfo afi) { 119 CarAudioFocus focus = getFocusForAudioFocusInfo(afi); 120 return focus.reevaluateAndRegainAudioFocus(afi); 121 } 122 123 124 /** 125 * Sets the owning policy of the audio focus 126 * 127 * <p><b>Note:</b> This has to happen after the construction to avoid a chicken and egg 128 * problem when setting up the AudioPolicy which must depend on this object. 129 130 * @param carAudioService owning car audio service 131 * @param parentPolicy owning parent car audio policy 132 */ setOwningPolicy(CarAudioService carAudioService, AudioPolicy parentPolicy)133 void setOwningPolicy(CarAudioService carAudioService, AudioPolicy parentPolicy) { 134 mAudioPolicy = parentPolicy; 135 mCarAudioService = carAudioService; 136 137 for (int zoneId : mFocusZones.keySet()) { 138 mFocusZones.get(zoneId).setOwningPolicy(mAudioPolicy); 139 } 140 } 141 142 @Override onAudioFocusRequest(AudioFocusInfo afi, int requestResult)143 public void onAudioFocusRequest(AudioFocusInfo afi, int requestResult) { 144 CarAudioFocus focus = getFocusForAudioFocusInfo(afi); 145 focus.onAudioFocusRequest(afi, requestResult); 146 } 147 148 /** 149 * @see AudioManager#abandonAudioFocus(AudioManager.OnAudioFocusChangeListener, AudioAttributes) 150 * Note that we'll get this call for a focus holder that dies while in the focus stack, so 151 * we don't need to watch for death notifications directly. 152 */ 153 @Override onAudioFocusAbandon(AudioFocusInfo afi)154 public void onAudioFocusAbandon(AudioFocusInfo afi) { 155 CarAudioFocus focus = getFocusForAudioFocusInfo(afi); 156 focus.onAudioFocusAbandon(afi); 157 } 158 getFocusForAudioFocusInfo(AudioFocusInfo afi)159 private CarAudioFocus getFocusForAudioFocusInfo(AudioFocusInfo afi) { 160 //getFocusForAudioFocusInfo defaults to returning default zoneId 161 //if uid has not been mapped, thus the value returned will be 162 //default zone focus 163 int zoneId = mCarAudioService.getZoneIdForUid(afi.getClientUid()); 164 165 // If the bundle attribute for AUDIOFOCUS_EXTRA_REQUEST_ZONE_ID has been assigned 166 // Use zone id from that instead. 167 Bundle bundle = afi.getAttributes().getBundle(); 168 169 if (bundle != null) { 170 int bundleZoneId = 171 bundle.getInt(CarAudioManager.AUDIOFOCUS_EXTRA_REQUEST_ZONE_ID, 172 -1); 173 // check if the zone id is within current zones bounds 174 if (mCarAudioService.isAudioZoneIdValid(bundleZoneId)) { 175 if (Log.isLoggable(CarLog.TAG_AUDIO, Log.DEBUG)) { 176 Log.d(CarLog.TAG_AUDIO, 177 "getFocusForAudioFocusInfo valid zoneId " + bundleZoneId 178 + " with bundle request for client " + afi.getClientId()); 179 } 180 zoneId = bundleZoneId; 181 } else { 182 Log.w(CarLog.TAG_AUDIO, 183 "getFocusForAudioFocusInfo invalid zoneId " + bundleZoneId 184 + " with bundle request for client " + afi.getClientId() 185 + ", dispatching focus request to zoneId " + zoneId); 186 } 187 } 188 189 CarAudioFocus focus = mFocusZones.get(zoneId); 190 return focus; 191 } 192 193 /** 194 * dumps the current state of the CarZoneAudioFocus 195 * 196 * @param indent indent to append to each new line 197 * @param writer stream to write current state 198 */ dump(String indent, PrintWriter writer)199 void dump(String indent, PrintWriter writer) { 200 writer.printf("%s*CarZonesAudioFocus*\n", indent); 201 writer.printf("%s\tDelayed Focus Enabled: %b\n", indent, mDelayedFocusEnabled); 202 writer.printf("%s\tCar Zones Audio Focus Listeners:\n", indent); 203 Integer[] keys = mFocusZones.keySet().stream().sorted().toArray(Integer[]::new); 204 for (Integer zoneId : keys) { 205 writer.printf("%s\tZone Id: %s\n", indent, zoneId.toString()); 206 mFocusZones.get(zoneId).dump(indent + "\t", writer); 207 } 208 } 209 updateUserForZoneId(int audioZoneId, @UserIdInt int userId)210 public void updateUserForZoneId(int audioZoneId, @UserIdInt int userId) { 211 Preconditions.checkArgument(mCarAudioService.isAudioZoneIdValid(audioZoneId), 212 "Invalid zoneId %d", audioZoneId); 213 mFocusZones.get(audioZoneId).getFocusInteraction().setUserIdForSettings(userId); 214 } 215 } 216