web-dev-qa-db-fra.com

Télécharger une image depuis une caméra ou une galerie dans WebView

WebView dans cette application ouvre une page avec un bouton de téléchargement.

Page in webview with upload button

Ci-dessous se trouve le bloc de code qui permet d'ouvrir une boîte de dialogue pour télécharger une image depuis une galerie ou un appareil photo.

Dans mon activité, j'ai:

 private WebView wv;  

//make HTML upload button work in Webview   
 private ValueCallback<Uri> mUploadMessage;  
 private final static int FILECHOOSER_RESULTCODE=1;

 @Override  
 protected void onActivityResult(int requestCode, int resultCode, Intent intent) {  
  if(requestCode==FILECHOOSER_RESULTCODE)  
  {  
   if (null == mUploadMessage) return;  
            Uri result = intent == null || resultCode != RESULT_OK ? null  
                    : intent.getData();  
            mUploadMessage.onReceiveValue(result);  
            mUploadMessage = null;        
  }  
 }  

Dans onCreate, j'ai ce qui suit:

    wv.setWebChromeClient(new WebChromeClient()  {
        private Uri imageUri;   

        public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType )  {      
             File imageStorageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "MyApp");
            // Create the storage directory if it does not exist
            if (! imageStorageDir.exists()){
                imageStorageDir.mkdirs();                  
            }
            File file = new File(imageStorageDir + File.separator + "IMG_" + String.valueOf(System.currentTimeMillis()) + ".jpg");  
            imageUri = Uri.fromFile(file); 

            final List<Intent> cameraIntents = new ArrayList<Intent>();
            final Intent captureIntent = new Intent(Android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
            final PackageManager packageManager = getPackageManager();
            final List<ResolveInfo> listCam = packageManager.queryIntentActivities(captureIntent, 0);
            for(ResolveInfo res : listCam) {
                final String packageName = res.activityInfo.packageName;
                final Intent i = new Intent(captureIntent);
                i.setComponent(new ComponentName(res.activityInfo.packageName, res.activityInfo.name));
                i.setPackage(packageName);
                i.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
                cameraIntents.add(i);

            }


            mUploadMessage = uploadMsg; 
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);  
            i.addCategory(Intent.CATEGORY_OPENABLE);  
            i.setType("image/*"); 
            Intent chooserIntent = Intent.createChooser(i,"Image Chooser");
            chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, cameraIntents.toArray(new Parcelable[]{}));
            MainActivity.this.startActivityForResult(chooserIntent,  FILECHOOSER_RESULTCODE); 
        }

Je peux voir l'option pour l'appareil photo, la galerie d'images et l'explorateur de fichiers en cliquant sur le bouton Télécharger. Camera, gallery and file Explorer upload option

L'Explorateur de fichiers et la Galerie fonctionnent comme prévu. Le problème est que, lorsque je prends une photo à l'aide d'un appareil photo, elle n'est pas téléchargée dans l'option "Choisir un fichier" qui affiche le statut "Aucun fichier choisi".

SUR LA SÉLECTION DE LA CAMÉRA:

camera

SUR LA PRISE DE PHOTOS À L'AIDE DE L'APPAREIL PHOTO: retour et les options de vérification apparaissent.

snapshot using camera

EN CHOISISSANT LE CHÈQUE:

