Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,38 @@ fun hasSigningConfig(storePath: String?, storePass: String?, alias: String?, key
fun gradleBooleanProperty(name: String): Boolean =
providers.gradleProperty(name).orNull?.equals("true", ignoreCase = true) == true

fun shouldValidateNightlySigning(taskNames: List<String>): Boolean {
if (taskNames.isEmpty()) {
return true
}
val signingTaskHints = listOf(
"assembleNightlyRelease",
"bundleNightlyRelease",
"packageNightlyRelease",
"publishNightlyRelease",
"installNightlyRelease"
)
return taskNames.any { task ->
signingTaskHints.any { hint -> task.contains(hint, ignoreCase = true) }
}
}

fun shouldValidateStableSigning(taskNames: List<String>): Boolean {
if (taskNames.isEmpty()) {
return true
}
val signingTaskHints = listOf(
"assembleStableRelease",
"bundleStableRelease",
"packageStableRelease",
"publishStableRelease",
"installStableRelease"
)
return taskNames.any { task ->
signingTaskHints.any { hint -> task.contains(hint, ignoreCase = true) }
}
}

android {
namespace = "it.palsoftware.pastiera"
compileSdk = 36
Expand Down Expand Up @@ -151,6 +183,10 @@ android {
tasks.whenTaskAdded {
if (!isFdroidBuild && name.equals("preStableReleaseBuild", ignoreCase = true)) {
doFirst {
if (!shouldValidateStableSigning(gradle.startParameter.taskNames)) {
logger.lifecycle("Skipping stable signing validation for non-packaging task(s): ${gradle.startParameter.taskNames}")
return@doFirst
}
val storePath = signingProp("storeFile", "PASTIERA_KEYSTORE_PATH")
val storePass = signingProp("storePassword", "PASTIERA_KEYSTORE_PASSWORD")
val alias = signingProp("keyAlias", "PASTIERA_KEY_ALIAS")
Expand All @@ -168,6 +204,10 @@ android {
}
if (name.equals("preNightlyReleaseBuild", ignoreCase = true)) {
doFirst {
if (!shouldValidateNightlySigning(gradle.startParameter.taskNames)) {
logger.lifecycle("Skipping nightly signing validation for non-packaging task(s): ${gradle.startParameter.taskNames}")
return@doFirst
}
val storePath = signingProp("nightlyStoreFile", "PASTIERA_NIGHTLY_KEYSTORE_PATH")
val storePass = signingProp("nightlyStorePassword", "PASTIERA_NIGHTLY_KEYSTORE_PASSWORD")
val alias = signingProp("nightlyKeyAlias", "PASTIERA_NIGHTLY_KEY_ALIAS")
Expand Down
32 changes: 32 additions & 0 deletions app/src/main/assets/devices/mp01/alt_key_mappings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"mappings": {
"KEYCODE_Q": "&",
"KEYCODE_W": "1",
"KEYCODE_E": "2",
"KEYCODE_R": "3",
"KEYCODE_T": "_",
"KEYCODE_Y": "-",
"KEYCODE_U": "+",
"KEYCODE_I": "!",
"KEYCODE_O": "#",
"KEYCODE_P": "$",
"KEYCODE_A": "@",
"KEYCODE_S": "4",
"KEYCODE_D": "5",
"KEYCODE_F": "6",
"KEYCODE_G": "=",
"KEYCODE_H": ":",
"KEYCODE_J": ";",
"KEYCODE_K": "'",
"KEYCODE_L": "\"",
"KEYCODE_Z": "7",
"KEYCODE_X": "8",
"KEYCODE_C": "9",
"KEYCODE_V": "*",
"KEYCODE_B": "%",
"KEYCODE_N": "?",
"KEYCODE_M": ",",
"KEYCODE_MIC": ".",
"KEYCODE_EM": "0"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ fun KeyboardLayoutSettingsScreen(
expanded = showPhysicalProfileMenu,
onDismissRequest = { showPhysicalProfileMenu = false }
) {
listOf("auto", "key2", "Q25", "titan2").forEach { profile ->
listOf("auto", "key2", "Q25", "titan2", "mp01").forEach { profile ->
DropdownMenuItem(
text = { Text(keyboardProfileLabel(context, profile)) },
onClick = {
Expand Down Expand Up @@ -615,6 +615,7 @@ private fun keyboardProfileLabel(context: Context, profile: String): String {
"key2" -> context.getString(R.string.keyboard_profile_option_key2)
"Q25" -> context.getString(R.string.keyboard_profile_option_q25)
"titan2" -> context.getString(R.string.keyboard_profile_option_titan2)
"mp01" -> context.getString(R.string.keyboard_profile_option_mp01)
else -> context.getString(R.string.keyboard_profile_option_auto)
}
}
Expand Down
5 changes: 3 additions & 2 deletions app/src/main/java/it/palsoftware/pastiera/SettingsManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ object SettingsManager {
private const val KEY_KEYBOARD_LAYOUT = "keyboard_layout" // "qwerty", "azerty", etc.
private const val KEY_KEYBOARD_LAYOUT_AUTO_BY_LOCALE = "keyboard_layout_auto_by_locale" // If true, resolve layout from subtype/locale mapping
private const val KEY_KEYBOARD_LAYOUT_LIST = "keyboard_layout_list" // JSON array of layout ids for cycling
private const val KEY_PHYSICAL_KEYBOARD_PROFILE_OVERRIDE = "physical_keyboard_profile_override" // auto | key2 | Q25 | titan2
private const val KEY_PHYSICAL_KEYBOARD_PROFILE_OVERRIDE = "physical_keyboard_profile_override" // auto | key2 | Q25 | titan2 | mp01
private const val KEY_RESTORE_SYM_PAGE = "restore_sym_page" // SYM page to restore when returning from settings
private const val KEY_PENDING_RESTORE_SYM_PAGE = "pending_restore_sym_page" // Temporary SYM page state saved when opening settings
private const val KEY_SYM_PAGES_CONFIG = "sym_pages_config" // Order/enabled pages for SYM
Expand Down Expand Up @@ -1574,7 +1574,7 @@ object SettingsManager {

/**
* Returns the manual physical keyboard profile override used for device-specific mappings.
* Supported values: auto, key2, Q25, titan2.
* Supported values: auto, key2, Q25, titan2, mp01.
*/
fun getPhysicalKeyboardProfileOverride(context: Context): String {
val value = getPreferences(context).getString(
Expand Down Expand Up @@ -1602,6 +1602,7 @@ object SettingsManager {
normalized.equals("key2", ignoreCase = true) -> "key2"
normalized.equals("q25", ignoreCase = true) -> "Q25"
normalized.equals("titan2", ignoreCase = true) -> "titan2"
normalized.equals("mp01", ignoreCase = true) -> "mp01"
else -> DEFAULT_PHYSICAL_KEYBOARD_PROFILE_OVERRIDE
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,10 @@ object KeyMappingLoader {
"KEYCODE_APOSTROPHE" to KeyEvent.KEYCODE_APOSTROPHE,
"KEYCODE_COMMA" to KeyEvent.KEYCODE_COMMA,
"KEYCODE_PERIOD" to KeyEvent.KEYCODE_PERIOD,
"KEYCODE_SLASH" to KeyEvent.KEYCODE_SLASH
"KEYCODE_SLASH" to KeyEvent.KEYCODE_SLASH,
// Minimal Phone (MP01) custom keycodes
"KEYCODE_EM" to 666, // Emoji key
"KEYCODE_MIC" to 667 // Mic key
)

fun loadAltKeyMappings(assets: AssetManager, context: Context? = null): Map<Int, String> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ object DeviceSpecific {
private enum class KeyboardFamily {
BLACKBERRY,
UNIHERTZ,
MINIMAL,
UNKNOWN
}

Expand All @@ -22,6 +23,7 @@ object DeviceSpecific {
TITAN_POCKET,
TITAN_SLIM,
TITAN_ORIGINAL,
MINIMAL_PHONE,
UNKNOWN
}

Expand Down Expand Up @@ -254,6 +256,14 @@ object DeviceSpecific {
needsEventRemapping = false
)
}
if (isMinimalPhone(fp)) {
return DeviceProfile(
family = KeyboardFamily.MINIMAL,
model = KeyboardModel.MINIMAL_PHONE,
physicalLayoutName = "mp01",
needsEventRemapping = false
)
}
return DeviceProfile(
family = KeyboardFamily.UNKNOWN,
model = KeyboardModel.UNKNOWN,
Expand All @@ -280,6 +290,7 @@ object DeviceSpecific {
"key2" -> KeyboardModel.KEY2
"q25" -> KeyboardModel.Q25
"titan2" -> KeyboardModel.TITAN_2
"mp01" -> KeyboardModel.MINIMAL_PHONE
else -> currentDeviceProfile().model
}
}
Expand All @@ -288,7 +299,7 @@ object DeviceSpecific {
val normalized = physicalProfileOverride?.trim()?.lowercase().orEmpty()
return when (normalized) {
"", "auto" -> null
"key2", "q25", "titan2" -> normalized
"key2", "q25", "titan2", "mp01" -> normalized
else -> null
}
}
Expand Down Expand Up @@ -329,6 +340,10 @@ object DeviceSpecific {
return fp.containsAny("blackberry key2", "key2", "bbf100")
}

private fun isMinimalPhone(fp: BuildFingerprint): Boolean {
return fp.containsAny("minimal_phone") || (fp.containsAny("mp01") && fp.containsAny("along"))
}

private fun isTitanFamily(fp: BuildFingerprint): Boolean {
return fp.containsAny("unihertz", "titan")
}
Expand All @@ -351,6 +366,7 @@ object DeviceSpecific {
return when (currentDeviceProfile().family) {
KeyboardFamily.BLACKBERRY -> "Blackberry"
KeyboardFamily.UNIHERTZ -> "Unihertz"
KeyboardFamily.MINIMAL -> "Minimal"
KeyboardFamily.UNKNOWN -> "unknown"
}
}
Expand All @@ -362,4 +378,8 @@ object DeviceSpecific {
fun isTitan2Device(): Boolean {
return currentDeviceProfile().model == KeyboardModel.TITAN_2
}

fun isMinimalPhoneDevice(physicalProfileOverride: String? = null): Boolean {
return resolveKeyboardModel(physicalProfileOverride) == KeyboardModel.MINIMAL_PHONE
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ class PhysicalKeyboardInputMethodService : InputMethodService() {
// Keycode for the SYM key
private val KEYCODE_SYM = 63

// Minimal Phone (MP01) custom hardware keycodes
private val KEYCODE_EM = 666 // Emoji key
private val KEYCODE_MIC = 667 // Mic / speech-to-text key

// Single instance to show toasts without overlapping
private var lastLayoutToastText: String? = null
private var lastLayoutToastTime: Long = 0
Expand Down Expand Up @@ -274,6 +278,10 @@ class PhysicalKeyboardInputMethodService : InputMethodService() {
keyCode == KeyEvent.KEYCODE_ALT_RIGHT ||
keyCode == KEYCODE_SYM
}

private fun isMinimalPhoneHardwareActive(): Boolean {
return DeviceSpecific.isMinimalPhoneDevice(physicalKeyboardProfileOverride)
}

/**
* Starts voice input using SpeechRecognizer via SpeechRecognitionManager.
Expand Down Expand Up @@ -2150,6 +2158,37 @@ class PhysicalKeyboardInputMethodService : InputMethodService() {
symChordUsedSinceKeyDown = false
}

// Minimal Phone dedicated keys (skip when Alt is active in any form so alt mappings can fire)
// Gate this behavior to Minimal Phone devices only.
val altActiveForDedicatedKeys = event?.isAltPressed == true || altLatchActive || altOneShot
if (
hasEditableField &&
isMinimalPhoneHardwareActive() &&
keyCode == KEYCODE_EM &&
event?.repeatCount == 0 &&
!altActiveForDedicatedKeys
) {
if (symPage == 4) {
symLayoutController.closeSymPage()
updateStatusBarText()
} else {
ensureInputViewCreated()
symLayoutController.openEmojiPickerPage()
updateStatusBarText()
}
return true
}
if (
hasEditableField &&
isMinimalPhoneHardwareActive() &&
keyCode == KEYCODE_MIC &&
event?.repeatCount == 0 &&
!altActiveForDedicatedKeys
) {
startSpeechRecognition()
return true
}

if (
hasEditableField &&
symTogglePendingOnKeyUp &&
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values-de/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@
<string name="keyboard_profile_option_key2">BlackBerry Key2</string>
<string name="keyboard_profile_option_q25">BlackBerry Q25</string>
<string name="keyboard_profile_option_titan2">Unihertz Titan / Titan 2</string>
<string name="keyboard_profile_option_mp01">Minimal Phone (MP01)</string>
<string name="app_picker_title">App auswählen</string>
<string name="app_picker_cancel">Abbrechen</string>
<string name="app_picker_search_placeholder">Apps suchen…</string>
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/res/values-fr/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,7 @@
<string name="keyboard_profile_option_key2">BlackBerry Key2</string>
<string name="keyboard_profile_option_q25">BlackBerry Q25</string>
<string name="keyboard_profile_option_titan2">Unihertz Titan / Titan 2</string>
<string name="keyboard_profile_option_mp01">Minimal Phone (MP01)</string>
<string name="emoji_picker_search_label">Search</string>
<string name="emoji_picker_search_placeholder">Search emoji...</string>
<string name="emoji_picker_no_results">No emoji found</string>
Expand All @@ -575,4 +576,4 @@
<string name="status_bar_button_language_state_description">Langue %1$s, disposition %2$s</string>
<string name="status_bar_button_hamburger">Menu</string>
<string name="status_bar_button_hamburger_description">Actions rapides</string>
</resources>
</resources>
1 change: 1 addition & 0 deletions app/src/main/res/values-it/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@
<string name="keyboard_profile_option_key2">BlackBerry Key2</string>
<string name="keyboard_profile_option_q25">BlackBerry Q25</string>
<string name="keyboard_profile_option_titan2">Unihertz Titan / Titan 2</string>
<string name="keyboard_profile_option_mp01">Minimal Phone (MP01)</string>
<!-- Static Variation Bar Mode -->
<string name="static_variation_bar_mode_title">Barra variazioni statica</string>
<string name="static_variation_bar_mode_description">Mostra sempre una serie fissa di tasti di utilità nella barra variazioni invece delle variazioni contestuali.</string>
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/res/values-ru/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,7 @@
<string name="keyboard_profile_option_key2">BlackBerry Key2</string>
<string name="keyboard_profile_option_q25">BlackBerry Q25</string>
<string name="keyboard_profile_option_titan2">Unihertz Titan / Titan 2</string>
<string name="keyboard_profile_option_mp01">Minimal Phone (MP01)</string>
<string name="emoji_picker_search_label">Search</string>
<string name="emoji_picker_search_placeholder">Search emoji...</string>
<string name="emoji_picker_no_results">No emoji found</string>
Expand All @@ -575,4 +576,4 @@
<string name="event_keycode_label">KeyCode: </string>
<string name="event_scancode_label">ScanCode: </string>
<string name="status_bar_button_language_state_description">Язык %1$s, раскладка %2$s</string>
</resources>
</resources>
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@
<string name="keyboard_profile_option_key2">BlackBerry Key2</string>
<string name="keyboard_profile_option_q25">BlackBerry Q25</string>
<string name="keyboard_profile_option_titan2">Unihertz Titan / Titan 2</string>
<string name="keyboard_profile_option_mp01">Minimal Phone (MP01)</string>

<!-- About Section -->
<string name="about_github">GitHub</string>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package it.palsoftware.pastiera.data.mappings

import android.view.KeyEvent
import it.palsoftware.pastiera.SettingsManager
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
import org.robolectric.annotation.Config

@RunWith(RobolectricTestRunner::class)
@Config(sdk = [33])
class KeyMappingLoaderTest {

@After
fun tearDown() {
val context = RuntimeEnvironment.getApplication()
SettingsManager.setPhysicalKeyboardProfileOverride(context, "auto")
}

@Test
fun loadAltMappings_mp01ManualOverride_exposesCustomDedicatedKeys() {
val context = RuntimeEnvironment.getApplication()
SettingsManager.setPhysicalKeyboardProfileOverride(context, "mp01")

val mappings = KeyMappingLoader.loadAltKeyMappings(context.assets, context)

assertTrue(mappings.isNotEmpty())
assertEquals("&", mappings[KeyEvent.KEYCODE_Q])
assertEquals("0", mappings[666])
assertEquals(".", mappings[667])
}
}
Loading