1 /* 2 * Copyright (C) 2018 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.android.car.settings.sound; 18 19 import static com.android.car.settings.sound.VolumeItemParser.VolumeItem; 20 21 import android.car.Car; 22 import android.car.CarNotConnectedException; 23 import android.car.drivingstate.CarUxRestrictions; 24 import android.car.media.CarAudioManager; 25 import android.content.ComponentName; 26 import android.content.Context; 27 import android.content.ServiceConnection; 28 import android.os.Bundle; 29 import android.os.IBinder; 30 import android.util.SparseArray; 31 32 import androidx.annotation.DrawableRes; 33 import androidx.annotation.StringRes; 34 import androidx.annotation.VisibleForTesting; 35 import androidx.annotation.XmlRes; 36 import androidx.preference.PreferenceGroup; 37 38 import com.android.car.apps.common.util.Themes; 39 import com.android.car.settings.R; 40 import com.android.car.settings.common.FragmentController; 41 import com.android.car.settings.common.Logger; 42 import com.android.car.settings.common.PreferenceController; 43 import com.android.car.settings.common.SeekBarPreference; 44 45 import java.util.ArrayList; 46 import java.util.List; 47 48 /** 49 * Business logic which parses car volume items into groups, creates a seek bar preference for each 50 * group, and interfaces with the ringtone manager and audio manager. 51 * 52 * @see VolumeSettingsRingtoneManager 53 * @see android.car.media.CarAudioManager 54 */ 55 public class VolumeSettingsPreferenceController extends PreferenceController<PreferenceGroup> { 56 private static final Logger LOG = new Logger(VolumeSettingsPreferenceController.class); 57 private static final String VOLUME_GROUP_KEY = "volume_group_key"; 58 private static final String VOLUME_USAGE_KEY = "volume_usage_key"; 59 60 private final SparseArray<VolumeItem> mVolumeItems; 61 private final List<SeekBarPreference> mVolumePreferences = new ArrayList<>(); 62 private final VolumeSettingsRingtoneManager mRingtoneManager; 63 64 private final ServiceConnection mServiceConnection = new ServiceConnection() { 65 @Override 66 public void onServiceConnected(ComponentName name, IBinder service) { 67 try { 68 mCarAudioManager = (CarAudioManager) mCar.getCarManager(Car.AUDIO_SERVICE); 69 int volumeGroupCount = mCarAudioManager.getVolumeGroupCount(); 70 cleanUpVolumePreferences(); 71 // Populates volume slider items from volume groups to UI. 72 for (int groupId = 0; groupId < volumeGroupCount; groupId++) { 73 VolumeItem volumeItem = getVolumeItemForUsages( 74 mCarAudioManager.getUsagesForVolumeGroupId(groupId)); 75 SeekBarPreference volumePreference = createVolumeSeekBarPreference( 76 groupId, volumeItem.getUsage(), volumeItem.getIcon(), 77 volumeItem.getTitle()); 78 mVolumePreferences.add(volumePreference); 79 } 80 81 refreshUi(); 82 } catch (CarNotConnectedException e) { 83 LOG.e("Car is not connected!", e); 84 } 85 } 86 87 /** Cleanup audio related fields when car is disconnected. */ 88 @Override 89 public void onServiceDisconnected(ComponentName name) { 90 cleanupAudioManager(); 91 } 92 }; 93 94 private Car mCar; 95 private CarAudioManager mCarAudioManager; 96 VolumeSettingsPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)97 public VolumeSettingsPreferenceController(Context context, String preferenceKey, 98 FragmentController fragmentController, 99 CarUxRestrictions uxRestrictions) { 100 super(context, preferenceKey, fragmentController, uxRestrictions); 101 mCar = Car.createCar(getContext(), mServiceConnection); 102 mVolumeItems = VolumeItemParser.loadAudioUsageItems(context, carVolumeItemsXml()); 103 mRingtoneManager = new VolumeSettingsRingtoneManager(getContext()); 104 } 105 106 @Override getPreferenceType()107 protected Class<PreferenceGroup> getPreferenceType() { 108 return PreferenceGroup.class; 109 } 110 111 /** Connect to car on create. */ 112 @Override onCreateInternal()113 protected void onCreateInternal() { 114 mCar.connect(); 115 } 116 117 /** Disconnect from car on destroy. */ 118 @Override onDestroyInternal()119 protected void onDestroyInternal() { 120 mCar.disconnect(); 121 } 122 123 @Override updateState(PreferenceGroup preferenceGroup)124 protected void updateState(PreferenceGroup preferenceGroup) { 125 for (SeekBarPreference preference : mVolumePreferences) { 126 preferenceGroup.addPreference(preference); 127 } 128 } 129 130 /** 131 * The resource which lists the car volume resources associated with the various usage enums. 132 */ 133 @XmlRes 134 @VisibleForTesting carVolumeItemsXml()135 int carVolumeItemsXml() { 136 return R.xml.car_volume_items; 137 } 138 createVolumeSeekBarPreference( int volumeGroupId, int usage, @DrawableRes int iconResId, @StringRes int titleId)139 private SeekBarPreference createVolumeSeekBarPreference( 140 int volumeGroupId, int usage, @DrawableRes int iconResId, 141 @StringRes int titleId) { 142 SeekBarPreference preference = new SeekBarPreference(getContext()); 143 preference.setTitle(getContext().getString(titleId)); 144 preference.setIcon(getContext().getDrawable(iconResId)); 145 preference.getIcon().setTintList( 146 Themes.getAttrColorStateList(getContext(), R.attr.iconColor)); 147 try { 148 preference.setValue(mCarAudioManager.getGroupVolume(volumeGroupId)); 149 preference.setMin(mCarAudioManager.getGroupMinVolume(volumeGroupId)); 150 preference.setMax(mCarAudioManager.getGroupMaxVolume(volumeGroupId)); 151 } catch (CarNotConnectedException e) { 152 LOG.e("Car is not connected!", e); 153 } 154 preference.setContinuousUpdate(true); 155 preference.setShowSeekBarValue(false); 156 Bundle bundle = preference.getExtras(); 157 bundle.putInt(VOLUME_GROUP_KEY, volumeGroupId); 158 bundle.putInt(VOLUME_USAGE_KEY, usage); 159 preference.setOnPreferenceChangeListener((pref, newValue) -> { 160 int prefGroup = pref.getExtras().getInt(VOLUME_GROUP_KEY); 161 int prefUsage = pref.getExtras().getInt(VOLUME_USAGE_KEY); 162 int newVolume = (Integer) newValue; 163 setGroupVolume(prefGroup, newVolume); 164 mRingtoneManager.playAudioFeedback(prefGroup, prefUsage); 165 return true; 166 }); 167 return preference; 168 } 169 setGroupVolume(int volumeGroupId, int newVolume)170 private void setGroupVolume(int volumeGroupId, int newVolume) { 171 try { 172 mCarAudioManager.setGroupVolume(volumeGroupId, newVolume, /* flags= */ 0); 173 } catch (CarNotConnectedException e) { 174 LOG.w("Ignoring volume change event because the car isn't connected", e); 175 } 176 } 177 cleanupAudioManager()178 private void cleanupAudioManager() { 179 cleanUpVolumePreferences(); 180 mCarAudioManager = null; 181 } 182 cleanUpVolumePreferences()183 private void cleanUpVolumePreferences() { 184 mRingtoneManager.stopCurrentRingtone(); 185 mVolumePreferences.clear(); 186 } 187 getVolumeItemForUsages(int[] usages)188 private VolumeItem getVolumeItemForUsages(int[] usages) { 189 int rank = Integer.MAX_VALUE; 190 VolumeItem result = null; 191 for (int usage : usages) { 192 VolumeItem volumeItem = mVolumeItems.get(usage); 193 if (volumeItem.getRank() < rank) { 194 rank = volumeItem.getRank(); 195 result = volumeItem; 196 } 197 } 198 return result; 199 } 200 } 201