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