diff --git a/app/src/main/java/org/koreader/launcher/TestActivity.kt b/app/src/main/java/org/koreader/launcher/TestActivity.kt index b93b25d9d..fea9cfeb9 100644 --- a/app/src/main/java/org/koreader/launcher/TestActivity.kt +++ b/app/src/main/java/org/koreader/launcher/TestActivity.kt @@ -27,6 +27,7 @@ import org.koreader.launcher.device.lights.OnyxWarmthController import org.koreader.launcher.device.lights.TolinoRootController import org.koreader.launcher.device.lights.TolinoNtxController import org.koreader.launcher.device.lights.TolinoNtxNoWarmthController +import org.koreader.launcher.device.lights.TolinoB300Controller import org.koreader.launcher.device.lights.BoyueS62RootController import org.koreader.launcher.dialog.LightDialog import org.koreader.launcher.dialog.ToolTip @@ -81,6 +82,7 @@ class TestActivity: AppCompatActivity() { lightsMap["Tolino Root"] = TolinoRootController() lightsMap["Tolino Ntx"] = TolinoNtxController() lightsMap["Tolino Ntx (no warmth)"] = TolinoNtxNoWarmthController() + lightsMap["Tolino B300"] = TolinoB300Controller() // Device ID binding.info.append("Manufacturer: ${DeviceInfo.MANUFACTURER}\n") diff --git a/app/src/main/java/org/koreader/launcher/device/DeviceInfo.kt b/app/src/main/java/org/koreader/launcher/device/DeviceInfo.kt index 3e0a3dc88..b299c193d 100644 --- a/app/src/main/java/org/koreader/launcher/device/DeviceInfo.kt +++ b/app/src/main/java/org/koreader/launcher/device/DeviceInfo.kt @@ -141,6 +141,7 @@ object DeviceInfo { TOLINO_EPOS3, TOLINO_PAGE2, TOLINO_SHINE3, + TOLINO_SHINE4, TOLINO_VISION4, TOLINO_VISION5, TOLINO_VISION6, @@ -614,6 +615,10 @@ object DeviceInfo { BRAND == STR_KOBO && MODEL == STR_TOLINO && DEVICE == STR_NTX && HARDWARE == "e60k00" -> Id.TOLINO_SHINE3 + // Tolino Shine 4 + BRAND == STR_KOBO && MODEL == "tolino shine 4" && DEVICE == STR_TOLINO && HARDWARE == "sun8iw15p1" + -> Id.TOLINO_SHINE4 + // Tolino Vision 4 also has warmth lights, but with ntx_io file BRAND == STR_KOBO && MODEL == STR_TOLINO && DEVICE == STR_NTX && HARDWARE == "e60q50" -> Id.TOLINO_VISION4 diff --git a/app/src/main/java/org/koreader/launcher/device/EPDFactory.kt b/app/src/main/java/org/koreader/launcher/device/EPDFactory.kt index 673a80211..0b74405c8 100644 --- a/app/src/main/java/org/koreader/launcher/device/EPDFactory.kt +++ b/app/src/main/java/org/koreader/launcher/device/EPDFactory.kt @@ -69,6 +69,7 @@ object EPDFactory { DeviceInfo.Id.NOOK_GL4, DeviceInfo.Id.TOLINO_EPOS3, DeviceInfo.Id.TOLINO_VISION6, + DeviceInfo.Id.TOLINO_SHINE4, -> { logController("NOOK_GL4") NGL4EPDController() diff --git a/app/src/main/java/org/koreader/launcher/device/LightsFactory.kt b/app/src/main/java/org/koreader/launcher/device/LightsFactory.kt index 8614bd339..eda2e6314 100644 --- a/app/src/main/java/org/koreader/launcher/device/LightsFactory.kt +++ b/app/src/main/java/org/koreader/launcher/device/LightsFactory.kt @@ -106,6 +106,13 @@ object LightsFactory { logController("TolinoRoot") TolinoRootController() } + DeviceInfo.Id.TOLINO_EPOS3, + DeviceInfo.Id.TOLINO_VISION6, + DeviceInfo.Id.TOLINO_SHINE4, + -> { + logController("TolinoB300Controller") + TolinoB300Controller() + } else -> { logController("Generic") GenericController() diff --git a/app/src/main/java/org/koreader/launcher/device/lights/TolinoB300Controller.kt b/app/src/main/java/org/koreader/launcher/device/lights/TolinoB300Controller.kt new file mode 100644 index 000000000..3c32767a8 --- /dev/null +++ b/app/src/main/java/org/koreader/launcher/device/lights/TolinoB300Controller.kt @@ -0,0 +1,158 @@ +package org.koreader.launcher.device.lights + +import android.app.Activity +import android.provider.Settings +import android.util.Log +import android.widget.Toast +import android.os.Looper +import android.os.Handler +import org.koreader.launcher.device.Ioctl +import org.koreader.launcher.device.LightsInterface +import org.koreader.launcher.device.DeviceInfo + +// Light and warmth controller for B300 Tolino devices (Epos 3, Vision 6, Shine 4) +class TolinoB300Controller : Ioctl(), LightsInterface { + + companion object { + private const val TAG = "Lights" + private const val BRIGHTNESS_MAX = 100 + private const val WARMTH_MAX = 10 + private const val MIN = 0 + private const val SCREEN_BRIGHTNESS = "screen_brightness" + private const val SCREEN_BRIGHTNESS_COLOR = "screen_brightness_color" + } + + private fun needsInvertedWarmth(): Boolean { + return DeviceInfo.ID == DeviceInfo.Id.TOLINO_VISION6 || + DeviceInfo.ID == DeviceInfo.Id.TOLINO_SHINE4 + } + + private fun showToastOnUiThread(activity: Activity, message: String) { + Handler(Looper.getMainLooper()).post { + Toast.makeText(activity.applicationContext, message, Toast.LENGTH_LONG).show() + } + } + + private fun ensureWriteSettingsPermission(activity: Activity): Boolean { + if (!Settings.System.canWrite(activity.applicationContext)) { + showToastOnUiThread( + activity, + "Please enable 'Modify system settings' for KOReader in Android settings." + ) + Log.w(TAG, "WRITE_SETTINGS permission not granted.") + return false + } + return true + } + + override fun getPlatform(): String { + return "tolino" + } + + override fun hasFallback(): Boolean { + return false + } + + override fun hasWarmth(): Boolean { + return true + } + + override fun needsPermission(): Boolean { + return true + } + + override fun enableFrontlightSwitch(activity: Activity): Int { + return 1 + } + + override fun getBrightness(activity: Activity): Int { + return try { + Settings.System.getInt(activity.applicationContext.contentResolver, SCREEN_BRIGHTNESS) + } catch (e: Exception) { + Log.w(TAG, e.toString()) + 0 + } + } + + override fun getWarmth(activity: Activity): Int { + return try { + val raw = Settings.System.getInt( + activity.applicationContext.contentResolver, + SCREEN_BRIGHTNESS_COLOR + ) + if (needsInvertedWarmth()) WARMTH_MAX - raw else raw + } catch (e: Exception) { + Log.w(TAG, e.toString()) + 0 + } + } + + override fun setBrightness(activity: Activity, brightness: Int) { + if (!ensureWriteSettingsPermission(activity)) return + if (brightness < MIN || brightness > BRIGHTNESS_MAX) { + Log.w(TAG, "brightness value of of range: $brightness") + return + } + Log.v(TAG, "Setting brightness to $brightness") + try { + Settings.System.putInt( + activity.applicationContext.contentResolver, + SCREEN_BRIGHTNESS, + brightness + ) + } catch (e: Exception) { + Log.w(TAG, "$e") + } + } + + override fun setWarmth(activity: Activity, warmth: Int) { + if (!ensureWriteSettingsPermission(activity)) return + if (warmth < MIN || warmth > WARMTH_MAX) { + Log.w(TAG, "warmth value of of range: $warmth") + return + } + val warmthToSet = if (needsInvertedWarmth()) WARMTH_MAX - warmth else warmth + Log.v(TAG, "Setting warmth to $warmth (actual: $warmthToSet)") + try { + Settings.System.putInt( + activity.applicationContext.contentResolver, + SCREEN_BRIGHTNESS_COLOR, + warmthToSet + ) + // workaround, toggle brightness to force warmth refresh + val currentBrightness: Int = getBrightness(activity) + Settings.System.putInt( + activity.applicationContext.contentResolver, + SCREEN_BRIGHTNESS, + currentBrightness + 1 + ) + Settings.System.putInt( + activity.applicationContext.contentResolver, + SCREEN_BRIGHTNESS, + currentBrightness + ) + } catch (e: Exception) { + Log.w(TAG, "$e") + } + } + + override fun getMinWarmth(): Int { + return MIN + } + + override fun getMaxWarmth(): Int { + return WARMTH_MAX + } + + override fun getMinBrightness(): Int { + return MIN + } + + override fun getMaxBrightness(): Int { + return BRIGHTNESS_MAX + } + + override fun hasStandaloneWarmth(): Boolean { + return false + } +}