Skip to content

Commit

Permalink
Allow AnkiDroid to register itself to open tsv/csv files (#15252)
Browse files Browse the repository at this point in the history
* allow AnkiDroid to import csv files through intent

* use TaskStackBuilder to go back to DeckPicker

* add test for tsv/csv type intent

* remove the unnecessary comment from code

* rename the method to resolveMimeType

* nit picks in IntentHandler
  • Loading branch information
criticalAY authored Jan 19, 2024
1 parent 4ce0389 commit 4a3506d
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 4 deletions.
53 changes: 52 additions & 1 deletion AnkiDroid/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,56 @@
android:pathPattern=".*\\.colpkg"
android:scheme="file"
/>

<!-- tsv files -->
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.tsv"
android:scheme="http"
/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.tsv"
android:scheme="https"
/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.tsv"
android:scheme="content"
/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.tsv"
android:scheme="file"
/>
<!-- csv files -->
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.csv"
android:scheme="http"
/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.csv"
android:scheme="https"
/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.csv"
android:scheme="content"
/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.csv"
android:scheme="file"
/>
</intent-filter>
<!-- MIME type matcher for .apkg files coming from providers like gmail which hide the file extension -->
<intent-filter>
Expand All @@ -187,6 +236,8 @@
<data android:mimeType="application/x-colpkg"/>
<data android:mimeType="application/octet-stream"/>
<data android:mimeType="application/zip"/>
<data android:mimeType="text/tab-separated-values"/>
<data android:mimeType="text/comma-separated-values"/>
</intent-filter>

<!-- Tasker DO_SYNC intent -->
Expand Down
9 changes: 8 additions & 1 deletion AnkiDroid/src/main/java/com/ichi2/anki/Import.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package com.ichi2.anki

import android.app.Activity
import android.content.Intent
import androidx.core.app.TaskStackBuilder
import androidx.core.content.edit
import com.google.android.material.snackbar.Snackbar
import com.ichi2.anki.dialogs.AsyncDialogFragment
Expand Down Expand Up @@ -54,7 +55,13 @@ fun DeckPicker.onSelectedPackageToImport(data: Intent) {

fun Activity.onSelectedCsvForImport(data: Intent) {
val path = ImportUtils.getFileCachedCopy(this, data) ?: return
startActivity(CsvImporter.getIntent(this, path))
val csvImporterIntent = CsvImporter.getIntent(this, path)

val stackBuilder = TaskStackBuilder.create(this)
stackBuilder.addNextIntentWithParentStack(Intent(this, DeckPicker::class.java))
stackBuilder.addNextIntent(csvImporterIntent)

stackBuilder.startActivities()
}

fun DeckPicker.showImportDialog(id: Int, importPath: String) {
Expand Down
26 changes: 24 additions & 2 deletions AnkiDroid/src/main/java/com/ichi2/anki/IntentHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.os.Message
import android.webkit.MimeTypeMap
import androidx.annotation.CheckResult
import androidx.annotation.VisibleForTesting
import androidx.core.content.FileProvider
Expand Down Expand Up @@ -71,6 +72,7 @@ class IntentHandler : Activity() {
val runIfStoragePermissions = { runnable: () -> Unit -> performActionIfStorageAccessible(reloadIntent, action) { runnable() } }
when (getLaunchType(intent)) {
LaunchType.FILE_IMPORT -> runIfStoragePermissions { handleFileImport(intent, reloadIntent, action) }
LaunchType.TEXT_IMPORT -> runIfStoragePermissions { onSelectedCsvForImport(intent) }
LaunchType.SYNC -> runIfStoragePermissions { handleSyncIntent(reloadIntent, action) }
LaunchType.REVIEW -> runIfStoragePermissions { handleReviewIntent(intent) }
LaunchType.DEFAULT_START_APP_IF_NEW -> {
Expand Down Expand Up @@ -202,7 +204,14 @@ class IntentHandler : Activity() {
// COULD_BE_BETTER: Also extract the parameters into here to reduce coupling
@VisibleForTesting
enum class LaunchType {
DEFAULT_START_APP_IF_NEW, FILE_IMPORT, SYNC, REVIEW, COPY_DEBUG_INFO
DEFAULT_START_APP_IF_NEW,

/** colpkg/apkg/unknown */
FILE_IMPORT,

/** csv/tsv */
TEXT_IMPORT,
SYNC, REVIEW, COPY_DEBUG_INFO
}

companion object {
Expand All @@ -220,7 +229,11 @@ class IntentHandler : Activity() {
fun getLaunchType(intent: Intent): LaunchType {
val action = intent.action
return if (Intent.ACTION_VIEW == action && isValidViewIntent(intent)) {
LaunchType.FILE_IMPORT
val mimeType = intent.resolveMimeType()
when (mimeType) {
"text/tab-separated-values", "text/comma-separated-values" -> LaunchType.TEXT_IMPORT
else -> LaunchType.FILE_IMPORT
}
} else if ("com.ichi2.anki.DO_SYNC" == action) {
LaunchType.SYNC
} else if (intent.hasExtra(ReminderService.EXTRA_DECK_ID)) {
Expand All @@ -232,6 +245,15 @@ class IntentHandler : Activity() {
}
}

private fun Intent.resolveMimeType(): String? {
return if (type == null) {
val extension = MimeTypeMap.getFileExtensionFromUrl(data.toString())
MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
} else {
type
}
}

/**
* Send a Message to AnkiDroidApp so that the DialogMessageHandler forces a sync
*/
Expand Down
15 changes: 15 additions & 0 deletions AnkiDroid/src/test/java/com/ichi2/anki/IntentHandlerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,21 @@ class IntentHandlerTest {
assertThat(expected, equalTo(LaunchType.DEFAULT_START_APP_IF_NEW))
}

@Test
fun textImportIntentReturnsTextImport() {
// TSV import
var intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(Uri.parse("content://valid"), "text/tab-separated-values")
var expected = getLaunchType(intent)
assertThat(expected, equalTo(LaunchType.TEXT_IMPORT))

// CSV import
intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(Uri.parse("content://valid"), "text/comma-separated-values")
expected = getLaunchType(intent)
assertThat(expected, equalTo(LaunchType.TEXT_IMPORT))
}

@Test
fun viewWithNoDataPerformsDefaultAction() {
// #6312 - Smart Launcher double-tap launches us with this. No data at all in the intent
Expand Down

0 comments on commit 4a3506d

Please sign in to comment.