Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Crosswalk Cordova Android multiple file select

I have a hybrid app built using cordova and angularjs, for Android I run the app using crosswalk.

I've been scouring the internet to find the solution for the html5 file input to allow selection of multiple files.

I'm using the following element for file selecting:

<input type="file" multiple="multiple" name="files[]" />

I am running Android Lollipop version 5.1.1 and Crosswalk version 20, I have tested with Crosswalk version 18 and 19 also. Chrome is installed on my device running the latest version although I don't think that makes a difference.

When I click the input element above I get the expected dialog asking me to select from my Documents or Camera. If I choose to select from my Documents then I am only able to select single files, in this case images. This is true for every App that I can select images from, so the default android 'Images', 'Videos', 'Audio', etc and external Apps such as Google Photos - All only allow me to select one single file at a time.

In the image below you can see the files listed, a long press on each tile does not add the file to a multiple selection.

enter image description here

This works on the IOS version of the App.

After digging through all the material I can find online it seems that the multiple attribute is supported on Android 5+ running Chrome 49+.

I'm unsure if this is a crosswalk browser implementation or Android Operating System issue, or something else? Could anyone advise.

Edit

Just to confirm this does not work with or without using Crosswalk.

like image 537
Lee Brindley Avatar asked Sep 08 '16 12:09

Lee Brindley


1 Answers

After weeks of trying to sort this out, I finally got it to work (Cordova without Crosswalk). This was done using Cordova Tools in Windows so please pardon the filespecs below.

Step 1: Change the minSdkVersion in platforms\Android\CordovaLib\AndroidManifest.xml to 21 Explanation: onShowFileChooser API was introduced in LOLLIPOP (API 21). It allows returning url[] instead of url returned by showFileChooser in earlier API versions. This gets called only when you change the API to 21 or greater.

Step 2: Update/Replace the onActivityResult method to retrieve multiple files. Append the following after creating intent using fileChooserParams to allow choosing multiple files:

    intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);

Location: platforms\android\CordovaLib\src\org\apache\cordova\engine\SystemWebChromeClient.java

Step 3: Update the corresponding onActivityResult method to return multiple urls using intent.getClipData().

Caveats:

  1. Enables Multi-upload for all calls. You could update the intent based on fileChooserParams mode.
  2. Disables Camera as a source in chooser which is available with crosswalk by default.

Final Code:

Uri photoUri;

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public boolean onShowFileChooser(WebView webView, final ValueCallback<Uri[]> filePathsCallback, final WebChromeClient.FileChooserParams fileChooserParams) {
    // Check and use MIME Type.
    String mimeType = "*/*";
    int ACTION_CODE = FILECHOOSER_RESULTCODE;
    try {
        if (fileChooserParams.getAcceptTypes().length > 0) {
            mimeType = fileChooserParams.getAcceptTypes()[0];
        } else {
            mimeType = "*/*";
        }
    } catch (Exception e) {
        mimeType = "*/*";
    };

    // Check if Mutiple is specified 
    Boolean selectMultiple = false;
    if (fileChooserParams.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE) {
        selectMultiple = true;
    };

    Intent intent = new Intent();
    intent.setAction(Intent.ACTION_GET_CONTENT);
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    if (selectMultiple) { intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); };
    intent.setType(mimeType);
    ACTION_CODE = FILECHOOSER_RESULTCODE;
    final Intent chooserIntent = Intent.createChooser(intent, "Select Source");

    // Add camera intent to the chooser if image and send URI to return full image 
    if (mimeType.equals("image/*")) {
        photoUri = null;
        try {
            File photoFile = createImageFile();
            photoUri = Uri.fromFile(photoFile);
        }
        catch (Exception ex) {
            photoUri = null;
        }
        if (photoUri != null) {
            Intent camIntent = new Intent();
            camIntent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
            camIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
            camIntent.putExtra("return-data", true);
            chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent [] {camIntent} );
        }
    }

    try {
        parentEngine.cordova.startActivityForResult(new CordovaPlugin() {
            @Override
            public void onActivityResult(int requestCode, int resultCode, Intent intent) {
                if (resultCode ==  Activity.RESULT_OK && intent != null) {
                    if (intent.getData() != null)
                    {
                        Uri[] result = WebChromeClient.FileChooserParams.parseResult(resultCode, intent);
                        filePathsCallback.onReceiveValue(result);
                    }
                    else
                    {
                        if (intent.getClipData() != null) {
                            final int numSelectedFiles = intent.getClipData().getItemCount();
                            Uri[] result = new Uri[numSelectedFiles];
                            for (int i = 0; i < numSelectedFiles; i++) {
                                result[i] = intent.getClipData().getItemAt(i).getUri();
                            }
                            filePathsCallback.onReceiveValue(result);
                        }
                        else {
                            filePathsCallback.onReceiveValue(null);
                        }
                    }
                }
                else if(resultCode ==  Activity.RESULT_OK && (intent == null || intent.getData() == null )) {
                    Uri[] result = new Uri[1];
                    result[0] = photoUri;
                    filePathsCallback.onReceiveValue(result);
                } else {
                    filePathsCallback.onReceiveValue(null);
                }
            }
        }, chooserIntent, ACTION_CODE);
    } catch (ActivityNotFoundException e) {
        Log.w("No activity found to handle file chooser intent.", e);
        filePathsCallback.onReceiveValue(null);
    }
    return true;
}
like image 125
RIBH Avatar answered Sep 27 '22 02:09

RIBH