diff --git a/app/src/main/java/com/limelight/preferences/StreamSettings.java b/app/src/main/java/com/limelight/preferences/StreamSettings.java index 7070104168..f184f8f6a4 100644 --- a/app/src/main/java/com/limelight/preferences/StreamSettings.java +++ b/app/src/main/java/com/limelight/preferences/StreamSettings.java @@ -26,6 +26,7 @@ import android.view.View; import android.view.ViewGroup; import android.view.WindowInsets; +import android.hardware.display.DisplayManager; import com.limelight.LimeLog; import com.limelight.PcView; @@ -351,263 +352,259 @@ else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || category_gamepad_settings.removePreference(findPreference("seekbar_vibrate_fallback_strength")); } - Display display = getActivity().getWindowManager().getDefaultDisplay(); - float maxSupportedFps = display.getRefreshRate(); - - // Hide non-supported resolution/FPS combinations - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - int maxSupportedResW = 0; - - // Add a native resolution with any insets included for users that don't want content - // behind the notch of their display - boolean hasInsets = false; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - DisplayCutout cutout; - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - // Use the much nicer Display.getCutout() API on Android 10+ - cutout = display.getCutout(); - } - else { - // Android 9 only - cutout = displayCutoutP; - } + // Iterate through all available displays + DisplayManager displayManager = (DisplayManager) getActivity().getSystemService(Context.DISPLAY_SERVICE); + Display[] displays = displayManager.getDisplays(); + + for (Display display : displays) { + float maxSupportedFps = display.getRefreshRate(); + + // Hide non-supported resolution/FPS combinations + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + int maxSupportedResW = 0; + + // Add a native resolution with any insets included for users that don't want content + // behind the notch of their display + boolean hasInsets = false; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + DisplayCutout cutout; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + // Use the much nicer Display.getCutout() API on Android 10+ + cutout = display.getCutout(); + } else { + // Android 9 only + cutout = displayCutoutP; + } - if (cutout != null) { - int widthInsets = cutout.getSafeInsetLeft() + cutout.getSafeInsetRight(); - int heightInsets = cutout.getSafeInsetBottom() + cutout.getSafeInsetTop(); + if (cutout != null) { + int widthInsets = cutout.getSafeInsetLeft() + cutout.getSafeInsetRight(); + int heightInsets = cutout.getSafeInsetBottom() + cutout.getSafeInsetTop(); - if (widthInsets != 0 || heightInsets != 0) { - DisplayMetrics metrics = new DisplayMetrics(); - display.getRealMetrics(metrics); + if (widthInsets != 0 || heightInsets != 0) { + DisplayMetrics metrics = new DisplayMetrics(); + display.getRealMetrics(metrics); - int width = Math.max(metrics.widthPixels - widthInsets, metrics.heightPixels - heightInsets); - int height = Math.min(metrics.widthPixels - widthInsets, metrics.heightPixels - heightInsets); + int width = Math.max(metrics.widthPixels - widthInsets, metrics.heightPixels - heightInsets); + int height = Math.min(metrics.widthPixels - widthInsets, metrics.heightPixels - heightInsets); - addNativeResolutionEntries(width, height, false); - hasInsets = true; + addNativeResolutionEntries(width, height, false); + hasInsets = true; + } } } - } - // Always allow resolutions that are smaller or equal to the active - // display resolution because decoders can report total non-sense to us. - // For example, a p201 device reports: - // AVC Decoder: OMX.amlogic.avc.decoder.awesome - // HEVC Decoder: OMX.amlogic.hevc.decoder.awesome - // AVC supported width range: 64 - 384 - // HEVC supported width range: 64 - 544 - for (Display.Mode candidate : display.getSupportedModes()) { - // Some devices report their dimensions in the portrait orientation - // where height > width. Normalize these to the conventional width > height - // arrangement before we process them. - - int width = Math.max(candidate.getPhysicalWidth(), candidate.getPhysicalHeight()); - int height = Math.min(candidate.getPhysicalWidth(), candidate.getPhysicalHeight()); - - // Some TVs report strange values here, so let's avoid native resolutions on a TV - // unless they report greater than 4K resolutions. - if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEVISION) || - (width > 3840 || height > 2160)) { - addNativeResolutionEntries(width, height, hasInsets); - } + // Always allow resolutions that are smaller or equal to the active + // display resolution because decoders can report total non-sense to us. + // For example, a p201 device reports: + // AVC Decoder: OMX.amlogic.avc.decoder.awesome + // HEVC Decoder: OMX.amlogic.hevc.decoder.awesome + // AVC supported width range: 64 - 384 + // HEVC supported width range: 64 - 544 + for (Display.Mode candidate : display.getSupportedModes()) { + // Some devices report their dimensions in the portrait orientation + // where height > width. Normalize these to the conventional width > height + // arrangement before we process them. + + int width = Math.max(candidate.getPhysicalWidth(), candidate.getPhysicalHeight()); + int height = Math.min(candidate.getPhysicalWidth(), candidate.getPhysicalHeight()); + + // Some TVs report strange values here, so let's avoid native resolutions on a TV + // unless they report greater than 4K resolutions. + if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEVISION) || + (width > 3840 || height > 2160)) { + addNativeResolutionEntries(width, height, hasInsets); + } - if ((width >= 3840 || height >= 2160) && maxSupportedResW < 3840) { - maxSupportedResW = 3840; - } - else if ((width >= 2560 || height >= 1440) && maxSupportedResW < 2560) { - maxSupportedResW = 2560; - } - else if ((width >= 1920 || height >= 1080) && maxSupportedResW < 1920) { - maxSupportedResW = 1920; - } + if ((width >= 3840 || height >= 2160) && maxSupportedResW < 3840) { + maxSupportedResW = 3840; + } else if ((width >= 2560 || height >= 1440) && maxSupportedResW < 2560) { + maxSupportedResW = 2560; + } else if ((width >= 1920 || height >= 1080) && maxSupportedResW < 1920) { + maxSupportedResW = 1920; + } - if (candidate.getRefreshRate() > maxSupportedFps) { - maxSupportedFps = candidate.getRefreshRate(); + if (candidate.getRefreshRate() > maxSupportedFps) { + maxSupportedFps = candidate.getRefreshRate(); + } } - } - // This must be called to do runtime initialization before calling functions that evaluate - // decoder lists. - MediaCodecHelper.initialize(getContext(), GlPreferences.readPreferences(getContext()).glRenderer); + // This must be called to do runtime initialization before calling functions that evaluate + // decoder lists. + MediaCodecHelper.initialize(getContext(), GlPreferences.readPreferences(getContext()).glRenderer); - MediaCodecInfo avcDecoder = MediaCodecHelper.findProbableSafeDecoder("video/avc", -1); - MediaCodecInfo hevcDecoder = MediaCodecHelper.findProbableSafeDecoder("video/hevc", -1); + MediaCodecInfo avcDecoder = MediaCodecHelper.findProbableSafeDecoder("video/avc", -1); + MediaCodecInfo hevcDecoder = MediaCodecHelper.findProbableSafeDecoder("video/hevc", -1); - if (avcDecoder != null) { - Range avcWidthRange = avcDecoder.getCapabilitiesForType("video/avc").getVideoCapabilities().getSupportedWidths(); + if (avcDecoder != null) { + Range avcWidthRange = avcDecoder.getCapabilitiesForType("video/avc").getVideoCapabilities().getSupportedWidths(); - LimeLog.info("AVC supported width range: "+avcWidthRange.getLower()+" - "+avcWidthRange.getUpper()); + LimeLog.info("AVC supported width range: " + avcWidthRange.getLower() + " - " + avcWidthRange.getUpper()); - // If 720p is not reported as supported, ignore all results from this API - if (avcWidthRange.contains(1280)) { - if (avcWidthRange.contains(3840) && maxSupportedResW < 3840) { - maxSupportedResW = 3840; - } - else if (avcWidthRange.contains(1920) && maxSupportedResW < 1920) { - maxSupportedResW = 1920; - } - else if (maxSupportedResW < 1280) { - maxSupportedResW = 1280; + // If 720p is not reported as supported, ignore all results from this API + if (avcWidthRange.contains(1280)) { + if (avcWidthRange.contains(3840) && maxSupportedResW < 3840) { + maxSupportedResW = 3840; + } else if (avcWidthRange.contains(1920) && maxSupportedResW < 1920) { + maxSupportedResW = 1920; + } else if (maxSupportedResW < 1280) { + maxSupportedResW = 1280; + } } } - } - if (hevcDecoder != null) { - Range hevcWidthRange = hevcDecoder.getCapabilitiesForType("video/hevc").getVideoCapabilities().getSupportedWidths(); + if (hevcDecoder != null) { + Range hevcWidthRange = hevcDecoder.getCapabilitiesForType("video/hevc").getVideoCapabilities().getSupportedWidths(); - LimeLog.info("HEVC supported width range: "+hevcWidthRange.getLower()+" - "+hevcWidthRange.getUpper()); + LimeLog.info("HEVC supported width range: " + hevcWidthRange.getLower() + " - " + hevcWidthRange.getUpper()); - // If 720p is not reported as supported, ignore all results from this API - if (hevcWidthRange.contains(1280)) { - if (hevcWidthRange.contains(3840) && maxSupportedResW < 3840) { - maxSupportedResW = 3840; + // If 720p is not reported as supported, ignore all results from this API + if (hevcWidthRange.contains(1280)) { + if (hevcWidthRange.contains(3840) && maxSupportedResW < 3840) { + maxSupportedResW = 3840; + } else if (hevcWidthRange.contains(1920) && maxSupportedResW < 1920) { + maxSupportedResW = 1920; + } else if (maxSupportedResW < 1280) { + maxSupportedResW = 1280; + } } - else if (hevcWidthRange.contains(1920) && maxSupportedResW < 1920) { - maxSupportedResW = 1920; + } + + LimeLog.info("Maximum resolution slot: " + maxSupportedResW); + + if (maxSupportedResW != 0) { + if (maxSupportedResW < 3840) { + // 4K is unsupported + removeValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, PreferenceConfiguration.RES_4K, new Runnable() { + @Override + public void run() { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsFragment.this.getActivity()); + setValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, PreferenceConfiguration.RES_1440P); + resetBitrateToDefault(prefs, null, null); + } + }); + } + if (maxSupportedResW < 2560) { + // 1440p is unsupported + removeValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, PreferenceConfiguration.RES_1440P, new Runnable() { + @Override + public void run() { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsFragment.this.getActivity()); + setValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, PreferenceConfiguration.RES_1080P); + resetBitrateToDefault(prefs, null, null); + } + }); } - else if (maxSupportedResW < 1280) { - maxSupportedResW = 1280; + if (maxSupportedResW < 1920) { + // 1080p is unsupported + removeValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, PreferenceConfiguration.RES_1080P, new Runnable() { + @Override + public void run() { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsFragment.this.getActivity()); + setValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, PreferenceConfiguration.RES_720P); + resetBitrateToDefault(prefs, null, null); + } + }); } + // Never remove 720p } + } else { + // We can get the true metrics via the getRealMetrics() function (unlike the lies + // that getWidth() and getHeight() tell to us). + DisplayMetrics metrics = new DisplayMetrics(); + display.getRealMetrics(metrics); + int width = Math.max(metrics.widthPixels, metrics.heightPixels); + int height = Math.min(metrics.widthPixels, metrics.heightPixels); + addNativeResolutionEntries(width, height, false); } - LimeLog.info("Maximum resolution slot: "+maxSupportedResW); - - if (maxSupportedResW != 0) { - if (maxSupportedResW < 3840) { - // 4K is unsupported - removeValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, PreferenceConfiguration.RES_4K, new Runnable() { - @Override - public void run() { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsFragment.this.getActivity()); - setValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, PreferenceConfiguration.RES_1440P); - resetBitrateToDefault(prefs, null, null); - } - }); - } - if (maxSupportedResW < 2560) { - // 1440p is unsupported - removeValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, PreferenceConfiguration.RES_1440P, new Runnable() { + if (!PreferenceConfiguration.readPreferences(this.getActivity()).unlockFps) { + // We give some extra room in case the FPS is rounded down + if (maxSupportedFps < 118) { + removeValue(PreferenceConfiguration.FPS_PREF_STRING, "120", new Runnable() { @Override public void run() { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsFragment.this.getActivity()); - setValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, PreferenceConfiguration.RES_1080P); + setValue(PreferenceConfiguration.FPS_PREF_STRING, "90"); resetBitrateToDefault(prefs, null, null); } }); } - if (maxSupportedResW < 1920) { + if (maxSupportedFps < 88) { // 1080p is unsupported - removeValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, PreferenceConfiguration.RES_1080P, new Runnable() { + removeValue(PreferenceConfiguration.FPS_PREF_STRING, "90", new Runnable() { @Override public void run() { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsFragment.this.getActivity()); - setValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, PreferenceConfiguration.RES_720P); + setValue(PreferenceConfiguration.FPS_PREF_STRING, "60"); resetBitrateToDefault(prefs, null, null); } }); } - // Never remove 720p - } - } - else { - // We can get the true metrics via the getRealMetrics() function (unlike the lies - // that getWidth() and getHeight() tell to us). - DisplayMetrics metrics = new DisplayMetrics(); - display.getRealMetrics(metrics); - int width = Math.max(metrics.widthPixels, metrics.heightPixels); - int height = Math.min(metrics.widthPixels, metrics.heightPixels); - addNativeResolutionEntries(width, height, false); - } - - if (!PreferenceConfiguration.readPreferences(this.getActivity()).unlockFps) { - // We give some extra room in case the FPS is rounded down - if (maxSupportedFps < 118) { - removeValue(PreferenceConfiguration.FPS_PREF_STRING, "120", new Runnable() { - @Override - public void run() { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsFragment.this.getActivity()); - setValue(PreferenceConfiguration.FPS_PREF_STRING, "90"); - resetBitrateToDefault(prefs, null, null); - } - }); - } - if (maxSupportedFps < 88) { - // 1080p is unsupported - removeValue(PreferenceConfiguration.FPS_PREF_STRING, "90", new Runnable() { - @Override - public void run() { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsFragment.this.getActivity()); - setValue(PreferenceConfiguration.FPS_PREF_STRING, "60"); - resetBitrateToDefault(prefs, null, null); - } - }); + // Never remove 30 FPS or 60 FPS } - // Never remove 30 FPS or 60 FPS - } - addNativeFrameRateEntry(maxSupportedFps); - - // Android L introduces the drop duplicate behavior of releaseOutputBuffer() - // that the unlock FPS option relies on to not massively increase latency. - findPreference(PreferenceConfiguration.UNLOCK_FPS_STRING).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - // HACK: We need to let the preference change succeed before reinitializing to ensure - // it's reflected in the new layout. - final Handler h = new Handler(); - h.postDelayed(new Runnable() { - @Override - public void run() { - // Ensure the activity is still open when this timeout expires - StreamSettings settingsActivity = (StreamSettings) SettingsFragment.this.getActivity(); - if (settingsActivity != null) { - settingsActivity.reloadSettings(); + addNativeFrameRateEntry(maxSupportedFps); + + + // Android L introduces the drop duplicate behavior of releaseOutputBuffer() + // that the unlock FPS option relies on to not massively increase latency. + findPreference(PreferenceConfiguration.UNLOCK_FPS_STRING).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + // HACK: We need to let the preference change succeed before reinitializing to ensure + // it's reflected in the new layout. + final Handler h = new Handler(); + h.postDelayed(new Runnable() { + @Override + public void run() { + // Ensure the activity is still open when this timeout expires + StreamSettings settingsActivity = (StreamSettings) SettingsFragment.this.getActivity(); + if (settingsActivity != null) { + settingsActivity.reloadSettings(); + } } - } - }, 500); - - // Allow the original preference change to take place - return true; - } - }); + }, 500); - // Remove HDR preference for devices below Nougat - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { - LimeLog.info("Excluding HDR toggle based on OS"); - PreferenceCategory category = - (PreferenceCategory) findPreference("category_advanced_settings"); - category.removePreference(findPreference("checkbox_enable_hdr")); - } - else { - Display.HdrCapabilities hdrCaps = display.getHdrCapabilities(); - - // We must now ensure our display is compatible with HDR10 - boolean foundHdr10 = false; - if (hdrCaps != null) { - // getHdrCapabilities() returns null on Lenovo Lenovo Mirage Solo (vega), Android 8.0 - for (int hdrType : hdrCaps.getSupportedHdrTypes()) { - if (hdrType == Display.HdrCapabilities.HDR_TYPE_HDR10) { - foundHdr10 = true; - break; - } + // Allow the original preference change to take place + return true; } - } + }); - if (!foundHdr10) { - LimeLog.info("Excluding HDR toggle based on display capabilities"); + // Remove HDR preference for devices below Nougat + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + LimeLog.info("Excluding HDR toggle based on OS"); PreferenceCategory category = (PreferenceCategory) findPreference("category_advanced_settings"); category.removePreference(findPreference("checkbox_enable_hdr")); - } - else if (PreferenceConfiguration.isShieldAtvFirmwareWithBrokenHdr()) { - LimeLog.info("Disabling HDR toggle on old broken SHIELD TV firmware"); - PreferenceCategory category = - (PreferenceCategory) findPreference("category_advanced_settings"); - CheckBoxPreference hdrPref = (CheckBoxPreference) category.findPreference("checkbox_enable_hdr"); - hdrPref.setEnabled(false); - hdrPref.setChecked(false); - hdrPref.setSummary("Update the firmware on your NVIDIA SHIELD Android TV to enable HDR"); + } else { + Display.HdrCapabilities hdrCaps = display.getHdrCapabilities(); + + // We must now ensure our display is compatible with HDR10 + boolean foundHdr10 = false; + if (hdrCaps != null) { + // getHdrCapabilities() returns null on Lenovo Lenovo Mirage Solo (vega), Android 8.0 + for (int hdrType : hdrCaps.getSupportedHdrTypes()) { + if (hdrType == Display.HdrCapabilities.HDR_TYPE_HDR10) { + foundHdr10 = true; + break; + } + } + } + + if (!foundHdr10) { + LimeLog.info("Excluding HDR toggle based on display capabilities"); + PreferenceCategory category = + (PreferenceCategory) findPreference("category_advanced_settings"); + category.removePreference(findPreference("checkbox_enable_hdr")); + } else if (PreferenceConfiguration.isShieldAtvFirmwareWithBrokenHdr()) { + LimeLog.info("Disabling HDR toggle on old broken SHIELD TV firmware"); + PreferenceCategory category = + (PreferenceCategory) findPreference("category_advanced_settings"); + CheckBoxPreference hdrPref = (CheckBoxPreference) category.findPreference("checkbox_enable_hdr"); + hdrPref.setEnabled(false); + hdrPref.setChecked(false); + hdrPref.setSummary("Update the firmware on your NVIDIA SHIELD Android TV to enable HDR"); + } } } diff --git a/build.gradle b/build.gradle index 1d7976c811..47f04b7eed 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:8.5.1' + classpath 'com.android.tools.build:gradle:8.8.1' } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 48c0a02ca4..1e2fbf0d45 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists