Android WebView: невозможно сохранить или загрузить изображение, снятое камерой, на HTML5 холст, но можно загрузить существующие изображения из хранилища. - PullRequest
0 голосов
/ 17 июня 2020

Фон: Я пытаюсь создать очень простое c приложение WebView для доступа к адаптивному веб-приложению / веб-сайту, которое я недавно создал. Одна из функций веб-сайта включает кнопку «выбрать фото», которая позволяет пользователю загружать изображение на холст HTML5 для временного просмотра изображения и извлечения данных. Эта кнопка основана на простом вводе файла HTML:

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

Эта функция отлично работает в «обычных» мобильных браузерах (Chrome, Safari, Firefox, и c. На различных Android и iOS устройств). Когда пользователи нажимают кнопку «выбрать фото» в любом из этих браузеров, они могут либо (1) использовать камеру своего устройства, чтобы сделать / сохранить / загрузить новое изображение на холст HTML5, либо (2) загрузить существующее изображение, хранящееся на их телефоне.

Вот проблема: В моем приложении WebView я могу загрузить существующую фотографию из локального хранилища на холст HTML5, но я не могу загрузить фотографии, сделанные камерой при открытии камеры из приложения WebView .

Когда я нажимаю кнопку веб-сайта «Выбрать фото» в приложении WebView, мне предлагается выбрать между камерой и локальным хранилищем (это хорошо). Просмотр / загрузка из локального хранилища на холст HTML5 работает нормально. Однако, когда я нажимаю на опцию камеры, я могу сделать снимок, как обычно, с помощью камеры (передней или задней камеры), но фотография не сохраняется и не загружается на холст HTML5 (это плохо!). Я просто получаю пустой холст HTML5 после съемки / принятия фотографии. Фотография также не отображается в локальном хранилище.

Я не могу найти никаких связанных ошибок в журналах (проверено на нескольких эмуляторах / различных версиях Android и на моем Pixel 3a с Android 10). Приложение запрашивает разрешения для камеры и хранения / записи, и приложение, похоже, сохраняет эти разрешения при предоставлении. Я предполагаю, что приложение не может сохранять / получать новые фотографии с камеры, потому что я указываю неправильный каталог или имя файла для нового файла фотографии. Не знаю, как с этим справиться.

Что я пробовал: Последние несколько дней я пытался установить это приложение для загрузки фотографий с камеры. Самое близкое, что я получил, - это код из этого сообщения (как показано в моем коде ниже).

Я попытался дополнить код из этого сообщения некоторой информацией из Android документы разработчика (особенно камера> сохранение медиафайлов ) и пример входного файла хрома от Google на GitHub . Я несколько раз переписывал приложение WebView частично и полностью. Также пробовал вариантов этого сообщения и , изменяя путь к каталогу, , а также все предложения здесь и этот собственный метод "Захват изображения с камеры с помощью File Provider "" и др.

Вот мой текущий код MainActivity. java. Пожалуйста, дайте мне знать, если вам нужны другие файлы. (примечание: url заменен на example.com)

package com.example.placeholderexamplename;

