• 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 android.car.media.CarAudioManager.AUDIO_FEATURE_VOLUME_GROUP_MUTING;
20 import static android.car.media.CarAudioManager.PRIMARY_AUDIO_ZONE;
21 import static android.os.UserManager.DISALLOW_ADJUST_VOLUME;
22 
23 import static com.android.car.settings.enterprise.ActionDisabledByAdminDialogFragment.DISABLED_BY_ADMIN_CONFIRM_DIALOG_TAG;
24 import static com.android.car.settings.enterprise.EnterpriseUtils.hasUserRestrictionByDpm;
25 import static com.android.car.settings.enterprise.EnterpriseUtils.hasUserRestrictionByUm;
26 import static com.android.car.settings.sound.VolumeItemParser.VolumeItem;
27 
28 import android.car.Car;
29 import android.car.CarNotConnectedException;
30 import android.car.drivingstate.CarUxRestrictions;
31 import android.car.media.CarAudioManager;
32 import android.content.Context;
33 import android.os.Bundle;
34 import android.os.Handler;
35 import android.os.Looper;
36 import android.util.SparseArray;
37 import android.widget.Toast;
38 
39 import androidx.annotation.DrawableRes;
40 import androidx.annotation.StringRes;
41 import androidx.annotation.VisibleForTesting;
42 import androidx.annotation.XmlRes;
43 import androidx.preference.PreferenceGroup;
44 
45 import com.android.car.settings.R;
46 import com.android.car.settings.common.FragmentController;
47 import com.android.car.settings.common.Logger;
48 import com.android.car.settings.common.PreferenceController;
49 import com.android.car.settings.common.SeekBarPreference;
50 import com.android.car.settings.enterprise.EnterpriseUtils;
51 
52 import java.util.ArrayList;
53 import java.util.List;
54 
55 /**
56  * Business logic which parses car volume items into groups, creates a seek bar preference for each
57  * group, and interfaces with the ringtone manager and audio manager.
58  *
59  * @see VolumeSettingsRingtoneManager
60  * @see android.car.media.CarAudioManager
61  */
62 public class VolumeSettingsPreferenceController extends PreferenceController<PreferenceGroup> {
63     private static final Logger LOG = new Logger(VolumeSettingsPreferenceController.class);
64     private static final String VOLUME_GROUP_KEY = "volume_group_key";
65     private static final String VOLUME_USAGE_KEY = "volume_usage_key";
66 
67     private final SparseArray<VolumeItem> mVolumeItems;
68     private final List<VolumeSeekBarPreference> mVolumePreferences = new ArrayList<>();
69     private final VolumeSettingsRingtoneManager mRingtoneManager;
70 
71     private final Handler mUiHandler;
72 
73     @VisibleForTesting
74     final CarAudioManager.CarVolumeCallback mVolumeChangeCallback =
75             new CarAudioManager.CarVolumeCallback() {
76                 @Override
77                 public void onGroupVolumeChanged(int zoneId, int groupId, int flags) {
78                     updateVolumeAndMute(zoneId, groupId);
79                 }
80 
81                 @Override
82                 public void onMasterMuteChanged(int zoneId, int flags) {
83 
84                     // Mute is not being used yet
85                 }
86 
87                 @Override
88                 public void onGroupMuteChanged(int zoneId, int groupId, int flags) {
89                     updateVolumeAndMute(zoneId, groupId);
90                 }
91             };
92 
93     private Car mCar;
94     private CarAudioManager mCarAudioManager;
95 
VolumeSettingsPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)96     public VolumeSettingsPreferenceController(Context context, String preferenceKey,
97             FragmentController fragmentController,
98             CarUxRestrictions uxRestrictions) {
99         this(context, preferenceKey, fragmentController, uxRestrictions, Car.createCar(context),
100                 new VolumeSettingsRingtoneManager(context));
101     }
102 
103     @VisibleForTesting
VolumeSettingsPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions, Car car, VolumeSettingsRingtoneManager ringtoneManager)104     VolumeSettingsPreferenceController(Context context, String preferenceKey,
105             FragmentController fragmentController,
106             CarUxRestrictions uxRestrictions, Car car,
107             VolumeSettingsRingtoneManager ringtoneManager) {
108         super(context, preferenceKey, fragmentController, uxRestrictions);
109         mCar = car;
110         mRingtoneManager = ringtoneManager;
111         mVolumeItems = VolumeItemParser.loadAudioUsageItems(context, carVolumeItemsXml());
112         mUiHandler = new Handler(Looper.getMainLooper());
113 
114         mCarAudioManager = (CarAudioManager) mCar.getCarManager(Car.AUDIO_SERVICE);
115         if (mCarAudioManager != null) {
116             int volumeGroupCount = mCarAudioManager.getVolumeGroupCount();
117             cleanUpVolumePreferences();
118             // Populates volume slider items from volume groups to UI.
119             for (int groupId = 0; groupId < volumeGroupCount; groupId++) {
120                 VolumeItem volumeItem = getVolumeItemForUsages(
121                         mCarAudioManager.getUsagesForVolumeGroupId(groupId));
122                 VolumeSeekBarPreference volumePreference = createVolumeSeekBarPreference(
123                         groupId, volumeItem.getUsage(), volumeItem.getIcon(),
124                         volumeItem.getMuteIcon(), volumeItem.getTitle());
125                 setClickableWhileDisabled(volumePreference, /* clickable= */ true, p -> {
126                     if (hasUserRestrictionByDpm(getContext(), DISALLOW_ADJUST_VOLUME)) {
127                         showActionDisabledByAdminDialog();
128                     } else {
129                         Toast.makeText(getContext(),
130                                 getContext().getString(R.string.action_unavailable),
131                                 Toast.LENGTH_LONG).show();
132                     }
133                 });
134                 mVolumePreferences.add(volumePreference);
135             }
136             mCarAudioManager.registerCarVolumeCallback(mVolumeChangeCallback);
137         }
138     }
139 
140     @Override
getPreferenceType()141     protected Class<PreferenceGroup> getPreferenceType() {
142         return PreferenceGroup.class;
143     }
144 
145     /** Disconnect from car on destroy. */
146     @Override
onDestroyInternal()147     protected void onDestroyInternal() {
148         mCar.disconnect();
149         cleanupAudioManager();
150     }
151 
152     @Override
updateState(PreferenceGroup preferenceGroup)153     protected void updateState(PreferenceGroup preferenceGroup) {
154         for (SeekBarPreference preference : mVolumePreferences) {
155             preferenceGroup.addPreference(preference);
156         }
157     }
158 
159     /**
160      * The resource which lists the car volume resources associated with the various usage enums.
161      */
162     @XmlRes
163     @VisibleForTesting
carVolumeItemsXml()164     int carVolumeItemsXml() {
165         return R.xml.car_volume_items;
166     }
167 
createVolumeSeekBarPreference( int volumeGroupId, int usage, @DrawableRes int primaryIconResId, @DrawableRes int secondaryIconResId, @StringRes int titleId)168     private VolumeSeekBarPreference createVolumeSeekBarPreference(
169             int volumeGroupId, int usage, @DrawableRes int primaryIconResId,
170             @DrawableRes int secondaryIconResId, @StringRes int titleId) {
171         VolumeSeekBarPreference preference = new VolumeSeekBarPreference(getContext());
172         preference.setTitle(getContext().getString(titleId));
173         preference.setUnMutedIcon(getContext().getDrawable(primaryIconResId));
174         preference.getUnMutedIcon().setTintList(
175                 getContext().getColorStateList(R.color.icon_color_default));
176         preference.setMutedIcon(getContext().getDrawable(secondaryIconResId));
177         preference.getMutedIcon().setTintList(
178                 getContext().getColorStateList(R.color.icon_color_default));
179         try {
180             preference.setValue(mCarAudioManager.getGroupVolume(volumeGroupId));
181             preference.setMin(mCarAudioManager.getGroupMinVolume(volumeGroupId));
182             preference.setMax(mCarAudioManager.getGroupMaxVolume(volumeGroupId));
183             preference.setIsMuted(isGroupMuted(volumeGroupId));
184         } catch (CarNotConnectedException e) {
185             LOG.e("Car is not connected!", e);
186         }
187         preference.setContinuousUpdate(true);
188         preference.setShowSeekBarValue(false);
189         Bundle bundle = preference.getExtras();
190         bundle.putInt(VOLUME_GROUP_KEY, volumeGroupId);
191         bundle.putInt(VOLUME_USAGE_KEY, usage);
192         preference.setOnPreferenceChangeListener((pref, newValue) -> {
193             int prefGroup = pref.getExtras().getInt(VOLUME_GROUP_KEY);
194             int prefUsage = pref.getExtras().getInt(VOLUME_USAGE_KEY);
195             int newVolume = (Integer) newValue;
196             setGroupVolume(prefGroup, newVolume);
197             mRingtoneManager.playAudioFeedback(prefGroup, prefUsage);
198             return true;
199         });
200         return preference;
201     }
202 
isGroupMuted(int volumeGroupId)203     private boolean isGroupMuted(int volumeGroupId) {
204         if (!mCarAudioManager.isAudioFeatureEnabled(AUDIO_FEATURE_VOLUME_GROUP_MUTING)) {
205             return false;
206         }
207         return mCarAudioManager.isVolumeGroupMuted(PRIMARY_AUDIO_ZONE, volumeGroupId);
208     }
209 
updateVolumeAndMute(int zoneId, int groupId)210     private void updateVolumeAndMute(int zoneId, int groupId) {
211         // Settings only handles primary zone changes
212         if (zoneId != PRIMARY_AUDIO_ZONE) {
213             return;
214         }
215         if (mCarAudioManager != null) {
216 
217             boolean isMuted = isGroupMuted(groupId);
218             int value = mCarAudioManager.getGroupVolume(groupId);
219 
220             for (VolumeSeekBarPreference volumePreference : mVolumePreferences) {
221                 Bundle extras = volumePreference.getExtras();
222                 if (extras.getInt(VOLUME_GROUP_KEY) == groupId) {
223                     if (volumePreference.isMuted() != isMuted
224                             || value != volumePreference.getValue()) {
225                         mUiHandler.post(() -> {
226                             volumePreference.setIsMuted(isMuted);
227                             volumePreference.setValue(value);
228                         });
229                     }
230                     break;
231                 }
232             }
233         }
234     }
235 
setGroupVolume(int volumeGroupId, int newVolume)236     private void setGroupVolume(int volumeGroupId, int newVolume) {
237         try {
238             mCarAudioManager.setGroupVolume(volumeGroupId, newVolume, /* flags= */ 0);
239         } catch (CarNotConnectedException e) {
240             LOG.w("Ignoring volume change event because the car isn't connected", e);
241         }
242     }
243 
cleanupAudioManager()244     private void cleanupAudioManager() {
245         cleanUpVolumePreferences();
246         mCarAudioManager.unregisterCarVolumeCallback(mVolumeChangeCallback);
247         mCarAudioManager = null;
248     }
249 
cleanUpVolumePreferences()250     private void cleanUpVolumePreferences() {
251         mRingtoneManager.stopCurrentRingtone();
252         mVolumePreferences.clear();
253     }
254 
getVolumeItemForUsages(int[] usages)255     private VolumeItem getVolumeItemForUsages(int[] usages) {
256         int rank = Integer.MAX_VALUE;
257         VolumeItem result = null;
258         for (int usage : usages) {
259             VolumeItem volumeItem = mVolumeItems.get(usage);
260             if (volumeItem.getRank() < rank) {
261                 rank = volumeItem.getRank();
262                 result = volumeItem;
263             }
264         }
265         return result;
266     }
267 
268     @Override
getAvailabilityStatus()269     public int getAvailabilityStatus() {
270         if (hasUserRestrictionByUm(getContext(), DISALLOW_ADJUST_VOLUME)
271                 || hasUserRestrictionByDpm(getContext(), DISALLOW_ADJUST_VOLUME)) {
272             return AVAILABLE_FOR_VIEWING;
273         }
274         return AVAILABLE;
275     }
276 
showActionDisabledByAdminDialog()277     private void showActionDisabledByAdminDialog() {
278         getFragmentController().showDialog(
279                 EnterpriseUtils.getActionDisabledByAdminDialog(getContext(),
280                         DISALLOW_ADJUST_VOLUME),
281                 DISABLED_BY_ADMIN_CONFIRM_DIALOG_TAG);
282     }
283 }
284