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