From de622ff84b68d93e92ed82f3ef9e6228895ec371 Mon Sep 17 00:00:00 2001 From: Faraz Sherwani Date: Wed, 8 Mar 2017 10:31:37 -0500 Subject: [PATCH 1/5] Add file input to webview --- .../react/uimanager/ThemedReactContext.java | 11 ++ .../views/webview/ReactWebViewManager.java | 110 +++++++++++++++++- 2 files changed, 120 insertions(+), 1 deletion(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ThemedReactContext.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ThemedReactContext.java index f3a1a2f10863c3..1a048c4adfca10 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ThemedReactContext.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ThemedReactContext.java @@ -14,6 +14,7 @@ import android.app.Activity; import android.content.Context; +import com.facebook.react.bridge.ActivityEventListener; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.LifecycleEventListener; @@ -48,6 +49,16 @@ public void removeLifecycleEventListener(LifecycleEventListener listener) { mReactApplicationContext.removeLifecycleEventListener(listener); } + @Override + public void addActivityEventListener(ActivityEventListener listener) { + mReactApplicationContext.addActivityEventListener(listener); + } + + @Override + public void removeActivityEventListener(ActivityEventListener listener) { + mReactApplicationContext.removeActivityEventListener(listener); + } + @Override public boolean hasCurrentActivity() { return mReactApplicationContext.hasCurrentActivity(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java index 7e246a7ff68f47..3c92f90ee462b0 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java @@ -11,17 +11,24 @@ import javax.annotation.Nullable; +import java.io.File; +import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.HashMap; import java.util.Locale; import java.util.Map; +import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Picture; import android.net.Uri; import android.os.Build; +import android.os.Environment; +import android.provider.MediaStore; import android.text.TextUtils; import android.view.ViewGroup.LayoutParams; import android.webkit.ConsoleMessage; @@ -34,6 +41,7 @@ import android.webkit.WebSettings; import com.facebook.common.logging.FLog; +import com.facebook.react.bridge.ActivityEventListener; import com.facebook.react.common.ReactConstants; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.LifecycleEventListener; @@ -103,8 +111,13 @@ public class ReactWebViewManager extends SimpleViewManager { // state and release page resources (including any running JavaScript). private static final String BLANK_URL = "about:blank"; + public static final int INPUT_FILE_REQUEST_CODE = 1; + public static final String EXTRA_FROM_NOTIFICATION = "EXTRA_FROM_NOTIFICATION"; + private WebViewConfig mWebViewConfig; private @Nullable WebView.PictureListener mPictureListener; + private ValueCallback mFilePathCallback; + private String mCameraPhotoPath; protected static class ReactWebViewClient extends WebViewClient { @@ -330,7 +343,7 @@ public String getName() { } @Override - protected WebView createViewInstance(ThemedReactContext reactContext) { + protected WebView createViewInstance(final ThemedReactContext reactContext) { ReactWebView webView = new ReactWebView(reactContext); webView.setWebChromeClient(new WebChromeClient() { @Override @@ -346,8 +359,103 @@ public boolean onConsoleMessage(ConsoleMessage message) { public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) { callback.invoke(origin, true, false); } + + private File createImageFile() throws IOException { + // Create an image file name + String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); + String imageFileName = "JPEG_" + timeStamp + "_"; + File storageDir = Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_PICTURES); + File imageFile = File.createTempFile( + imageFileName, /* prefix */ + ".jpg", /* suffix */ + storageDir /* directory */ + ); + return imageFile; + } + + public boolean onShowFileChooser( + WebView webView, + ValueCallback filePathCallback, + WebChromeClient.FileChooserParams fileChooserParams) { + if(mFilePathCallback != null) { + mFilePathCallback.onReceiveValue(null); + } + mFilePathCallback = filePathCallback; + + Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + if (takePictureIntent.resolveActivity(reactContext.getCurrentActivity().getPackageManager()) != null) { + // Create the File where the photo should go + File photoFile = null; + try { + photoFile = createImageFile(); + takePictureIntent.putExtra("PhotoPath", mCameraPhotoPath); + } catch (IOException ex) { + // Error occurred while creating the File + FLog.e(ReactConstants.TAG, "Unable to create Image File", ex); + } + + // Continue only if the File was successfully created + if (photoFile != null) { + mCameraPhotoPath = "file:" + photoFile.getAbsolutePath(); + takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, + Uri.fromFile(photoFile)); + } else { + takePictureIntent = null; + } + } + + Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT); + contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE); + contentSelectionIntent.setType("image/*"); + + Intent[] intentArray; + if(takePictureIntent != null) { + intentArray = new Intent[]{takePictureIntent}; + } else { + intentArray = new Intent[0]; + } + + Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER); + chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent); + chooserIntent.putExtra(Intent.EXTRA_TITLE, "Image Chooser"); + chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray); + + reactContext.getCurrentActivity().startActivityForResult(chooserIntent, INPUT_FILE_REQUEST_CODE); + + return true; + } }); + reactContext.addLifecycleEventListener(webView); + reactContext.addActivityEventListener(new ActivityEventListener() { + @Override + public void onActivityResult (Activity activity, int requestCode, int resultCode, Intent data) { + Uri[] results = null; + + // Check that the response is a good one + if(resultCode == Activity.RESULT_OK) { + if(data == null) { + // If there is not data, then we may have taken a photo + if(mCameraPhotoPath != null) { + results = new Uri[]{Uri.parse(mCameraPhotoPath)}; + } + } else { + String dataString = data.getDataString(); + if (dataString != null) { + results = new Uri[]{Uri.parse(dataString)}; + } + } + } + + mFilePathCallback.onReceiveValue(results); + mFilePathCallback = null; + return; + } + + @Override + public void onNewIntent(Intent intent) {} + }); mWebViewConfig.configWebView(webView); webView.getSettings().setBuiltInZoomControls(true); webView.getSettings().setDisplayZoomControls(false); From abb39c24ae08973595ae749016ebd0d49541a25d Mon Sep 17 00:00:00 2001 From: Faraz Sherwani Date: Thu, 9 Mar 2017 13:28:05 -0500 Subject: [PATCH 2/5] Check for input file requestCode in onActivityResult --- .../views/webview/ReactWebViewManager.java | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java index 3c92f90ee462b0..025e92e63bba0b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java @@ -431,26 +431,28 @@ public boolean onShowFileChooser( reactContext.addActivityEventListener(new ActivityEventListener() { @Override public void onActivityResult (Activity activity, int requestCode, int resultCode, Intent data) { - Uri[] results = null; - - // Check that the response is a good one - if(resultCode == Activity.RESULT_OK) { - if(data == null) { - // If there is not data, then we may have taken a photo - if(mCameraPhotoPath != null) { - results = new Uri[]{Uri.parse(mCameraPhotoPath)}; - } - } else { - String dataString = data.getDataString(); - if (dataString != null) { - results = new Uri[]{Uri.parse(dataString)}; + if(requestCode == INPUT_FILE_REQUEST_CODE) { + Uri[] results = null; + + // Check that the response is a good one + if(resultCode == Activity.RESULT_OK) { + if(data == null) { + // If there is not data, then we may have taken a photo + if(mCameraPhotoPath != null) { + results = new Uri[]{Uri.parse(mCameraPhotoPath)}; + } + } else { + String dataString = data.getDataString(); + if (dataString != null) { + results = new Uri[]{Uri.parse(dataString)}; + } } } - } - mFilePathCallback.onReceiveValue(results); - mFilePathCallback = null; - return; + mFilePathCallback.onReceiveValue(results); + mFilePathCallback = null; + return; + } } @Override From 7b3b06b93f2bcdc66db592028664d7ea57859b2e Mon Sep 17 00:00:00 2001 From: Faraz Sherwani Date: Thu, 9 Mar 2017 14:10:29 -0500 Subject: [PATCH 3/5] Check for null results --- .../views/webview/ReactWebViewManager.java | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java index 025e92e63bba0b..0a42e0f9fa17eb 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java @@ -111,7 +111,7 @@ public class ReactWebViewManager extends SimpleViewManager { // state and release page resources (including any running JavaScript). private static final String BLANK_URL = "about:blank"; - public static final int INPUT_FILE_REQUEST_CODE = 1; + public static final int INPUT_FILE_REQUEST_CODE = 1001; public static final String EXTRA_FROM_NOTIFICATION = "EXTRA_FROM_NOTIFICATION"; private WebViewConfig mWebViewConfig; @@ -431,28 +431,29 @@ public boolean onShowFileChooser( reactContext.addActivityEventListener(new ActivityEventListener() { @Override public void onActivityResult (Activity activity, int requestCode, int resultCode, Intent data) { - if(requestCode == INPUT_FILE_REQUEST_CODE) { - Uri[] results = null; - - // Check that the response is a good one - if(resultCode == Activity.RESULT_OK) { - if(data == null) { - // If there is not data, then we may have taken a photo - if(mCameraPhotoPath != null) { - results = new Uri[]{Uri.parse(mCameraPhotoPath)}; - } - } else { - String dataString = data.getDataString(); - if (dataString != null) { - results = new Uri[]{Uri.parse(dataString)}; - } + if(requestCode != INPUT_FILE_REQUEST_CODE) { + return; + } + Uri[] results = null; + + // Check that the response is a good one + if(resultCode == Activity.RESULT_OK) { + if(data == null) { + // If there is not data, then we may have taken a photo + if(mCameraPhotoPath != null) { + results = new Uri[]{Uri.parse(mCameraPhotoPath)}; + } + } else { + String dataString = data.getDataString(); + if (dataString != null) { + results = new Uri[]{Uri.parse(dataString)}; } } - - mFilePathCallback.onReceiveValue(results); - mFilePathCallback = null; - return; } + + if(results != null) mFilePathCallback.onReceiveValue(results); + mFilePathCallback = null; + return; } @Override From d1e4cc7168867cb66e68a0df3b1a71aa59d6b79d Mon Sep 17 00:00:00 2001 From: Faraz Sherwani Date: Mon, 13 Mar 2017 14:29:00 -0400 Subject: [PATCH 4/5] Handle back button correctly --- .../react/views/webview/ReactWebViewManager.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java index 0a42e0f9fa17eb..66a4cbed2c10a1 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java @@ -431,7 +431,7 @@ public boolean onShowFileChooser( reactContext.addActivityEventListener(new ActivityEventListener() { @Override public void onActivityResult (Activity activity, int requestCode, int resultCode, Intent data) { - if(requestCode != INPUT_FILE_REQUEST_CODE) { + if(requestCode != INPUT_FILE_REQUEST_CODE || mFilePathCallback == null) { return; } Uri[] results = null; @@ -451,7 +451,12 @@ public void onActivityResult (Activity activity, int requestCode, int resultCode } } - if(results != null) mFilePathCallback.onReceiveValue(results); + if(results == null) { + mFilePathCallback.onReceiveValue(new Uri[]{}); + } + else { + mFilePathCallback.onReceiveValue(results); + } mFilePathCallback = null; return; } From 1a44b86e4af6dbb62819348cd963ffa6a443f32e Mon Sep 17 00:00:00 2001 From: Faraz Sherwani Date: Mon, 13 Mar 2017 16:18:04 -0400 Subject: [PATCH 5/5] Do not create temporary file --- .../facebook/react/views/webview/ReactWebViewManager.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java index 66a4cbed2c10a1..bcb7cbc7b1d45c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java @@ -366,10 +366,9 @@ private File createImageFile() throws IOException { String imageFileName = "JPEG_" + timeStamp + "_"; File storageDir = Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_PICTURES); - File imageFile = File.createTempFile( - imageFileName, /* prefix */ - ".jpg", /* suffix */ - storageDir /* directory */ + File imageFile = new File( + storageDir, /* directory */ + imageFileName+".jpg" /* filename */ ); return imageFile; }