29

So i made a cordova app, i added android platform and made a simple html with an imput field

<input type="file" capture="camera" accept="image/*" id="takePictureField">

I have added

<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<feature name="http://api.phonegap.com/1.0/camera" />

to the manifest file.

but when i press the button I cannot choose to take a new picture with the camera. are there any permission i miss, or anything else ??

I cannot use the cordova take picture functions, it has to be done in pure html.

aWebDeveloper
  • 36,687
  • 39
  • 170
  • 242
pjensen68321
  • 521
  • 1
  • 5
  • 15

6 Answers6

16

After a bit of Googling, this is what I could conclude:

Media capture in mobile browsers still seems to have some issues. Check out this link. The excerpt says:

Actually, it seems that current implementations don’t rely on the capture attribute at all, but only on the type and accept attributes: the browser displays a dialog box in which the user can choose where the file has to be taken, and the capture attribute is not taken into consideration. For example, iOS Safari relies on the accept attribute (not capture) for images and videos (not audio). Even if you don’t use the accept attribute, the browser will let you choose between “Take Photo or Video” and “Choose Existing”

So looks like capture attribute does not make any impact.

Also, suggest you look at this SO post for more info on making this work. Hope it helps. Cheers.

UPDATE: After downvoting, I dig further deep regarding the issue. Most of the searches resulted in no success as the optimum solution for this issue is to use Cordova camera plugin. Finally stumbled on this SO post which is exactly duplicate of this question. The user was able to resolve the issue (using crosswalk web view, though). The answer in that post is mentioned here already by @Fabio. But instead of adding the plugin just for including the permissions, you can make use of cordova-custom-plugin to add the required permissions.

Also as per @jcesarmobile's comment (who is a Cordova expert) in the post without crosswalk web view plugin, input type works fine only on iOS and not on Android. So using camera plugin is the only way to make it work without using crosswalk plugin.Hopefully, it helps.

UPDATE 2: After the updated question, I dug bit more deep for the resolution of this issue. But now I can assure that this issue is still not resolved for Android Webview. Looks like it is not a Cordova issue but issue with Chromium web view.

For detailed info, request you to please look at these issues in Apache Cordova Issue Tracker:

Both these issues are unresolved till date. So I m sure that for now, you cannot make it work on Android unless you use Cordova camera plugin. Hope you agree with me and accept the resolution. Cheers

Community
  • 1
  • 1
Gandhi
  • 11,875
  • 4
  • 39
  • 63
  • this question is for cordova – aWebDeveloper Feb 28 '17 at 06:46
  • @aWebDeveloperOf course its for cordova. Cordova app also uses webview which in turn uses browser capabilities. Also this question talks about cordova 3.6 which is way too old. If you have someother problems, please update the question. Also a kind request, downvoting never motivates any one. If the answer was not helpful please comment it. – Gandhi Feb 28 '17 at 07:28
  • @aWebDeveloper Updated my answer with some more info. – Gandhi Feb 28 '17 at 10:25
  • thanks for the time taken. I have update the question to remove cordova 3.6 and also stressed that i want it with without cordova camera. +1 for custom config plugin – aWebDeveloper Mar 01 '17 at 01:59
  • 1
    @aWebDeveloper First of all thanks for updating the question and the upvote. I have updated my answer which should give you more clarity. Hope you accept it. – Gandhi Mar 01 '17 at 04:53
  • @aWebDeveloper did it help? – Gandhi Mar 01 '17 at 15:21
  • lets see if some one has a polyfill or some thing like that solution – aWebDeveloper Mar 02 '17 at 05:11
  • @aWebDeveloper i guess it will be a major enhancement kind of change. Only cordova experts need to come up with a fix i hope. – Gandhi Mar 02 '17 at 12:13
  • @aWebDeveloper i dont see any polyfill or tweak fix for this yet and the issues mentioned in the answers are still open. Also more importantly the bounty is gone. I feel this is a limitation for now – Gandhi Mar 06 '17 at 11:42
3

My project was using cordova-plugin-inappbrowser.

I solve it merging solution in webview open camera from input field without filechooser with methods onActivityResult and onShowFileChooser from class InAppBrowser in plugin source.

See the changes made in InAppBrowser class from plugin version 3.0.0

1 - include imports:

import android.app.Activity;
import android.Manifest;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

2 - declare variable:

    private String mCM;