FILE IS NOT UPLOADED :( IN OPTION "CHOISIR FICHIER"

enter image description here

QUOI IS ATTENDU:

image uploaded

J'ai vérifié que j'ai la permission d'écriture appropriée et donc un répertoire nommé "MyApp" est généré et l'image y est stockée (si elle est prise en invoquant la caméra après avoir cliqué sur le bouton de téléchargement sur la page Web).

Comment dire par programme à l'application de choisir une photo prise depuis l'appareil photo (qui a été stockée dans le répertoire MyApp) après avoir coché la case?

24
Chirag

Je suppose que la méthode onActivityResult est en fait appelée, mais le 3ème paramètre Intent intent est nul. Il semble que ce soit un bug des téléphones Nexus.

Mais vous pouvez enregistrer l'URI de l'image de sortie dans la variable privée et l'utiliser à la place de l'intention:

private Uri imageUri;

private void showAttachmentDialog(ValueCallback<Uri> uploadMsg) {
    this.mUploadMessage = uploadMsg;

    File imageStorageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "TestApp");
    if (!imageStorageDir.exists()) {
        imageStorageDir.mkdirs();
    }
    File file = new File(imageStorageDir + File.separator + "IMG_" + String.valueOf(System.currentTimeMillis()) + ".jpg");
    this.imageUri = Uri.fromFile(file); // save to the private variable

    final Intent captureIntent = new Intent(Android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
    captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);

    Intent i = new Intent(Intent.ACTION_GET_CONTENT);
    i.addCategory(Intent.CATEGORY_OPENABLE);
    i.setType("image/*");

    Intent chooserIntent = Intent.createChooser(i, "Image Chooser");
    chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Parcelable[] { captureIntent });

    this.startActivityForResult(chooserIntent, FILECHOOSER_RESULTCODE);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
    if (requestCode == FILECHOOSER_RESULTCODE) {
        if (null == this.mUploadMessage) {
            return;
        }

        Uri result;
        if (resultCode != RESULT_OK) {
            result = null;
        } else {
            result = intent == null ? this.imageUri : intent.getData(); // retrieve from the private variable if the intent is null
        }

        this.mUploadMessage.onReceiveValue(result);
        this.mUploadMessage = null;
    }
}

Dans ce code, j'ai ajouté la variable imageUri à l'activité et l'ai utilisée dans les deux méthodes.

10
vortexwolf

Après beaucoup de difficultés, j'ai trouvé un code qui fonctionne pour prendre des fichiers de la cuisine et de la caméra à partir d'appareils 5.0+

    private ValueCallback<Uri> mUploadMessage;
private Uri mCapturedImageURI = null;
private ValueCallback<Uri[]> mFilePathCallback;
private String mCameraPhotoPath;
private static final int INPUT_FILE_REQUEST_CODE = 1;
private static final int FILECHOOSER_RESULTCODE = 1;



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;
}

c'est l'initialisation et la définition de la vue Web

     mWebView= (WebView) findViewById(R.id.webview);
        mWebView.getSettings().setJavaScriptEnabled(true);
        mWebView.getSettings().setPluginState(WebSettings.PluginState.OFF);
        mWebView.getSettings().setLoadWithOverviewMode(true);
        mWebView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
        mWebView.getSettings().setUseWideViewPort(true);
        mWebView.getSettings().setUserAgentString("Android Mozilla/5.0 AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30");
        mWebView.getSettings().setAllowFileAccess(true);
        mWebView.getSettings().setAllowFileAccess(true);
        mWebView.getSettings().setAllowContentAccess(true);
        mWebView.getSettings().supportZoom();
        mWebView.loadUrl(Common.adPostUrl);

        mWebView.setWebViewClient(new WebViewClient() {
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                // do your handling codes here, which url is the requested url
                // probably you need to open that url rather than redirect:
                if ( url.contains(".pdf")){
                    Intent intent = new Intent(Intent.ACTION_VIEW);
                    intent.setDataAndType(Uri.parse(url), "application/pdf");
                    try{
                        view.getContext().startActivity(intent);
                    } catch (ActivityNotFoundException e) {
                        //user does not have a pdf viewer installed
                    }
                } else {
                    mWebView.loadUrl(url);
                }
                return false; // then it is not handled by default action
            }


            @Override
            public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {

Log.e("error",description);
            }


            @Override
            public void onPageStarted(WebView view, String url, Bitmap favicon) {        //show progressbar here

                super.onPageStarted(view, url, favicon);
            }

            @Override
            public void onPageFinished(WebView view, String url) {
          //hide progressbar here

            }

        });
        mWebView.setWebChromeClient(new ChromeClient());

et voici ma méthode ChomeClient ()

public class ChromeClient extends WebChromeClient {

