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 android.view.Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION;
20 import static android.view.Display.HdrCapabilities.HDR_TYPE_HDR10;
21 import static android.view.Display.HdrCapabilities.HDR_TYPE_HDR10_PLUS;
22 import static android.view.Display.HdrCapabilities.HDR_TYPE_HLG;
23 
24 import static com.android.tv.settings.overlay.FlavorUtils.FLAVOR_CLASSIC;
25 import static com.android.tv.settings.util.InstrumentationUtils.logEntrySelected;
26 import static com.android.tv.settings.util.InstrumentationUtils.logToggleInteracted;
27 
28 import android.app.tvsettings.TvSettingsEnums;
29 import android.content.Context;
30 import android.hardware.display.DisplayManager;
31 import android.os.Bundle;
32 import android.view.Display;
33 import android.widget.Toast;
34 
35 import androidx.annotation.Keep;
36 import androidx.annotation.VisibleForTesting;
37 import androidx.preference.Preference;
38 import androidx.preference.PreferenceCategory;
39 import androidx.preference.PreferenceGroup;
40 import androidx.preference.PreferenceViewHolder;
41 import androidx.preference.SwitchPreference;
42 
43 import com.android.settingslib.core.AbstractPreferenceController;
44 import com.android.tv.settings.PreferenceControllerFragment;
45 import com.android.tv.settings.R;
46 import com.android.tv.settings.RadioPreference;
47 import com.android.tv.settings.overlay.FlavorUtils;
48 
49 import java.util.ArrayList;
50 import java.util.Arrays;
51 import java.util.List;
52 import java.util.Set;
53 import java.util.stream.Collectors;
54 
55 /**
56  * This Fragment is responsible for allowing the user enable or disable the Hdr types which are
57  * supported by device.
58  */
59 @Keep
60 public class HdrFormatSelectionFragment extends PreferenceControllerFragment {
61 
62     static final String KEY_HDR_FORMAT_SELECTION = "hdr_format_selection_option";
63     static final String KEY_SUPPORTED_HDR_FORMATS = "supported_formats";
64     static final String KEY_UNSUPPORTED_HDR_FORMATS = "unsupported_formats";
65     static final String KEY_HDR_FORMAT_SELECTION_AUTO = "hdr_format_selection_auto";
66     static final String KEY_HDR_FORMAT_SELECTION_MANUAL = "hdr_format_selection_manual";
67     static final String KEY_HDR_FORMAT_PREFIX = "hdr_format_";
68     static final String KEY_HDR_FORMAT_INFO_PREFIX = "hdr_format_info_";
69     static final String KEY_FORMAT_INFO = "hdr_format_info";
70     static final String KEY_SHOW_HIDE_FORMAT_INFO = "hdr_show_hide_format_info";
71     static final String KEY_ENABLED_FORMATS = "enabled_formats";
72     static final String KEY_DISABLED_FORMATS = "disabled_formats";
73 
74     static final int[] HDR_FORMATS_DISPLAY_ORDER = {
75         HDR_TYPE_DOLBY_VISION, HDR_TYPE_HDR10, HDR_TYPE_HDR10_PLUS, HDR_TYPE_HLG
76     };
77 
78     private PreferenceCategory mSupportedFormatsPreferenceCategory;
79     private PreferenceCategory mUnsupportedFormatsPreferenceCategory;
80     private PreferenceCategory mFormatsInfoPreferenceCategory;
81     private PreferenceCategory mEnabledFormatsPreferenceCategory;
82     private PreferenceCategory mDisabledFormatsPreferenceCategory;
83 
84     private List<AbstractPreferenceController> mPreferenceControllers;
85 
86     private Set<Integer> mDeviceHdrTypes;
87     private Set<Integer> mDisplayReportedHdrTypes;
88     private Set<Integer> mUserDisabledHdrTypes;
89 
90     private DisplayManager mDisplayManager;
91 
92     /** @return the new instance of the class */
newInstance()93     public static HdrFormatSelectionFragment newInstance() {
94         return new HdrFormatSelectionFragment();
95     }
96 
97     @Override
onAttach(Context context)98     public void onAttach(Context context) {
99         mDisplayManager = getDisplayManager();
100         mDeviceHdrTypes = toSet(getDeviceSupportedHdrTypes());
101         mUserDisabledHdrTypes = toSet(mDisplayManager.getUserDisabledHdrTypes());
102 
103         Display display = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
104         mDisplayReportedHdrTypes = toSet(display.getReportedHdrTypes());
105 
106         super.onAttach(context);
107     }
108 
109     @Override
getPreferenceScreenResId()110     protected int getPreferenceScreenResId() {
111         return R.xml.hdr_format_selection;
112     }
113 
114     @Override
onCreatePreferences(Bundle savedInstanceState, String rootKey)115     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
116         setPreferencesFromResource(R.xml.hdr_format_selection, null);
117 
118         createFormatInfoPreferences();
119         createFormatPreferences();
120 
121         String currentPreferenceKey;
122         if (mDisplayManager.areUserDisabledHdrTypesAllowed()) {
123             currentPreferenceKey = KEY_HDR_FORMAT_SELECTION_AUTO;
124             hideFormatPreferences();
125         } else {
126             currentPreferenceKey = KEY_HDR_FORMAT_SELECTION_MANUAL;
127             showFormatPreferences();
128         }
129         selectRadioPreference(findPreference(currentPreferenceKey));
130 
131         // Do not show sidebar info texts in case of 1 panel settings.
132         if (FlavorUtils.getFlavor(getContext()) != FLAVOR_CLASSIC) {
133             createInfoFragments();
134         }
135     }
136 
137     @Override
onCreatePreferenceControllers(Context context)138     protected List<AbstractPreferenceController> onCreatePreferenceControllers(Context context) {
139         mPreferenceControllers = new ArrayList<>();
140         for (int hdrType : mDeviceHdrTypes) {
141             if (mDisplayReportedHdrTypes.contains(hdrType)) {
142                 mPreferenceControllers.add(
143                         new HdrFormatPreferenceController(getContext(), hdrType, mDisplayManager));
144             }
145         }
146         return mPreferenceControllers;
147     }
148 
149     @Override
onPreferenceTreeClick(Preference preference)150     public boolean onPreferenceTreeClick(Preference preference) {
151         String key = preference.getKey();
152 
153         if (key == null) {
154             return super.onPreferenceTreeClick(preference);
155         }
156 
157         if (preference instanceof RadioPreference) {
158             selectRadioPreference(preference);
159 
160             switch (key) {
161                 case KEY_HDR_FORMAT_SELECTION_AUTO: {
162                     logEntrySelected(
163                             TvSettingsEnums.DISPLAY_SOUND_ADVANCED_DISPLAY_FORMAT_SELECTION_AUTO);
164                     mDisplayManager.setAreUserDisabledHdrTypesAllowed(true);
165                     hideFormatPreferences();
166                     break;
167                 }
168                 case KEY_HDR_FORMAT_SELECTION_MANUAL: {
169                     logEntrySelected(
170                             TvSettingsEnums.DISPLAY_SOUND_ADVANCED_DISPLAY_FORMAT_SELECTION_MANUAL);
171                     mDisplayManager.setAreUserDisabledHdrTypesAllowed(false);
172                     showFormatPreferences();
173                     break;
174                 }
175                 default:
176                     throw new IllegalArgumentException("Unknown hdr type selection pref value"
177                             + ": " + key);
178             }
179         }
180 
181         if (key.equals(KEY_SHOW_HIDE_FORMAT_INFO)) {
182             if (preference.getTitle().equals(
183                     getContext().getString(R.string.hdr_hide_formats))) {
184                 hideFormatInfoPreferences();
185                 preference.setTitle(R.string.hdr_show_formats);
186             } else {
187                 showFormatInfoPreferences();
188                 preference.setTitle(R.string.hdr_hide_formats);
189             }
190         }
191 
192         if (key.contains(KEY_HDR_FORMAT_INFO_PREFIX)) {
193             if (preference.getParent() == mEnabledFormatsPreferenceCategory) {
194                 showToast(R.string.hdr_enabled_format_info_clicked);
195             }
196         }
197         return super.onPreferenceTreeClick(preference);
198     }
199 
getPreferenceGroup()200     private PreferenceGroup getPreferenceGroup() {
201         return (PreferenceGroup) findPreference(KEY_HDR_FORMAT_SELECTION);
202     }
203 
204     @VisibleForTesting
getDisplayManager()205     DisplayManager getDisplayManager() {
206         return getContext().getSystemService(DisplayManager.class);
207     }
208 
209     @VisibleForTesting
getDeviceSupportedHdrTypes()210     int[] getDeviceSupportedHdrTypes() {
211         return getContext().getResources().getIntArray(R.array.config_deviceSupportedHdrFormats);
212     }
213 
selectRadioPreference(Preference preference)214     private void selectRadioPreference(Preference preference) {
215         final RadioPreference radioPreference = (RadioPreference) preference;
216         radioPreference.setChecked(true);
217         radioPreference.clearOtherRadioPreferences(getPreferenceGroup());
218     }
219 
createFormatPreferences()220     private void createFormatPreferences() {
221         mSupportedFormatsPreferenceCategory = createPreferenceCategory(
222                 R.string.hdr_format_supported_title,
223                 KEY_SUPPORTED_HDR_FORMATS);
224         getPreferenceScreen().addPreference(mSupportedFormatsPreferenceCategory);
225         mUnsupportedFormatsPreferenceCategory = createPreferenceCategory(
226                 R.string.hdr_format_unsupported_title,
227                 KEY_UNSUPPORTED_HDR_FORMATS);
228         getPreferenceScreen().addPreference(mUnsupportedFormatsPreferenceCategory);
229 
230         for (int hdrType : HDR_FORMATS_DISPLAY_ORDER) {
231             if (mDeviceHdrTypes.contains(hdrType)) {
232                 int titleId = getFormatPreferenceTitleId(hdrType);
233                 if (titleId == -1) {
234                     continue;
235                 }
236                 if (mDisplayReportedHdrTypes.contains(hdrType)) {
237                     boolean enabled = !mUserDisabledHdrTypes.contains(hdrType);
238                     mSupportedFormatsPreferenceCategory.addPreference(
239                             createSupportedFormatPreference(titleId, hdrType, enabled));
240                 } else {
241                     mUnsupportedFormatsPreferenceCategory.addPreference(
242                             createUnsupportedFormatPreference(titleId, hdrType));
243                 }
244             }
245         }
246     }
247 
248     /** Creates titles and preferences for each hdr format. */
createFormatInfoPreferences()249     private void createFormatInfoPreferences() {
250         mFormatsInfoPreferenceCategory = createPreferenceCategory(
251                 R.string.hdr_format_info, KEY_FORMAT_INFO);
252         getPreferenceScreen().addPreference(mFormatsInfoPreferenceCategory);
253 
254         Preference pref = createPreference(
255                 R.string.hdr_show_formats, KEY_SHOW_HIDE_FORMAT_INFO);
256         mFormatsInfoPreferenceCategory.addPreference(pref);
257 
258         mEnabledFormatsPreferenceCategory = createPreferenceCategory(
259                 R.string.hdr_enabled_formats, KEY_ENABLED_FORMATS);
260         mFormatsInfoPreferenceCategory.addPreference(mEnabledFormatsPreferenceCategory);
261 
262         mDisabledFormatsPreferenceCategory = createPreferenceCategory(
263                 R.string.hdr_disabled_formats, KEY_DISABLED_FORMATS);
264         mFormatsInfoPreferenceCategory.addPreference(mDisabledFormatsPreferenceCategory);
265 
266         for (int hdrType : HDR_FORMATS_DISPLAY_ORDER) {
267             if (mDeviceHdrTypes.contains(hdrType)) {
268                 int titleId = getFormatPreferenceTitleId(hdrType);
269                 if (titleId == -1) {
270                     continue;
271                 }
272 
273                 pref = createPreference(titleId, KEY_HDR_FORMAT_INFO_PREFIX + hdrType);
274                 if (mDisplayReportedHdrTypes.contains(hdrType)) {
275                     mEnabledFormatsPreferenceCategory.addPreference(pref);
276                 } else {
277                     mDisabledFormatsPreferenceCategory.addPreference(pref);
278                 }
279             }
280         }
281         hideFormatInfoPreferences();
282     }
283 
284 
285     /** Returns a switch preference for each supported HDR format. */
createSupportedFormatPreference(int titleId, int hdrType, boolean enabled)286     private Preference createSupportedFormatPreference(int titleId, int hdrType, boolean enabled) {
287         final SwitchPreference pref = new SwitchPreference(getContext()) {
288             @Override
289             public void onBindViewHolder(PreferenceViewHolder holder) {
290                 super.onBindViewHolder(holder);
291                 holder.itemView.setEnabled(true);
292             }
293         };
294         pref.setTitle(titleId);
295         pref.setKey(KEY_HDR_FORMAT_PREFIX + hdrType);
296         pref.setChecked(enabled);
297         if (getLogEntryId(hdrType) != -1) {
298             pref.setOnPreferenceClickListener(
299                     preference -> {
300                         logToggleInteracted(getLogEntryId(hdrType), pref.isChecked());
301                         return false;
302                     });
303         }
304         return pref;
305     }
306 
307     /** Returns a non-switch preference for each unsupported HDR format. */
createUnsupportedFormatPreference(int titleId, int hdrType)308     private Preference createUnsupportedFormatPreference(int titleId, int hdrType) {
309         Preference pref = new Preference(getContext());
310         pref.setTitle(titleId);
311         pref.setKey(KEY_HDR_FORMAT_PREFIX + hdrType);
312         return pref;
313     }
314 
showFormatPreferences()315     private void showFormatPreferences() {
316         getPreferenceScreen().addPreference(mSupportedFormatsPreferenceCategory);
317         getPreferenceScreen().addPreference(mUnsupportedFormatsPreferenceCategory);
318         updateFormatPreferencesStates();
319         // hide the formats info section.
320         getPreferenceScreen().removePreference(mFormatsInfoPreferenceCategory);
321     }
322 
hideFormatPreferences()323     private void hideFormatPreferences() {
324         getPreferenceScreen().removePreference(mSupportedFormatsPreferenceCategory);
325         getPreferenceScreen().removePreference(mUnsupportedFormatsPreferenceCategory);
326         updateFormatPreferencesStates();
327         // show the formats info section.
328         getPreferenceScreen().addPreference(mFormatsInfoPreferenceCategory);
329     }
330 
showFormatInfoPreferences()331     private void showFormatInfoPreferences() {
332         mFormatsInfoPreferenceCategory.addPreference(mEnabledFormatsPreferenceCategory);
333         mFormatsInfoPreferenceCategory.addPreference(mDisabledFormatsPreferenceCategory);
334     }
335 
hideFormatInfoPreferences()336     private void hideFormatInfoPreferences() {
337         mFormatsInfoPreferenceCategory.removePreference(mEnabledFormatsPreferenceCategory);
338         mFormatsInfoPreferenceCategory.removePreference(mDisabledFormatsPreferenceCategory);
339     }
340 
showToast(int resId)341     private void showToast(int resId) {
342         Toast.makeText(getContext(), getContext().getString(resId), Toast.LENGTH_SHORT)
343                 .show();
344     }
345 
createPreferenceCategory(int titleResourceId, String key)346     private PreferenceCategory createPreferenceCategory(int titleResourceId, String key) {
347         PreferenceCategory preferenceCategory = new PreferenceCategory(getContext());
348         preferenceCategory.setTitle(titleResourceId);
349         preferenceCategory.setKey(key);
350         return preferenceCategory;
351     }
352 
createPreference(int titleResourceId, String key)353     private Preference createPreference(int titleResourceId, String key) {
354         Preference preference = new Preference(getContext());
355         preference.setTitle(titleResourceId);
356         preference.setKey(key);
357         return preference;
358     }
359 
updateFormatPreferencesStates()360     private void updateFormatPreferencesStates() {
361         for (AbstractPreferenceController controller : mPreferenceControllers) {
362             Preference preference = findPreference(controller.getPreferenceKey());
363             if (preference != null && preference instanceof SwitchPreference) {
364                 controller.updateState(preference);
365             }
366         }
367     }
368 
369     /**
370      * @return the display id for each hdr type.
371      */
getFormatPreferenceTitleId(int hdrType)372     private int getFormatPreferenceTitleId(int hdrType) {
373         switch (hdrType) {
374             case HDR_TYPE_DOLBY_VISION:
375                 return R.string.hdr_format_dolby_vision;
376             case HDR_TYPE_HDR10:
377                 return R.string.hdr_format_hdr10;
378             case HDR_TYPE_HLG:
379                 return R.string.hdr_format_hlg;
380             case HDR_TYPE_HDR10_PLUS:
381                 return R.string.hdr_format_hdr10plus;
382             default:
383                 return -1;
384         }
385     }
386 
getLogEntryId(int hdrType)387     private int getLogEntryId(int hdrType) {
388         switch(hdrType) {
389             case HDR_TYPE_DOLBY_VISION:
390                 return TvSettingsEnums.DISPLAY_SOUND_ADVANCED_DISPLAY_FORMAT_SELECTION_DOLBY_VISION;
391             case HDR_TYPE_HDR10:
392                 return TvSettingsEnums.DISPLAY_SOUND_ADVANCED_DISPLAY_FORMAT_SELECTION_HDR10;
393             case HDR_TYPE_HLG:
394                 return TvSettingsEnums.DISPLAY_SOUND_ADVANCED_DISPLAY_FORMAT_SELECTION_HLG;
395             case HDR_TYPE_HDR10_PLUS:
396                 return TvSettingsEnums.DISPLAY_SOUND_ADVANCED_DISPLAY_FORMAT_SELECTION_HDR10_PLUS;
397             default:
398                 return -1;
399         }
400     }
401 
toSet(int[] array)402     private Set<Integer> toSet(int[] array) {
403         return Arrays.stream(array).boxed().collect(Collectors.toSet());
404     }
405 
createInfoFragments()406     private void createInfoFragments() {
407         Preference autoPreference = findPreference(KEY_HDR_FORMAT_SELECTION_AUTO);
408         autoPreference.setFragment(HdrFormatSelectionInfo.AutoInfoFragment.class.getName());
409 
410         Preference manualPreference = findPreference(KEY_HDR_FORMAT_SELECTION_MANUAL);
411         manualPreference.setFragment(HdrFormatSelectionInfo.ManualInfoFragment.class.getName());
412     }
413 }
414