3 - Replace onShowFileChooser code:


                    // For Android 5.0+
                    public boolean onShowFileChooser (WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams)
                    {
                        if(Build.VERSION.SDK_INT >=23 && (cordova.getActivity().checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED || cordova.getActivity().checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED)) {
                            cordova.getActivity().requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA}, 1);
                        }

                        LOG.d(LOG_TAG, "File Chooser 5.0+");

                        // If callback exists, finish it.
                        if(mUploadCallbackLollipop != null) {
                            mUploadCallbackLollipop.onReceiveValue(null);
                        }
                        mUploadCallbackLollipop = filePathCallback;

                        Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

                        if(takePictureIntent.resolveActivity(cordova.getActivity().getPackageManager()) != null) {

                            File photoFile = null;
                            try{
                                photoFile = createImageFile();
                                takePictureIntent.putExtra("PhotoPath", mCM);
                            }catch(IOException ex){
                                Log.e(LOG_TAG, "Image file creation failed", ex);
                            }
                            if(photoFile != null){
                                mCM = "file:" + photoFile.getAbsolutePath();
                                takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile));
                            }else{
                                takePictureIntent = null;
                            }
                        }
                        // Create File Chooser Intent
                        Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
                        contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
                        contentSelectionIntent.setType("*/*");
                        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, "Selecione a imagem");
                        chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);

                        // Run cordova startActivityForResult
                        cordova.startActivityForResult(InAppBrowser.this, chooserIntent, FILECHOOSER_REQUESTCODE);

                        return true;
                    }

4 - create method


    private File createImageFile() throws IOException{
        @SuppressLint("SimpleDateFormat") String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        String imageFileName = "img_"+timeStamp+"_";
        File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
        return File.createTempFile(imageFileName,".jpg",storageDir);
    }

5 - Replace onActivityResult


    public void onActivityResult(int requestCode, int resultCode, Intent intent) {
        // For Android >= 5.0
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {

            LOG.d(LOG_TAG, "onActivityResult (For Android >= 5.0)");

            Uri[] results = null;
            //Check if response is positive
            if(resultCode== Activity.RESULT_OK){
                if(requestCode == FILECHOOSER_REQUESTCODE){
                    if(null == mUploadCallbackLollipop){
                        return;
                    }
                    if(intent == null || intent.getData() == null){
                        //Capture Photo if no image available
                        if(mCM != null){
                            results = new Uri[]{Uri.parse(mCM)};
                        }
                    }else{
                        String dataString = intent.getDataString();
                        if(dataString != null){
                            results = new Uri[]{Uri.parse(dataString)};
                        }
                    }
                }
            }
            mUploadCallbackLollipop .onReceiveValue(results);
            mUploadCallbackLollipop = null;
        }
        // For Android < 5.0
        else {
            LOG.d(LOG_TAG, "onActivityResult (For Android < 5.0)");
            // If RequestCode or Callback is Invalid
            if(requestCode != FILECHOOSER_REQUESTCODE || mUploadCallback == null) {
                super.onActivityResult(requestCode, resultCode, intent);
                return;
            }

            if (null == mUploadCallback) return;
            Uri result = intent == null || resultCode != cordova.getActivity().RESULT_OK ? null : intent.getData();

            mUploadCallback.onReceiveValue(result);
            mUploadCallback = null;
        }
    }

Jordan Bradford
  • 337
  • 2
  • 8
Gilberto Alexandre
  • 2,227
  • 1
  • 18
  • 20
2

Add these permissions into AndroidManifest.xml file

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />

Add these permission requests into the MainActivity.java

@Override
public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);

  ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);

Add android:requestLegacyExternalStorage="true" into AndroidManifest.xml file on this tag

<application
    android:requestLegacyExternalStorage="true"

Add provider in AndroidManifest.xml file on this area

<application>
...
    <provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="${applicationId}.provider"
    android:exported="false"
    android:grantUriPermissions="true">
        <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths">
        </meta-data>
    </provider>
....
</application>

Create new xml file in res/xml/file_paths.xml

content:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="external_files" path="." />
</paths>

Add these lines end of CordovaLib/build.gradle file

dependencies {
    implementation 'com.android.support:support-v4:28.0.0'
}

Change the SystemWebChromeClient.java file like this

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public boolean onShowFileChooser(WebView webView, final ValueCallback<Uri[]> filePathsCallback, final WebChromeClient.FileChooserParams fileChooserParams) {
    Intent intent = createChooserIntentWithImageSelection();
    try {
        parentEngine.cordova.startActivityForResult(new CordovaPlugin() {
            @Override
            public void onActivityResult(int requestCode, int resultCode, Intent intent) {
                Uri[] result = WebChromeClient.FileChooserParams.parseResult(resultCode, intent);
                if(result==null){
                    if(mCameraPhotoPath!=null && Uri.parse(mCameraPhotoPath)!=null) {
                        File returnFile = new File(Uri.parse(mCameraPhotoPath).getPath());
                        if (returnFile.length() > 0) {
                            result = new Uri[1];
                            result[0] = Uri.parse(mCameraPhotoPath);
                        }
                    }
                }
                LOG.d(LOG_TAG, "Receive file chooser URL: " + result);
                filePathsCallback.onReceiveValue(result);
            }
        }, intent, INPUT_FILE_REQUEST_CODE);
    } catch (ActivityNotFoundException e) {
        LOG.w("No activity found to handle file chooser intent.", e);
        filePathsCallback.onReceiveValue(null);
    }
    return true;
}