    // For Android 5.0
    public boolean onShowFileChooser(WebView view, ValueCallback<Uri[]> filePath, WebChromeClient.FileChooserParams fileChooserParams) {
        // Double check that we don't have any existing callbacks
        if (mFilePathCallback != null) {
            mFilePathCallback.onReceiveValue(null);
        }
        mFilePathCallback = filePath;

        Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        if (takePictureIntent.resolveActivity(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
                Log.e(Common.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);

        startActivityForResult(chooserIntent, INPUT_FILE_REQUEST_CODE);

        return true;

    }

    // openFileChooser for Android 3.0+
    public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {

        mUploadMessage = uploadMsg;
        // Create AndroidExampleFolder at sdcard
        // Create AndroidExampleFolder at sdcard

        File imageStorageDir = new File(
                Environment.getExternalStoragePublicDirectory(
                        Environment.DIRECTORY_PICTURES)
                , "AndroidExampleFolder");

        if (!imageStorageDir.exists()) {
            // Create AndroidExampleFolder at sdcard
            imageStorageDir.mkdirs();
        }

        // Create camera captured image file path and name
        File file = new File(
                imageStorageDir + File.separator + "IMG_"
                        + String.valueOf(System.currentTimeMillis())
                        + ".jpg");

        mCapturedImageURI = Uri.fromFile(file);

        // Camera capture image intent
        final Intent captureIntent = new Intent(
                Android.provider.MediaStore.ACTION_IMAGE_CAPTURE);

        captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, mCapturedImageURI);

        Intent i = new Intent(Intent.ACTION_GET_CONTENT);
        i.addCategory(Intent.CATEGORY_OPENABLE);
        i.setType("image/*");

        // Create file chooser intent
        Intent chooserIntent = Intent.createChooser(i, "Image Chooser");

        // Set camera intent to file chooser
        chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS
                , new Parcelable[] { captureIntent });

        // On select image call onActivityResult method of activity
        startActivityForResult(chooserIntent, FILECHOOSER_RESULTCODE);


    }

    // openFileChooser for Android < 3.0
    public void openFileChooser(ValueCallback<Uri> uploadMsg) {
        openFileChooser(uploadMsg, "");
    }

    //openFileChooser for other Android versions
    public void openFileChooser(ValueCallback<Uri> uploadMsg,
                                String acceptType,
                                String capture) {

        openFileChooser(uploadMsg, acceptType);
    }

}

// voici ma méthode onActivityResult pour gérer les données de la galerie ou l'intention de la caméra

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Lollipop) {

        if (requestCode != INPUT_FILE_REQUEST_CODE || mFilePathCallback == null) {
            super.onActivityResult(requestCode, resultCode, data);
            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;

    } else if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KitKat) {
        if (requestCode != FILECHOOSER_RESULTCODE || mUploadMessage == null) {
            super.onActivityResult(requestCode, resultCode, data);
            return;
        }

        if (requestCode == FILECHOOSER_RESULTCODE) {

            if (null == this.mUploadMessage) {
                return;

            }

            Uri result = null;

            try {
                if (resultCode != RESULT_OK) {

                    result = null;

                } else {

                    // retrieve from the private variable if the intent is null
                    result = data == null ? mCapturedImageURI : data.getData();
                }
            } catch (Exception e) {
                Toast.makeText(getApplicationContext(), "activity :" + e,
                        Toast.LENGTH_LONG).show();
            }

            mUploadMessage.onReceiveValue(result);
            mUploadMessage = null;

        }
    }

    return;
}

et voici les autorisations nécessaires pour ouvrir la caméra

<uses-permission Android:name="Android.permission.CAMERA" />
<uses-permission Android:name="Android.permission.CAMERA2" /> // for new versions api 21+
<uses-permission Android:name="Android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission Android:name="Android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission Android:name="Android.permission.RECORD_AUDIO" />

Remarque: Mon code contient également le code pour les appareils exécutant 3.0+ mais je ne les ai jamais testés, le code ci-dessus a fonctionné sur les émulateurs Lolipop, Marshmallow et Nougat. Encore une chose, si vous voyez et l'icône de Android Système à la place de l'appareil photo, cela signifie que vous avez de nombreuses applications disponibles sur votre appareil pour gérer l'appareil photo.

9
Umar Ata

Mise à jour 6/18: Cela ne semble pas fonctionner sur Samsung Galaxy S2 avec Android 4.2.1. Cela a bien fonctionné sur HTC One X + avec 4.1.2. Soyez prudent.


J'ai fait face au même problème. Voici le problème et comment je l'ai résolu.

Problème:

