-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implementing automatic "safe display mode" for e-ink devices. #17646
Changes from all commits
e6efbdc
d7a3cf9
8967aec
fc29239
87a54de
2f4fbf7
b991ef2
0836814
4ce520c
36f6448
2ea33e0
8ae2dd0
9fb9fd0
b384c00
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -264,6 +264,25 @@ open class AnkiDroidApp : | |
TtsVoices.launchBuildLocalesJob() | ||
// enable {{tts-voices:}} field filter | ||
TtsVoicesFieldFilter.ensureApplied() | ||
// initialize safe display mode for e-ink devices | ||
initializeSafeDisplayMode() | ||
} | ||
|
||
/** | ||
* Initializes the safe display mode for e-ink devices. | ||
* | ||
* This method checks if the `safe_display` key is already set in the shared preferences. | ||
* If the key is not set and the device has an e-ink display, it sets the `safe_display` key to `true`. | ||
*/ | ||
private fun initializeSafeDisplayMode() { | ||
val preferences = this.sharedPrefs() | ||
val isSafeDisplaySet = preferences.contains("safeDisplay") | ||
val deviceIdentifier = EInkDeviceIdentifier() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This likely doesn't need to be a class if you're calling a method on it and it has no state There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I’m having difficulty understanding the point you’re trying to convey. Could you clarify or elaborate further? |
||
if (!isSafeDisplaySet && deviceIdentifier.isEInkDevice()) { | ||
preferences.edit { | ||
putBoolean("safeDisplay", true) | ||
} | ||
} | ||
} | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
package com.ichi2.anki | ||
|
||
import android.os.Build | ||
import timber.log.Timber | ||
import java.util.Locale | ||
|
||
class EInkDeviceIdentifier { | ||
data class DeviceInfo( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Data class doesn't feel useful here: we're not using exact comparators There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I really think we should keep DeviceInfo as a data class. It’s all about storing data in a clean and simple way, which makes sense since it's just holding the device's information. Mixing it into EInkDeviceIdentifier feels like it’s overcomplicating things and doesn’t really follow the separation of concerns principle. Also, looking ahead, I see EInkDeviceIdentifier evolving with more features as e-ink devices continue to grow in popularity. These devices are more energy-efficient, and as research progresses, we’ll likely see more of them in the future. Keeping things separate now will help us easily scale and add more functionality later on. |
||
private val originalManufacturer: String, | ||
private val originalModel: String, | ||
) { | ||
val manufacturer = originalManufacturer.lowercase(Locale.ROOT).trim() | ||
val model = originalModel.lowercase(Locale.ROOT).trim() | ||
} | ||
|
||
companion object { | ||
val current = DeviceInfo(Build.MANUFACTURER, Build.MODEL) | ||
} | ||
|
||
private val knownEInkDevices = | ||
setOf( | ||
// Source: https://github.com/Hagb/decryptBooxUpdateUpx/blob/162c29f99bc6b725d1be265cfc17359aa5b55150/BooxKeys.csv | ||
DeviceInfo("onyx", "FLOW"), | ||
DeviceInfo("onyx", "FLOWPro"), | ||
DeviceInfo("onyx", "FLOW.Pro Max"), | ||
DeviceInfo("onyx", "GALILEO"), | ||
DeviceInfo("onyx", "Go103"), | ||
DeviceInfo("onyx", "Go6"), | ||
DeviceInfo("onyx", "GoColor7"), | ||
DeviceInfo("onyx", "MC_GULLIVER"), | ||
DeviceInfo("onyx", "KANT"), | ||
DeviceInfo("onyx", "Kon_Tiki2"), | ||
DeviceInfo("onyx", "Leaf"), | ||
DeviceInfo("onyx", "Leaf2"), | ||
DeviceInfo("onyx", "Leaf2_P"), | ||
DeviceInfo("onyx", "Leaf3"), | ||
DeviceInfo("onyx", "Leaf3C"), | ||
DeviceInfo("onyx", "LIVINGSTONE"), | ||
DeviceInfo("onyx", "Lomonosov"), | ||
DeviceInfo("onyx", "Max2"), | ||
DeviceInfo("onyx", "Max2Pro"), | ||
DeviceInfo("onyx", "Max3"), | ||
DeviceInfo("onyx", "MaxLumi"), | ||
DeviceInfo("onyx", "MaxLumi2"), | ||
DeviceInfo("onyx", "Note"), | ||
DeviceInfo("onyx", "Note2"), | ||
DeviceInfo("onyx", "Note3"), | ||
DeviceInfo("onyx", "NoteAir"), | ||
DeviceInfo("onyx", "NoteAir2"), | ||
DeviceInfo("onyx", "NoteAir2P"), | ||
DeviceInfo("onyx", "NoteAir3"), | ||
DeviceInfo("onyx", "NoteAir3C"), | ||
DeviceInfo("onyx", "NoteAir4C"), | ||
DeviceInfo("onyx", "NotePro"), | ||
DeviceInfo("onyx", "NoteS"), | ||
DeviceInfo("onyx", "NoteX"), | ||
DeviceInfo("onyx", "NoteX2"), | ||
DeviceInfo("onyx", "NoteX3"), | ||
DeviceInfo("onyx", "NoteX3S"), | ||
DeviceInfo("onyx", "NoteX3Pro"), | ||
DeviceInfo("onyx", "Note_YDT"), | ||
DeviceInfo("onyx", "Nova"), | ||
DeviceInfo("onyx", "Nova2"), | ||
DeviceInfo("onyx", "Nova3"), | ||
DeviceInfo("onyx", "Nova3Color"), | ||
DeviceInfo("onyx", "Nova5"), | ||
DeviceInfo("onyx", "NovaAir"), | ||
DeviceInfo("onyx", "NovaAir2"), | ||
DeviceInfo("onyx", "NovaAirC"), | ||
DeviceInfo("onyx", "NovaPlus"), | ||
DeviceInfo("onyx", "NovaPro"), | ||
DeviceInfo("onyx", "PadMuAP3"), | ||
DeviceInfo("onyx", "Page"), | ||
DeviceInfo("onyx", "Palma"), | ||
DeviceInfo("onyx", "Palma2"), | ||
DeviceInfo("onyx", "Poke2"), | ||
DeviceInfo("onyx", "Poke2Color"), | ||
DeviceInfo("onyx", "Poke3"), | ||
DeviceInfo("onyx", "Poke4"), | ||
DeviceInfo("onyx", "Poke4Lite"), | ||
DeviceInfo("onyx", "Poke4S"), | ||
DeviceInfo("onyx", "Poke5"), | ||
DeviceInfo("onyx", "Poke5P"), | ||
DeviceInfo("onyx", "Poke5S"), | ||
DeviceInfo("onyx", "Poke_Pro"), | ||
DeviceInfo("onyx", "SP_NoteS"), | ||
DeviceInfo("onyx", "SP_PokeL"), | ||
DeviceInfo("onyx", "T10C"), | ||
DeviceInfo("onyx", "Tab10"), | ||
DeviceInfo("onyx", "Tab10C"), | ||
DeviceInfo("onyx", "Tab10CPro"), | ||
DeviceInfo("onyx", "Tab13"), | ||
DeviceInfo("onyx", "Tab8"), | ||
DeviceInfo("onyx", "Tab8C"), | ||
DeviceInfo("onyx", "TabMiniC"), | ||
DeviceInfo("onyx", "TabUltra"), | ||
DeviceInfo("onyx", "TabUltraC"), | ||
DeviceInfo("onyx", "TabUltraCPro"), | ||
DeviceInfo("onyx", "TabX"), | ||
// Source: https://github.com/koreader/android-luajit-launcher/blob/6bba3f4bb4da8073d0f4ea4f270828c8603aa54d/app/src/main/java/org/koreader/launcher/device/DeviceInfo.kt | ||
DeviceInfo("boyue", "rk30sdk"), | ||
DeviceInfo("boeye", "rk30sdk"), | ||
DeviceInfo("fidibo", "fidibook"), | ||
DeviceInfo("hyread", "k06nu"), | ||
DeviceInfo("rockchip", "inkpalmplus"), | ||
DeviceInfo("onyx", "jdread"), | ||
DeviceInfo("linfiny", "ent-13t1"), | ||
DeviceInfo("haoqing", "m6"), | ||
DeviceInfo("haoqing", "m7"), | ||
DeviceInfo("haoqing", "p6"), | ||
DeviceInfo("rockchip", "moaanmix7"), | ||
DeviceInfo("onyx", "nabukreg_hd"), | ||
DeviceInfo("barnesandnoble", "bnrv1000"), | ||
DeviceInfo("barnesandnoble", "bnrv1100"), | ||
DeviceInfo("barnesandnoble", "bnrv1300"), | ||
DeviceInfo("barnesandnoble", "bnrv510"), | ||
DeviceInfo("barnesandnoble", "bnrv520"), | ||
DeviceInfo("barnesandnoble", "bnrv700"), | ||
DeviceInfo("barnesandnoble", "evk_mx6s1"), | ||
DeviceInfo("barnesandnoble", "ereader"), // For a partial match | ||
DeviceInfo("freescale", "bnrv510"), | ||
DeviceInfo("freescale", "bnrv520"), | ||
DeviceInfo("freescale", "bnrv700"), | ||
DeviceInfo("freescale", "evk_mx6s1"), | ||
DeviceInfo("onyx", "rk30sdk"), | ||
DeviceInfo("onyx", "mc_note4"), | ||
DeviceInfo("rockchip", "pubook"), | ||
DeviceInfo("sony", "dpt-cp1"), | ||
DeviceInfo("sony", "dpt-rp1"), | ||
DeviceInfo("onyx", "tagus_pokep"), | ||
DeviceInfo("xiaomi", "xiaomi_reader"), | ||
DeviceInfo("artatech", "pri"), // For a partial match | ||
DeviceInfo("crema", "crema-0710c"), // For a partial match | ||
DeviceInfo("crema", "crema-0670c"), // For a partial match | ||
// Source: https://github.com/plotn/coolreader/blob/e5baf0607e678468aa045053ba5f092164aa1dd7/android/src/org/coolreader/crengine/DeviceInfo.java | ||
DeviceInfo("barnesandnoble", "NOOK"), | ||
DeviceInfo("barnesandnoble", "bnrv350"), | ||
DeviceInfo("barnesandnoble", "bnrv300"), | ||
DeviceInfo("barnesandnoble", "bnrv500"), | ||
DeviceInfo("sony", "PRS-T"), // For a partial match | ||
DeviceInfo("dns", "DNS Airbook EGH"), | ||
// Source: https://github.com/ankidroid/Anki-Android/issues/17618 | ||
DeviceInfo("Viwoods", "Viwoods AiPaper"), | ||
) | ||
|
||
private val eInkManufacturersList = | ||
setOf( | ||
"onyx", | ||
"boyue", | ||
"boeye", | ||
"fidibo", | ||
"hyread", | ||
"rockchip", | ||
"linfiny", | ||
"haoqing", | ||
"sony", | ||
"barnesandnoble", | ||
"freescale", | ||
"viwoods", | ||
"artatech", | ||
"dns", | ||
"crema", | ||
"foxconn", | ||
"bigme", | ||
) | ||
|
||
/** | ||
* @return `true` if a match is found, `false` otherwise. | ||
*/ | ||
// Checks if the device has an E-Ink display by matching its manufacturer and model. | ||
fun isEInkDevice(): Boolean { | ||
val currentDevice = current | ||
Timber.v("Checking device: %s", currentDevice) | ||
|
||
val isExactMatch = | ||
knownEInkDevices.any { device -> | ||
currentDevice.manufacturer == device.manufacturer && | ||
currentDevice.model == device.model | ||
} | ||
|
||
if (isExactMatch) { | ||
Timber.d("Confirmed E-ink device: %s", currentDevice) | ||
return true | ||
} | ||
|
||
val isPartialMatch = | ||
knownEInkDevices.any { device -> | ||
( | ||
currentDevice.manufacturer.startsWith(device.manufacturer) || | ||
device.manufacturer.startsWith(currentDevice.manufacturer) | ||
) && | ||
( | ||
currentDevice.model.startsWith(device.model) || | ||
device.model.startsWith(currentDevice.model) | ||
) | ||
} | ||
|
||
if (isPartialMatch || eInkManufacturersList.contains(currentDevice.manufacturer)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Split out obtaining the output, and performing actions based on the outputs There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. both the manufaturer match and partial match results in repoting the device only so why should i seperate them |
||
Timber.w("Potential E-ink device: %s", currentDevice) | ||
CrashReportService.sendExceptionReport( | ||
Exception("Potential E-ink device: ${Build.MANUFACTURER} | ${Build.BRAND} | ${Build.DEVICE} | ${Build.PRODUCT} | ${Build.MODEL} | ${Build.HARDWARE}"), | ||
origin = "EInkDeviceIdentifier", | ||
additionalInfo = null, | ||
onlyIfSilent = true | ||
) | ||
} | ||
|
||
return false | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
performance: this should be done in the background if at all possible so it doesn't block startup
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm unsure how to handle this perfectly, but the safe display mode must be initialized at app startup if the device is e-ink. This won't block startup since it only checks for e-ink and initializes safe mode if needed, without interfering with other processes.