diff --git a/sdk-core/src/main/java/com/xuqm/sdk/file/FileSDK.kt b/sdk-core/src/main/java/com/xuqm/sdk/file/FileSDK.kt index e513b7d..c81d2cb 100644 --- a/sdk-core/src/main/java/com/xuqm/sdk/file/FileSDK.kt +++ b/sdk-core/src/main/java/com/xuqm/sdk/file/FileSDK.kt @@ -251,7 +251,9 @@ object FileSDK { fileName: String, destination: FileDownloadDestination = FileDownloadDestination.Sandbox, ): File { + android.util.Log.d("XWV", "saveBlobDownload: fileName=$fileName, dest=$destination, b64Len=${base64Data.length}") val bytes = android.util.Base64.decode(base64Data, android.util.Base64.DEFAULT) + android.util.Log.d("XWV", "saveBlobDownload: decoded ${bytes.size} bytes") val baseDir = when (destination) { FileDownloadDestination.PublicDownloads -> { val appName = context.applicationInfo.loadLabel(context.packageManager).toString() @@ -260,14 +262,21 @@ object FileSDK { android.os.Environment.DIRECTORY_DOWNLOADS ), appName, - ).apply { mkdirs() } + ).apply { + val created = mkdirs() + android.util.Log.d("XWV", "saveBlobDownload: PublicDownloads dir=$absolutePath, created=$created, exists=${exists()}") + } } FileDownloadDestination.Sandbox -> { - context.getExternalFilesDir(null) ?: context.filesDir + (context.getExternalFilesDir(null) ?: context.filesDir).also { + android.util.Log.d("XWV", "saveBlobDownload: Sandbox dir=${it.absolutePath}") + } } } val target = uniqueFile(baseDir, fileName.takeIf { it.isNotBlank() } ?: "download.bin") + android.util.Log.d("XWV", "saveBlobDownload: writing to ${target.absolutePath}") target.writeBytes(bytes) + android.util.Log.d("XWV", "saveBlobDownload: done, size=${target.length()}") return target } diff --git a/sdk-webview/src/main/java/com/xuqm/sdk/webview/XWebViewView.kt b/sdk-webview/src/main/java/com/xuqm/sdk/webview/XWebViewView.kt index fd7dd5c..44586a9 100644 --- a/sdk-webview/src/main/java/com/xuqm/sdk/webview/XWebViewView.kt +++ b/sdk-webview/src/main/java/com/xuqm/sdk/webview/XWebViewView.kt @@ -100,17 +100,29 @@ internal fun buildDialogOverrideJs(bridgeName: String) = """ URL.revokeObjectURL = function() {}; function readBlobAndPost(blobUrl, filename) { + console.log('[XWV] readBlobAndPost start, url=' + blobUrl + ', filename=' + filename); fetch(blobUrl) - .then(function(r) { return r.blob(); }) + .then(function(r) { + console.log('[XWV] fetch ok, size=' + r.size); + return r.blob(); + }) .then(function(blob) { + console.log('[XWV] blob ok, size=' + blob.size + ', type=' + blob.type); var reader = new FileReader(); reader.onloadend = function() { var b64 = reader.result.split(',')[1]; + console.log('[XWV] base64 ready, len=' + (b64 ? b64.length : 0)); post({ __xwv: 'blobdownload', url: blobUrl, filename: filename, data: b64 }); + console.log('[XWV] blobdownload posted'); + }; + reader.onerror = function(e) { + console.log('[XWV] FileReader error: ' + String(e)); + post({ __xwv: 'bloberror', msg: 'FileReader error: ' + String(e) }); }; reader.readAsDataURL(blob); }) .catch(function(err) { + console.log('[XWV] readBlobAndPost ERROR: ' + String(err)); post({ __xwv: 'bloberror', msg: String(err) }); }); } @@ -125,6 +137,7 @@ internal fun buildDialogOverrideJs(bridgeName: String) = """ var hasDownloadAttr = el.hasAttribute('download'); var dlName = el.getAttribute('download') || ''; var isDL = hasDownloadAttr || dlRe.test(href); + console.log('[XWV] tryInterceptAnchor href=' + href + ', hasDownload=' + hasDownloadAttr + ', isDL=' + isDL); if (isDL) { if (e) { e.preventDefault(); e.stopPropagation(); } if (href.startsWith('blob:')) { @@ -180,9 +193,11 @@ internal class XWebViewJsBridge( ) { @JavascriptInterface fun postMessage(data: String) { + android.util.Log.d("XWV", "postMessage: ${data.take(200)}") mainHandler.post { val json = runCatching { JSONObject(data) }.getOrNull() val xwv = json?.optString("__xwv")?.takeIf { it.isNotEmpty() } + android.util.Log.d("XWV", "postMessage parsed: xwv=$xwv") if (xwv != null && json != null) { onXwvMessage()?.invoke(xwv, json) } else { @@ -229,6 +244,7 @@ fun XWebViewView( // Handles __xwv: 'download' / 'blobdownload' messages from the injected JS. val xwvMessageHandler: (String, JSONObject) -> Unit = handler@{ type, payload -> + android.util.Log.d("XWV", "xwvMessage type=$type, payload=${payload.toString().take(200)}") when (type) { "download" -> { val url = payload.optString("url").takeIf { it.isNotBlank() } ?: return@handler @@ -267,19 +283,26 @@ fun XWebViewView( "blobdownload" -> { val url = payload.optString("url") val filename = payload.optString("filename").takeIf { it.isNotBlank() } ?: "download.bin" - val b64 = payload.optString("data").takeIf { it.isNotBlank() } ?: return@handler + val b64 = payload.optString("data").takeIf { it.isNotBlank() } ?: run { + android.util.Log.e("XWV", "blobdownload: empty data") + return@handler + } + android.util.Log.d("XWV", "blobdownload: filename=$filename, b64Len=${b64.length}, dest=${config.downloadDestination}") coroutineScope.launch(Dispatchers.IO) { runCatching { FileSDK.saveBlobDownload(context, b64, filename, config.downloadDestination) }.onSuccess { file -> + android.util.Log.d("XWV", "blobdownload saved: ${file.absolutePath}, size=${file.length()}") // For image files, save to the system gallery (相册) so they appear in Photos val savedToGallery = runCatching { FileSDK.saveImageToGallery(context, file) }.getOrDefault(false) + android.util.Log.d("XWV", "blobdownload savedToGallery=$savedToGallery") withContext(Dispatchers.Main) { dispatchDownloadEvent("__xwvDownloadDone", url, ",success:true") // Only open with file viewer when not saved to gallery (e.g. zip, docx) if (!savedToGallery) FileSDK.openFile(context, file) } }.onFailure { e -> + android.util.Log.e("XWV", "blobdownload FAILED", e) withContext(Dispatchers.Main) { dispatchDownloadEvent("__xwvDownloadDone", url, ",success:false,error:'${e.message?.escapeJs()}'") }