Lorsque openFileChooser est appelé, l'objet de rappel ValueCallback<Uri> Est passé. Il s'agit du rappel réel de la vue Web lorsque nous avons un fichier prêt pour cela. Nous enregistrons cet objet dans mUploadMessage et utilisons la fonction mUploadMessage.onReceiveValue() dans onActivityResult pour renvoyer le fichier à Webview. Pendant que vous choisissez la caméra, cliquez sur une image, enregistrez-la et revenez à l'activité de visualisation Web, notre activité peut-être est recyclée, ce qui signifie que nous perdons réellement l'objet de rappel mUploadMessage. Et par conséquent, le fichier ne peut pas être renvoyé à la vue Web pour le téléchargement.

Correctif:

Le correctif implique d'exécuter du javascript, donc activez le javascript sur la vue Web. Fondamentalement, nous aurons un autre objet de rappel si nous avons perdu le précédent.

Nous devons créer un champ booléen 'mUploadFileOnLoad' et trois champs.

    private int mReturnCode;
    private int mResultCode;
    private Intent mResultIntent;
    private boolean mUploadFileOnLoad = false;

Lorsque nous revenons à notre activité depuis la caméra, onActivityResult sera appelé. Si l'activité est reconstruite, mUploadMessage est nul. Nous allons donc enregistrer les paramètres dans les champs et définir mUploadFileOnLoad sur true et retourner. L'autre partie est très importante.

    @Override
    protected void onActivityResult(int requestCode, 
                                    int resultCode,
                                    Intent intent) 
    {  
      //if the callback object has been recycled      
      if(null==mUploadMessage)
      {
        //Save the result
        mReturnCode = requestCode;
        mResultCode = resultCode;
        mResultIntent = intent;
        //remember to invoke file upload using Javascript
        mUploadFileOnLoad = true;
        return;
      }else
        mUploadFileOnLoad = false;
      //rest of the code
    }

Les parties importantes de cette solution sont dans WebViewClient et WebChromeClient

    new WebChromeClient() {

        //Other overloaded functions

        //See http://stackoverflow.com/a/15423907/375093 for full explanation
        //The undocumented magic method override
        //Eclipse will swear at you if you try to put @Override here
        // For Android < 3.0
        public void openFileChooser(ValueCallback<Uri> uploadMsg) {
            //If we lost the callback object
            if(mUploadFileOnLoad)
            {
                mUploadMessage = uploadMsg;
                //use the saved result objects to invoke onActivityResult
                onActivityResult(mReturnCode, mResultCode, mResultIntent);
                return;
            }
         //Rest of the code....
         }

et

        new WebViewClient() {
        @Override
        public void onPageFinished(WebView view, String url) {
            if(mUploadFileOnLoad)
            {
               webview.loadUrl("javascript:document.getElementById('my_file').click()");
            }
        }

Dans ce qui précède, my_file Est l'ID de l'élément <input> Dans la page Web.

<input type="file" id="my_file">

Donc, en résumé, ce que nous avons fait est - Lorsque nous n'avons pas d'objet de rappel, nous enregistrons les données reçues d'une autre activité et définissons mUploadFileOnLoad sur true et attendons que la page se charge. Lorsque la page se charge, nous utilisons Javascript pour invoquer le sélecteur de fichiers afin d'obtenir un objet de rappel. Puisque nous avons déjà des résultats, nous invoquons onActivityResult et retournons. onActivityResult a maintenant un rappel à partir de la vue Web.

6
Sundeep

Je suis désolé mon anglais.

C'est une solution.

Le premier, vous définissez le membre de fichier comme ceci.

public File mTempFile;

votre openFileChooser est ok.

La méthode onActivityResult est si importante.

l'application appareil photo ne renvoie pas l'URL, mais ValueCallback doit avoir une URL.

Obtenez l'URI de mTempFile.

c'est du travail.

J'utilise comme ça.

if ( mTempFile.exists() ) {

    mUploadMessage.onReceiveValue(Uri.fromFile(mTempFile));
    mUploadMessage = null;

} else {

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

S'il existe mTempFile qui s'appelait camera, autre cas de la galerie.

3
user3558502

Assurez-vous que vous n'avez PAS Android:launchMode="singleInstance" dans le fichier manifeste

2
user2381711