1 /*
2  * Copyright (C) 2021 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.qc;
18 
19 import static android.car.media.CarAudioManager.AUDIO_FEATURE_VOLUME_GROUP_MUTING;
20 
21 import static com.android.car.qc.QCItem.QC_ACTION_SLIDER_VALUE;
22 import static com.android.car.settings.qc.QCUtils.getActionDisabledDialogIntent;
23 import static com.android.car.settings.qc.QCUtils.getAvailabilityStatusForZoneFromXml;
24 
25 import android.app.PendingIntent;
26 import android.car.CarNotConnectedException;
27 import android.car.media.CarAudioManager;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.graphics.drawable.Icon;
31 import android.os.Bundle;
32 import android.os.UserManager;
33 import android.util.SparseArray;
34 
35 import androidx.annotation.VisibleForTesting;
36 import androidx.annotation.XmlRes;
37 
38 import com.android.car.qc.QCItem;
39 import com.android.car.qc.QCList;
40 import com.android.car.qc.QCRow;
41 import com.android.car.qc.QCSlider;
42 import com.android.car.settings.CarSettingsApplication;
43 import com.android.car.settings.R;
44 import com.android.car.settings.common.Logger;
45 import com.android.car.settings.enterprise.EnterpriseUtils;
46 import com.android.car.settings.sound.VolumeItemParser;
47 
48 /**
49  * Base class for showing a volume slider quick control view.
50  * Extended classes should override {@link #getUsages} and specify the array of audio usages that
51  * should be shown as part of the quick control.
52  */
53 public abstract class BaseVolumeSlider extends SettingsQCItem {
54     static final int QC_VOLUME_SELF_CHANGE = 7918;
55     @VisibleForTesting
56     static final String EXTRA_GROUP_ID = "QC_VOLUME_EXTRA_GROUP_ID";
57     private static final Logger LOG = new Logger(BaseVolumeSlider.class);
58 
59     private final Context mContext;
60     private final SparseArray<VolumeItemParser.VolumeItem> mVolumeItems;
61 
BaseVolumeSlider(Context context)62     public BaseVolumeSlider(Context context) {
63         super(context);
64         mContext = context;
65         setAvailabilityStatusForZone(getAvailabilityStatusForZoneFromXml(context,
66                 R.xml.sound_settings_fragment, R.string.pk_volume_settings));
67         mVolumeItems = VolumeItemParser.loadAudioUsageItems(context, carVolumeItemsXml());
68     }
69 
getUsages()70     protected abstract int[] getUsages();
71 
72     @Override
getQCItem()73     QCItem getQCItem() {
74         if (isHiddenForZone()) {
75             return null;
76         }
77         CarAudioManager carAudioManager = getCarAudioManager();
78         int zoneId = getMyAudioZoneId();
79         if (carAudioManager == null || zoneId == CarAudioManager.INVALID_AUDIO_ZONE) {
80             return null;
81         }
82 
83         String userRestriction = UserManager.DISALLOW_ADJUST_VOLUME;
84         boolean hasDpmRestrictions = EnterpriseUtils.hasUserRestrictionByDpm(getContext(),
85                 userRestriction);
86         boolean hasUmRestrictions = EnterpriseUtils.hasUserRestrictionByUm(getContext(),
87                 userRestriction);
88 
89 
90         boolean isReadOnlyForZone = isReadOnlyForZone();
91         PendingIntent disabledPendingIntent = isReadOnlyForZone
92                 ? QCUtils.getDisabledToastBroadcastIntent(getContext())
93                 : getActionDisabledDialogIntent(getContext(), userRestriction);
94 
95         QCList.Builder listBuilder = new QCList.Builder();
96         for (int usage : getUsages()) {
97             VolumeItemParser.VolumeItem volumeItem = mVolumeItems.get(usage);
98             int groupId = carAudioManager.getVolumeGroupIdForUsage(zoneId, usage);
99             int min = carAudioManager.getGroupMinVolume(zoneId, groupId);
100             int max = carAudioManager.getGroupMaxVolume(zoneId, groupId);
101             int value = carAudioManager.getGroupVolume(zoneId, groupId);
102             int iconResId = volumeItem.getIcon();
103             if (carAudioManager.isAudioFeatureEnabled(AUDIO_FEATURE_VOLUME_GROUP_MUTING)
104                     && carAudioManager.isVolumeGroupMuted(zoneId, groupId)) {
105                 iconResId = volumeItem.getMuteIcon();
106             }
107             listBuilder.addRow(new QCRow.Builder()
108                     .setTitle(getContext().getString(volumeItem.getTitle()))
109                     .setIcon(showSliderWithIcon()
110                             ? Icon.createWithResource(getContext(), iconResId) : null)
111                     .addSlider(new QCSlider.Builder()
112                             .setMin(min)
113                             .setMax(max)
114                             .setValue(value)
115                             .setInputAction(createSliderAction(groupId))
116                             .setEnabled(!hasUmRestrictions && !hasDpmRestrictions
117                                     && isWritableForZone())
118                             .setClickableWhileDisabled(hasDpmRestrictions || isReadOnlyForZone)
119                             .setDisabledClickAction(disabledPendingIntent)
120                             .build()
121                     )
122                     .build()
123             );
124         }
125         return listBuilder.build();
126     }
127 
128     @Override
onNotifyChange(Intent intent)129     void onNotifyChange(Intent intent) {
130         int value = intent.getIntExtra(QC_ACTION_SLIDER_VALUE, Integer.MIN_VALUE);
131         int groupId = intent.getIntExtra(EXTRA_GROUP_ID, Integer.MIN_VALUE);
132         if (value == Integer.MIN_VALUE || groupId == Integer.MIN_VALUE) {
133             return;
134         }
135         setGroupVolume(groupId, value);
136     }
137 
138     /**
139      * The resource which lists the car volume resources associated with the various usage enums.
140      */
141     @XmlRes
carVolumeItemsXml()142     protected int carVolumeItemsXml() {
143         return R.xml.car_volume_items;
144     }
145 
createSliderAction(int groupId)146     private PendingIntent createSliderAction(int groupId) {
147         Bundle extras = new Bundle();
148         extras.putInt(EXTRA_GROUP_ID, groupId);
149         return getBroadcastIntent(extras, groupId);
150     }
151 
setGroupVolume(int volumeGroupId, int newVolume)152     private void setGroupVolume(int volumeGroupId, int newVolume) {
153         CarAudioManager carAudioManager = getCarAudioManager();
154         int zoneId = getMyAudioZoneId();
155         if (carAudioManager == null || zoneId == CarAudioManager.INVALID_AUDIO_ZONE) {
156             return;
157         }
158         try {
159             carAudioManager.setGroupVolume(zoneId, volumeGroupId, newVolume,
160                     QC_VOLUME_SELF_CHANGE);
161         } catch (CarNotConnectedException e) {
162             LOG.w("Ignoring volume change event because the car isn't connected", e);
163         }
164     }
165 
showSliderWithIcon()166     protected boolean showSliderWithIcon() {
167         return true; // by default
168     }
169 
getMyAudioZoneId()170     private int getMyAudioZoneId() {
171         return ((CarSettingsApplication) mContext.getApplicationContext())
172                 .getMyAudioZoneId();
173     }
174 
getCarAudioManager()175     private CarAudioManager getCarAudioManager() {
176         return ((CarSettingsApplication) mContext.getApplicationContext())
177                 .getCarAudioManager();
178     }
179 }
180