diff --git a/Android/app/src/main/AndroidManifest.xml b/Android/app/src/main/AndroidManifest.xml
index e35061b..c389764 100644
--- a/Android/app/src/main/AndroidManifest.xml
+++ b/Android/app/src/main/AndroidManifest.xml
@@ -22,32 +22,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/Android/app/src/main/java/com/majinnaibu/monstercards/MainActivity.java b/Android/app/src/main/java/com/majinnaibu/monstercards/MainActivity.java
index efac09e..14f2f94 100644
--- a/Android/app/src/main/java/com/majinnaibu/monstercards/MainActivity.java
+++ b/Android/app/src/main/java/com/majinnaibu/monstercards/MainActivity.java
@@ -1,8 +1,10 @@
package com.majinnaibu.monstercards;
import android.content.Intent;
+import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
+import android.provider.OpenableColumns;
import android.view.MenuItem;
import androidx.annotation.NonNull;
@@ -23,6 +25,7 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.util.Locale;
import java.util.Objects;
public class MainActivity extends AppCompatActivity {
@@ -76,26 +79,61 @@ public class MainActivity extends AppCompatActivity {
private String readMonsterJSONFromIntent(@NonNull Intent intent) {
String action = intent.getAction();
Bundle extras = intent.getExtras();
- String type = intent.getType();
- String json;
Uri uri = null;
- if ("android.intent.action.SEND".equals(action) && "text/plain".equals(type)) {
- uri = extras.getParcelable("android.intent.extra.STREAM");
- } else if ("android.intent.action.VIEW".equals(action) && ("text/plain".equals(type) || "application/octet-stream".equals(type))) {
+ if ("android.intent.action.SEND".equals(action)) {
+ if (extras != null) {
+ uri = extras.getParcelable(Intent.EXTRA_STREAM);
+ }
+ } else if ("android.intent.action.VIEW".equals(action) || "android.intent.action.EDIT".equals(action)) {
uri = intent.getData();
} else {
- Logger.logError(String.format("unexpected launch configuration action: %s, type: %s", action, type));
+ Logger.logError(String.format("unexpected launch configuration action: %s", action));
}
- if (uri == null) {
+
+ if (uri == null || !isMonsterFile(uri)) {
+ if (uri != null) {
+ Logger.logError("Ignored file because extension is not .monster or .monster.txt: " + uri);
+ }
return null;
}
- json = readContentsOfUri(uri);
+
+ String json = readContentsOfUri(uri);
if (StringHelper.isNullOrEmpty(json)) {
return null;
}
return json;
}
+ private boolean isMonsterFile(@NonNull Uri uri) {
+ String fileName = getFileNameFromUri(uri);
+ if (fileName == null) {
+ return false;
+ }
+ String lowerName = fileName.toLowerCase(Locale.ROOT);
+ return lowerName.endsWith(".monster") || lowerName.endsWith(".monster.txt");
+ }
+
+ @Nullable
+ private String getFileNameFromUri(@NonNull Uri uri) {
+ String displayName = null;
+ if ("content".equals(uri.getScheme())) {
+ try (Cursor cursor = getContentResolver().query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null)) {
+ if (cursor != null && cursor.moveToFirst()) {
+ int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
+ if (nameIndex != -1) {
+ displayName = cursor.getString(nameIndex);
+ }
+ }
+ } catch (Exception e) {
+ Logger.logError("Error querying display name from content URI", e);
+ }
+ }
+ if (displayName == null) {
+ displayName = uri.getPath();
+ }
+ return displayName;
+ }
+
@Nullable
private String readContentsOfUri(Uri uri) {
StringBuilder builder = new StringBuilder();
diff --git a/docs/Import-Export.md b/docs/Import-Export.md
index a3f65b1..b951597 100644
--- a/docs/Import-Export.md
+++ b/docs/Import-Export.md
@@ -162,3 +162,16 @@ Extend the generic sharing feature with specialized, direct sharing channels (to
- **Sub-Step 7.1 (NFC Sharing)**: Share monster data directly between devices via NFC (NDEF records / Android Beam).
- **Sub-Step 7.2 (Bluetooth / Wi-Fi Direct)**: Share monster files directly between nearby Android devices via Bluetooth / Wi-Fi.
- **Sub-Step 7.3 (Web URL with Embedded Payload)**: Generate a shareable Web URL containing compressed/base64-encoded monster JSON data.
+
+---
+
+## 3. Implementation Checklist & Status
+
+- [x] **Step 0**: Restrict app intent filters in `AndroidManifest.xml` (`.monster` & `.monster.txt`) and add runtime filename validation in `MainActivity.java`.
+- [ ] **Step 1**: Refactor import & conversion code into a shared `EntityImporter` interface and `TetraCubeMonsterImporter` class.
+- [ ] **Step 2**: Update Tetra-cube importer class to support the newest Tetra-cube format (`bonusActions`, `mythics`, `blind`, intro descriptions).
+- [ ] **Step 3**: Import from D&D Beyond URL (`https://www.dndbeyond.com/characters/49074997` fetching from character service endpoint `character/v2/character/49074997`).
+- [ ] **Step 4**: Export to internal format described by Open5e document (`Open5eExporter`).
+- [ ] **Step 5**: Import from internal format described by Open5e document (`Open5eImporter`).
+- [ ] **Step 6**: Generic Android Share button feature (`ACTION_SEND`).
+- [ ] **Step 7**: Specific share targets (7.1 NFC, 7.2 Bluetooth, 7.3 Embedded Web URL).