blob: fae748eaf89e0f70ce8fd9d39df218c311d1e84f [file] [log] [blame]
/*
* Copyright (C) 2018 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.android.car.settings.sound;
import static com.android.car.settings.sound.VolumeItemParser.VolumeItem;
import android.car.Car;
import android.car.CarNotConnectedException;
import android.car.drivingstate.CarUxRestrictions;
import android.car.media.CarAudioManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.SparseArray;
import androidx.annotation.DrawableRes;
import androidx.annotation.StringRes;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.XmlRes;
import androidx.preference.PreferenceGroup;
import com.android.car.apps.common.util.Themes;
import com.android.car.settings.R;
import com.android.car.settings.common.FragmentController;
import com.android.car.settings.common.Logger;
import com.android.car.settings.common.PreferenceController;
import com.android.car.settings.common.SeekBarPreference;
import java.util.ArrayList;
import java.util.List;
/**
* Business logic which parses car volume items into groups, creates a seek bar preference for each
* group, and interfaces with the ringtone manager and audio manager.
*
* @see VolumeSettingsRingtoneManager
* @see android.car.media.CarAudioManager
*/
public class VolumeSettingsPreferenceController extends PreferenceController<PreferenceGroup> {
private static final Logger LOG = new Logger(VolumeSettingsPreferenceController.class);
private static final String VOLUME_GROUP_KEY = "volume_group_key";
private static final String VOLUME_USAGE_KEY = "volume_usage_key";
private final SparseArray<VolumeItem> mVolumeItems;
private final List<SeekBarPreference> mVolumePreferences = new ArrayList<>();
private final VolumeSettingsRingtoneManager mRingtoneManager;
private final ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
try {
mCarAudioManager = (CarAudioManager) mCar.getCarManager(Car.AUDIO_SERVICE);
int volumeGroupCount = mCarAudioManager.getVolumeGroupCount();
cleanUpVolumePreferences();
// Populates volume slider items from volume groups to UI.
for (int groupId = 0; groupId < volumeGroupCount; groupId++) {
VolumeItem volumeItem = getVolumeItemForUsages(
mCarAudioManager.getUsagesForVolumeGroupId(groupId));
SeekBarPreference volumePreference = createVolumeSeekBarPreference(
groupId, volumeItem.getUsage(), volumeItem.getIcon(),
volumeItem.getTitle());
mVolumePreferences.add(volumePreference);
}
refreshUi();
} catch (CarNotConnectedException e) {
LOG.e("Car is not connected!", e);
}
}
/** Cleanup audio related fields when car is disconnected. */
@Override
public void onServiceDisconnected(ComponentName name) {
cleanupAudioManager();
}
};
private Car mCar;
private CarAudioManager mCarAudioManager;
public VolumeSettingsPreferenceController(Context context, String preferenceKey,
FragmentController fragmentController,
CarUxRestrictions uxRestrictions) {
super(context, preferenceKey, fragmentController, uxRestrictions);
mCar = Car.createCar(getContext(), mServiceConnection);
mVolumeItems = VolumeItemParser.loadAudioUsageItems(context, carVolumeItemsXml());
mRingtoneManager = new VolumeSettingsRingtoneManager(getContext());
}
@Override
protected Class<PreferenceGroup> getPreferenceType() {
return PreferenceGroup.class;
}
/** Connect to car on create. */
@Override
protected void onCreateInternal() {
mCar.connect();
}
/** Disconnect from car on destroy. */
@Override
protected void onDestroyInternal() {
mCar.disconnect();
}
@Override
protected void updateState(PreferenceGroup preferenceGroup) {
for (SeekBarPreference preference : mVolumePreferences) {
preferenceGroup.addPreference(preference);
}
}
/**
* The resource which lists the car volume resources associated with the various usage enums.
*/
@XmlRes
@VisibleForTesting
int carVolumeItemsXml() {
return R.xml.car_volume_items;
}
private SeekBarPreference createVolumeSeekBarPreference(
int volumeGroupId, int usage, @DrawableRes int iconResId,
@StringRes int titleId) {
SeekBarPreference preference = new SeekBarPreference(getContext());
preference.setTitle(getContext().getString(titleId));
preference.setIcon(getContext().getDrawable(iconResId));
preference.getIcon().setTintList(
Themes.getAttrColorStateList(getContext(), R.attr.iconColor));
try {
preference.setValue(mCarAudioManager.getGroupVolume(volumeGroupId));
preference.setMin(mCarAudioManager.getGroupMinVolume(volumeGroupId));
preference.setMax(mCarAudioManager.getGroupMaxVolume(volumeGroupId));
} catch (CarNotConnectedException e) {
LOG.e("Car is not connected!", e);
}
preference.setContinuousUpdate(true);
preference.setShowSeekBarValue(false);
Bundle bundle = preference.getExtras();
bundle.putInt(VOLUME_GROUP_KEY, volumeGroupId);
bundle.putInt(VOLUME_USAGE_KEY, usage);
preference.setOnPreferenceChangeListener((pref, newValue) -> {
int prefGroup = pref.getExtras().getInt(VOLUME_GROUP_KEY);
int prefUsage = pref.getExtras().getInt(VOLUME_USAGE_KEY);
int newVolume = (Integer) newValue;
setGroupVolume(prefGroup, newVolume);
mRingtoneManager.playAudioFeedback(prefGroup, prefUsage);
return true;
});
return preference;
}
private void setGroupVolume(int volumeGroupId, int newVolume) {
try {
mCarAudioManager.setGroupVolume(volumeGroupId, newVolume, /* flags= */ 0);
} catch (CarNotConnectedException e) {
LOG.w("Ignoring volume change event because the car isn't connected", e);
}
}
private void cleanupAudioManager() {
cleanUpVolumePreferences();
mCarAudioManager = null;
}
private void cleanUpVolumePreferences() {
mRingtoneManager.stopCurrentRingtone();
mVolumePreferences.clear();
}
private VolumeItem getVolumeItemForUsages(int[] usages) {
int rank = Integer.MAX_VALUE;
VolumeItem result = null;
for (int usage : usages) {
VolumeItem volumeItem = mVolumeItems.get(usage);
if (volumeItem.getRank() < rank) {
rank = volumeItem.getRank();
result = volumeItem;
}
}
return result;
}
}