Skip to content
Merged
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
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,36 @@ GitHub Release 的发布说明;版本与发布流程见 [docs/RELEASE.md](docs

## [Unreleased]

## [0.0.8-beta.1] - 2026-10-09

第八个测试版:英文大写手势、长按后滑动选字、横屏浮动与左右分离键盘、工具栏的复制和验证码提示,修复九键拼音栏缺音节和排序错误、一次点按两次反馈、分段选词学不到。从 0.0.7-beta.1 覆盖安装即可,用户数据保留。

- 修复九键拼音栏里缺少 xian、liu、pin、tian、tuan、yuan、yue 七个音节:它们和某个词同名(西安、浏览、拼音、提案……),被当成词组而不是音节,选不到,这些音节下的字(先、现、天、月、院……)也排到词后面。
- 九键拼音栏排序:原来按整条读法的分数总和排,两个音节的读法永远压过一个音节的(输入 94264 先出 xia、yi,输入 7426 先出 qi);现在按每个音节的平均分排,输入 94264 的 xiang 排第一,qian 排在 qi 前面。去掉 ng、hm、重复的 lue、nue,以及没有音节以它开头的字母(i、u、v)。
- 候选里的错词:删掉内置词表里 18 个手写错词(泥壕、审么、屑屑、美关系、块乐、膨友……);模糊音候选排在原拼写候选后面;按前缀补全时按字频取音节,不再按字母顺序取到 96 个就停(输入 sh 到不了 shi,输入 x 到不了 xiang);带分词符的输入(xi'an)先查连写后的词。
- 选词学习:只选了一部分输入时(先选“你”,剩下的 haoma 继续打),点中的词现在也会让 Rime 记住,分段选的词组常用后也能前移,不再只有一次选完的单字才会前移。剩余没选的输入不会被学习。
- 英文模式的候选行在上方留白里偏上约 9dp,现在在工具栏条里垂直居中。
- 一次点按两次反馈:系统在手指抬起时多播一次点击音,长按成功时多震一次(不受应用内开关控制),现在都关掉,只由键盘自己按下时反馈一次;文本编辑方向键、剪贴板“重新读取”、符号“管理自定义符号”、语音语言键、面板返回箭头、展开候选长按、退格上滑清空松手时,都不再多响一次;不可点的占位格不再响。长按弹窗打开时震一次,滑过格子和抬起选字时静音(和 HeliBoard、fcitx5 一致)。
- 长按弹窗里滑到格子再抬起、按住 Shift 滑选时,第二根手指按下后不再多打一个字母;手指刚出 Shift 边缘不再改变 Shift 状态;弹窗里选英文字母会用掉“大写一次”;内嵌输入框有焦点时长按弹窗不再输入两次;长按后点同一个键的上半部分不再误触发上滑数字。
- 分离键盘右半边的 G、V 现在能被“按住 Shift 滑选”选中;分离布局在设置面板打开时旋转屏幕,关掉面板后会按新方向重建。
- 横屏:窗口高度改为取键盘自己的期望高度(新进程直接横屏浮动时窗口曾被压成 1 像素);在横屏里新建的键盘顶栏和“左右分离”按钮会立即生效;手动浮动后在横屏停靠,换输入框不再被自动浮起来。
- 工具栏提示:验证码提取改为取与所有关键词的最小间距,排除 yyyyMMdd 日期,新增“动态密码、随机码、登录码、授权码”;点过的验证码不再重复出现;系统自动填充返回空结果时不再清掉键盘自己的提示;遵守“关闭个性化学习”的输入框(无痕);设置里“短信验证码”开关按真实权限显示;权限申请页旋转不重复请求,不进最近任务。
- 文档:README 隐私一节补充可选的短信权限;测试清单、兼容性、端到端脚本同步长按行为和横屏默认浮动。

- 英文键盘的大写:单击 Shift 输入一个大写;快速连点两下锁定大写;长按 Shift 直接锁定(再长按解除)。Shift 键三个状态图标不同:空心 = 小写,实心 = 下一个大写,实心带横线 = 锁定。
- 键盘上方的工具栏降低 10dp(70 → 60),拼音行和候选行压矮;键位上方留白 6dp → 4dp,底部空白 10dp → 19dp,键盘总高度净减 3dp。九键、26 键、数字、笔画键盘都一样。
- 26 键字母键长按也弹出选择面板(另一种大小写 + 右上角的数字或符号),按住滑到目标、松手输入,和搜狗、讯飞一致;上滑仍直接输入右上角的数字或符号。
- 回车键统一:输入拼音时是“确定”;编辑框要求了动作(发送、搜索、前往、下一项、完成)时显示动作文字并高亮;其余(换行、多行、无动作)一律是不高亮的“↵”。九键、26 键、笔画、数字键盘都按同一规则。
- 按键气泡和长按选择面板同高(52dp);长按选择面板缩小(格子 40×44dp),松手后没选就自动消失(松在原键上 1.5 秒后,没松手 4 秒后)。
- 候选字背景上下各缩 0.5dp,工具栏总高不变;键盘整体再上抬 2dp。
- 工具栏提示:刚复制的文字(3 分钟内)显示在工具栏,点一下输入;设置里新增“短信验证码”(默认关闭):打开后请求“读取短信”权限,5 分钟内的验证码短信显示成“验证码 123456”,点一下输入。两项都只在本机读取,不联网;密码框不显示。可在“设置 → 工具栏提示”关闭。
- 按住 Shift 滑到字母再松手,输入那一个大写字母,Shift 状态保持原样(和 Gboard 一样);松手在别处则什么都不输入。
- 长按后滑动选择:九键长按弹出的“数字 + 大小写字母”面板,按住不放滑到目标、松手即输入;松手在原按键上则面板保留,可以再点。小写和数字一行离手指最近,大写在上一行;横屏空间不够时合成一行。长按“1”(@ # /)同样支持。长按之后不再同时触发上滑数字。
- 横屏布局可选:设置里新增“横屏布局”:浮动(默认)、整宽、左右分离。浮动模式自动进入、转回竖屏自动恢复;手动贴底后本次横屏不再自动浮动。左右分离参照搜狗:26 键分成左右两半,G、V 两边都有,两边各一个空格,中间留空;九键一边是九宫格(删除、重输、0、确定在左,符号列在右),另一边是常用字面板(的了我你是……),中间的“镜像”键左右互换。横屏工具栏多一个“左右分离键盘”按钮,点一下分离或恢复。
- 横屏整宽键盘铺满屏宽,之前被缩成中间一小块。
- 横屏顶栏压缩到只比按键行高一点(48dp),输入时拼音在左、候选在右同一行;横屏下按键下方的留白只有 6dp(竖屏仍是 19dp),横屏键盘高度相应降低。HeliBoard、fcitx5 的横屏底部留白为 0。
- 浮动键盘:整张卡片圆角并带细边,底部为系统“返回/切换输入法”按钮留出一条空位,不再盖住最下面一行。

## [0.0.7-beta.1] - 2026-10-06

第七个测试版:九键预编辑不再出现数字,拼音栏点哪个都管用,符号、表情、颜文字大幅补全。从 0.0.6-beta.1 覆盖安装即可,用户数据保留。
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ The app has no `INTERNET` permission, so your text cannot leave the phone.
The word list is in the APK.
- **Autofill (Android 11 and later):** account names and codes from your password manager appear in the toolbar.
- **Numbers and symbols:** each letter key shows a number or symbol.
Swipe up or press and hold to enter it.
Swipe up to enter it. Press and hold to open a popup, then slide to a cell and release.
- **Display support:** portrait, landscape, tablets, foldables, dark theme and large font sizes.
Raw key input fields (terminals, remote desktops, games) and physical keyboards also work.
See [Compatibility](docs/COMPATIBILITY.md).
Expand Down Expand Up @@ -116,6 +116,7 @@ You can install a new release over an old one.
- Voice audio stays in memory. openIME clears it when recognition ends, is canceled or fails.
- In password fields, openIME does not compose pinyin, learn words, read the field text or write logs.
Voice input inserts the final text only.
- The optional SMS code chip is off by default. It asks for the read-SMS permission only when you turn it on, and reads only the last 5 minutes of messages on the device. The copied-text chip shows only text copied in the last 3 minutes. Neither chip shows in password fields or in fields that ask for no personalised learning.
- Crash records contain the exception type and the stack trace. They never contain typed text.
- Uninstall removes all data on the device, including learned words.

Expand Down
3 changes: 2 additions & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ openIME 是一款独立的 Android 系统输入法。拼音候选、用户词库
- **手势**:删除键上滑清空;空格左右滑动移动光标,滑动时底行其他按键锁定。
- **表情联想**:选词后联想栏先给出相关表情(开心 → 😊),词表内置,不联网。
- **自动填充**:Android 11+ 上,密码管理器的账号、验证码直接显示在键盘工具栏位置。
- **数字和符号**:26 键字母键右上角印着数字和符号,上滑或长按直接输入。
- **数字和符号**:26 键字母键右上角印着数字和符号,上滑直接输入;长按弹出选择面板,滑到目标松手输入。
- **适配**:横竖屏、平板与折叠屏、深色模式、大字号;终端、远程桌面、游戏等原始按键输入框;
外接键盘可直接打拼音。详见 [兼容性说明](docs/COMPATIBILITY.md)。
- **自我保护**:连续崩溃或卡死后自动进入安全模式,保证仍然可以打字;
Expand Down Expand Up @@ -85,6 +85,7 @@ openIME 是一款独立的 Android 系统输入法。拼音候选、用户词库

- 应用不声明 `INTERNET` 权限,`allowBackup` 关闭;词典、语音模型和 runtime 都随 APK 提供。
- 语音音频只在内存中处理,结束、取消或失败时清空,不落盘。
- 可选的“短信验证码”默认关闭,打开后才请求“读取短信”权限,只在本机读取最近 5 分钟的验证码;“复制内容提示”只显示 3 分钟内复制的文字,密码框和要求关闭个性化学习的输入框都不显示。
- 密码输入框不组合拼音、不学习词库、不读取输入框内容、不写日志;可使用剪贴板历史(来源应用标记为敏感的内容和要求关闭个性化学习的输入框除外);语音可用,识别结果只在结束时一次性上屏。
- 崩溃记录只保存异常类型和调用栈,不含输入内容。
- 卸载会清除本机全部数据,包括学习到的用户词库。
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.0.7-beta.1
0.0.8-beta.1
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,8 @@ class DisplayEnvironmentInstrumentedTest {

@Test
fun landscapeContentBoxKeepsEveryRowInsideIt() {
// A landscape configuration makes the reference scale follow the height,
// so the content box (padding on both sides) is narrower than the window.
// Rows used to keep the width they were measured with before that padding
// Landscape fills the whole width (padding 0), like Gboard and the Chinese keyboards.
// Rows used to keep the width they were measured with before the padding
// was applied: as wide as the window, clipped to the box.
for (mode in listOf(KeyboardMode.PINYIN_26, KeyboardMode.ENGLISH_26, KeyboardMode.PINYIN_9, KeyboardMode.DIGITS)) {
withKeyboard(
Expand All @@ -110,7 +109,7 @@ class DisplayEnvironmentInstrumentedTest {
val body = keyboard.findViewWithTag<ViewGroup>("keyboard-body")
assertNotNull("keyboard body", body)
val content = body.width - body.paddingLeft - body.paddingRight
assertTrue("$mode must be laid out in a clamped content box (padding ${body.paddingLeft})", body.paddingLeft > 0)
assertEquals("$mode landscape fills the full width", 0, body.paddingLeft)
for (index in 0 until body.childCount) {
val row = body.getChildAt(index)
assertTrue(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -361,15 +361,27 @@ class SpaceCursorAndLetterHintsInstrumentedTest {
}

@Test
fun longPressTypesTheSameHint() = withKeyboard { harness, recorder, keyboard ->
fun longPressOffersTheHintInAPopup() = withKeyboard { harness, recorder, keyboard ->
harness.awaitMain { keyboard.setMode(KeyboardMode.PINYIN_26); true }
listOf('w', 'd', 'k', 'v').forEach { letter ->
listOf('w' to "2", 'd' to "@", 'k' to "*", 'v' to "_").forEach { (letter, hint) ->
val key = settled(harness, keyboard, "key:$letter")
assertTrue(harness.awaitMain { key.performLongClick() })
// The popup lists the other case, then the hint; a tap on its cell types it.
assertTrue("popup cell $hint", harness.awaitMain { chooseFromPopup(keyboard, hint) })
}
assertEquals(listOf("2", "@", "*", "_"), recorder.characters.toList())
}

/** Taps the long-press popup cell showing [text]; false when no such cell is on screen. */
private fun chooseFromPopup(root: View, text: String): Boolean {
fun walk(view: View): android.widget.TextView? {
if (view is android.widget.TextView && view.text.toString() == text && view.hasOnClickListeners()) return view
if (view is android.view.ViewGroup) for (i in 0 until view.childCount) walk(view.getChildAt(i))?.let { return it }
return null
}
return walk(root)?.performClick() == true
}

@Test
fun aPlainTapStillTypesTheLetter() = withKeyboard { harness, recorder, keyboard ->
harness.awaitMain { keyboard.setMode(KeyboardMode.ENGLISH_26); true }
Expand All @@ -386,6 +398,7 @@ class SpaceCursorAndLetterHintsInstrumentedTest {
assertTrue("swipe is off: ${recorder.characters}", recorder.characters.isEmpty())
harness.awaitMain { assertTrue("1" in texts(q)); true }
assertTrue(harness.awaitMain { q.performLongClick() })
assertTrue("popup cell 1", harness.awaitMain { chooseFromPopup(keyboard, "1") })
assertEquals("long press still works", listOf("1"), recorder.characters.toList())
}

Expand Down
11 changes: 11 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<!-- Key-press clicks drive the vibrator directly (normal permission, granted at install). -->
<uses-permission android:name="android.permission.VIBRATE" />
<!-- Optional: show a fresh verification code from a text message in the toolbar. Off until the user turns it on. -->
<uses-permission android:name="android.permission.READ_SMS" />
<uses-feature android:name="android.hardware.telephony" android:required="false" />

<application
android:allowBackup="false"
Expand All @@ -27,6 +30,14 @@
</intent-filter>
</activity>

<activity
android:name=".SmsPermissionActivity"
android:exported="false"
android:taskAffinity=""
android:excludeFromRecents="true"
android:noHistory="true"
android:theme="@android:style/Theme.Translucent.NoTitleBar" />

<activity
android:name=".ImeSettingsActivity"
android:exported="false"
Expand Down
Loading
Loading