1 /*
2  * Copyright (C) 2015 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.tv.settings.device.sound;
18 
19 import android.content.ContentResolver;
20 import android.content.Context;
21 import android.media.AudioFormat;
22 import android.media.AudioManager;
23 import android.os.Bundle;
24 import android.provider.Settings;
25 import android.text.TextUtils;
26 
27 import androidx.annotation.Keep;
28 import androidx.preference.ListPreference;
29 import androidx.preference.Preference;
30 import androidx.preference.PreferenceCategory;
31 import androidx.preference.PreferenceViewHolder;
32 import androidx.preference.SwitchPreference;
33 import androidx.preference.TwoStatePreference;
34 
35 import com.android.internal.logging.nano.MetricsProto;
36 import com.android.settingslib.core.AbstractPreferenceController;
37 import com.android.tv.settings.PreferenceControllerFragment;
38 import com.android.tv.settings.R;
39 
40 import java.util.ArrayList;
41 import java.util.List;
42 import java.util.Map;
43 
44 /**
45  * The "Sound" screen in TV Settings.
46  */
47 @Keep
48 public class SoundFragment extends PreferenceControllerFragment implements
49         Preference.OnPreferenceChangeListener {
50 
51     static final String KEY_SOUND_EFFECTS = "sound_effects";
52     static final String KEY_SURROUND_PASSTHROUGH = "surround_passthrough";
53     static final String KEY_SURROUND_SOUND_CATEGORY = "surround_sound_category";
54     static final String KEY_SURROUND_SOUND_FORMAT_PREFIX = "surround_sound_format_";
55 
56     static final String VAL_SURROUND_SOUND_AUTO = "auto";
57     static final String VAL_SURROUND_SOUND_NEVER = "never";
58     static final String VAL_SURROUND_SOUND_ALWAYS = "always";
59     static final String VAL_SURROUND_SOUND_MANUAL = "manual";
60 
61     private AudioManager mAudioManager;
62     private Map<Integer, Boolean> mFormats;
63     private Map<Integer, Boolean> mReportedFormats;
64     private List<AbstractPreferenceController> mPreferenceControllers;
65     private PreferenceCategory mSurroundSoundCategoryPref;
66 
newInstance()67     public static SoundFragment newInstance() {
68         return new SoundFragment();
69     }
70 
71     @Override
onAttach(Context context)72     public void onAttach(Context context) {
73         mAudioManager = context.getSystemService(AudioManager.class);
74         mFormats = mAudioManager.getSurroundFormats();
75         mReportedFormats = mAudioManager.getReportedSurroundFormats();
76         super.onAttach(context);
77     }
78 
79     @Override
getPreferenceScreenResId()80     protected int getPreferenceScreenResId() {
81         return R.xml.sound;
82     }
83 
84     @Override
onCreatePreferences(Bundle savedInstanceState, String rootKey)85     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
86         setPreferencesFromResource(R.xml.sound, null);
87 
88         final TwoStatePreference soundPref = (TwoStatePreference) findPreference(KEY_SOUND_EFFECTS);
89         soundPref.setChecked(getSoundEffectsEnabled());
90 
91         final ListPreference surroundPref =
92                 (ListPreference) findPreference(KEY_SURROUND_PASSTHROUGH);
93         surroundPref.setValue(getSurroundPassthroughSetting(getContext()));
94         surroundPref.setOnPreferenceChangeListener(this);
95 
96         mSurroundSoundCategoryPref =
97                 (PreferenceCategory) findPreference(KEY_SURROUND_SOUND_CATEGORY);
98         createFormatPreferences();
99         updateFormatPreferencesStates();
100     }
101 
102     @Override
onCreatePreferenceControllers(Context context)103     protected List<AbstractPreferenceController> onCreatePreferenceControllers(Context context) {
104         mPreferenceControllers = new ArrayList<>(mFormats.size());
105         for (Map.Entry<Integer, Boolean> format : mFormats.entrySet()) {
106             mPreferenceControllers.add(new SoundFormatPreferenceController(context,
107                     format.getKey() /*formatId*/, mFormats, mReportedFormats));
108         }
109         return mPreferenceControllers;
110     }
111 
112     /** Creates and adds switches for each surround sound format. */
createFormatPreferences()113     private void createFormatPreferences() {
114         for (Map.Entry<Integer, Boolean> format : mFormats.entrySet()) {
115             int formatId = format.getKey();
116             boolean enabled = format.getValue();
117             SwitchPreference pref = new SwitchPreference(getPreferenceManager().getContext()) {
118                 @Override
119                 public void onBindViewHolder(PreferenceViewHolder holder) {
120                     super.onBindViewHolder(holder);
121                     // Enabling the view will ensure that the preference is focusable even if it
122                     // the preference is disabled. This allows the user to scroll down over the
123                     // disabled surround sound formats and see them all.
124                     holder.itemView.setEnabled(true);
125                 }
126             };
127             pref.setTitle(getFormatDisplayName(formatId));
128             pref.setKey(KEY_SURROUND_SOUND_FORMAT_PREFIX + formatId);
129             pref.setChecked(enabled);
130             mSurroundSoundCategoryPref.addPreference(pref);
131         }
132     }
133 
134     /**
135      * @return the display name for each surround sound format.
136      */
getFormatDisplayName(int formatId)137     private String getFormatDisplayName(int formatId) {
138         switch (formatId) {
139             case AudioFormat.ENCODING_AC3:
140                 return getContext().getResources().getString(R.string.surround_sound_format_ac3);
141             case AudioFormat.ENCODING_E_AC3:
142                 return getContext().getResources().getString(R.string.surround_sound_format_e_ac3);
143             case AudioFormat.ENCODING_DTS:
144                 return getContext().getResources().getString(R.string.surround_sound_format_dts);
145             case AudioFormat.ENCODING_DTS_HD:
146                 return getContext().getResources().getString(R.string.surround_sound_format_dts_hd);
147             default:
148                 // Fallback in case new formats have been added that we don't know of.
149                 return AudioFormat.toDisplayName(formatId);
150         }
151     }
152 
updateFormatPreferencesStates()153     private void updateFormatPreferencesStates() {
154         for (AbstractPreferenceController controller : mPreferenceControllers) {
155             Preference preference = mSurroundSoundCategoryPref.findPreference(
156                     controller.getPreferenceKey());
157             if (preference != null) {
158                 controller.updateState(preference);
159             }
160         }
161     }
162 
163     @Override
onPreferenceTreeClick(Preference preference)164     public boolean onPreferenceTreeClick(Preference preference) {
165         if (TextUtils.equals(preference.getKey(), KEY_SOUND_EFFECTS)) {
166             final TwoStatePreference soundPref = (TwoStatePreference) preference;
167             setSoundEffectsEnabled(soundPref.isChecked());
168         }
169         return super.onPreferenceTreeClick(preference);
170     }
171 
172     @Override
onPreferenceChange(Preference preference, Object newValue)173     public boolean onPreferenceChange(Preference preference, Object newValue) {
174         if (TextUtils.equals(preference.getKey(), KEY_SURROUND_PASSTHROUGH)) {
175             final String selection = (String) newValue;
176             switch (selection) {
177                 case VAL_SURROUND_SOUND_AUTO:
178                     setSurroundPassthroughSetting(Settings.Global.ENCODED_SURROUND_OUTPUT_AUTO);
179                     break;
180                 case VAL_SURROUND_SOUND_NEVER:
181                     setSurroundPassthroughSetting(Settings.Global.ENCODED_SURROUND_OUTPUT_NEVER);
182                     break;
183                 case VAL_SURROUND_SOUND_ALWAYS:
184                     setSurroundPassthroughSetting(Settings.Global.ENCODED_SURROUND_OUTPUT_ALWAYS);
185                     break;
186                 case VAL_SURROUND_SOUND_MANUAL:
187                     setSurroundPassthroughSetting(Settings.Global.ENCODED_SURROUND_OUTPUT_MANUAL);
188                     break;
189                 default:
190                     throw new IllegalArgumentException("Unknown surround sound pref value: "
191                             + selection);
192             }
193             updateFormatPreferencesStates();
194             return true;
195         }
196         return true;
197     }
198 
getSoundEffectsEnabled()199     private boolean getSoundEffectsEnabled() {
200         return getSoundEffectsEnabled(getActivity().getContentResolver());
201     }
202 
getSoundEffectsEnabled(ContentResolver contentResolver)203     public static boolean getSoundEffectsEnabled(ContentResolver contentResolver) {
204         return Settings.System.getInt(contentResolver, Settings.System.SOUND_EFFECTS_ENABLED, 1)
205                 != 0;
206     }
207 
setSoundEffectsEnabled(boolean enabled)208     private void setSoundEffectsEnabled(boolean enabled) {
209         if (enabled) {
210             mAudioManager.loadSoundEffects();
211         } else {
212             mAudioManager.unloadSoundEffects();
213         }
214         Settings.System.putInt(getActivity().getContentResolver(),
215                 Settings.System.SOUND_EFFECTS_ENABLED, enabled ? 1 : 0);
216     }
217 
setSurroundPassthroughSetting(int newVal)218     private void setSurroundPassthroughSetting(int newVal) {
219         Settings.Global.putInt(getContext().getContentResolver(),
220                 Settings.Global.ENCODED_SURROUND_OUTPUT, newVal);
221     }
222 
getSurroundPassthroughSetting(Context context)223     static String getSurroundPassthroughSetting(Context context) {
224         final int value = Settings.Global.getInt(context.getContentResolver(),
225                 Settings.Global.ENCODED_SURROUND_OUTPUT,
226                 Settings.Global.ENCODED_SURROUND_OUTPUT_AUTO);
227 
228         switch (value) {
229             case Settings.Global.ENCODED_SURROUND_OUTPUT_AUTO:
230             default:
231                 return VAL_SURROUND_SOUND_AUTO;
232             case Settings.Global.ENCODED_SURROUND_OUTPUT_NEVER:
233                 return VAL_SURROUND_SOUND_NEVER;
234             // On Android P ALWAYS is replaced by MANUAL.
235             case Settings.Global.ENCODED_SURROUND_OUTPUT_ALWAYS:
236             case Settings.Global.ENCODED_SURROUND_OUTPUT_MANUAL:
237                 return VAL_SURROUND_SOUND_MANUAL;
238         }
239     }
240 
241     @Override
getMetricsCategory()242     public int getMetricsCategory() {
243         return MetricsProto.MetricsEvent.SOUND;
244     }
245 }
246