• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.google.android.car.kitchensink.volume;
18 
19 import static android.car.media.CarAudioManager.AUDIO_FEATURE_VOLUME_GROUP_MUTING;
20 import static android.media.AudioManager.FLAG_PLAY_SOUND;
21 
22 import android.car.media.CarAudioManager;
23 import android.media.AudioAttributes;
24 import android.media.AudioAttributes.AttributeUsage;
25 import android.media.AudioManager;
26 import android.media.Ringtone;
27 import android.media.RingtoneManager;
28 import android.net.Uri;
29 import android.os.Bundle;
30 import android.os.Handler;
31 import android.os.Message;
32 import android.util.Log;
33 import android.util.SparseIntArray;
34 import android.view.LayoutInflater;
35 import android.view.View;
36 import android.view.ViewGroup;
37 import android.widget.ListView;
38 
39 import androidx.fragment.app.Fragment;
40 
41 import com.android.internal.annotations.GuardedBy;
42 
43 import com.google.android.car.kitchensink.R;
44 import com.google.android.car.kitchensink.volume.VolumeTestFragment.CarAudioZoneVolumeInfo;
45 
46 public final class CarAudioZoneVolumeFragment extends Fragment {
47     private static final String TAG = "CarVolumeTest."
48             + CarAudioZoneVolumeFragment.class.getSimpleName();
49     private static final boolean DEBUG = true;
50 
51     private static final int MSG_VOLUME_CHANGED = 0;
52     private static final int MSG_REQUEST_FOCUS = 1;
53     private static final int MSG_FOCUS_CHANGED = 2;
54     private static final int MSG_STOP_RINGTONE = 3;
55     private static final int MSG_ADJUST_VOLUME = 4;
56     private static final long RINGTONE_STOP_TIME_MS = 3_000;
57     private static final int ADJUST_VOLUME_UP = 0;
58     private static final int ADJUST_VOLUME_DOWN = 1;
59 
60     private final int mZoneId;
61     private final Object mLock = new Object();
62     private final CarAudioManager mCarAudioManager;
63     private final AudioManager mAudioManager;
64     private CarAudioZoneVolumeInfo[] mVolumeInfos =
65             new CarAudioZoneVolumeInfo[0];
66     private final Handler mHandler = new VolumeHandler();
67 
68     private CarAudioZoneVolumeAdapter mCarAudioZoneVolumeAdapter;
69     private final SparseIntArray mGroupIdIndexMap = new SparseIntArray();
70 
71     @GuardedBy("mLock")
72     private Ringtone mRingtone;
73 
sendVolumeChangedMessage(int groupId, int flags)74     void sendVolumeChangedMessage(int groupId, int flags) {
75         mHandler.sendMessage(mHandler.obtainMessage(MSG_VOLUME_CHANGED, groupId, flags));
76     }
77 
adjustVolumeUp(int groupId)78     void adjustVolumeUp(int groupId) {
79         mHandler.sendMessage(mHandler.obtainMessage(MSG_ADJUST_VOLUME, groupId, ADJUST_VOLUME_UP));
80     }
81 
adjustVolumeDown(int groupId)82     void adjustVolumeDown(int groupId) {
83         mHandler.sendMessage(mHandler
84                 .obtainMessage(MSG_ADJUST_VOLUME, groupId, ADJUST_VOLUME_DOWN));
85     }
86 
87     private class VolumeHandler extends Handler {
88         private AudioFocusListener mFocusListener;
89 
90         @Override
handleMessage(Message msg)91         public void handleMessage(Message msg) {
92             if (DEBUG) {
93                 Log.d(TAG, "zone " + mZoneId + " handleMessage : " + getMessageName(msg));
94             }
95             switch (msg.what) {
96                 case MSG_VOLUME_CHANGED:
97                     initVolumeInfo();
98                     playRingtoneForGroup(msg.arg1, msg.arg2);
99                     break;
100                 case MSG_STOP_RINGTONE:
101                     stopRingtone();
102                     break;
103                 case MSG_REQUEST_FOCUS:
104                     int groupId = msg.arg1;
105                     if (mFocusListener != null) {
106                         mAudioManager.abandonAudioFocus(mFocusListener);
107                         mVolumeInfos[mGroupIdIndexMap.get(groupId)].hasAudioFocus = false;
108                         mCarAudioZoneVolumeAdapter.notifyDataSetChanged();
109                     }
110 
111                     mFocusListener = new AudioFocusListener(groupId);
112                     mAudioManager.requestAudioFocus(mFocusListener, groupId,
113                             AudioManager.AUDIOFOCUS_GAIN);
114                     break;
115                 case MSG_FOCUS_CHANGED:
116                     int focusGroupId = msg.arg1;
117                     mVolumeInfos[mGroupIdIndexMap.get(focusGroupId)].hasAudioFocus = true;
118                     mCarAudioZoneVolumeAdapter.refreshVolumes(mVolumeInfos);
119                     break;
120                 case MSG_ADJUST_VOLUME:
121                     adjustVolumeByOne(msg.arg1, msg.arg2 == ADJUST_VOLUME_UP);
122                     break;
123                 default:
124                     Log.wtf(TAG, "VolumeHandler handleMessage called with unknown message"
125                             + msg.what);
126             }
127         }
128     }
129 
CarAudioZoneVolumeFragment(int zoneId, CarAudioManager carAudioManager, AudioManager audioManager)130     public CarAudioZoneVolumeFragment(int zoneId, CarAudioManager carAudioManager,
131             AudioManager audioManager) {
132         mZoneId = zoneId;
133         mCarAudioManager = carAudioManager;
134         mAudioManager = audioManager;
135     }
136 
137     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)138     public View onCreateView(LayoutInflater inflater, ViewGroup container,
139             Bundle savedInstanceState) {
140         if (DEBUG) {
141             Log.d(TAG, "onCreateView " + mZoneId);
142         }
143         View v = inflater.inflate(R.layout.zone_volume_tab, container, false);
144         ListView volumeListView = v.findViewById(R.id.volume_list);
145         mCarAudioZoneVolumeAdapter =
146                 new CarAudioZoneVolumeAdapter(getContext(), R.layout.volume_item, mVolumeInfos,
147                         this, mCarAudioManager.isAudioFeatureEnabled(
148                         AUDIO_FEATURE_VOLUME_GROUP_MUTING));
149         initVolumeInfo();
150         volumeListView.setAdapter(mCarAudioZoneVolumeAdapter);
151         return v;
152     }
153 
initVolumeInfo()154     void initVolumeInfo() {
155         int volumeGroupCount = mCarAudioManager.getVolumeGroupCount(mZoneId);
156         mVolumeInfos = new CarAudioZoneVolumeInfo[volumeGroupCount + 1];
157         mGroupIdIndexMap.clear();
158         CarAudioZoneVolumeInfo titlesInfo = new CarAudioZoneVolumeInfo();
159         titlesInfo.id = "Group id";
160         titlesInfo.currentGain = "Current";
161         mVolumeInfos[0] = titlesInfo;
162 
163         int i = 1;
164         for (int groupId = 0; groupId < volumeGroupCount; groupId++) {
165             CarAudioZoneVolumeInfo volumeInfo = new CarAudioZoneVolumeInfo();
166             mGroupIdIndexMap.put(groupId, i);
167             volumeInfo.groupId = groupId;
168             volumeInfo.id = String.valueOf(groupId);
169             int current = mCarAudioManager.getGroupVolume(mZoneId, groupId);
170             int max = mCarAudioManager.getGroupMaxVolume(mZoneId, groupId);
171             int min = mCarAudioManager.getGroupMinVolume(mZoneId, groupId);
172             volumeInfo.currentGain = String.valueOf(current);
173             volumeInfo.maxGain = max;
174             volumeInfo.minGain = min;
175             volumeInfo.isMuted = mCarAudioManager.isVolumeGroupMuted(mZoneId, groupId);
176 
177             mVolumeInfos[i] = volumeInfo;
178             if (DEBUG) {
179                 Log.d(TAG, groupId + " max: " + volumeInfo.maxGain + " current: "
180                         + volumeInfo.currentGain + " is muted " + volumeInfo.isMuted);
181             }
182             i++;
183         }
184         mCarAudioZoneVolumeAdapter.refreshVolumes(mVolumeInfos);
185     }
186 
adjustVolumeByOne(int groupId, boolean up)187     private void adjustVolumeByOne(int groupId, boolean up) {
188         if (mCarAudioManager == null) {
189             Log.e(TAG, "CarAudioManager is null");
190             return;
191         }
192         int current = mCarAudioManager.getGroupVolume(mZoneId, groupId);
193         CarAudioZoneVolumeInfo info = getVolumeInfo(groupId);
194         int volume = up ? current + 1 : current - 1;
195         if (volume > info.maxGain) {
196             if (DEBUG) {
197                 Log.d(TAG, "Reached " + groupId + " max volume "
198                         + " limit " + volume);
199             }
200             return;
201         }
202         if (volume < info.minGain) {
203             if (DEBUG) {
204                 Log.d(TAG, "Reached " + groupId + " min volume "
205                         + " limit " + volume);
206             }
207             return;
208         }
209         mCarAudioManager.setGroupVolume(mZoneId, groupId, volume, /* flags= */ 0);
210         if (DEBUG) {
211             Log.d(TAG, "Set group " + groupId + " volume "
212                     + mCarAudioManager.getGroupVolume(mZoneId, groupId)
213                     + " in audio zone " + mZoneId);
214         }
215     }
216 
getVolumeInfo(int groupId)217     private CarAudioZoneVolumeInfo getVolumeInfo(int groupId) {
218         return mVolumeInfos[mGroupIdIndexMap.get(groupId)];
219     }
220 
toggleMute(int groupId)221     public void toggleMute(int groupId) {
222         if (mCarAudioManager == null) {
223             Log.e(TAG, "CarAudioManager is null");
224             return;
225         }
226         boolean isMuted = mCarAudioManager.isVolumeGroupMuted(mZoneId, groupId);
227         mCarAudioManager.setVolumeGroupMute(mZoneId, groupId, !isMuted, AudioManager.FLAG_SHOW_UI);
228         if (DEBUG) {
229             Log.d(TAG, "Set group mute " + groupId + " mute " + !isMuted + " in audio zone "
230                     + mZoneId);
231         }
232     }
233 
requestFocus(int groupId)234     void requestFocus(int groupId) {
235         // Automatic volume change only works for primary audio zone.
236         if (mZoneId == CarAudioManager.PRIMARY_AUDIO_ZONE) {
237             mHandler.sendMessage(mHandler
238                     .obtainMessage(MSG_REQUEST_FOCUS, groupId, /* arg2= */ 0));
239         }
240     }
241 
playRingtoneForGroup(int groupId, int flags)242     private void playRingtoneForGroup(int groupId, int flags) {
243         if (DEBUG) {
244             Log.d(TAG, "playRingtoneForGroup(" + groupId + ") in zone " + mZoneId);
245         }
246 
247         if ((flags & FLAG_PLAY_SOUND) == 0) {
248             return;
249         }
250 
251         int usage = mCarAudioManager.getUsagesForVolumeGroupId(mZoneId, groupId)[0];
252         if (isRingtoneActiveForUsage(usage)) {
253             return;
254         }
255 
256         mHandler.removeMessages(MSG_STOP_RINGTONE);
257 
258         stopRingtone();
259         startRingtone(usage);
260 
261         mHandler.sendEmptyMessageDelayed(MSG_STOP_RINGTONE, RINGTONE_STOP_TIME_MS);
262     }
263 
startRingtone(@ttributeUsage int usage)264     private void startRingtone(@AttributeUsage int usage) {
265         if (DEBUG) {
266             Log.d(TAG, "Start ringtone for zone " + mZoneId + " and usage "
267                     + AudioAttributes.usageToString(usage));
268         }
269 
270         AudioAttributes.Builder builder = new AudioAttributes.Builder()
271                 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION);
272 
273         if (AudioAttributes.isSystemUsage(usage)) {
274             builder.setSystemUsage(usage);
275         } else {
276             builder.setUsage(usage);
277         }
278 
279         AudioAttributes attributes = builder.build();
280 
281         Uri uri = RingtoneManager.getActualDefaultRingtoneUri(getContext(),
282                 AudioAttributes.toLegacyStreamType(attributes));
283 
284         Ringtone ringtone =
285                 RingtoneManager.getRingtone(mCarAudioZoneVolumeAdapter.getContext(), uri);
286         ringtone.setAudioAttributes(attributes);
287         ringtone.setLooping(true);
288 
289         ringtone.play();
290 
291         synchronized (mLock) {
292             mRingtone = ringtone;
293         }
294     }
295 
stopRingtone()296     private void stopRingtone() {
297         synchronized (mLock) {
298             if (mRingtone == null) {
299                 return;
300             }
301             if (mRingtone.isPlaying()) {
302                 mRingtone.stop();
303             }
304             mRingtone = null;
305         }
306     }
307 
isRingtoneActiveForUsage(@ttributeUsage int usage)308     boolean isRingtoneActiveForUsage(@AttributeUsage int usage) {
309         synchronized (mLock) {
310             return mRingtone != null && mRingtone.isPlaying()
311                     && mRingtone.getAudioAttributes().getUsage() == usage;
312         }
313     }
314 
315     private class AudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
316         private final int mGroupId;
AudioFocusListener(int groupId)317         AudioFocusListener(int groupId) {
318             mGroupId = groupId;
319         }
320         @Override
onAudioFocusChange(int focusChange)321         public void onAudioFocusChange(int focusChange) {
322             if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
323                 mHandler.sendMessage(mHandler
324                         .obtainMessage(MSG_FOCUS_CHANGED, mGroupId, /* arg2= */ 0));
325             } else {
326                 Log.e(TAG, "Audio focus request failed");
327             }
328         }
329     }
330 }
331