1 /* 2 * Copyright (C) 2020 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.systemui.car.volume; 18 19 import static android.car.media.CarAudioManager.AUDIO_FEATURE_VOLUME_GROUP_EVENTS; 20 import static android.car.media.CarAudioManager.INVALID_AUDIO_ZONE; 21 import static android.car.media.CarAudioManager.PRIMARY_AUDIO_ZONE; 22 import static android.car.media.CarVolumeGroupEvent.EXTRA_INFO_SHOW_UI; 23 import static android.car.media.CarVolumeGroupEvent.EXTRA_INFO_VOLUME_INDEX_CHANGED_BY_AUDIO_SYSTEM; 24 25 import android.app.ActivityManager; 26 import android.car.Car; 27 import android.car.CarOccupantZoneManager; 28 import android.car.media.CarAudioManager; 29 import android.car.media.CarVolumeGroupEvent; 30 import android.car.media.CarVolumeGroupEventCallback; 31 import android.car.media.CarVolumeGroupInfo; 32 import android.content.res.Configuration; 33 import android.content.res.Resources; 34 import android.media.AudioManager; 35 import android.os.Handler; 36 import android.os.HandlerExecutor; 37 import android.util.Log; 38 39 import com.android.systemui.CoreStartable; 40 import com.android.systemui.R; 41 import com.android.systemui.car.CarServiceProvider; 42 import com.android.systemui.dagger.SysUISingleton; 43 import com.android.systemui.dagger.qualifiers.Main; 44 import com.android.systemui.settings.UserTracker; 45 import com.android.systemui.statusbar.policy.ConfigurationController; 46 import com.android.systemui.volume.VolumeDialogComponent; 47 48 import dagger.Lazy; 49 50 import java.io.PrintWriter; 51 import java.util.List; 52 import java.util.concurrent.Executor; 53 54 import javax.inject.Inject; 55 56 /** The entry point for controlling the volume ui in cars. */ 57 @SysUISingleton 58 public class VolumeUI implements CoreStartable, ConfigurationController.ConfigurationListener { 59 60 private static final String TAG = "VolumeUI"; 61 private final Resources mResources; 62 private final Handler mMainHandler; 63 private final CarServiceProvider mCarServiceProvider; 64 private final Lazy<VolumeDialogComponent> mVolumeDialogComponentLazy; 65 private final UserTracker mUserTracker; 66 private int mAudioZoneId = INVALID_AUDIO_ZONE; 67 68 private final CarAudioManager.CarVolumeCallback mVolumeChangeCallback = 69 new CarAudioManager.CarVolumeCallback() { 70 @Override 71 public void onGroupVolumeChanged(int zoneId, int groupId, int flags) { 72 handleVolumeCallback(zoneId, flags); 73 } 74 75 @Override 76 public void onMasterMuteChanged(int zoneId, int flags) { 77 handleVolumeCallback(zoneId, flags); 78 } 79 80 private void handleVolumeCallback(int zoneId, int flags) { 81 if (mAudioZoneId != zoneId || (flags & AudioManager.FLAG_SHOW_UI) == 0) { 82 // only initialize for current audio zone when show requested 83 return; 84 } 85 initVolumeDialogComponent(); 86 unregistarCarAudioManagerCallbacks(); 87 } 88 89 }; 90 91 private final CarVolumeGroupEventCallback mCarVolumeGroupEventCallback = 92 new CarVolumeGroupEventCallback() { 93 @Override 94 public void onVolumeGroupEvent(List<CarVolumeGroupEvent> volumeGroupEvents) { 95 if (!hasEventsForZone(volumeGroupEvents)) { 96 return; 97 } 98 initVolumeDialogComponent(); 99 unregistarCarAudioManagerCallbacks(); 100 } 101 102 private boolean hasEventsForZone(List<CarVolumeGroupEvent> events) { 103 for (int index = 0; index < events.size(); index++) { 104 List<CarVolumeGroupInfo> infos = events.get(index).getCarVolumeGroupInfos(); 105 if (!shouldShowUi(events.get(index).getExtraInfos())) { 106 continue; 107 } 108 109 for (int infoIndex = 0; infoIndex < infos.size(); infoIndex++) { 110 if (infos.get(infoIndex).getZoneId() == mAudioZoneId) { 111 // at least one event for this zone exists that needs to show UI 112 return true; 113 } 114 } 115 } 116 return false; 117 } 118 119 private boolean shouldShowUi(List<Integer> extraInfos) { 120 if (extraInfos.contains(EXTRA_INFO_SHOW_UI) 121 || extraInfos.contains( 122 EXTRA_INFO_VOLUME_INDEX_CHANGED_BY_AUDIO_SYSTEM)) { 123 return true; 124 } 125 return false; 126 } 127 }; 128 129 private boolean mEnabled; 130 private CarAudioManager mCarAudioManager; 131 private VolumeDialogComponent mVolumeDialogComponent; 132 private final Executor mExecutor; 133 134 @Inject VolumeUI( @ain Resources resources, @Main Handler mainHandler, CarServiceProvider carServiceProvider, Lazy<VolumeDialogComponent> volumeDialogComponentLazy, UserTracker userTracker )135 public VolumeUI( 136 @Main Resources resources, 137 @Main Handler mainHandler, 138 CarServiceProvider carServiceProvider, 139 Lazy<VolumeDialogComponent> volumeDialogComponentLazy, 140 UserTracker userTracker 141 ) { 142 mResources = resources; 143 mMainHandler = mainHandler; 144 mCarServiceProvider = carServiceProvider; 145 mVolumeDialogComponentLazy = volumeDialogComponentLazy; 146 mUserTracker = userTracker; 147 mExecutor = new HandlerExecutor(mainHandler); 148 } 149 150 @Override start()151 public void start() { 152 boolean enableVolumeUi = mResources.getBoolean(R.bool.enable_volume_ui); 153 mEnabled = enableVolumeUi; 154 if (!mEnabled) return; 155 156 mCarServiceProvider.addListener(car -> { 157 if (mCarAudioManager != null) { 158 // already initialized 159 return; 160 } 161 162 CarOccupantZoneManager carOccupantZoneManager = car.getCarManager( 163 CarOccupantZoneManager.class); 164 if (carOccupantZoneManager != null) { 165 CarOccupantZoneManager.OccupantZoneInfo info = 166 carOccupantZoneManager.getOccupantZoneForUser(mUserTracker.getUserHandle()); 167 if (info != null) { 168 mAudioZoneId = carOccupantZoneManager.getAudioZoneIdForOccupant(info); 169 } 170 } 171 172 if (mAudioZoneId == INVALID_AUDIO_ZONE) { 173 if (mUserTracker.getUserId() == ActivityManager.getCurrentUser()) { 174 // Certain devices may not have occupant zones configured. As a fallback for 175 // this situation, if the user is the foreground user, assume driver and use the 176 // primary audio zone. 177 mAudioZoneId = PRIMARY_AUDIO_ZONE; 178 } else { 179 return; 180 } 181 } 182 183 mCarAudioManager = (CarAudioManager) car.getCarManager(Car.AUDIO_SERVICE); 184 if (mCarAudioManager.isAudioFeatureEnabled(AUDIO_FEATURE_VOLUME_GROUP_EVENTS)) { 185 Log.d(TAG, "Registering mCarVolumeGroupEventCallback."); 186 mCarAudioManager.registerCarVolumeGroupEventCallback(mExecutor, 187 mCarVolumeGroupEventCallback); 188 } else { 189 Log.d(TAG, "Registering mVolumeChangeCallback."); 190 // This volume call back is never unregistered because CarStatusBar is 191 // never destroyed. 192 mCarAudioManager.registerCarVolumeCallback(mVolumeChangeCallback); 193 } 194 }); 195 } 196 197 @Override onConfigChanged(Configuration newConfig)198 public void onConfigChanged(Configuration newConfig) { 199 if (!mEnabled) return; 200 if (mVolumeDialogComponent != null) { 201 mVolumeDialogComponent.onConfigurationChanged(newConfig); 202 } 203 } 204 205 @Override dump(PrintWriter pw, String[] args)206 public void dump(PrintWriter pw, String[] args) { 207 pw.print("mEnabled="); pw.println(mEnabled); 208 if (!mEnabled) return; 209 if (mVolumeDialogComponent != null) { 210 mVolumeDialogComponent.dump(pw, args); 211 } 212 } 213 initVolumeDialogComponent()214 private void initVolumeDialogComponent() { 215 if (mVolumeDialogComponent == null) { 216 mMainHandler.post(() -> { 217 mVolumeDialogComponent = mVolumeDialogComponentLazy.get(); 218 mVolumeDialogComponent.register(); 219 }); 220 } 221 } 222 unregistarCarAudioManagerCallbacks()223 private void unregistarCarAudioManagerCallbacks() { 224 mCarAudioManager.unregisterCarVolumeCallback(mVolumeChangeCallback); 225 if (mCarAudioManager.isAudioFeatureEnabled(AUDIO_FEATURE_VOLUME_GROUP_EVENTS)) { 226 mCarAudioManager.unregisterCarVolumeGroupEventCallback(mCarVolumeGroupEventCallback); 227 } 228 } 229 } 230