1 /* 2 * Copyright (C) 2015 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 package com.google.android.car.kitchensink.volume; 17 18 import static android.car.media.CarAudioManager.AUDIO_FEATURE_DYNAMIC_ROUTING; 19 import static android.car.media.CarAudioManager.AUDIO_FEATURE_VOLUME_GROUP_EVENTS; 20 21 import android.car.Car; 22 import android.car.Car.CarServiceLifecycleListener; 23 import android.car.media.CarAudioManager; 24 import android.car.media.CarAudioManager.CarVolumeCallback; 25 import android.car.media.CarVolumeGroupEvent; 26 import android.car.media.CarVolumeGroupEventCallback; 27 import android.car.media.CarVolumeGroupInfo; 28 import android.content.Context; 29 import android.media.AudioManager; 30 import android.os.Bundle; 31 import android.util.Log; 32 import android.util.SparseArray; 33 import android.view.LayoutInflater; 34 import android.view.View; 35 import android.view.ViewGroup; 36 import android.widget.SeekBar; 37 38 import androidx.annotation.Nullable; 39 import androidx.core.content.ContextCompat; 40 import androidx.fragment.app.Fragment; 41 import androidx.viewpager.widget.ViewPager; 42 43 import com.google.android.car.kitchensink.R; 44 import com.google.android.material.tabs.TabLayout; 45 46 import java.util.List; 47 48 import javax.annotation.concurrent.GuardedBy; 49 50 public final class VolumeTestFragment extends Fragment { 51 private static final String TAG = "CarVolumeTest"; 52 private static final boolean DEBUG = true; 53 54 private AudioManager mAudioManager; 55 private AudioZoneVolumeTabAdapter mAudioZoneAdapter; 56 @GuardedBy("mLock") 57 private final SparseArray<CarAudioZoneVolumeFragment> mZoneVolumeFragments = 58 new SparseArray<>(); 59 60 private CarAudioManager mCarAudioManager; 61 private Car mCar; 62 63 private SeekBar mFader; 64 private SeekBar mBalance; 65 66 private TabLayout mZonesTabLayout; 67 private final Object mLock = new Object(); 68 69 public static final class CarAudioZoneVolumeInfo { 70 public int groupId; 71 public String id; 72 public int maxGain; 73 public int minGain; 74 public String currentGain; 75 public boolean hasAudioFocus; 76 public boolean isMuted; 77 public boolean isBlocked; 78 public boolean isAttenuated; 79 public boolean isSystemMuted; 80 } 81 82 private final class CarVolumeChangeListener extends CarVolumeCallback { 83 @Override onGroupVolumeChanged(int zoneId, int groupId, int flags)84 public void onGroupVolumeChanged(int zoneId, int groupId, int flags) { 85 if (DEBUG) { 86 Log.d(TAG, "onGroupVolumeChanged volume changed for zone " 87 + zoneId); 88 } 89 sendFragmentChangedMessage(zoneId, groupId, flags); 90 } 91 92 @Override onGroupMuteChanged(int zoneId, int groupId, int flags)93 public void onGroupMuteChanged(int zoneId, int groupId, int flags) { 94 if (DEBUG) { 95 Log.d(TAG, "onGroupMuteChanged mute changed for zone " + zoneId); 96 } 97 sendFragmentChangedMessage(zoneId, groupId, flags); 98 } 99 100 @Override onMasterMuteChanged(int zoneId, int flags)101 public void onMasterMuteChanged(int zoneId, int flags) { 102 if (DEBUG) { 103 Log.d(TAG, "onMasterMuteChanged master mute " 104 + mAudioManager.isMasterMute()); 105 } 106 } 107 sendFragmentChangedMessage(int zoneId, int groupId, int flags)108 private void sendFragmentChangedMessage(int zoneId, int groupId, int flags) { 109 CarAudioZoneVolumeFragment fragment; 110 111 synchronized (mLock) { 112 fragment = mZoneVolumeFragments.get(zoneId); 113 } 114 115 if (fragment != null) { 116 fragment.sendVolumeChangedMessage(groupId, flags); 117 } 118 } 119 } 120 121 private CarVolumeGroupEventCallback mEventCallback = (volumeGroupEvents) -> { 122 if (DEBUG) { 123 Log.d(TAG, "onVolumeGroupEvent received events: " + volumeGroupEvents); 124 } 125 sendFragmentChangedMessageForEvents(volumeGroupEvents); 126 }; 127 128 private final CarVolumeCallback mCarVolumeCallback = new CarVolumeChangeListener(); 129 130 private CarServiceLifecycleListener mCarServiceLifecycleListener = (car, ready) -> { 131 if (!ready) { 132 if (DEBUG) { 133 Log.d(TAG, "Disconnect from Car Service"); 134 } 135 return; 136 } 137 if (DEBUG) { 138 Log.d(TAG, "Connected to Car Service"); 139 } 140 mCarAudioManager = (CarAudioManager) car.getCarManager(Car.AUDIO_SERVICE); 141 initVolumeInfo(); 142 if (mCarAudioManager.isAudioFeatureEnabled(AUDIO_FEATURE_VOLUME_GROUP_EVENTS) 143 && mCarAudioManager.isAudioFeatureEnabled(AUDIO_FEATURE_DYNAMIC_ROUTING)) { 144 mCarAudioManager.registerCarVolumeGroupEventCallback( 145 ContextCompat.getMainExecutor(getActivity().getApplicationContext()), 146 mEventCallback); 147 } 148 mCarAudioManager.registerCarVolumeCallback(mCarVolumeCallback); 149 }; 150 151 @Override onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)152 public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, 153 @Nullable Bundle savedInstanceState) { 154 mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); 155 156 View v = inflater.inflate(R.layout.volume_test, container, false); 157 158 mZonesTabLayout = v.findViewById(R.id.zones_tab); 159 ViewPager viewPager = (ViewPager) v.findViewById(R.id.zone_view_pager); 160 161 mAudioZoneAdapter = new AudioZoneVolumeTabAdapter(getChildFragmentManager()); 162 viewPager.setAdapter(mAudioZoneAdapter); 163 mZonesTabLayout.setupWithViewPager(viewPager); 164 165 SeekBar.OnSeekBarChangeListener seekListener = 166 new SeekBar.OnSeekBarChangeListener() { 167 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 168 final float percent = (progress - 100) / 100.0f; 169 if (seekBar.getId() == R.id.fade_bar) { 170 mCarAudioManager.setFadeTowardFront(percent); 171 } else { 172 mCarAudioManager.setBalanceTowardRight(percent); 173 } 174 } 175 176 public void onStartTrackingTouch(SeekBar seekBar) {} 177 178 public void onStopTrackingTouch(SeekBar seekBar) {} 179 }; 180 181 mFader = v.findViewById(R.id.fade_bar); 182 mFader.setOnSeekBarChangeListener(seekListener); 183 184 mBalance = v.findViewById(R.id.balance_bar); 185 mBalance.setOnSeekBarChangeListener(seekListener); 186 187 mCar = Car.createCar(getActivity(), /* handler= */ null, 188 Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER, mCarServiceLifecycleListener); 189 return v; 190 } 191 192 @Override onDestroyView()193 public void onDestroyView() { 194 if (mCar != null && mCar.isConnected()) { 195 mCar.disconnect(); 196 mCar = null; 197 } 198 super.onDestroyView(); 199 } 200 initVolumeInfo()201 private void initVolumeInfo() { 202 synchronized (mLock) { 203 List<Integer> audioZoneIds = mCarAudioManager.getAudioZoneIds(); 204 for (int index = 0; index < audioZoneIds.size(); index++) { 205 int zoneId = audioZoneIds.get(index); 206 CarAudioZoneVolumeFragment fragment = 207 new CarAudioZoneVolumeFragment(zoneId, mCarAudioManager, mAudioManager); 208 mZonesTabLayout.addTab(mZonesTabLayout.newTab().setText("Audio Zone " + zoneId)); 209 mAudioZoneAdapter.addFragment(fragment, "Audio Zone " + zoneId); 210 if (DEBUG) { 211 Log.d(TAG, "Adding audio volume for zone " + zoneId); 212 } 213 mZoneVolumeFragments.put(zoneId, fragment); 214 } 215 } 216 } 217 sendFragmentChangedMessageForEvents(List<CarVolumeGroupEvent> volumeGroupEvents)218 private void sendFragmentChangedMessageForEvents(List<CarVolumeGroupEvent> volumeGroupEvents) { 219 CarAudioZoneVolumeFragment fragment; 220 for (int index = 0; index < volumeGroupEvents.size(); index++) { 221 CarVolumeGroupEvent event = volumeGroupEvents.get(index); 222 List<CarVolumeGroupInfo> groupInfos = event.getCarVolumeGroupInfos(); 223 224 synchronized (mLock) { 225 fragment = mZoneVolumeFragments.get(groupInfos.get(0).getZoneId()); 226 } 227 228 if (fragment != null) { 229 fragment.sendEventReceivedMessage(event); 230 } 231 } 232 } 233 } 234