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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
- Dark theme support and redesigned application/IME interfaces.
- Real Grammalecte engine regression tests covering representative French corrections.
- Android instrumentation coverage for the packaged Grammalecte engine and spell-checker result mapping.
- Android framework integration tests covering IME discovery, `PROCESS_TEXT` exposure and end-to-end `SpellCheckerService` requests.
- Opt-in real-engine performance characterization for cold start, sentence analysis and paragraph analysis.

### Changed
Expand Down
4 changes: 4 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Exit criterion: an installable APK performs local French spelling and grammar co
- [x] Validate native Android text fields
- [x] Validate SMS editing
- [x] Identify applications that bypass the Android spell-checker framework
- [x] Add an end-to-end Android framework test for `SpellCheckerService`
- [ ] Build and publish a reproducible compatibility matrix
- [ ] Test additional messaging and mail applications
- [ ] Test additional Chromium/WebView editors
Expand All @@ -53,6 +54,7 @@ Exit criterion: an installable APK performs local French spelling and grammar co
- [x] Copy corrected text when the caller exposes a read-only selection
- [x] Validate the workflow in Firefox
- [x] Identify applications that ignore returned `PROCESS_TEXT` replacement
- [x] Add automated `ACTION_PROCESS_TEXT` exposure coverage

### IME / InputConnection fallback

Expand All @@ -68,6 +70,8 @@ Exit criterion: an installable APK performs local French spelling and grammar co
- [x] Add an easy way to return to the previous keyboard
- [ ] Review QuickJS execution serialization inside the IME
- [ ] Add automated IME integration tests where practical
- [x] Cover IME discovery, permission and metadata
- [ ] Cover IME lifecycle and selected-text replacement through `InputConnection`

Accessibility-based replacement remains intentionally out of scope. The IME/InputConnection path is the preferred generic fallback for editable fields.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package fr.grammalecteandroid.unofficial

import android.Manifest
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class ImeManifestTest {
@Suppress("DEPRECATION")
@Test
fun imeServiceIsDiscoverableAndProtected() {
val context =
ApplicationProvider.getApplicationContext<Context>()

val services =
context.packageManager.queryIntentServices(
Intent(INPUT_METHOD_ACTION)
.setPackage(context.packageName),
PackageManager.GET_META_DATA,
)

val service =
services.single {
it.serviceInfo.name ==
GrammalecteImeService::class.java.name
}

assertEquals(
Manifest.permission.BIND_INPUT_METHOD,
service.serviceInfo.permission,
)
}

@Suppress("DEPRECATION")
@Test
fun imeServiceDeclaresExpectedMetadata() {
val context =
ApplicationProvider.getApplicationContext<Context>()

val services =
context.packageManager.queryIntentServices(
Intent(INPUT_METHOD_ACTION)
.setPackage(context.packageName),
PackageManager.GET_META_DATA,
)

val service =
services.single {
it.serviceInfo.name ==
GrammalecteImeService::class.java.name
}

val metadata = service.serviceInfo.metaData

assertNotNull(
"Expected android.view.im metadata on GrammalecteImeService",
metadata,
)

assertEquals(
R.xml.grammalecte_input_method,
metadata.getInt(INPUT_METHOD_METADATA),
)
}

private companion object {
const val INPUT_METHOD_ACTION = "android.view.InputMethod"
const val INPUT_METHOD_METADATA = "android.view.im"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package fr.grammalecteandroid.unofficial

import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class ProcessTextManifestTest {
@Suppress("DEPRECATION")
@Test
fun processTextActivityIsDiscoverableAndExported() {
val context =
ApplicationProvider.getApplicationContext<Context>()

val activities =
context.packageManager.queryIntentActivities(
Intent(Intent.ACTION_PROCESS_TEXT).apply {
type = "text/plain"
setPackage(context.packageName)
},
PackageManager.MATCH_DEFAULT_ONLY,
)

val activity =
activities.single {
it.activityInfo.name ==
ProcessTextActivity::class.java.name
}

assertTrue(
"PROCESS_TEXT activity must be exported",
activity.activityInfo.exported,
)
}

@Suppress("DEPRECATION")
@Test
fun processTextActivityAcceptsPlainText() {
val context =
ApplicationProvider.getApplicationContext<Context>()

val activities =
context.packageManager.queryIntentActivities(
Intent(Intent.ACTION_PROCESS_TEXT).apply {
type = "text/plain"
setPackage(context.packageName)
},
PackageManager.MATCH_DEFAULT_ONLY,
)

assertTrue(
"Expected Corriger avec Grammalecte for text/plain PROCESS_TEXT",
activities.any {
it.activityInfo.name ==
ProcessTextActivity::class.java.name
},
)
}
}
Loading
Loading