feat: Cutout support on pre-Android P

This commit is contained in:
ziro 2024-02-17 08:01:30 +07:00
parent afe3fd64af
commit 9644b71e57
Signed by: null2264
GPG key ID: BA64F8B60AF3EFB6
8 changed files with 119 additions and 71 deletions

View file

@ -7,3 +7,8 @@
## Other
-->
## Additions
- Added cutout support for some pre-Android P devices
## Fixes
- Fixed cutout behaviour for Android P

View file

@ -76,6 +76,7 @@ class ReaderGeneralView @JvmOverloads constructor(context: Context, attrs: Attri
}
private fun updatePrefs() {
binding.cutoutShort.isVisible = DeviceUtil.hasCutout(context) && preferences.fullscreen().get()
binding.cutoutShort.isVisible =
DeviceUtil.hasCutout(context as ReaderActivity).ordinal >= DeviceUtil.CutoutSupport.MODERN.ordinal && preferences.fullscreen().get()
}
}

View file

@ -3,14 +3,9 @@ package eu.kanade.tachiyomi.ui.reader.settings
import android.app.Activity
import android.content.Context
import android.content.res.Configuration
import android.hardware.display.DisplayManager
import android.os.Build
import android.util.AttributeSet
import android.view.Display
import androidx.core.content.getSystemService
import androidx.core.view.isVisible
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import dev.yokai.domain.ui.settings.ReaderPreferences
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.ReaderPagedLayoutBinding
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
@ -18,7 +13,6 @@ import eu.kanade.tachiyomi.util.bindToPreference
import eu.kanade.tachiyomi.util.lang.addBetaTag
import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.widget.BaseReaderSettingsView
import uy.kohesive.injekt.injectLazy
class ReaderPagedView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
BaseReaderSettingsView<ReaderPagedLayoutBinding>(context, attrs) {
@ -110,15 +104,15 @@ class ReaderPagedView @JvmOverloads constructor(context: Context, attrs: Attribu
else -> false
}
val ogView = (context as? Activity)?.window?.decorView
val hasCutout = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
ogView?.rootWindowInsets?.displayCutout?.safeInsetTop != null || ogView?.rootWindowInsets?.displayCutout?.safeInsetBottom != null
} else {
false
}
binding.landscapeZoom.isVisible = show && preferences.imageScaleType().get() == SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE
binding.extendPastCutout.isVisible = show && isFullFit && hasCutout && preferences.fullscreen().get()
binding.extendPastCutoutLandscape.isVisible = DeviceUtil.hasCutout(context) && preferences.fullscreen().get() &&
ogView?.resources?.configuration?.orientation == Configuration.ORIENTATION_LANDSCAPE
binding.extendPastCutout.isVisible =
show && isFullFit
&& DeviceUtil.hasCutout(context as? Activity).ordinal >= DeviceUtil.CutoutSupport.LEGACY.ordinal
&& preferences.fullscreen().get()
binding.extendPastCutoutLandscape.isVisible =
DeviceUtil.hasCutout(context as? Activity).ordinal >= DeviceUtil.CutoutSupport.MODERN.ordinal
&& preferences.fullscreen().get()
&& ogView?.resources?.configuration?.orientation == Configuration.ORIENTATION_LANDSCAPE
if (binding.extendPastCutoutLandscape.isVisible) {
binding.filterLinearLayout.removeView(binding.extendPastCutoutLandscape)
binding.filterLinearLayout.addView(

View file

@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.ui.reader.viewer
import android.app.Activity
import android.content.Context
import android.graphics.PointF
import android.graphics.drawable.Animatable
@ -16,7 +17,6 @@ import androidx.annotation.AttrRes
import androidx.annotation.CallSuper
import androidx.annotation.StyleRes
import androidx.appcompat.widget.AppCompatImageView
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible
import coil.dispose
import coil.imageLoader
@ -29,6 +29,7 @@ import com.github.chrisbanes.photoview.PhotoView
import dev.yokai.domain.ui.settings.ReaderPreferences.CutoutBehaviour
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonSubsamplingImageView
import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.system.GLUtil
import eu.kanade.tachiyomi.util.system.animatorDurationScale
import java.io.InputStream
@ -190,15 +191,16 @@ open class ReaderPageImageView @JvmOverloads constructor(
config.insetInfo.scaleTypeIsFullFit && topInsets + bottomInsets > 0,
)
if ((config.insetInfo.cutoutBehavior != CutoutBehaviour.IGNORE || !config.insetInfo.scaleTypeIsFullFit) &&
android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q &&
config.insetInfo.isFullscreen
) {
val insets: WindowInsets? = config.insetInfo.insets
setExtraSpace(
0f,
insets?.displayCutout?.boundingRectTop?.height()?.toFloat() ?: 0f,
DeviceUtil.getCutoutHeight(context as? Activity, config.insetInfo.cutoutSupport).toFloat(),
0f,
insets?.displayCutout?.boundingRectBottom?.height()?.toFloat() ?: 0f,
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q)
insets?.displayCutout?.boundingRectBottom?.height()?.toFloat() ?: 0f
else 0f,
)
}
}
@ -318,6 +320,7 @@ open class ReaderPageImageView @JvmOverloads constructor(
)
data class InsetInfo(
val cutoutSupport: DeviceUtil.CutoutSupport,
val cutoutBehavior: CutoutBehaviour,
val topCutoutInset: Float,
val bottomCutoutInset: Float,

View file

@ -12,6 +12,7 @@ import eu.kanade.tachiyomi.ui.reader.viewer.navigation.EdgeNavigation
import eu.kanade.tachiyomi.ui.reader.viewer.navigation.KindlishNavigation
import eu.kanade.tachiyomi.ui.reader.viewer.navigation.LNavigation
import eu.kanade.tachiyomi.ui.reader.viewer.navigation.RightAndLeftNavigation
import eu.kanade.tachiyomi.util.system.DeviceUtil
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.launchIn

View file

@ -26,6 +26,7 @@ import eu.kanade.tachiyomi.ui.reader.viewer.ReaderErrorView
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderPageImageView
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressBar
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig.ZoomType
import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.system.ImageUtil
import eu.kanade.tachiyomi.util.system.ImageUtil.isPagePadded
import eu.kanade.tachiyomi.util.system.ThemeUtil
@ -543,12 +544,13 @@ class PagerPageHolder(
zoomStartPosition = viewer.config.imageZoomType,
landscapeZoom = viewer.config.landscapeZoom,
insetInfo = InsetInfo(
cutoutSupport = DeviceUtil.hasCutout(viewer.activity),
cutoutBehavior = viewer.config.cutoutBehavior,
topCutoutInset = viewer.activity.window.decorView.rootWindowInsets?.topCutoutInset()?.toFloat() ?: 0f,
bottomCutoutInset = viewer.activity.window.decorView.rootWindowInsets?.bottomCutoutInset()?.toFloat() ?: 0f,
scaleTypeIsFullFit = viewer.config.scaleTypeIsFullFit(),
isFullscreen = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
viewer.config.isFullscreen && !viewer.activity.isInMultiWindowMode,
isFullscreen = viewer.config.isFullscreen
&& if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) !viewer.activity.isInMultiWindowMode else true,
isSplitScreen = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && viewer.activity.isInMultiWindowMode,
insets = viewer.activity.window.decorView.rootWindowInsets,
),

View file

@ -121,7 +121,9 @@ class SettingsReaderController : SettingsController() {
// FIXME: Transition from reader to homepage is broken when cutout short is disabled
title = context.getString(R.string.pref_cutout_short).addBetaTag(context)
preferences.fullscreen().changesIn(viewScope) { isVisible = DeviceUtil.hasCutout(activity) && it}
preferences.fullscreen().changesIn(viewScope) {
isVisible = DeviceUtil.hasCutout(activity).ordinal >= DeviceUtil.CutoutSupport.MODERN.ordinal && it
}
}
listPreference(activity) {
bindTo(readerPreferences.landscapeCutoutBehavior())
@ -130,7 +132,9 @@ class SettingsReaderController : SettingsController() {
entriesRes = values.map { it.titleResId }.toTypedArray()
entryValues = values.map { it.name }
preferences.fullscreen().changesIn(viewScope) { isVisible = DeviceUtil.hasCutout(activity) && it}
preferences.fullscreen().changesIn(viewScope) {
isVisible = DeviceUtil.hasCutout(activity).ordinal >= DeviceUtil.CutoutSupport.MODERN.ordinal && it
}
}
switchPreference {
key = Keys.keepScreenOn
@ -221,22 +225,13 @@ class SettingsReaderController : SettingsController() {
entryValues = values.map { it.name }
// Calling this once to show only on cutout
isVisible = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
activityBinding?.root?.rootWindowInsets?.displayCutout?.safeInsetTop != null ||
activityBinding?.root?.rootWindowInsets?.displayCutout?.safeInsetBottom != null
} else {
false
}
isVisible = DeviceUtil.hasCutout(activity).ordinal >= DeviceUtil.CutoutSupport.LEGACY.ordinal
// Calling this a second time in case activity is recreated while on this page
// Keep the first so it shouldn't animate hiding the preference for phones without
// cutouts
activityBinding?.root?.post {
isVisible = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
activityBinding?.root?.rootWindowInsets?.displayCutout?.safeInsetTop != null ||
activityBinding?.root?.rootWindowInsets?.displayCutout?.safeInsetBottom != null
} else {
false
}
isVisible = DeviceUtil.hasCutout(activity).ordinal >= DeviceUtil.CutoutSupport.LEGACY.ordinal
}
}

View file

@ -1,15 +1,17 @@
package eu.kanade.tachiyomi.util.system
import android.annotation.SuppressLint
import android.content.Context
import android.app.Activity
import android.hardware.display.DisplayManager
import android.os.Build
import android.view.Display
import android.view.View
import android.view.Window
import android.view.WindowManager
import androidx.core.content.getSystemService
import androidx.core.view.WindowInsetsCompat
import timber.log.Timber
import java.lang.reflect.InvocationTargetException
object DeviceUtil {
@ -92,59 +94,104 @@ object DeviceUtil {
fun setLegacyCutoutMode(window: Window, mode: LegacyCutoutMode) {
when (mode) {
LegacyCutoutMode.SHORT_EDGES -> {
/* Deprecated method
// Vivo doesn't support this, user had to set it from Settings
/*
if (isVivo) {
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
var systemUiVisibility = window.decorView.systemUiVisibility
systemUiVisibility = systemUiVisibility or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
systemUiVisibility = systemUiVisibility or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
window.decorView.systemUiVisibility = systemUiVisibility
}
*/
}
LegacyCutoutMode.NEVER -> {
/* Deprecated method
// Vivo doesn't support this, user had to set it from Settings
/*
if (isVivo) {
window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
var systemUiVisibility = window.decorView.systemUiVisibility
systemUiVisibility = systemUiVisibility and View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN.inv()
systemUiVisibility = systemUiVisibility and View.SYSTEM_UI_FLAG_LAYOUT_STABLE.inv()
window.decorView.systemUiVisibility = systemUiVisibility
}
*/
}
}
}
fun hasCutout(context: Context? = null): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
fun hasCutout(context: Activity?): CutoutSupport {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
return context?.getSystemService<DisplayManager>()
?.getDisplay(Display.DEFAULT_DISPLAY)?.cutout != null
}
// TODO: Actually check for cutout
return true
}
/*
else if (isVivo && context != null) {
if (context?.getSystemService<DisplayManager>()
?.getDisplay(Display.DEFAULT_DISPLAY)?.cutout != null)
return CutoutSupport.EXTENDED
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val displayCutout = context?.window?.decorView?.rootWindowInsets?.displayCutout
if (displayCutout?.safeInsetTop != null || displayCutout?.safeInsetBottom != null)
return CutoutSupport.MODERN
} else if (isVivo) {
// https://swsdl.vivo.com.cn/appstore/developer/uploadfile/20180328/20180328152252602.pdf
try {
@SuppressLint("PrivateApi")
val ftFeature = context.classLoader
.loadClass("android.util.FtFeature")
val isFeatureSupportMethod = ftFeature.getMethod(
val ftFeature = context?.classLoader
?.loadClass("android.util.FtFeature")
val isFeatureSupportMethod = ftFeature?.getMethod(
"isFeatureSupport",
Int::class.javaPrimitiveType
Int::class.javaPrimitiveType,
)
val isNotchOnScreen = 0x00000020
return isFeatureSupportMethod.invoke(ftFeature, isNotchOnScreen) as Boolean
val isSupported = isFeatureSupportMethod?.invoke(ftFeature, isNotchOnScreen) as Boolean
if (isSupported) return CutoutSupport.LEGACY
} catch (_: Exception) {
}
} else if (isMiui) {
try {
@SuppressLint("PrivateApi")
val sysProp = context?.classLoader?.loadClass("android.os.SystemProperties")
val method = sysProp?.getMethod("getInt", String::class.java, Int::class.javaPrimitiveType)
val rt = method?.invoke(sysProp, "ro.miui.notch", 0) as Int
if (rt == 1) return CutoutSupport.LEGACY
} catch (_: Exception) {
}
}
*/
return false
return CutoutSupport.NONE
}
fun getCutoutHeight(context: Activity?, cutoutSupport: CutoutSupport): Number {
return when (cutoutSupport) {
CutoutSupport.MODERN -> {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P)
throw IllegalStateException("Modern cutout only available on Android P or higher")
context?.window?.decorView?.rootWindowInsets?.displayCutout?.safeInsetTop ?: 0
}
CutoutSupport.EXTENDED -> {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q)
throw IllegalStateException("Extended cutout only available on Android Q or higher")
context?.window?.decorView?.rootWindowInsets?.displayCutout?.boundingRectTop?.height()?.toFloat() ?: 0f
}
CutoutSupport.LEGACY -> {
if (isVivo) {
val insetCompat = context?.window?.decorView?.rootWindowInsets?.let {
WindowInsetsCompat.toWindowInsetsCompat(it)
}
val statusBarHeight = insetCompat?.getInsets(WindowInsetsCompat.Type.statusBars())?.top
?: 24.dpToPx // 24dp is "standard" height for Android since Marshmallow
var notchHeight = 32.dpToPx
if (notchHeight < statusBarHeight) {
notchHeight = statusBarHeight
}
notchHeight
} else if (isMiui) {
val resourceId = context?.resources?.getIdentifier("notch_height",
"dimen", "android") ?: 0
if (resourceId > 0) {
context?.resources?.getDimensionPixelSize(resourceId) ?: 0
} else {
0
}
} else {
0
}
}
else -> 0
}
}
enum class CutoutSupport {
NONE,
LEGACY, // Pre-Android P, the start of this hell
MODERN, // Android P
EXTENDED, // Android Q
}
enum class LegacyCutoutMode {