SHA256
Add real native onboarding/auth/dashboard/tasks/notes/shopping, origin-bound HTTPS and encrypted AndroidKeyStore session persistence. Archive the superseded browser renderer and tests. Verify 10 JVM tests, 6 native API-35 emulator tests, lintDebug/lintRelease and debug/unsigned release APK/AAB builds; the real upstream suite passes 7 tests and exports a nonblank native fixture screenshot. Tink host-tool update removes the old generated-protobuf warning. Full module/visual parity remains explicitly incomplete.
72 lines
3.5 KiB
Kotlin
72 lines
3.5 KiB
Kotlin
package community.yuvomi.mobile
|
|
|
|
import android.Manifest
|
|
import android.accessibilityservice.AccessibilityServiceInfo
|
|
import android.content.Context
|
|
import android.content.pm.PackageManager
|
|
import android.os.Build
|
|
import android.view.accessibility.AccessibilityNodeInfo
|
|
import androidx.test.core.app.ActivityScenario
|
|
import androidx.test.core.app.ApplicationProvider
|
|
import androidx.test.espresso.Espresso.onView
|
|
import androidx.test.espresso.action.ViewActions.click
|
|
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
|
|
import androidx.test.espresso.matcher.ViewMatchers.withText
|
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
import androidx.test.platform.app.InstrumentationRegistry
|
|
import org.junit.Assert.assertEquals
|
|
import org.junit.Assert.assertTrue
|
|
import org.junit.Test
|
|
import org.junit.runner.RunWith
|
|
|
|
@RunWith(AndroidJUnit4::class)
|
|
class LocalNetworkPermissionTest {
|
|
@Test fun permissionIsOptionalAndHonorsSystemDenialAndGrant() {
|
|
val context = ApplicationProvider.getApplicationContext<Context>()
|
|
context.getSharedPreferences("server", Context.MODE_PRIVATE).edit().clear().commit()
|
|
ActivityScenario.launch(MainActivity::class.java).use {
|
|
if (Build.VERSION.SDK_INT < 37 ||
|
|
context.checkSelfPermission(Manifest.permission.ACCESS_LOCAL_NETWORK) == PackageManager.PERMISSION_GRANTED
|
|
) {
|
|
onView(withText("Allow private-network servers")).check(doesNotExist())
|
|
return@use
|
|
}
|
|
val automation = InstrumentationRegistry.getInstrumentation().uiAutomation
|
|
automation.serviceInfo = automation.serviceInfo.apply { flags = flags or AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS }
|
|
onView(withText("Allow private-network servers")).perform(click())
|
|
onView(withText("Cancel")).perform(click())
|
|
assertEquals(PackageManager.PERMISSION_DENIED, context.checkSelfPermission(Manifest.permission.ACCESS_LOCAL_NETWORK))
|
|
onView(withText("Allow private-network servers")).perform(click())
|
|
onView(withText("Continue")).perform(click())
|
|
chooseSystemButton("permission_deny_button")
|
|
await { context.checkSelfPermission(Manifest.permission.ACCESS_LOCAL_NETWORK) == PackageManager.PERMISSION_DENIED }
|
|
onView(withText("Allow private-network servers")).perform(click())
|
|
onView(withText("Continue")).perform(click())
|
|
chooseSystemButton("permission_allow_button")
|
|
await { context.checkSelfPermission(Manifest.permission.ACCESS_LOCAL_NETWORK) == PackageManager.PERMISSION_GRANTED }
|
|
}
|
|
}
|
|
|
|
private fun chooseSystemButton(id: String) {
|
|
val automation = InstrumentationRegistry.getInstrumentation().uiAutomation
|
|
await {
|
|
val node = find(automation.rootInActiveWindow, id) ?: return@await false
|
|
node.performAction(AccessibilityNodeInfo.ACTION_CLICK)
|
|
}
|
|
}
|
|
private fun find(node: AccessibilityNodeInfo?, id: String): AccessibilityNodeInfo? {
|
|
if (node == null) return null
|
|
if (node.viewIdResourceName?.endsWith("/$id") == true) return node
|
|
for (index in 0 until node.childCount) find(node.getChild(index), id)?.let { return it }
|
|
return null
|
|
}
|
|
private fun await(condition: () -> Boolean) {
|
|
val deadline = System.nanoTime() + 10_000_000_000L
|
|
while (System.nanoTime() < deadline) {
|
|
if (condition()) return
|
|
Thread.sleep(100)
|
|
}
|
|
assertTrue("System permission response timed out", condition())
|
|
}
|
|
}
|