import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import androidx.core.app.ActivityCompat;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.view.KeyEvent;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.widget.Toast;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private static final int INPUT_FILE_REQUEST_CODE = 2;
    private static final int FILECHOOSER_RESULTCODE = 1;
    private static final String TAG = MainActivity.class.getSimpleName();
    Context context;
    WebView webView;
    private WebSettings webSettings;
    private ValueCallback<Uri> mUploadMessage;
    private Uri mCapturedImageURI = null;
    private ValueCallback<Uri[]> mFilePathCallback;
    private String mCameraPhotoPath;
    private static final int CAMERA_PERMISSION_CODE = 100;
    private static final int STORAGE_PERMISSION_CODE = 101;

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {

        if (requestCode != INPUT_FILE_REQUEST_CODE || mFilePathCallback == null) {
            super.onActivityResult(requestCode, resultCode, data);
            return;
        }
        Uri[] results = null;
        // Check response
        if (resultCode == Activity.RESULT_OK) {
            if (data == null) {
                // If no data, 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;
    }



    //start check permissions

    private final static int REQUEST_CODE_ASK_PERMISSIONS = 1;
    private static final String[] REQUIRED_SDK_PERMISSIONS = new String[] {
            Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE };


    protected void checkPermissions() {

        final List<String> missingPermissions = new ArrayList<String>();

        // check all required dynamic permissions
        for (final String permission : REQUIRED_SDK_PERMISSIONS) {
            final int result = ContextCompat.checkSelfPermission(this, permission);
            if (result != PackageManager.PERMISSION_GRANTED) {
                missingPermissions.add(permission);

            }



        }
        if (!missingPermissions.isEmpty()) {

            // request all missing permissions
            final String[] permissions = missingPermissions
                    .toArray(new String[missingPermissions.size()]);
            ActivityCompat.requestPermissions(this, permissions, REQUEST_CODE_ASK_PERMISSIONS);
        } else {
            final int[] grantResults = new int[REQUIRED_SDK_PERMISSIONS.length];
            Arrays.fill(grantResults, PackageManager.PERMISSION_GRANTED);
            onRequestPermissionsResult(REQUEST_CODE_ASK_PERMISSIONS, REQUIRED_SDK_PERMISSIONS,
                    grantResults);
        }
    }


    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[],
                                           @NonNull int[] grantResults) {
        switch (requestCode) {
            case REQUEST_CODE_ASK_PERMISSIONS:
                for (int index = permissions.length - 1; index >= 0; --index) {
                    if (grantResults[index] != PackageManager.PERMISSION_GRANTED) {
                        Toast.makeText(this, "To search by photo, please grant camera and storage access via device settings.", Toast.LENGTH_LONG).show();
                    }
                }
        }
    }
    //end check permissions


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

        View decorView = getWindow().getDecorView();
        int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN;
        decorView.setSystemUiVisibility(uiOptions);
        requestWindowFeature(Window.FEATURE_NO_TITLE);

        setContentView(R.layout.activity_main);
        context = this;
        webView = (WebView) findViewById(R.id.activity_main_webview);
        WebSettings settings = webView.getSettings();
        settings.setJavaScriptEnabled(true);
        webView.loadUrl("https://www.example.com");
        webView.setWebChromeClient(new ChromeClient());
        checkPermissions();

        webView.setWebViewClient(new WebViewClient() {

            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                boolean isLocalUrl = false;
                try {
                    URL givenUrl = new URL(url);
                    String host = givenUrl.getHost();
                    if(host.contains("example.com"))
                        isLocalUrl = true;

                } catch (MalformedURLException e) {

                }

                if (isLocalUrl)
                    return super.shouldOverrideUrlLoading(view, url);
                else
                {
                    Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
                    startActivity(intent);
                    return true;
                }
            }

            @Override
            public void onPageFinished(WebView view, String url) {
                super.onPageFinished(view, url);

                Log.w("----------", "page finish : " + url);
            }

            public void onReceivedError(WebView webView, int errorCode, String description, String failingUrl) {
                try {
                    webView.stopLoading();
                } catch (Exception e) {
                }
                if (webView.canGoBack()) {
                    webView.goBack();
                }
                webView.loadUrl("about:blank");
                AlertDialog alertDialog = new AlertDialog.Builder(context).create();
                alertDialog.setTitle("Error");
                alertDialog.setCancelable(false);
                alertDialog.setMessage("Check your internet connection and try again.");
                alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "Try Again", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        startActivity(getIntent());
                        finish();
                    }
                });
                alertDialog.show();
                super.onReceivedError(webView, errorCode, description, failingUrl);
            }
        });
    }

    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.getExternalStorageState());
        File storageDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);

        File imageFile = File.createTempFile(
                imageFileName,  // prefix
                ".jpg",         // suffix
                storageDir      // directory
        );

        return imageFile;

    }

    public boolean onKeyDown(int keyCode, KeyEvent event) {
        // Check if the key event was the Back button and if there's history
        if ((keyCode == KeyEvent.KEYCODE_BACK) && webView.canGoBack()) {
            webView.goBack();
            return true;
        }

        return super.onKeyDown(keyCode, event);
    }




    @Override
    public void onBackPressed() {
        super.onBackPressed();
    }

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


            //Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
            // startActivity(intent);

            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(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/*");
            //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, "Choose from...");
            chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);
            startActivityForResult(chooserIntent, INPUT_FILE_REQUEST_CODE);
            return true;
        }

    }
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        if (hasFocus) {
            View decorView = getWindow().getDecorView();
            int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN;
            decorView.setSystemUiVisibility(uiOptions);
        }
    }
}

(1-е изменение) Вот мой код AndroidManifest. xml (для url используются значения-заполнители, et c.). Это включает в себя некоторый неиспользованный код, оставшийся от предыдущих попыток (например, раздел); удаление неиспользуемого кода не повлияло на проблему. В настоящее время я запрашиваю и записываю только разрешения «КАМЕРА» и «WRITE_EXTERNAL_STORAGE» (это правильные разрешения?):

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.placeholderexamplename">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/example_icon"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/example_icon_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name="com.example.placeholderexamplename.MainActivity"
            android:configChanges="orientation|screenSize">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

            <intent-filter>
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="https" android:host="www.example.com" />
                <data android:scheme="https" />
            </intent-filter>
        </activity>
        <provider
            android:name="androidx.core.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>

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



</manifest>

И мой activity_main. xml код:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.placeholderexamplename.MainActivity">

    <WebView
        android:id="@+id/activity_main_webview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true" />



    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />


</androidx.constraintlayout.widget.ConstraintLayout>

Я хотел бы поддерживать KitKat и более поздние версии, но на данный момент подойдет решение, ограниченное Android 10. Приложение WebView должно иметь возможность получать изображение на холст HTML5 либо (1) с камеры устройства, либо (2) из ​​локального хранилища. Новые фотографии, сделанные из приложения, могут быть сохранены либо в каталоге publi c, либо в каталоге, привязанном к приложению - никаких предпочтений, мне просто нужно иметь возможность сделать снимок из приложения и показать его здесь HTML5 холст для быстрого действия. Я буду работать оттуда, как только разблокирую.

Это моя первая попытка Android development / Android Studio. Я новичок, и я застрял. Буду очень признателен за любую помощь! Все остальное в приложении WebView работает, это мой последний блокировщик.

Заранее благодарим вас за ваш вклад.

...