2 * Copyright (C) 2011-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
8 #include "AndroidUtils.h"
10 #include "ServiceBroker.h"
11 #include "settings/DisplaySettings.h"
12 #include "settings/Settings.h"
13 #include "settings/SettingsComponent.h"
14 #include "settings/lib/SettingsManager.h"
15 #include "utils/StringUtils.h"
16 #include "utils/log.h"
17 #include "windowing/GraphicContext.h"
19 #include "platform/android/activity/XBMCApp.h"
21 #include <androidjni/MediaCodecInfo.h>
22 #include <androidjni/MediaCodecList.h>
23 #include <androidjni/System.h>
24 #include <androidjni/SystemProperties.h>
25 #include <androidjni/View.h>
26 #include <androidjni/Window.h>
27 #include <androidjni/WindowManager.h>
29 static bool s_hasModeApi
= false;
30 static std::vector
<RESOLUTION_INFO
> s_res_displayModes
;
31 static RESOLUTION_INFO s_res_cur_displayMode
;
33 static float currentRefreshRate()
36 return s_res_cur_displayMode
.fRefreshRate
;
38 CJNIWindow window
= CXBMCApp::getWindow();
41 float preferredRate
= window
.getAttributes().getpreferredRefreshRate();
42 if (preferredRate
> 20.0f
)
44 CLog::Log(LOGINFO
, "CAndroidUtils: Preferred refresh rate: {:f}", preferredRate
);
47 CJNIView
view(window
.getDecorView());
50 CJNIDisplay
display(view
.getDisplay());
53 float reportedRate
= display
.getRefreshRate();
54 if (reportedRate
> 20.0f
)
56 CLog::Log(LOGINFO
, "CAndroidUtils: Current display refresh rate: {:f}", reportedRate
);
62 CLog::Log(LOGDEBUG
, "found no refresh rate");
66 static void fetchDisplayModes()
69 s_res_displayModes
.clear();
71 CJNIDisplay display
= CXBMCApp::getWindow().getDecorView().getDisplay();
75 CJNIDisplayMode m
= display
.getMode();
78 if (m
.getPhysicalWidth() > m
.getPhysicalHeight()) // Assume unusable if portrait is returned
82 CLog::Log(LOGDEBUG
, "CAndroidUtils: current mode: {}: {}x{}@{:f}", m
.getModeId(),
83 m
.getPhysicalWidth(), m
.getPhysicalHeight(), m
.getRefreshRate());
84 s_res_cur_displayMode
.strId
= std::to_string(m
.getModeId());
85 s_res_cur_displayMode
.iWidth
= s_res_cur_displayMode
.iScreenWidth
= m
.getPhysicalWidth();
86 s_res_cur_displayMode
.iHeight
= s_res_cur_displayMode
.iScreenHeight
= m
.getPhysicalHeight();
87 s_res_cur_displayMode
.fRefreshRate
= m
.getRefreshRate();
88 s_res_cur_displayMode
.dwFlags
= D3DPRESENTFLAG_PROGRESSIVE
;
89 s_res_cur_displayMode
.bFullScreen
= true;
90 s_res_cur_displayMode
.iSubtitles
= s_res_cur_displayMode
.iHeight
;
91 s_res_cur_displayMode
.fPixelRatio
= 1.0f
;
92 s_res_cur_displayMode
.strMode
= StringUtils::Format(
93 "{}x{} @ {:.6f}{} - Full Screen", s_res_cur_displayMode
.iScreenWidth
,
94 s_res_cur_displayMode
.iScreenHeight
, s_res_cur_displayMode
.fRefreshRate
,
95 s_res_cur_displayMode
.dwFlags
& D3DPRESENTFLAG_INTERLACED
? "i" : "");
97 std::vector
<CJNIDisplayMode
> modes
= display
.getSupportedModes();
98 for (CJNIDisplayMode
& m
: modes
)
100 CLog::Log(LOGDEBUG
, "CAndroidUtils: available mode: {}: {}x{}@{:f}", m
.getModeId(),
101 m
.getPhysicalWidth(), m
.getPhysicalHeight(), m
.getRefreshRate());
104 res
.strId
= std::to_string(m
.getModeId());
105 res
.iWidth
= res
.iScreenWidth
= m
.getPhysicalWidth();
106 res
.iHeight
= res
.iScreenHeight
= m
.getPhysicalHeight();
107 res
.fRefreshRate
= m
.getRefreshRate();
108 res
.dwFlags
= D3DPRESENTFLAG_PROGRESSIVE
;
109 res
.bFullScreen
= true;
110 res
.iSubtitles
= res
.iHeight
;
111 res
.fPixelRatio
= 1.0f
;
112 res
.strMode
= StringUtils::Format("{}x{} @ {:.6f}{} - Full Screen", res
.iScreenWidth
,
113 res
.iScreenHeight
, res
.fRefreshRate
,
114 res
.dwFlags
& D3DPRESENTFLAG_INTERLACED
? "i" : "");
116 s_res_displayModes
.push_back(res
);
125 std::string
HdrTypeString(int type
)
127 const std::map
<int, std::string
> hdrTypeMap
= {
128 {CJNIDisplayHdrCapabilities::HDR_TYPE_HDR10
, "HDR10"},
129 {CJNIDisplayHdrCapabilities::HDR_TYPE_HLG
, "HLG"},
130 {CJNIDisplayHdrCapabilities::HDR_TYPE_HDR10_PLUS
, "HDR10+"},
131 {CJNIDisplayHdrCapabilities::HDR_TYPE_DOLBY_VISION
, "Dolby Vision"}};
133 auto hdr
= hdrTypeMap
.find(type
);
134 if (hdr
!= hdrTypeMap
.end())
139 } // unnamed namespace
141 const std::string
CAndroidUtils::SETTING_LIMITGUI
= "videoscreen.limitgui";
143 CAndroidUtils::CAndroidUtils()
145 std::string displaySize
;
146 m_width
= m_height
= 0;
148 if (CJNIBase::GetSDKVersion() >= 23)
151 for (const RESOLUTION_INFO
& res
: s_res_displayModes
)
153 if (res
.iWidth
> m_width
|| res
.iHeight
> m_height
)
155 m_width
= res
.iWidth
;
156 m_height
= res
.iHeight
;
161 if (!m_width
|| !m_height
)
163 // Property available on some devices
164 displaySize
= CJNISystemProperties::get("sys.display-size", "");
165 if (!displaySize
.empty())
167 std::vector
<std::string
> aSize
= StringUtils::Split(displaySize
, "x");
168 if (aSize
.size() == 2)
170 m_width
= StringUtils::IsInteger(aSize
[0]) ? atoi(aSize
[0].c_str()) : 0;
171 m_height
= StringUtils::IsInteger(aSize
[1]) ? atoi(aSize
[1].c_str()) : 0;
173 CLog::Log(LOGDEBUG
, "CAndroidUtils: display-size: {}({}x{})", displaySize
, m_width
, m_height
);
177 CLog::Log(LOGDEBUG
, "CAndroidUtils: maximum/current resolution: {}x{}", m_width
, m_height
);
178 int limit
= CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
179 CAndroidUtils::SETTING_LIMITGUI
);
187 case 9999: // unlimited
206 CLog::Log(LOGDEBUG
, "CAndroidUtils: selected resolution: {}x{}", m_width
, m_height
);
208 CServiceBroker::GetSettingsComponent()->GetSettings()->GetSettingsManager()->RegisterCallback(
209 this, {CAndroidUtils::SETTING_LIMITGUI
});
211 LogDisplaySupportedHdrTypes();
214 bool CAndroidUtils::GetNativeResolution(RESOLUTION_INFO
* res
) const
216 const std::shared_ptr
<CNativeWindow
> nativeWindow
= CXBMCApp::Get().GetNativeWindow(30000);
220 if (!m_width
|| !m_height
)
222 m_width
= nativeWindow
->GetWidth();
223 m_height
= nativeWindow
->GetHeight();
224 CLog::Log(LOGINFO
, "CAndroidUtils: window resolution: {}x{}", m_width
, m_height
);
229 *res
= s_res_cur_displayMode
;
230 res
->iWidth
= m_width
;
231 res
->iHeight
= m_height
;
236 res
->fRefreshRate
= currentRefreshRate();
237 res
->dwFlags
= D3DPRESENTFLAG_PROGRESSIVE
;
238 res
->bFullScreen
= true;
239 res
->iWidth
= m_width
;
240 res
->iHeight
= m_height
;
241 res
->fPixelRatio
= 1.0f
;
242 res
->iScreenWidth
= res
->iWidth
;
243 res
->iScreenHeight
= res
->iHeight
;
245 res
->iSubtitles
= res
->iHeight
;
247 StringUtils::Format("{}x{} @ {:.6f}{} - Full Screen", res
->iScreenWidth
, res
->iScreenHeight
,
248 res
->fRefreshRate
, res
->dwFlags
& D3DPRESENTFLAG_INTERLACED
? "i" : "");
249 CLog::Log(LOGINFO
, "CAndroidUtils: Current resolution: {}x{} {}", res
->iWidth
, res
->iHeight
,
254 bool CAndroidUtils::SetNativeResolution(const RESOLUTION_INFO
& res
)
256 CLog::Log(LOGINFO
, "CAndroidUtils: SetNativeResolution: {}: {}x{} {}x{}@{:f}", res
.strId
,
257 res
.iWidth
, res
.iHeight
, res
.iScreenWidth
, res
.iScreenHeight
, res
.fRefreshRate
);
261 CXBMCApp::Get().SetDisplayMode(std::atoi(res
.strId
.c_str()), res
.fRefreshRate
);
262 s_res_cur_displayMode
= res
;
265 CXBMCApp::Get().SetRefreshRate(res
.fRefreshRate
);
267 CXBMCApp::Get().SetBuffersGeometry(res
.iWidth
, res
.iHeight
, 0);
272 bool CAndroidUtils::ProbeResolutions(std::vector
<RESOLUTION_INFO
>& resolutions
)
274 RESOLUTION_INFO cur_res
;
275 bool ret
= GetNativeResolution(&cur_res
);
277 CLog::Log(LOGDEBUG
, "CAndroidUtils: ProbeResolutions: {}x{}", m_width
, m_height
);
281 for (RESOLUTION_INFO res
: s_res_displayModes
)
283 if (m_width
&& m_height
)
285 res
.iWidth
= std::min(res
.iWidth
, m_width
);
286 res
.iHeight
= std::min(res
.iHeight
, m_height
);
287 res
.iSubtitles
= res
.iHeight
;
289 resolutions
.push_back(res
);
294 if (ret
&& cur_res
.iWidth
> 1 && cur_res
.iHeight
> 1)
296 std::vector
<float> refreshRates
;
297 CJNIWindow window
= CXBMCApp::getWindow();
300 CJNIView view
= window
.getDecorView();
303 CJNIDisplay display
= view
.getDisplay();
306 refreshRates
= display
.getSupportedRefreshRates();
310 if (!refreshRates
.empty())
312 for (unsigned int i
= 0; i
< refreshRates
.size(); i
++)
314 if (refreshRates
[i
] < 20.0f
)
316 cur_res
.fRefreshRate
= refreshRates
[i
];
317 cur_res
.strMode
= StringUtils::Format(
318 "{}x{} @ {:.6f}{} - Full Screen", cur_res
.iScreenWidth
, cur_res
.iScreenHeight
,
319 cur_res
.fRefreshRate
, cur_res
.dwFlags
& D3DPRESENTFLAG_INTERLACED
? "i" : "");
320 resolutions
.push_back(cur_res
);
324 if (resolutions
.empty())
326 /* No valid refresh rates available, just provide the current one */
327 resolutions
.push_back(cur_res
);
334 bool CAndroidUtils::UpdateDisplayModes()
336 if (CJNIBase::GetSDKVersion() >= 23)
341 bool CAndroidUtils::IsHDRDisplay()
343 CJNIWindow window
= CXBMCApp::getWindow();
348 CJNIView view
= window
.getDecorView();
351 CJNIDisplay display
= view
.getDisplay();
353 ret
= display
.isHdr();
356 CLog::Log(LOGDEBUG
, "CAndroidUtils: IsHDRDisplay: {}", ret
? "true" : "false");
360 void CAndroidUtils::OnSettingChanged(const std::shared_ptr
<const CSetting
>& setting
)
362 const std::string
& settingId
= setting
->GetId();
363 /* Calibration (overscan / subtitles) are based on GUI size -> reset required */
364 if (settingId
== CAndroidUtils::SETTING_LIMITGUI
)
365 CDisplaySettings::GetInstance().ClearCalibrations();
368 std::vector
<int> CAndroidUtils::GetDisplaySupportedHdrTypes()
370 CJNIWindow window
= CXBMCApp::getWindow();
374 CJNIView view
= window
.getDecorView();
377 CJNIDisplay display
= view
.getDisplay();
380 CJNIDisplayHdrCapabilities caps
= display
.getHdrCapabilities();
382 return caps
.getSupportedHdrTypes();
390 void CAndroidUtils::LogDisplaySupportedHdrTypes()
392 const std::vector
<int> hdrTypes
= GetDisplaySupportedHdrTypes();
395 for (const int& type
: hdrTypes
)
397 text
+= " " + HdrTypeString(type
);
400 CLog::Log(LOGDEBUG
, "CAndroidUtils: Display supported HDR types:{}",
401 text
.empty() ? " None" : text
);
404 CHDRCapabilities
CAndroidUtils::GetDisplayHDRCapabilities()
406 CHDRCapabilities caps
;
407 const std::vector
<int> types
= GetDisplaySupportedHdrTypes();
409 if (std::find(types
.begin(), types
.end(), CJNIDisplayHdrCapabilities::HDR_TYPE_HDR10
) !=
413 if (std::find(types
.begin(), types
.end(), CJNIDisplayHdrCapabilities::HDR_TYPE_HLG
) !=
417 if (std::find(types
.begin(), types
.end(), CJNIDisplayHdrCapabilities::HDR_TYPE_HDR10_PLUS
) !=
421 if (std::find(types
.begin(), types
.end(), CJNIDisplayHdrCapabilities::HDR_TYPE_DOLBY_VISION
) !=
423 caps
.SetDolbyVision();
428 bool CAndroidUtils::SupportsMediaCodecMimeType(const std::string
& mimeType
)
430 const std::vector
<CJNIMediaCodecInfo
> codecInfos
=
431 CJNIMediaCodecList(CJNIMediaCodecList::REGULAR_CODECS
).getCodecInfos();
433 for (const CJNIMediaCodecInfo
& codec_info
: codecInfos
)
435 if (codec_info
.isEncoder())
438 std::vector
<std::string
> types
= codec_info
.getSupportedTypes();
439 if (std::find(types
.begin(), types
.end(), mimeType
) != types
.end())
446 std::pair
<bool, bool> CAndroidUtils::GetDolbyVisionCapabilities()
448 const bool displaySupportsDovi
= GetDisplayHDRCapabilities().SupportsDolbyVision();
449 const bool mediaCodecSupportsDovi
= SupportsMediaCodecMimeType("video/dolby-vision");
451 CLog::Log(LOGDEBUG
, "CAndroidUtils::GetDolbyVisionCapabilities Display: {}, MediaCodec: {}",
452 displaySupportsDovi
, mediaCodecSupportsDovi
);
454 return std::make_pair(displaySupportsDovi
, mediaCodecSupportsDovi
);