blob: 675f495fc9adc8d32f8787150854cae6e710efb4 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.car.kitchensink.volume;
import static android.car.media.CarAudioManager.AUDIO_FEATURE_VOLUME_GROUP_MUTING;
import static android.media.AudioManager.FLAG_PLAY_SOUND;
import android.car.media.CarAudioManager;
import android.media.AudioAttributes;
import android.media.AudioAttributes.AttributeUsage;
import android.media.AudioManager;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.util.SparseIntArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;
import androidx.fragment.app.Fragment;
import com.android.internal.annotations.GuardedBy;
import com.google.android.car.kitchensink.R;
import com.google.android.car.kitchensink.volume.VolumeTestFragment.CarAudioZoneVolumeInfo;
public final class CarAudioZoneVolumeFragment extends Fragment {
private static final String TAG = "CarVolumeTest."
+ CarAudioZoneVolumeFragment.class.getSimpleName();
private static final boolean DEBUG = true;
private static final int MSG_VOLUME_CHANGED = 0;
private static final int MSG_REQUEST_FOCUS = 1;
private static final int MSG_FOCUS_CHANGED = 2;
private static final int MSG_STOP_RINGTONE = 3;
private static final long RINGTONE_STOP_TIME_MS = 3_000;
private final int mZoneId;
private final Object mLock = new Object();
private final CarAudioManager mCarAudioManager;
private final AudioManager mAudioManager;
private CarAudioZoneVolumeInfo[] mVolumeInfos =
new CarAudioZoneVolumeInfo[0];
private final Handler mHandler = new VolumeHandler();
private CarAudioZoneVolumeAdapter mCarAudioZoneVolumeAdapter;
private final SparseIntArray mGroupIdIndexMap = new SparseIntArray();
@GuardedBy("mLock")
private Ringtone mRingtone;
public void sendVolumeChangedMessage(int groupId, int flags) {
mHandler.sendMessage(mHandler.obtainMessage(MSG_VOLUME_CHANGED, groupId, flags));
}
private class VolumeHandler extends Handler {
private AudioFocusListener mFocusListener;
@Override
public void handleMessage(Message msg) {
if (DEBUG) {
Log.d(TAG, "zone " + mZoneId + " handleMessage : " + getMessageName(msg));
}
switch (msg.what) {
case MSG_VOLUME_CHANGED:
initVolumeInfo();
playRingtoneForGroup(msg.arg1, msg.arg2);
break;
case MSG_STOP_RINGTONE:
stopRingtone();
break;
case MSG_REQUEST_FOCUS:
int groupId = msg.arg1;
if (mFocusListener != null) {
mAudioManager.abandonAudioFocus(mFocusListener);
mVolumeInfos[mGroupIdIndexMap.get(groupId)].hasAudioFocus = false;
mCarAudioZoneVolumeAdapter.notifyDataSetChanged();
}
mFocusListener = new AudioFocusListener(groupId);
mAudioManager.requestAudioFocus(mFocusListener, groupId,
AudioManager.AUDIOFOCUS_GAIN);
break;
case MSG_FOCUS_CHANGED:
int focusGroupId = msg.arg1;
mVolumeInfos[mGroupIdIndexMap.get(focusGroupId)].hasAudioFocus = true;
mCarAudioZoneVolumeAdapter.refreshVolumes(mVolumeInfos);
break;
default :
Log.wtf(TAG,"VolumeHandler handleMessage called with unknown message"
+ msg.what);
}
}
}
public CarAudioZoneVolumeFragment(int zoneId, CarAudioManager carAudioManager,
AudioManager audioManager) {
mZoneId = zoneId;
mCarAudioManager = carAudioManager;
mAudioManager = audioManager;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (DEBUG) {
Log.d(TAG, "onCreateView " + mZoneId);
}
View v = inflater.inflate(R.layout.zone_volume_tab, container, false);
ListView volumeListView = v.findViewById(R.id.volume_list);
mCarAudioZoneVolumeAdapter =
new CarAudioZoneVolumeAdapter(getContext(), R.layout.volume_item, mVolumeInfos,
this, mCarAudioManager.isAudioFeatureEnabled(
AUDIO_FEATURE_VOLUME_GROUP_MUTING));
initVolumeInfo();
volumeListView.setAdapter(mCarAudioZoneVolumeAdapter);
return v;
}
void initVolumeInfo() {
int volumeGroupCount = mCarAudioManager.getVolumeGroupCount(mZoneId);
mVolumeInfos = new CarAudioZoneVolumeInfo[volumeGroupCount + 1];
mGroupIdIndexMap.clear();
CarAudioZoneVolumeInfo titlesInfo = new CarAudioZoneVolumeInfo();
titlesInfo.id = "Group id";
titlesInfo.currentGain = "Current";
titlesInfo.maxGain = "Max";
mVolumeInfos[0] = titlesInfo;
int i = 1;
for (int groupId = 0; groupId < volumeGroupCount; groupId++) {
CarAudioZoneVolumeInfo volumeInfo = new CarAudioZoneVolumeInfo();
mGroupIdIndexMap.put(groupId, i);
volumeInfo.groupId = groupId;
volumeInfo.id = String.valueOf(groupId);
int current = mCarAudioManager.getGroupVolume(mZoneId, groupId);
int max = mCarAudioManager.getGroupMaxVolume(mZoneId, groupId);
volumeInfo.currentGain = String.valueOf(current);
volumeInfo.maxGain = String.valueOf(max);
volumeInfo.isMuted = mCarAudioManager.isVolumeGroupMuted(mZoneId, groupId);
mVolumeInfos[i] = volumeInfo;
if (DEBUG)
{
Log.d(TAG, groupId + " max: " + volumeInfo.maxGain + " current: "
+ volumeInfo.currentGain + " is muted " + volumeInfo.isMuted);
}
i++;
}
mCarAudioZoneVolumeAdapter.refreshVolumes(mVolumeInfos);
}
public void adjustVolumeByOne(int groupId, boolean up) {
if (mCarAudioManager == null) {
Log.e(TAG, "CarAudioManager is null");
return;
}
int current = mCarAudioManager.getGroupVolume(mZoneId, groupId);
int volume = current + (up ? 1 : -1);
mCarAudioManager.setGroupVolume(mZoneId, groupId, volume, AudioManager.FLAG_SHOW_UI);
if (DEBUG) {
Log.d(TAG, "Set group " + groupId + " volume " + volume + " in audio zone "
+ mZoneId);
}
}
public void toggleMute(int groupId) {
if (mCarAudioManager == null) {
Log.e(TAG, "CarAudioManager is null");
return;
}
boolean isMuted = mCarAudioManager.isVolumeGroupMuted(mZoneId, groupId);
mCarAudioManager.setVolumeGroupMute(mZoneId, groupId, !isMuted, AudioManager.FLAG_SHOW_UI);
if (DEBUG) {
Log.d(TAG, "Set group mute " + groupId + " mute " + !isMuted + " in audio zone "
+ mZoneId);
}
}
public void requestFocus(int groupId) {
// Automatic volume change only works for primary audio zone.
if (mZoneId == CarAudioManager.PRIMARY_AUDIO_ZONE) {
mHandler.sendMessage(mHandler
.obtainMessage(MSG_REQUEST_FOCUS, groupId, /* arg2= */ 0));
}
}
private void playRingtoneForGroup(int groupId, int flags) {
if (DEBUG) {
Log.d(TAG, "playRingtoneForGroup(" + groupId + ") in zone " + mZoneId);
}
if ((flags & FLAG_PLAY_SOUND) == 0) {
return;
}
int usage = mCarAudioManager.getUsagesForVolumeGroupId(mZoneId, groupId)[0];
if (isRingtoneActiveForUsage(usage)) {
return;
}
mHandler.removeMessages(MSG_STOP_RINGTONE);
stopRingtone();
startRingtone(usage);
mHandler.sendEmptyMessageDelayed(MSG_STOP_RINGTONE, RINGTONE_STOP_TIME_MS);
}
private void startRingtone(@AttributeUsage int usage) {
if (DEBUG) {
Log.d(TAG, "Start ringtone for zone " + mZoneId + " and usage "
+ AudioAttributes.usageToString(usage));
}
AudioAttributes attributes = new AudioAttributes.Builder()
.setUsage(usage)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.build();
Uri uri = RingtoneManager.getActualDefaultRingtoneUri(getContext(),
AudioAttributes.toLegacyStreamType(attributes));
Ringtone ringtone =
RingtoneManager.getRingtone(mCarAudioZoneVolumeAdapter.getContext(), uri);
ringtone.setAudioAttributes(attributes);
ringtone.setLooping(true);
ringtone.play();
synchronized (mLock) {
mRingtone = ringtone;
}
}
private void stopRingtone() {
synchronized (mLock) {
if (mRingtone == null) {
return;
}
if (mRingtone.isPlaying()) {
mRingtone.stop();
}
mRingtone = null;
}
}
boolean isRingtoneActiveForUsage(@AttributeUsage int usage) {
synchronized (mLock) {
return mRingtone != null && mRingtone.isPlaying()
&& mRingtone.getAudioAttributes().getUsage() == usage;
}
}
private class AudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
private final int mGroupId;
AudioFocusListener(int groupId) {
mGroupId = groupId;
}
@Override
public void onAudioFocusChange(int focusChange) {
if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
mHandler.sendMessage(mHandler
.obtainMessage(MSG_FOCUS_CHANGED, mGroupId, /* arg2= */ 0));
} else {
Log.e(TAG, "Audio focus request failed");
}
}
}
}