diff options
| author | Joel Klinghed <the_jk@spawned.biz> | 2024-09-10 23:46:21 +0200 |
|---|---|---|
| committer | Joel Klinghed <the_jk@spawned.biz> | 2024-09-10 23:50:27 +0200 |
| commit | 994672608db65a68b3ba3db8fa37bb613de89c20 (patch) | |
| tree | 4873d7177a7949f7e1501e9494e2897e2da35d03 /libs/documents/src/androidTest | |
| parent | 3e1b734cd804dbdb8bff8bbdc944a0fd141bed75 (diff) | |
Add libs:documents
Reads the abomination that is SAF, or Androids best effort to make
files and directories completely and utterly unusable on Android.
The androidTest was (and is) a pain, only known to work on a
Pixel3 API 34 emulator but it showed a lot of things that the
fake content provider in the unit tests failed to show.
Diffstat (limited to 'libs/documents/src/androidTest')
3 files changed, 248 insertions, 0 deletions
diff --git a/libs/documents/src/androidTest/AndroidManifest.xml b/libs/documents/src/androidTest/AndroidManifest.xml new file mode 100644 index 0000000..ddb7408 --- /dev/null +++ b/libs/documents/src/androidTest/AndroidManifest.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> + <application> + <activity android:name="org.the_jk.cleversync.documents.test.TestActivity" /> + </application> +</manifest> diff --git a/libs/documents/src/androidTest/java/org/the_jk/cleversync/documents/DocumentTreeAndroidTest.kt b/libs/documents/src/androidTest/java/org/the_jk/cleversync/documents/DocumentTreeAndroidTest.kt new file mode 100644 index 0000000..d0d6bb3 --- /dev/null +++ b/libs/documents/src/androidTest/java/org/the_jk/cleversync/documents/DocumentTreeAndroidTest.kt @@ -0,0 +1,216 @@ +package org.the_jk.cleversync.documents + +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Environment +import android.os.Handler +import android.os.Looper +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.By +import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.Until +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.the_jk.cleversync.TreeAbstractTest +import org.the_jk.cleversync.documents.test.TestActivity +import java.io.File +import kotlin.time.Duration.Companion.seconds + +@RunWith(AndroidJUnit4::class) +class DocumentTreeAndroidTest : TreeAbstractTest() { + @get:Rule + val activityRule = ActivityScenarioRule(TestActivity::class.java) + + private lateinit var testDir: File + private lateinit var mainHandler: Handler + + @Before + fun setUp() { + // If tests fail in a certain way we lose the right to download the + // directory which confuses the rest of the tests as they expect + // an empty directory. Better, create a new directory if needed. + // Build script tries to remove them all after a run. + var i = 0 + while (true) { + testDir = File(Environment.getExternalStorageDirectory(), "Download/DocumentTreeTest-$i") + if (!testDir.exists()) break + testDir.deleteRecursively() + if (!testDir.exists()) break + i++ + } + testDir.mkdirs() + } + + @After + fun tearDown() { + testDir.deleteRecursively() + } + + @Test + override fun empty() { + runTest { super.empty() } + } + + @Test + override fun emptyLive() { + runTest { super.emptyLive() } + } + + @Test + override fun createDirectory() { + runTest { super.createDirectory() } + } + + @Test(timeout = 30000) + override fun observeCreateDirectory() { + runTest { super.observeCreateDirectory() } + } + + @Test + override fun createFile() { + runTest { super.createFile() } + } + + @Test + override fun overwriteFile() { + runTest { super.overwriteFile() } + } + + @Test + override fun removeDir() { + runTest { super.removeDir() } + } + + @Test(timeout = 30000) + override fun removeDirLive() { + runTest { super.removeDirLive() } + } + + @Test + override fun sameDir() { + runTest { super.sameDir() } + } + + @Test + override fun sameFile() { + runTest { super.sameFile() } + } + + @Test + override fun removeDirWithContent() { + runTest { super.removeDirWithContent() } + } + + @Test + override fun removeWrongType() { + runTest { super.removeWrongType() } + } + + @Test + override fun removeFile() { + runTest { super.removeFile() } + } + + @Test + override fun names() { + runTest { super.names() } + } + + @Test + override fun openNonExistent() { + runTest { super.openNonExistent() } + } + + @Test + override fun lastModified() { + runTest { super.lastModified() } + } + + override fun supportSymlinks() = false + + override fun idle() { + // Called on mainLooper while waiting for testLooper, should usually + // not happen as the events we wait for have often already happened + // but make sure to not busy loop. + Thread.sleep(1000) + } + + override fun onMain(block: () -> Unit) { + mainHandler.post(block) + } + + // TODO: Figure out how to do this with @Before and @After + private fun runTest(body: () -> Unit) { + val context = ApplicationProvider.getApplicationContext<Context>() + val contentResolver = context.contentResolver + var bodyException: Throwable? = null + + val fullPath = testDir.toString() + val index = fullPath.indexOf("/Download/") + val path = fullPath.substring(index + 1) + val initialUri = Uri.Builder() + .scheme("content") + .authority("com.android.externalstorage.documents") + .encodedPath("document/primary%3A" + path.replace("/", "%2F")) + .build() + + Looper.prepare() + val testLooper = Looper.myLooper()!! + + activityRule.scenario.onActivity { activity -> + mainHandler = Handler(Looper.getMainLooper()) + + activity.setReceiver { uri -> + contentResolver.takePersistableUriPermission( + uri, + Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION, + ) + + tree = DocumentTreeFactory.createModifiableTree( + contentResolver, + uri, + 1.seconds, + ) + + try { + body() + } catch (e: Throwable) { + // Exceptions here are not well handled, re-throw + // outside scenario + bodyException = e + } + + // Quit the test loop when main looper is idle + Looper.getMainLooper().queue.addIdleHandler { + testLooper.quit() + false + } + } + activity.launcher.launch(initialUri) + + val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + device.wait( + Until.hasObject(By.pkg("com.google.android.documentsui").depth(0)), + 10000, + ) + + device.findObject(By.text("USE THIS FOLDER").clazz("android.widget.Button")).clickAndWait( + Until.newWindow(), + 500, + ) + + device.findObject(By.text("ALLOW").clazz("android.widget.Button")).click() + } + + Looper.loop() + + bodyException?.let { throw it } + } +} diff --git a/libs/documents/src/androidTest/java/org/the_jk/cleversync/documents/test/TestActivity.kt b/libs/documents/src/androidTest/java/org/the_jk/cleversync/documents/test/TestActivity.kt new file mode 100644 index 0000000..d6e60cb --- /dev/null +++ b/libs/documents/src/androidTest/java/org/the_jk/cleversync/documents/test/TestActivity.kt @@ -0,0 +1,26 @@ +package org.the_jk.cleversync.documents.test + +import android.net.Uri +import android.os.Bundle +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.fragment.app.FragmentActivity + +class TestActivity : FragmentActivity() { + lateinit var launcher: ActivityResultLauncher<Uri?> + private var receiver: ((Uri) -> Unit)? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + launcher = registerForActivityResult( + ActivityResultContracts.OpenDocumentTree(), + ) { uri -> + if (uri != null) receiver?.invoke(uri) + } + } + + fun setReceiver(receiver: (Uri) -> Unit) { + this.receiver = receiver + } +} |
