From c008440c80b7532c561224dcc74d3ce03a6cce52 Mon Sep 17 00:00:00 2001 From: Gabriel Debes Date: Thu, 7 Apr 2022 14:50:24 -0700 Subject: [PATCH] Fix Java ESlint --- .../src/main/java/com/getcapacitor/App.java | 6 +- .../main/java/com/getcapacitor/Bridge.java | 1 - .../com/getcapacitor/DownloadJSInterface.java | 142 +++++++++++------- .../DownloadJSOperationController.java | 70 +++++---- .../com/getcapacitor/DownloadJSProxy.java | 32 ++-- 5 files changed, 154 insertions(+), 97 deletions(-) diff --git a/android/capacitor/src/main/java/com/getcapacitor/App.java b/android/capacitor/src/main/java/com/getcapacitor/App.java index 3b3713b5ce..b297996f13 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/App.java +++ b/android/capacitor/src/main/java/com/getcapacitor/App.java @@ -18,7 +18,11 @@ public interface AppRestoredListener { void onAppRestored(PluginResult result); } - public enum DownloadStatus { STARTED, COMPLETED, FAILED } + public enum DownloadStatus { + STARTED, + COMPLETED, + FAILED + } /** * Interface for callbacks when app is receives download request from webview. diff --git a/android/capacitor/src/main/java/com/getcapacitor/Bridge.java b/android/capacitor/src/main/java/com/getcapacitor/Bridge.java index e9f20f1b5d..fddea441b5 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/Bridge.java +++ b/android/capacitor/src/main/java/com/getcapacitor/Bridge.java @@ -426,7 +426,6 @@ public void reset() { private void initWebView() { WebSettings settings = webView.getSettings(); settings.setJavaScriptEnabled(true); - settings.setAllowFileAccess(true); webView.addJavascriptInterface(this.downloadProxy.jsInterface(), this.downloadProxy.jsInterfaceName()); webView.setDownloadListener(this.downloadProxy); settings.setDomStorageEnabled(true); diff --git a/android/capacitor/src/main/java/com/getcapacitor/DownloadJSInterface.java b/android/capacitor/src/main/java/com/getcapacitor/DownloadJSInterface.java index 56a71f4160..bacc1e0809 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/DownloadJSInterface.java +++ b/android/capacitor/src/main/java/com/getcapacitor/DownloadJSInterface.java @@ -1,10 +1,8 @@ package com.getcapacitor; import android.webkit.JavascriptInterface; - import androidx.activity.result.ActivityResultLauncher; import androidx.annotation.Nullable; - import java.util.HashMap; import java.util.UUID; @@ -15,17 +13,24 @@ * to the proxy in order to have that code executed exclusively for that request. */ public class DownloadJSInterface { - final private DownloadJSOperationController operationsController; - final private ActivityResultLauncher launcher; - final private HashMap pendingInputs; - final private Bridge bridge; + + private final DownloadJSOperationController operationsController; + private final ActivityResultLauncher launcher; + private final HashMap pendingInputs; + private final Bridge bridge; + // public DownloadJSInterface(Bridge bridge) { this.operationsController = new DownloadJSOperationController(bridge.getActivity()); this.pendingInputs = new HashMap<>(); this.bridge = bridge; - this.launcher = bridge.getActivity().registerForActivityResult(this.operationsController, - result -> Logger.debug("DownloadJSActivity result", String.valueOf(result))); + this.launcher = + bridge + .getActivity() + .registerForActivityResult( + this.operationsController, + result -> Logger.debug("DownloadJSActivity result", String.valueOf(result)) + ); } /* JavascriptInterface imp. */ @@ -34,6 +39,7 @@ public void receiveContentTypeFromJavascript(String contentType, String operatio //Transition pending input operation to started with resolved content type this.transitionPendingInputOperation(operationID, contentType, null); } + @JavascriptInterface public void receiveStreamChunkFromJavascript(String chunk, String operationID) { //Guarantee pending input transition to started operation (when no content type is resolved) @@ -41,6 +47,7 @@ public void receiveStreamChunkFromJavascript(String chunk, String operationID) { //Append data to operation this.operationsController.appendToOperation(operationID, chunk); } + @JavascriptInterface public void receiveStreamErrorFromJavascript(String error, String operationID) { //Guarantee pending input transition to 'started-but-stale' operation before actually failing @@ -50,6 +57,7 @@ public void receiveStreamErrorFromJavascript(String error, String operationID) { //Notify this.bridge.getApp().fireDownloadUpdate(operationID, App.DownloadStatus.FAILED, error); } + @JavascriptInterface public void receiveStreamCompletionFromJavascript(String operationID) { //Complete operation signal @@ -70,64 +78,82 @@ public String getJavascriptBridgeForURL(String fileURL, String contentDispositio //will wait either stream start on content-type resolution to start asking //for file pick and stream drain String operationID = UUID.randomUUID().toString(); - DownloadJSOperationController.Input input = new DownloadJSOperationController.Input(operationID, fileURL, mimeType, contentDisposition); + DownloadJSOperationController.Input input = new DownloadJSOperationController.Input( + operationID, + fileURL, + mimeType, + contentDisposition + ); this.pendingInputs.put(operationID, input); //Return JS bridge with operationID tagged return this.getJavascriptInterfaceBridgeForReadyAvailableData(fileURL, mimeType, operationID); } return null; } + /* Injectors */ private String getJavascriptInterfaceBridgeForReadyAvailableData(String blobUrl, String mimeType, String operationID) { - return "javascript: " + - "" + - "function parseFile(file, chunkReadCallback, errorCallback, successCallback) {\n" + - " let fileSize = file.size;" + - " let chunkSize = 64 * 1024;" + - " let offset = 0;" + - " let self = this;" + - " let readBlock = null;" + - " let onLoadHandler = function(evt) {" + - " if (evt.target.error == null) {" + - " offset += evt.target.result.length;" + - " chunkReadCallback(evt.target.result);" + - " } else {" + - " errorCallback(evt.target.error);" + - " return;" + - " }" + - " if (offset >= fileSize) {" + - " if (successCallback) successCallback();" + - " return;" + - " }" + - " readBlock(offset, chunkSize, file);" + - " };" + - " readBlock = function(_offset, length, _file) {" + - " var r = new FileReader();" + - " var blob = _file.slice(_offset, length + _offset);" + - " r.onload = onLoadHandler;" + - " r.readAsBinaryString(blob);" + - " };" + - " readBlock(offset, chunkSize, file);" + - "};\n" + - "(() => { let xhr = new XMLHttpRequest();" + - "xhr.open('GET', '"+ blobUrl +"', true);" + - ((mimeType != null && mimeType.length() > 0) ? "xhr.setRequestHeader('Content-type','" + mimeType + "');" : "") + - "xhr.responseType = 'blob';" + - "xhr.onerror = xhr.onload = function(e) {" + - " if (this.status == 200) {" + - " let contentType = this.getResponseHeader('content-type');" + - " if (contentType) { CapacitorDownloadInterface.receiveContentTypeFromJavascript(contentType, '" + operationID + "'); }" + - " var blob = this.response;" + - " parseFile(blob, " + - " function(chunk) { CapacitorDownloadInterface.receiveStreamChunkFromJavascript(chunk, '" + operationID + "'); }," + - " function(err) { console.error('[Capacitor XHR] - error:', err); CapacitorDownloadInterface.receiveStreamChunkFromJavascript(err.message, '" + operationID + "'); }, " + - " function() { console.log('[Capacitor XHR] - Drained!'); CapacitorDownloadInterface.receiveStreamCompletionFromJavascript('" + operationID + "'); } " + - " );" + - " } else {" + - " console.error('[Capacitor XHR] - error:', this.status, (e ? e.loaded : this.responseText));" + - " }" + - "};" + - "xhr.send();})()"; + return ( + "javascript: " + + "" + + "function parseFile(file, chunkReadCallback, errorCallback, successCallback) {\n" + + " let fileSize = file.size;" + + " let chunkSize = 64 * 1024;" + + " let offset = 0;" + + " let self = this;" + + " let readBlock = null;" + + " let onLoadHandler = function(evt) {" + + " if (evt.target.error == null) {" + + " offset += evt.target.result.length;" + + " chunkReadCallback(evt.target.result);" + + " } else {" + + " errorCallback(evt.target.error);" + + " return;" + + " }" + + " if (offset >= fileSize) {" + + " if (successCallback) successCallback();" + + " return;" + + " }" + + " readBlock(offset, chunkSize, file);" + + " };" + + " readBlock = function(_offset, length, _file) {" + + " var r = new FileReader();" + + " var blob = _file.slice(_offset, length + _offset);" + + " r.onload = onLoadHandler;" + + " r.readAsBinaryString(blob);" + + " };" + + " readBlock(offset, chunkSize, file);" + + "};\n" + + "(() => { let xhr = new XMLHttpRequest();" + + "xhr.open('GET', '" + + blobUrl + + "', true);" + + ((mimeType != null && mimeType.length() > 0) ? "xhr.setRequestHeader('Content-type','" + mimeType + "');" : "") + + "xhr.responseType = 'blob';" + + "xhr.onerror = xhr.onload = function(e) {" + + " if (this.status == 200) {" + + " let contentType = this.getResponseHeader('content-type');" + + " if (contentType) { CapacitorDownloadInterface.receiveContentTypeFromJavascript(contentType, '" + + operationID + + "'); }" + + " var blob = this.response;" + + " parseFile(blob, " + + " function(chunk) { CapacitorDownloadInterface.receiveStreamChunkFromJavascript(chunk, '" + + operationID + + "'); }," + + " function(err) { console.error('[Capacitor XHR] - error:', err); CapacitorDownloadInterface.receiveStreamChunkFromJavascript(err.message, '" + + operationID + + "'); }, " + + " function() { console.log('[Capacitor XHR] - Drained!'); CapacitorDownloadInterface.receiveStreamCompletionFromJavascript('" + + operationID + + "'); } " + + " );" + + " } else {" + + " console.error('[Capacitor XHR] - error:', this.status, (e ? e.loaded : this.responseText));" + + " }" + + "};" + + "xhr.send();})()" + ); } /* Helpers */ diff --git a/android/capacitor/src/main/java/com/getcapacitor/DownloadJSOperationController.java b/android/capacitor/src/main/java/com/getcapacitor/DownloadJSOperationController.java index 2c28addd23..7c93152cb5 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/DownloadJSOperationController.java +++ b/android/capacitor/src/main/java/com/getcapacitor/DownloadJSOperationController.java @@ -1,8 +1,8 @@ package com.getcapacitor; +import android.app.Activity; import android.content.Context; import android.content.Intent; -import android.app.Activity; import android.media.MediaScannerConnection; import android.net.Uri; import android.os.Build; @@ -10,12 +10,10 @@ import android.provider.DocumentsContract; import android.text.TextUtils; import android.webkit.URLUtil; - import androidx.activity.result.contract.ActivityResultContract; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; - import java.io.File; import java.io.IOException; import java.io.OutputStream; @@ -29,12 +27,15 @@ import java.util.concurrent.Executors; public class DownloadJSOperationController extends ActivityResultContract { + /* DownloadJSActivity Input */ public static class Input { + public String fileNameURL; public String optionalMimeType; public String contentDisposition; public String operationID; + public Input(String operationID, String fileNameURL, String optionalMimeType, String contentDisposition) { this.operationID = operationID; this.fileNameURL = fileNameURL; @@ -42,9 +43,11 @@ public Input(String operationID, String fileNameURL, String optionalMimeType, St this.contentDisposition = contentDisposition; } } + /* DownloadJSActivity internal operation */ public static class Operation { - final private Input input; + + private final Input input; public String operationID; public PipedOutputStream outStream; public PipedInputStream inStream; @@ -53,6 +56,7 @@ public static class Operation { public Boolean started; public Boolean pendingClose; public Boolean failureClose; + // public Operation(Input input) { this.input = input; @@ -71,10 +75,11 @@ public Operation(Input input) { } /* DownloadJSActivity */ - final private static String EXTRA_OPERATION_ID = "OPERATION_ID"; - final private AppCompatActivity activity; - final private HashMap operations; + private static final String EXTRA_OPERATION_ID = "OPERATION_ID"; + private final AppCompatActivity activity; + private final HashMap operations; private Operation pendingOperation; + // public DownloadJSOperationController(AppCompatActivity activity) { this.activity = activity; @@ -97,6 +102,7 @@ public boolean appendToOperation(String operationID, String data) { } return !operation.pendingClose; } + public boolean failOperation(String operationID) { //get operation status Operation operation = this.operations.get(operationID); @@ -108,6 +114,7 @@ public boolean failOperation(String operationID) { // return true; } + public boolean completeOperation(String operationID) { //get operation status Operation operation = this.operations.get(operationID); @@ -119,24 +126,28 @@ public boolean completeOperation(String operationID) { return true; } - /* ActivityResultContract Implementation */ @NonNull public Intent createIntent(@NonNull Context context, DownloadJSOperationController.Input input) { //ask path - String[] paths = this.getUniqueDownloadFileNameFromDetails(input.fileNameURL, input.contentDisposition, input.optionalMimeType, null); + String[] paths = + this.getUniqueDownloadFileNameFromDetails(input.fileNameURL, input.contentDisposition, input.optionalMimeType, null); //Create/config intent to prompt for file selection Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); if (paths != null && paths[1] != null) intent.putExtra(Intent.EXTRA_TITLE, paths[1]); intent.putExtra(EXTRA_OPERATION_ID, input.operationID); if (input.optionalMimeType != null) intent.setType(input.optionalMimeType); - if (paths != null && paths[0] != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, paths[0]); + if (paths != null && paths[0] != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) intent.putExtra( + DocumentsContract.EXTRA_INITIAL_URI, + paths[0] + ); //Add operation this.pendingOperation = new Operation(input); // return intent; } + public Boolean parseResult(int resultCode, @Nullable Intent result) { //get operation status Operation operation = this.pendingOperation; @@ -159,6 +170,7 @@ private void createThreadedPipeForOperation(Operation operation, Uri uri) { DownloadJSOperationController upperRef = this; Executors.newSingleThreadExecutor().execute(() -> upperRef.createPipeForOperation(operation, uri)); } + private void createPipeForOperation(Operation operation, Uri uri) { //check for operation finished if (operation.started || operation.closed) return; @@ -213,8 +225,9 @@ private void cancelPreOperation(Operation operation) { try { operation.outStream.close(); operation.inStream.close(); - } catch (IOException ignored) { } //failsafe stream close + } catch (IOException ignored) {} //failsafe stream close } + private void releaseOperation(String operationID) { //get operation status Operation operation = this.operations.get(operationID); @@ -230,18 +243,22 @@ private void releaseOperation(String operationID) { private void performMediaScan(Uri uri) { // Tell the media scanner about the new file so that it is // immediately available to the user. - MediaScannerConnection.scanFile(this.activity, - new String[] { uri.toString() }, null, - (path, uri2) -> { -// Logger.debug("ExternalStorage", "Scanned " + path + ":"); -// Logger.debug("ExternalStorage", "-> uri=" + uri2); - }); + MediaScannerConnection.scanFile( + this.activity, + new String[] { uri.toString() }, + null, + (path, uri2) -> { + // Logger.debug("ExternalStorage", "Scanned " + path + ":"); + // Logger.debug("ExternalStorage", "-> uri=" + uri2); + } + ); } /* FS Utils */ private String getDownloadFilePath(String fileName) { return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath() + '/' + fileName; } + private boolean checkCreateDefaultDir() { boolean created = false; try { @@ -254,11 +271,17 @@ private boolean checkCreateDefaultDir() { } return created; } - private String[] getUniqueDownloadFileNameFromDetails(String fileDownloadURL, String optionalCD, String optionalMimeType, @Nullable Integer optionalSuffix) { + + private String[] getUniqueDownloadFileNameFromDetails( + String fileDownloadURL, + String optionalCD, + String optionalMimeType, + @Nullable Integer optionalSuffix + ) { //Auxs for filename gen. String suggestedFilename = URLUtil.guessFileName(fileDownloadURL, optionalCD, optionalMimeType); ArrayList fileComps = new ArrayList<>(Arrays.asList(suggestedFilename.split("."))); - String suffix = (optionalSuffix != null ? " (" + optionalSuffix + ")" : ""); + String suffix = (optionalSuffix != null ? " (" + optionalSuffix + ")" : ""); //Check for invalid filename if (suggestedFilename.length() <= 0) suggestedFilename = UUID.randomUUID().toString(); //Generate filename @@ -273,12 +296,7 @@ private String[] getUniqueDownloadFileNameFromDetails(String fileDownloadURL, St if (!this.checkCreateDefaultDir()) return null; //Check if file with generated name exists String fullPath = this.getDownloadFilePath(fileName); - //Comment since file picker should do this for us -// File file = new File(fullPath); -// if (file.exists()) { -// Integer nextSuffix = (optionalSuffix != null ? optionalSuffix + 1 : 1); -// return this.getUniqueDownloadFileNameFromDetails(fileDownloadURL, optionalCD, optionalMimeType, nextSuffix); -// } - return new String[]{fullPath, fileName}; + // + return new String[] { fullPath, fileName }; } } diff --git a/android/capacitor/src/main/java/com/getcapacitor/DownloadJSProxy.java b/android/capacitor/src/main/java/com/getcapacitor/DownloadJSProxy.java index 2661fce9c5..85caa02789 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/DownloadJSProxy.java +++ b/android/capacitor/src/main/java/com/getcapacitor/DownloadJSProxy.java @@ -11,8 +11,10 @@ * dynamic javascript upon the 'protocol' interface availability. */ public class DownloadJSProxy implements android.webkit.DownloadListener { - final private Bridge bridge; - final private DownloadJSInterface downloadInterface; + + private final Bridge bridge; + private final DownloadJSInterface downloadInterface; + public DownloadJSProxy(Bridge bridge) { this.bridge = bridge; this.downloadInterface = new DownloadJSInterface(this.bridge); @@ -20,8 +22,13 @@ public DownloadJSProxy(Bridge bridge) { } // - public DownloadJSInterface jsInterface() { return this.downloadInterface; } - public String jsInterfaceName() { return "CapacitorDownloadInterface"; } + public DownloadJSInterface jsInterface() { + return this.downloadInterface; + } + + public String jsInterfaceName() { + return "CapacitorDownloadInterface"; + } /* Public interceptors */ public boolean shouldOverrideLoad(String url) { @@ -40,6 +47,7 @@ public boolean shouldOverrideLoad(String url) { return false; } } + /* Public DownloadListener implementation */ @Override public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimeType, long contentLength) { @@ -60,13 +68,15 @@ private void installServiceWorkerProxy() { //Downloads can be done via webworker, webworkers might need local resources, we enable that if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { ServiceWorkerController swController = ServiceWorkerController.getInstance(); - swController.setServiceWorkerClient(new ServiceWorkerClient() { - @Override - public WebResourceResponse shouldInterceptRequest(WebResourceRequest request) { - Logger.debug("ServiceWorker Request", request.getUrl().toString()); - return bridge.getLocalServer().shouldInterceptRequest(request); + swController.setServiceWorkerClient( + new ServiceWorkerClient() { + @Override + public WebResourceResponse shouldInterceptRequest(WebResourceRequest request) { + Logger.debug("ServiceWorker Request", request.getUrl().toString()); + return bridge.getLocalServer().shouldInterceptRequest(request); + } } - }); + ); } } -} \ No newline at end of file +}