private static final String PATH_PREFIX = "file:";

public Intent createChooserIntentWithImageSelection() {
    Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
    contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
    contentSelectionIntent.setType("image/*");
    ArrayList<Intent> extraIntents = new ArrayList<>();
    Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    takePictureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
    takePictureIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    File photoFile = createImageFile();
    if (photoFile != null) {
        mCameraPhotoPath = PATH_PREFIX + photoFile.getAbsolutePath();
        takePictureIntent.putExtra("PhotoPath", mCameraPhotoPath);
        takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,
                FileProvider.getUriForFile(appContext,
                        appContext.getPackageName() + ".provider",
                        photoFile));
    }

    Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
    chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);

    if (takePictureIntent != null) {
        extraIntents.add(takePictureIntent);
    }
    if (!extraIntents.isEmpty()) {
        chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS,
                extraIntents.toArray(new Intent[]{}));
    }
    return chooserIntent;
}

//creating temp picture file
private File createImageFile() {
    String state = Environment.getExternalStorageState();
    if (!state.equals(Environment.MEDIA_MOUNTED)) {
        Log.e(TAG, "External storage is not mounted.");
        return null;
    }

    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    String imageFileName = "JPEG_" + timeStamp + "_";
    File storageDir = getExternalSdCardPath();
    storageDir.mkdirs();

    try {
        File file = File.createTempFile(imageFileName, ".jpg", storageDir);
        Log.d(TAG, "Created image file: " + file.getAbsolutePath());
        return file;
    } catch (IOException e) {
        Log.e(TAG, "Unable to create Image File, " +
                "please make sure permission 'WRITE_EXTERNAL_STORAGE' was added.");
        return null;
    }
}

//for external sd card check
public static File getExternalSdCardPath() {
    String path = null;

    File sdCardFile = null;
    List<String> sdCardPossiblePath = Arrays.asList("external_sd", "ext_sd", "external", "extSdCard");

    for (String sdPath : sdCardPossiblePath) {
        File file = new File("/mnt/", sdPath);

        if (file.isDirectory() && file.canWrite()) {
            path = file.getAbsolutePath();

            String timeStamp = new SimpleDateFormat("ddMMyyyy_HHmmss").format(new Date());
            File testWritable = new File(path, "test_" + timeStamp);

            if (testWritable.mkdirs()) {
                testWritable.delete();
            } else {
                path = null;
            }
        }
    }

    if (path != null) {
        sdCardFile = new File(path);
    } else {
        sdCardFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath());
    }

    return sdCardFile;
}
0

Disclaimer: recent comments about this answer report that its not working anymore. This question and answers relates to old cordova and plugins versions and so they may not be applicable to your current situation.

An old question but I just faced this problem. It turns out that the simplest way to achieve what you want is include these cordova plugins in you app, even if you don't need to use them

cordova-plugin-camera
cordova-plugin-media-capture
cordova-plugin-device
cordova-plugin-file
cordova-plugin-media

With these loaded I found this behaviour

Clicking on

<input type="file" />

Ask you to choose from camera, camcorder, microphone or documents

Clicking on

<input type="file" accept="image/*" />

Ask you to choose from camera or documents

Clicking on

<input type="file" accept="image/*" capture="capture" />

Immediatly start the camera.

Fabio C.
  • 347
  • 4
  • 15
  • not working. Why is there 1 upvote, that made me try it at least once. – Sam Aug 20 '18 at 07:29
  • hi Sam at the time of writing the solution was working and so the upvotes, now cordova, plugins and webviews versions changed and maybe its not working anymore, i'm not intrested in trying again now. I updated the answer to reflect it. Cheers, Fabio – Fabio C. Aug 21 '18 at 08:31
  • 1
    It is a terrible experience working with camera in Cordova, even it works in Android x, it doesn't work in Android y, when it finally works in all Androids, it doesn't work in iPhones – Kenneth Li Jan 12 '19 at 18:42
0

Inspired from Gilberto answer, here is my adaptation:

  1. Always open the camera
  2. Ask for permissions before opening the actual camera, when permission is granted, open camera, otherwise, display a Toast.
  3. If permissions are denied, clicking on the input fill ask for it, no need to refresh or change screen etc.

There is 4 new methods: one for receiving the permission, one to check the permission, one for create the image file before taking the picture and one to open the camera.

