1 /*
2  * Copyright (C) 2020 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.displaysound;
18 
19 import static com.android.tv.settings.overlay.FlavorUtils.FLAVOR_CLASSIC;
20 import static com.android.tv.settings.util.InstrumentationUtils.logEntrySelected;
21 import static com.android.tv.settings.util.InstrumentationUtils.logToggleInteracted;
22 
23 import android.app.tvsettings.TvSettingsEnums;
24 import android.content.Context;
25 import android.media.AudioFormat;
26 import android.media.AudioManager;
27 import android.os.Bundle;
28 import android.provider.Settings;
29 import android.widget.Toast;
30 
31 import androidx.annotation.Keep;
32 import androidx.preference.Preference;
33 import androidx.preference.PreferenceCategory;
34 import androidx.preference.PreferenceGroup;
35 import androidx.preference.PreferenceViewHolder;
36 import androidx.preference.SwitchPreference;
37 
38 import com.android.internal.annotations.VisibleForTesting;
39 import com.android.settingslib.core.AbstractPreferenceController;
40 import com.android.tv.settings.PreferenceControllerFragment;
41 import com.android.tv.settings.R;
42 import com.android.tv.settings.RadioPreference;
43 import com.android.tv.settings.overlay.FlavorUtils;
44 
45 import java.util.ArrayList;
46 import java.util.List;
47 import java.util.Map;
48 
49 /**
50  * The "Advanced sound settings" screen in TV Settings.
51  */
52 @Keep
53 public class AdvancedVolumeFragment extends PreferenceControllerFragment {
54     static final String KEY_ADVANCED_SOUND_OPTION = "advanced_sound_settings_option";
55     static final String KEY_SURROUND_SOUND_AUTO = "surround_sound_auto";
56     static final String KEY_SURROUND_SOUND_NONE = "surround_sound_none";
57     static final String KEY_SURROUND_SOUND_MANUAL = "surround_sound_manual";
58     static final String KEY_SURROUND_SOUND_FORMAT_PREFIX = "surround_sound_format_";
59     static final String KEY_SURROUND_SOUND_FORMAT_INFO_PREFIX = "surround_sound_format_info_";
60     static final String KEY_SUPPORTED_SURROUND_SOUND = "supported_formats";
61     static final String KEY_UNSUPPORTED_SURROUND_SOUND = "unsupported_formats";
62     static final String KEY_FORMAT_INFO = "surround_sound_format_info";
63     static final String KEY_SHOW_HIDE_FORMAT_INFO = "surround_sound_show_hide_format_info";
64     static final String KEY_ENABLED_FORMATS = "enabled_formats";
65     static final String KEY_DISABLED_FORMATS = "disabled_formats";
66 
67     static final int[] SURROUND_SOUND_DISPLAY_ORDER = {
68             AudioFormat.ENCODING_AC3, AudioFormat.ENCODING_E_AC3, AudioFormat.ENCODING_DOLBY_TRUEHD,
69             AudioFormat.ENCODING_E_AC3_JOC, AudioFormat.ENCODING_DOLBY_MAT,
70             AudioFormat.ENCODING_DTS, AudioFormat.ENCODING_DTS_HD, AudioFormat.ENCODING_DTS_UHD,
71             AudioFormat.ENCODING_DRA
72     };
73 
74     private Map<Integer, Boolean> mFormats;
75     private List<Integer> mReportedFormats;
76     private AudioManager mAudioManager;
77     private List<AbstractPreferenceController> mPreferenceControllers;
78     private PreferenceCategory mSupportedFormatsPreferenceCategory;
79     private PreferenceCategory mUnsupportedFormatsPreferenceCategory;
80     private PreferenceCategory mFormatsInfoPreferenceCategory;
81     private PreferenceCategory mEnabledFormatsPreferenceCategory;
82     private PreferenceCategory mDisabledFormatsPreferenceCategory;
83 
84     @Override
onAttach(Context context)85     public void onAttach(Context context) {
86         mAudioManager = getAudioManager();
87         mFormats = mAudioManager.getSurroundFormats();
88         mReportedFormats = mAudioManager.getReportedSurroundFormats();
89         super.onAttach(context);
90     }
91 
92     @Override
getPreferenceScreenResId()93     protected int getPreferenceScreenResId() {
94         return R.xml.advanced_sound;
95     }
96 
97     @Override
onCreatePreferences(Bundle savedInstanceState, String rootKey)98     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
99         setPreferencesFromResource(R.xml.advanced_sound, null /* key */);
100 
101         String surroundSoundSettingKey = getSurroundPassthroughSetting(getContext());
102         selectRadioPreference(findPreference(surroundSoundSettingKey));
103 
104         // Do not show sidebar info texts in case of 1 panel settings.
105         if (FlavorUtils.getFlavor(getContext()) != FLAVOR_CLASSIC) {
106             createInfoFragments();
107         }
108 
109         createFormatInfoPreferences();
110         createFormatPreferences();
111         if (surroundSoundSettingKey == KEY_SURROUND_SOUND_MANUAL) {
112             showFormatPreferences();
113         } else {
114             hideFormatPreferences();
115         }
116     }
117 
118     @Override
onCreatePreferenceControllers(Context context)119     protected List<AbstractPreferenceController> onCreatePreferenceControllers(Context context) {
120         mPreferenceControllers = new ArrayList<>(mFormats.size());
121         for (Map.Entry<Integer, Boolean> format : mFormats.entrySet()) {
122             mPreferenceControllers.add(new SoundFormatPreferenceController(context,
123                     format.getKey() /*formatId*/, mAudioManager, mFormats, mReportedFormats));
124         }
125         return mPreferenceControllers;
126     }
127 
128     @Override
onPreferenceTreeClick(Preference preference)129     public boolean onPreferenceTreeClick(Preference preference) {
130         final String key = preference.getKey();
131         if (key == null) {
132             return super.onPreferenceTreeClick(preference);
133         }
134 
135         if (preference instanceof RadioPreference) {
136             selectRadioPreference(preference);
137 
138             switch (key) {
139                 case KEY_SURROUND_SOUND_AUTO: {
140                     logEntrySelected(
141                             TvSettingsEnums.DISPLAY_SOUND_ADVANCED_SOUNDS_SELECT_FORMATS_AUTO);
142                     mAudioManager.setEncodedSurroundMode(
143                             Settings.Global.ENCODED_SURROUND_OUTPUT_AUTO);
144                     hideFormatPreferences();
145                     break;
146                 }
147                 case KEY_SURROUND_SOUND_NONE: {
148                     logEntrySelected(
149                             TvSettingsEnums.DISPLAY_SOUND_ADVANCED_SOUNDS_SELECT_FORMATS_NONE);
150                     mAudioManager.setEncodedSurroundMode(
151                             Settings.Global.ENCODED_SURROUND_OUTPUT_NEVER);
152                     hideFormatPreferences();
153                     break;
154                 }
155                 case KEY_SURROUND_SOUND_MANUAL: {
156                     logEntrySelected(
157                             TvSettingsEnums.DISPLAY_SOUND_ADVANCED_SOUNDS_SELECT_FORMATS_MANUAL);
158                     mAudioManager.setEncodedSurroundMode(
159                             Settings.Global.ENCODED_SURROUND_OUTPUT_MANUAL);
160                     showFormatPreferences();
161                     break;
162                 }
163                 default:
164                     throw new IllegalArgumentException("Unknown surround sound pref value: "
165                             + key);
166             }
167             updateFormatPreferencesStates();
168         }
169 
170         if (key.equals(KEY_SHOW_HIDE_FORMAT_INFO)) {
171             if (preference.getTitle().equals(
172                     getContext().getString(R.string.surround_sound_hide_formats))) {
173                 hideFormatInfoPreferences();
174                 preference.setTitle(R.string.surround_sound_show_formats);
175             } else {
176                 showFormatInfoPreferences();
177                 preference.setTitle(R.string.surround_sound_hide_formats);
178             }
179         }
180 
181         if (key.contains(KEY_SURROUND_SOUND_FORMAT_INFO_PREFIX)) {
182             if (preference.getParent() == mEnabledFormatsPreferenceCategory) {
183                 showToast(R.string.surround_sound_enabled_format_info_clicked);
184             } else {
185                 showToast(R.string.surround_sound_disabled_format_info_clicked);
186             }
187         }
188 
189         return super.onPreferenceTreeClick(preference);
190     }
191 
192     @VisibleForTesting
getAudioManager()193     AudioManager getAudioManager() {
194         return getContext().getSystemService(AudioManager.class);
195     }
196 
getPreferenceGroup()197     private PreferenceGroup getPreferenceGroup() {
198         return (PreferenceGroup) findPreference(KEY_ADVANCED_SOUND_OPTION);
199     }
200 
selectRadioPreference(Preference preference)201     private void selectRadioPreference(Preference preference) {
202         final RadioPreference radioPreference = (RadioPreference) preference;
203         radioPreference.setChecked(true);
204         radioPreference.clearOtherRadioPreferences(getPreferenceGroup());
205     }
206 
207     /** Creates titles and switches for each surround sound format. */
createFormatPreferences()208     private void createFormatPreferences() {
209         mSupportedFormatsPreferenceCategory = createPreferenceCategory(
210                 R.string.surround_sound_supported_title, KEY_SUPPORTED_SURROUND_SOUND);
211         getPreferenceScreen().addPreference(mSupportedFormatsPreferenceCategory);
212         mUnsupportedFormatsPreferenceCategory = createPreferenceCategory(
213                 R.string.surround_sound_unsupported_title, KEY_UNSUPPORTED_SURROUND_SOUND);
214         getPreferenceScreen().addPreference(mUnsupportedFormatsPreferenceCategory);
215 
216         for (int formatId : SURROUND_SOUND_DISPLAY_ORDER) {
217             if (mFormats.containsKey(formatId)) {
218                 boolean enabled = mFormats.get(formatId);
219 
220                 // If the format is not a known surround sound format, do not create a preference
221                 // for it.
222                 int titleId = getFormatDisplayResourceId(formatId);
223                 if (titleId == -1) {
224                     continue;
225                 }
226                 final SwitchPreference pref = new SwitchPreference(getContext()) {
227                     @Override
228                     public void onBindViewHolder(PreferenceViewHolder holder) {
229                         super.onBindViewHolder(holder);
230                         // Enabling the view will ensure that the preference is focusable even if it
231                         // the preference is disabled. This allows the user to scroll down over the
232                         // disabled surround sound formats and see them all.
233                         holder.itemView.setEnabled(true);
234                     }
235                 };
236                 pref.setTitle(titleId);
237                 pref.setKey(KEY_SURROUND_SOUND_FORMAT_PREFIX + formatId);
238                 pref.setChecked(enabled);
239                 if (getEntryId(formatId) != -1) {
240                     pref.setOnPreferenceClickListener(
241                             preference -> {
242                                 logToggleInteracted(getEntryId(formatId), pref.isChecked());
243                                 return false;
244                             }
245                     );
246                 }
247                 if (mReportedFormats.contains(formatId)) {
248                     mSupportedFormatsPreferenceCategory.addPreference(pref);
249                 } else {
250                     mUnsupportedFormatsPreferenceCategory.addPreference(pref);
251                 }
252             }
253         }
254     }
255 
256     /** Creates titles and preferences for each surround sound format. */
createFormatInfoPreferences()257     private void createFormatInfoPreferences() {
258         mFormatsInfoPreferenceCategory = createPreferenceCategory(
259                 R.string.surround_sound_format_info, KEY_FORMAT_INFO);
260         getPreferenceScreen().addPreference(mFormatsInfoPreferenceCategory);
261 
262         Preference pref = createPreference(
263                 R.string.surround_sound_show_formats, KEY_SHOW_HIDE_FORMAT_INFO);
264         mFormatsInfoPreferenceCategory.addPreference(pref);
265 
266         mEnabledFormatsPreferenceCategory = createPreferenceCategory(
267                 R.string.surround_sound_enabled_formats, KEY_ENABLED_FORMATS);
268         mFormatsInfoPreferenceCategory.addPreference(mEnabledFormatsPreferenceCategory);
269 
270         mDisabledFormatsPreferenceCategory = createPreferenceCategory(
271                 R.string.surround_sound_disabled_formats, KEY_DISABLED_FORMATS);
272         mFormatsInfoPreferenceCategory.addPreference(mDisabledFormatsPreferenceCategory);
273 
274         for (int formatId : SURROUND_SOUND_DISPLAY_ORDER) {
275             if (mFormats.containsKey(formatId)) {
276                 // If the format is not a known surround sound format, do not create a preference
277                 // for it.
278                 int titleId = getFormatDisplayResourceId(formatId);
279                 if (titleId == -1) {
280                     continue;
281                 }
282                 pref = createPreference(titleId, KEY_SURROUND_SOUND_FORMAT_INFO_PREFIX + formatId);
283                 if (mReportedFormats.contains(formatId)) {
284                     mEnabledFormatsPreferenceCategory.addPreference(pref);
285                 } else {
286                     mDisabledFormatsPreferenceCategory.addPreference(pref);
287                 }
288             }
289         }
290         hideFormatInfoPreferences();
291     }
292 
showFormatPreferences()293     private void showFormatPreferences() {
294         getPreferenceScreen().addPreference(mSupportedFormatsPreferenceCategory);
295         getPreferenceScreen().addPreference(mUnsupportedFormatsPreferenceCategory);
296         updateFormatPreferencesStates();
297         // hide the formats info section.
298         getPreferenceScreen().removePreference(mFormatsInfoPreferenceCategory);
299     }
300 
hideFormatPreferences()301     private void hideFormatPreferences() {
302         getPreferenceScreen().removePreference(mSupportedFormatsPreferenceCategory);
303         getPreferenceScreen().removePreference(mUnsupportedFormatsPreferenceCategory);
304         updateFormatPreferencesStates();
305         // show the formats info section.
306         getPreferenceScreen().addPreference(mFormatsInfoPreferenceCategory);
307     }
308 
showFormatInfoPreferences()309     private void showFormatInfoPreferences() {
310         mFormatsInfoPreferenceCategory.addPreference(mEnabledFormatsPreferenceCategory);
311         mFormatsInfoPreferenceCategory.addPreference(mDisabledFormatsPreferenceCategory);
312     }
313 
hideFormatInfoPreferences()314     private void hideFormatInfoPreferences() {
315         mFormatsInfoPreferenceCategory.removePreference(mEnabledFormatsPreferenceCategory);
316         mFormatsInfoPreferenceCategory.removePreference(mDisabledFormatsPreferenceCategory);
317     }
318 
showToast(int resId)319     private void showToast(int resId) {
320         Toast.makeText(getContext(), getContext().getString(resId), Toast.LENGTH_SHORT)
321                 .show();
322     }
323 
createPreferenceCategory(int titleResourceId, String key)324     private PreferenceCategory createPreferenceCategory(int titleResourceId, String key) {
325         PreferenceCategory preferenceCategory = new PreferenceCategory(getContext());
326         preferenceCategory.setTitle(titleResourceId);
327         preferenceCategory.setKey(key);
328         return preferenceCategory;
329     }
330 
createPreference(int titleResourceId, String key)331     private Preference createPreference(int titleResourceId, String key) {
332         Preference preference = new Preference(getContext());
333         preference.setTitle(titleResourceId);
334         preference.setKey(key);
335         return preference;
336     }
337 
338     /**
339      * @return the display id for each surround sound format.
340      */
getFormatDisplayResourceId(int formatId)341     private int getFormatDisplayResourceId(int formatId) {
342         switch (formatId) {
343             case AudioFormat.ENCODING_AC3:
344                 return R.string.surround_sound_format_ac3;
345             case AudioFormat.ENCODING_E_AC3:
346                 return R.string.surround_sound_format_e_ac3;
347             case AudioFormat.ENCODING_DTS:
348                 return R.string.surround_sound_format_dts;
349             case AudioFormat.ENCODING_DTS_HD:
350                 return R.string.surround_sound_format_dts_hd;
351             case AudioFormat.ENCODING_DTS_UHD:
352                 return R.string.surround_sound_format_dts_uhd;
353             case AudioFormat.ENCODING_DOLBY_TRUEHD:
354                 return R.string.surround_sound_format_dolby_truehd;
355             case AudioFormat.ENCODING_E_AC3_JOC:
356                 return R.string.surround_sound_format_e_ac3_joc;
357             case AudioFormat.ENCODING_DOLBY_MAT:
358                 return R.string.surround_sound_format_dolby_mat;
359             case AudioFormat.ENCODING_DRA:
360                 return R.string.surround_sound_format_dra;
361             default:
362                 return -1;
363         }
364     }
365 
updateFormatPreferencesStates()366     private void updateFormatPreferencesStates() {
367         for (AbstractPreferenceController controller : mPreferenceControllers) {
368             Preference preference = findPreference(
369                     controller.getPreferenceKey());
370             if (preference != null) {
371                 controller.updateState(preference);
372             }
373         }
374     }
375 
getSurroundPassthroughSetting(Context context)376     static String getSurroundPassthroughSetting(Context context) {
377         final int value = Settings.Global.getInt(context.getContentResolver(),
378                 Settings.Global.ENCODED_SURROUND_OUTPUT,
379                 Settings.Global.ENCODED_SURROUND_OUTPUT_AUTO);
380 
381         switch (value) {
382             case Settings.Global.ENCODED_SURROUND_OUTPUT_MANUAL:
383                 return KEY_SURROUND_SOUND_MANUAL;
384             case Settings.Global.ENCODED_SURROUND_OUTPUT_NEVER:
385                 return KEY_SURROUND_SOUND_NONE;
386             case Settings.Global.ENCODED_SURROUND_OUTPUT_AUTO:
387             default:
388                 return KEY_SURROUND_SOUND_AUTO;
389         }
390     }
391 
getEntryId(int formatId)392     private int getEntryId(int formatId) {
393         switch(formatId) {
394             case AudioFormat.ENCODING_AC4:
395                 return TvSettingsEnums.DISPLAY_SOUND_ADVANCED_SOUNDS_DAC4;
396             case AudioFormat.ENCODING_E_AC3_JOC:
397                 return TvSettingsEnums.DISPLAY_SOUND_ADVANCED_SOUNDS_DADDP;
398             case AudioFormat.ENCODING_AC3:
399                 return TvSettingsEnums.DISPLAY_SOUND_ADVANCED_SOUNDS_DD;
400             case AudioFormat.ENCODING_E_AC3:
401                 return TvSettingsEnums.DISPLAY_SOUND_ADVANCED_SOUNDS_DDP;
402             case AudioFormat.ENCODING_DTS:
403                 return TvSettingsEnums.DISPLAY_SOUND_ADVANCED_SOUNDS_DTS;
404             case AudioFormat.ENCODING_DTS_HD:
405                 return TvSettingsEnums.DISPLAY_SOUND_ADVANCED_SOUNDS_DTSHD;
406             case AudioFormat.ENCODING_DTS_UHD:
407                 return TvSettingsEnums.DISPLAY_SOUND_ADVANCED_SOUNDS_DTSUHD;
408             case AudioFormat.ENCODING_AAC_LC:
409                 return TvSettingsEnums.DISPLAY_SOUND_ADVANCED_SOUNDS_AAC;
410             case AudioFormat.ENCODING_DOLBY_TRUEHD:
411                 return TvSettingsEnums.DISPLAY_SOUND_ADVANCED_SOUNDS_DTHD;
412             case AudioFormat.ENCODING_DRA:
413                 return TvSettingsEnums.DISPLAY_SOUND_ADVANCED_SOUNDS_DRA;
414             default:
415                 return -1;
416         }
417     }
418 
createInfoFragments()419     private void createInfoFragments() {
420         Preference autoPreference = findPreference(KEY_SURROUND_SOUND_AUTO);
421         autoPreference.setFragment(AdvancedVolumeInfo.AutoInfoFragment.class.getName());
422 
423         Preference manualPreference = findPreference(KEY_SURROUND_SOUND_MANUAL);
424         manualPreference.setFragment(AdvancedVolumeInfo.ManualInfoFragment.class.getName());
425     }
426 
427     @Override
getPageId()428     protected int getPageId() {
429         return TvSettingsEnums.DISPLAY_SOUND_ADVANCED_SOUNDS;
430     }
431 }
432