1 /*
2  * Copyright (C) 2023 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_INVALID;
21 
22 import android.annotation.Nullable;
23 import android.app.AlertDialog;
24 import android.content.Context;
25 import android.content.DialogInterface.OnClickListener;
26 import android.content.Intent;
27 import android.hardware.display.DisplayManager;
28 import android.hardware.display.HdrConversionMode;
29 import android.os.UserHandle;
30 import android.view.Display;
31 
32 import com.android.tv.settings.R;
33 
34 import java.util.Arrays;
35 import java.util.Set;
36 import java.util.stream.Collectors;
37 
38 /**
39  * Helper methods for display and sound settings.
40  *
41  * @hide
42  */
43 public class DisplaySoundUtils {
44     private static final String ACTION_HDR_SETTINGS_CHANGED =
45             "com.android.tv.settings.display.HDR_SETTINGS_CHANGED";
46 
sendHdrSettingsChangedBroadcast(Context context)47     public static void sendHdrSettingsChangedBroadcast(Context context) {
48         final String target_package =
49                 context.getResources().getString(R.string.hdr_settings_changed_broadcast_package);
50         if (target_package.isEmpty()) {
51             return;
52         }
53         context.sendBroadcastAsUser(
54                 new Intent(ACTION_HDR_SETTINGS_CHANGED)
55                         .setPackage(target_package)
56                         .setFlags(
57                                 Intent.FLAG_INCLUDE_STOPPED_PACKAGES
58                                         | Intent.FLAG_RECEIVER_FOREGROUND
59                                         | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND),
60                 UserHandle.SYSTEM);
61     }
62 
63     /** Gets the match-content dynamic range status */
getMatchContentDynamicRangeStatus(DisplayManager displayManager)64     public static boolean getMatchContentDynamicRangeStatus(DisplayManager displayManager) {
65         return displayManager.getHdrConversionModeSetting().getConversionMode()
66                 == HdrConversionMode.HDR_CONVERSION_PASSTHROUGH;
67     }
68 
69     /** Sets the match-content dynamic range status */
setMatchContentDynamicRangeStatus(Context context, DisplayManager displayManager, boolean status)70     public static void setMatchContentDynamicRangeStatus(Context context,
71             DisplayManager displayManager,
72             boolean status) {
73         if (status) {
74             // When enabling Match Content Dymanic Range, check if the current Preferred Dynamic
75             // Range is set to Force SDR. Since Force SDR means that all HDR types have been
76             // disabled, when enabling Match Content Dynamic Range, Format Selection must be reset
77             // to Auto to allow for all supported HDR formats
78             if (displayManager.getHdrConversionModeSetting().equals(new HdrConversionMode(
79                     HdrConversionMode.HDR_CONVERSION_FORCE, HDR_TYPE_INVALID))) {
80                 displayManager.setAreUserDisabledHdrTypesAllowed(true);
81             }
82             displayManager.setHdrConversionMode(
83                     new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_PASSTHROUGH));
84         } else {
85             displayManager.setHdrConversionMode(
86                     new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM));
87         }
88         sendHdrSettingsChangedBroadcast(context);
89     }
90 
91     /** Returns if Dolby vision is supported by the device */
isDolbyVisionSupported(Display.Mode[] modes)92     public static boolean isDolbyVisionSupported(Display.Mode[] modes) {
93         for (int i = 0; i < modes.length; i++) {
94             if (isHdrFormatSupported(modes[i], HDR_TYPE_DOLBY_VISION)) {
95                 return true;
96             }
97         }
98         return false;
99     }
100 
101     /** Returns if the passed HDR format is supported by the device in case of a specific mode */
isHdrFormatSupported(Display.Mode mode, int hdrFormat)102     public static boolean isHdrFormatSupported(Display.Mode mode, int hdrFormat) {
103         return Arrays.stream(mode.getSupportedHdrTypes()).anyMatch(
104                 hdr -> hdr == hdrFormat);
105     }
106 
107     /**
108      * Returns true if the current mode is above 4k30Hz and this is a device that does not support
109      * Dolby Vision at resolutions above 4k30Hz
110      */
doesCurrentModeNotSupportDvBecauseLimitedTo4k30(Display display)111     public static boolean doesCurrentModeNotSupportDvBecauseLimitedTo4k30(Display display) {
112         Display.Mode[] supportedModes = display.getSupportedModes();
113         Display.Mode currentMode = display.getMode();
114         boolean is4k60HzMode = (currentMode.getPhysicalHeight() >= 2160
115                 || currentMode.getPhysicalWidth() >= 2160)
116                 && currentMode.getRefreshRate() >= 59.9;
117 
118         return is4k60HzMode && isDolbyVisionSupported(supportedModes)
119                 && !isHdrFormatSupported(currentMode, HDR_TYPE_DOLBY_VISION);
120     }
121 
122     /**
123      * Returns a 1080p 60Hz mode if one is supported by the display, null otherwise
124      */
125     @Nullable
findMode1080p60(Display display)126     public static Display.Mode findMode1080p60(Display display) {
127         for (Display.Mode mode : display.getSupportedModes()) {
128             if (mode.getPhysicalWidth() == 1920 && mode.getPhysicalHeight() == 1080
129                     && mode.getRefreshRate() >= 59.9) {
130                 return mode;
131             }
132         }
133         return null;
134     }
135 
createAlertDialog(Context context, String title, String description, OnClickListener onOkClicked, OnClickListener onCancelClicked)136     static AlertDialog createAlertDialog(Context context, String title, String description,
137             OnClickListener onOkClicked, OnClickListener onCancelClicked) {
138         return new AlertDialog.Builder(context)
139                 .setTitle(title)
140                 .setMessage(description)
141                 .setPositiveButton(R.string.resolution_selection_dialog_ok, onOkClicked)
142                 .setNegativeButton(R.string.resolution_selection_dialog_cancel, onCancelClicked)
143                 .create();
144     }
145 
enableHdrType(DisplayManager displayManager, @Display.HdrCapabilities.HdrType int hdrType)146     static void enableHdrType(DisplayManager displayManager,
147             @Display.HdrCapabilities.HdrType int hdrType) {
148         Set<Integer> disabledHdrTypes = toSet(displayManager.getUserDisabledHdrTypes());
149         disabledHdrTypes.remove(hdrType);
150         displayManager.setUserDisabledHdrTypes(toArray(disabledHdrTypes));
151     }
152 
disableHdrType(DisplayManager displayManager, @Display.HdrCapabilities.HdrType int hdrType)153     static void disableHdrType(DisplayManager displayManager,
154             @Display.HdrCapabilities.HdrType int hdrType) {
155         Set<Integer> disabledHdrTypes = toSet(displayManager.getUserDisabledHdrTypes());
156         disabledHdrTypes.add(hdrType);
157         displayManager.setUserDisabledHdrTypes(toArray(disabledHdrTypes));
158     }
159 
160     /** Returns true when Preferred Dynamic Range is set to Force SDR */
isForceSdr(DisplayManager displayManager)161     static boolean isForceSdr(DisplayManager displayManager) {
162         return displayManager.getHdrConversionModeSetting().equals(new HdrConversionMode(
163                 HdrConversionMode.HDR_CONVERSION_FORCE, HDR_TYPE_INVALID));
164     }
165 
166     /** Converts set to int array */
toArray(Set<Integer> set)167     public static int[] toArray(Set<Integer> set) {
168         return set.stream().mapToInt(Integer::intValue).toArray();
169     }
170 
171     /** Converts int array to set */
toSet(int[] array)172     public static Set<Integer> toSet(int[] array) {
173         return Arrays.stream(array).boxed().collect(Collectors.toSet());
174     }
175 }
176