Here are those methods


    /**
     * Called by the system when the user grants permissions
     *
     * @param requestCode
     * @param permissions
     * @param grantResults
     */
    public void onRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults)
            throws JSONException {

        for (int r : grantResults) {
            if (r == PackageManager.PERMISSION_DENIED) {
                Toast.makeText(cordova.getActivity(), "Autorisation pour ouvrir la caméra refusé", Toast.LENGTH_LONG)
                        .show();
                mUploadCallbackLollipop.onReceiveValue(null);
                mUploadCallbackLollipop = null;
                return;
            }
        }

        if (requestCode == PERM_REQUEST_CAMERA_FOR_FILE) {
            startCameraActivityForAndroidFivePlus();
        }
    }

    private File createImageFile() throws IOException {
        @SuppressLint("SimpleDateFormat")
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        String imageFileName = "img_" + timeStamp + "_";
        File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
        return File.createTempFile(imageFileName, ".jpg", storageDir);
    }

    private void startCameraActivityForAndroidFivePlus() {
        Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        File photoFile = null;
        try {
            photoFile = createImageFile();
            takePictureIntent.putExtra("PhotoPath", mCapturedPhoto);
        } catch (IOException ex) {
            LOG.e(LOG_TAG, "Image file creation failed", ex);
        }
        if (photoFile != null) {
            mCapturedPhoto = "file:" + photoFile.getAbsolutePath();
            takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile));
        } else {
            takePictureIntent = null;
        }

        // Fix FileUriExposedException exposed beyond app through ClipData.Item.getUri()
        StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
        StrictMode.setVmPolicy(builder.build());

        // Run cordova startActivityForResult
        cordova.startActivityForResult(InAppBrowser.this, takePictureIntent, FILECHOOSER_REQUESTCODE_LOLLIPOP);
    }

    private boolean checkPermissionForCamera() {
        if (Build.VERSION.SDK_INT >= 23) {
            List<String> permToAsk = new ArrayList<String>();
            if (cordova.getActivity().checkSelfPermission(
                    Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                permToAsk.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
            }
            if (cordova.getActivity()
                    .checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
                permToAsk.add(Manifest.permission.CAMERA);
            }
            if (permToAsk.size() > 0) {
                cordova.requestPermissions(InAppBrowser.this, PERM_REQUEST_CAMERA_FOR_FILE,
                        permToAsk.toArray(new String[permToAsk.size()]));
                return true;
            }
        }
        return false;
    }

The InAppChromeClient implementation has been updated to just this:

inAppWebView.setWebChromeClient(new InAppChromeClient(thatWebView) {
                    // For Android 5.0+
                    public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
                            WebChromeClient.FileChooserParams fileChooserParams) {

                        LOG.d(LOG_TAG, "File Chooser 5.0+");
                        // If callback exists, finish it.
                        if (mUploadCallbackLollipop != null) {
                            mUploadCallbackLollipop.onReceiveValue(null);
                        }
                        mUploadCallbackLollipop = filePathCallback;

                        // #Update to always open camera app
                        if (checkPermissionForCamera()) {
                            return true;
                        }

                        startCameraActivityForAndroidFivePlus();
                        return true;
                    }
                });

And here are those constants:

    private String mCapturedPhoto;
    private final static int PERM_REQUEST_CAMERA_FOR_FILE = 3;

The complete InAppBrowser.java can be found here.

Hugo Gresse
  • 17,195
  • 9
  • 77
  • 119
0

Here's another work around if you don't want to modify plugin source code. Create a separate control for taking pictures. Set the control's click event to the following handler:

(event) => {
    event.stopPropagation();
    Camera.sourceType = Camera.PictureSourceType.CAMERA;

    const onCameraSuccess = (imgURL) => {
        window.resolveLocalFileSystemURL(imgURL, (entry) => {
            const onFileSuccess = (file) => this._onSelectMultipleFiles(event, file);
            const onFileFail = (error) => console.log(error);
            entry.file(onFileSuccess, onFileFail);
        });

        console.log("picture retrieved successfully");
    };

    const onCameraFail = () => {
        console.log("picture retrieval failed");
    };

    navigator.camera.getPicture(onCameraSuccess, onCameraFail, {
        quality: 100, 
        destinationType: Camera.DestinationType.FILE_URI,
    });
}

This uses cordova-plugin-camera to launch the camera app when the control is clicked, and calls resolveLocalFileSystemURL from cordova-plugin-file to convert the image URL returned by the camera into a File object to be processed by my _onSelectMultipleFiles method. see example from cordova docs

Note that this implementation is specific to my project, so you may not need to call resolveLocalFileSystemURL depending on how you intend to use the image URL passed in the onSuccess callback of camera.getPicture.

Obviously the down side to this is the need to use two controls: one for retrieving images from file, the other for retrieving images from the camera.

Yoshimitsu
  • 65
  • 1
  • 9