• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Context;
26 import android.os.Bundle;
27 import android.os.Handler;
28 import android.os.Looper;
29 import android.util.SparseArray;
30 
31 import androidx.annotation.DrawableRes;
32 import androidx.annotation.StringRes;
33 import androidx.annotation.VisibleForTesting;
34 import androidx.annotation.XmlRes;
35 import androidx.preference.PreferenceGroup;
36 
37 import com.android.car.apps.common.util.Themes;
38 import com.android.car.settings.R;
39 import com.android.car.settings.common.FragmentController;
40 import com.android.car.settings.common.Logger;
41 import com.android.car.settings.common.PreferenceController;
42 import com.android.car.settings.common.SeekBarPreference;
43 
44 import java.util.ArrayList;
45 import java.util.List;
46 
47 /**
48  * Business logic which parses car volume items into groups, creates a seek bar preference for each
49  * group, and interfaces with the ringtone manager and audio manager.
50  *
51  * @see VolumeSettingsRingtoneManager
52  * @see android.car.media.CarAudioManager
53  */
54 public class VolumeSettingsPreferenceController extends PreferenceController<PreferenceGroup> {
55     private static final Logger LOG = new Logger(VolumeSettingsPreferenceController.class);
56     private static final String VOLUME_GROUP_KEY = "volume_group_key";
57     private static final String VOLUME_USAGE_KEY = "volume_usage_key";
58 
59     private final SparseArray<VolumeItem> mVolumeItems;
60     private final List<SeekBarPreference> mVolumePreferences = new ArrayList<>();
61     private final VolumeSettingsRingtoneManager mRingtoneManager;
62 
63     private final Handler mUiHandler;
64 
65     @VisibleForTesting
66     final CarAudioManager.CarVolumeCallback mVolumeChangeCallback =
67             new CarAudioManager.CarVolumeCallback() {
68                 @Override
69                 public void onGroupVolumeChanged(int zoneId, int groupId, int flags) {
70                     if (mCarAudioManager != null) {
71                         int value = mCarAudioManager.getGroupVolume(groupId);
72 
73                         for (SeekBarPreference volumePreference : mVolumePreferences) {
74                             Bundle extras = volumePreference.getExtras();
75                             if (extras.getInt(VOLUME_GROUP_KEY) == groupId) {
76                                 // Only setValue if the value is different, since changing the
77                                 // seekbar of the volume directly will trigger CarVolumeCallback as
78                                 // well, causing janky movement.
79                                 if (volumePreference.getValue() != value) {
80                                     // CarVolumeCallback is run on a binder thread. In order to
81                                     // make updates to the SeekBarPreference, we need to switch
82                                     // over to the UI thread.
83                                     mUiHandler.post(() -> {
84                                         volumePreference.setValue(value);
85                                     });
86                                 }
87                                 break;
88                             }
89                         }
90                     }
91                 }
92 
93                 @Override
94                 public void onMasterMuteChanged(int zoneId, int flags) {
95                     // Mute is not being used yet
96                 }
97             };
98 
99     private Car mCar;
100     private CarAudioManager mCarAudioManager;
101 
VolumeSettingsPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)102     public VolumeSettingsPreferenceController(Context context, String preferenceKey,
103             FragmentController fragmentController,
104             CarUxRestrictions uxRestrictions) {
105         this(context, preferenceKey, fragmentController, uxRestrictions, Car.createCar(context),
106                 new VolumeSettingsRingtoneManager(context));
107     }
108 
109     @VisibleForTesting
VolumeSettingsPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions, Car car, VolumeSettingsRingtoneManager ringtoneManager)110     VolumeSettingsPreferenceController(Context context, String preferenceKey,
111             FragmentController fragmentController,
112             CarUxRestrictions uxRestrictions, Car car,
113             VolumeSettingsRingtoneManager ringtoneManager) {
114         super(context, preferenceKey, fragmentController, uxRestrictions);
115         mCar = car;
116         mRingtoneManager = ringtoneManager;
117         mVolumeItems = VolumeItemParser.loadAudioUsageItems(context, carVolumeItemsXml());
118         mUiHandler = new Handler(Looper.getMainLooper());
119 
120         mCarAudioManager = (CarAudioManager) mCar.getCarManager(Car.AUDIO_SERVICE);
121         if (mCarAudioManager != null) {
122             int volumeGroupCount = mCarAudioManager.getVolumeGroupCount();
123             cleanUpVolumePreferences();
124             // Populates volume slider items from volume groups to UI.
125             for (int groupId = 0; groupId < volumeGroupCount; groupId++) {
126                 VolumeItem volumeItem = getVolumeItemForUsages(
127                         mCarAudioManager.getUsagesForVolumeGroupId(groupId));
128                 SeekBarPreference volumePreference = createVolumeSeekBarPreference(
129                         groupId, volumeItem.getUsage(), volumeItem.getIcon(),
130                         volumeItem.getTitle());
131                 mVolumePreferences.add(volumePreference);
132             }
133             mCarAudioManager.registerCarVolumeCallback(mVolumeChangeCallback);
134         }
135     }
136 
137     @Override
getPreferenceType()138     protected Class<PreferenceGroup> getPreferenceType() {
139         return PreferenceGroup.class;
140     }
141 
142     /** Disconnect from car on destroy. */
143     @Override
onDestroyInternal()144     protected void onDestroyInternal() {
145         mCar.disconnect();
146         cleanupAudioManager();
147     }
148 
149     @Override
updateState(PreferenceGroup preferenceGroup)150     protected void updateState(PreferenceGroup preferenceGroup) {
151         for (SeekBarPreference preference : mVolumePreferences) {
152             preferenceGroup.addPreference(preference);
153         }
154     }
155 
156     /**
157      * The resource which lists the car volume resources associated with the various usage enums.
158      */
159     @XmlRes
160     @VisibleForTesting
carVolumeItemsXml()161     int carVolumeItemsXml() {
162         return R.xml.car_volume_items;
163     }
164 
createVolumeSeekBarPreference( int volumeGroupId, int usage, @DrawableRes int iconResId, @StringRes int titleId)165     private SeekBarPreference createVolumeSeekBarPreference(
166             int volumeGroupId, int usage, @DrawableRes int iconResId,
167             @StringRes int titleId) {
168         SeekBarPreference preference = new SeekBarPreference(getContext());
169         preference.setTitle(getContext().getString(titleId));
170         preference.setIcon(getContext().getDrawable(iconResId));
171         preference.getIcon().setTintList(
172                 Themes.getAttrColorStateList(getContext(), R.attr.iconColor));
173         try {
174             preference.setValue(mCarAudioManager.getGroupVolume(volumeGroupId));
175             preference.setMin(mCarAudioManager.getGroupMinVolume(volumeGroupId));
176             preference.setMax(mCarAudioManager.getGroupMaxVolume(volumeGroupId));
177         } catch (CarNotConnectedException e) {
178             LOG.e("Car is not connected!", e);
179         }
180         preference.setContinuousUpdate(true);
181         preference.setShowSeekBarValue(false);
182         Bundle bundle = preference.getExtras();
183         bundle.putInt(VOLUME_GROUP_KEY, volumeGroupId);
184         bundle.putInt(VOLUME_USAGE_KEY, usage);
185         preference.setOnPreferenceChangeListener((pref, newValue) -> {
186             int prefGroup = pref.getExtras().getInt(VOLUME_GROUP_KEY);
187             int prefUsage = pref.getExtras().getInt(VOLUME_USAGE_KEY);
188             int newVolume = (Integer) newValue;
189             setGroupVolume(prefGroup, newVolume);
190             mRingtoneManager.playAudioFeedback(prefGroup, prefUsage);
191             return true;
192         });
193         return preference;
194     }
195 
setGroupVolume(int volumeGroupId, int newVolume)196     private void setGroupVolume(int volumeGroupId, int newVolume) {
197         try {
198             mCarAudioManager.setGroupVolume(volumeGroupId, newVolume, /* flags= */ 0);
199         } catch (CarNotConnectedException e) {
200             LOG.w("Ignoring volume change event because the car isn't connected", e);
201         }
202     }
203 
cleanupAudioManager()204     private void cleanupAudioManager() {
205         cleanUpVolumePreferences();
206         mCarAudioManager.unregisterCarVolumeCallback(mVolumeChangeCallback);
207         mCarAudioManager = null;
208     }
209 
cleanUpVolumePreferences()210     private void cleanUpVolumePreferences() {
211         mRingtoneManager.stopCurrentRingtone();
212         mVolumePreferences.clear();
213     }
214 
getVolumeItemForUsages(int[] usages)215     private VolumeItem getVolumeItemForUsages(int[] usages) {
216         int rank = Integer.MAX_VALUE;
217         VolumeItem result = null;
218         for (int usage : usages) {
219             VolumeItem volumeItem = mVolumeItems.get(usage);
220             if (volumeItem.getRank() < rank) {
221                 rank = volumeItem.getRank();
222                 result = volumeItem;
223             }
224         }
225         return result;
226     }
227 }
228