AndroidのWebViewの「ファイルを選択」ボタン(input typ=”file”)で写真を撮るかファイルを選択する

AndroidのWebViewで「ファイルを選択」ボタン(input typ=”file”)を押した時に、カメラアプリかファイルアプリから画像をアップロードする方法。

対象はAndroid5以上。

HTMLでは、「ファイルを選択」ボタンで選択された画像をimgタグに表示する。

<input type="file" accept="image/*" capture="camera" id="camera">
<img id="frame">
<script>
const camera = document.getElementById('camera');
const frame = document.getElementById('frame');
camera.addEventListener('change', function(e) {
    console.log(e.target.files);
    if (e.target.files.length > 0) {
        frame.src = URL.createObjectURL(e.target.files[0]);
    } else {
        frame.src = '';
    }
});
</script>

このHTMLをAndroidのWebViewで表示して、選択した画像を表示できるようにする。

以下、Androidアプリの作成手順。

AndroidManifest.xmlにインターネットとカメラの権限を追加する。

<manifest ... >
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.CAMERA" />
    ...
</manifest>

Android9以上で、HTTP通信(http://〜)をする場合は、AndroidManifest.xmlに「android:usesCleartextTraffic=”true”」を追加する。


<application android:usesCleartextTraffic="true"

カメラアプリで撮影した写真はFileProviderを使って共有するため、FileProviderを設定する。

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

app/src/main/res/xml/provider_path.xmlを作成する。

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

カメラの撮影に必要な権限を要求するクラスを作成する。

public class CameraPermission {
    /**
     * 権限があるか確認し、権限がなければ要求する
     *
     * @return 権限があるときはtrue
     */
    public static boolean checkAndRequestPermissions(final @NonNull Activity activity, final @IntRange(from = 0) int requestCode) {
        String[] permissionNeeded = permissionNeeded(activity, new String[]{
                Manifest.permission.CAMERA,
        });
        if (permissionNeeded.length > 0) {
            ActivityCompat.requestPermissions(activity, permissionNeeded, requestCode);
            return false;
        }
        return true;
    }

    private static String[] permissionNeeded(final @NonNull Activity activity, String[] permissions) {
        List<String> listPermissionsNeeded = new ArrayList<>();
        for (String permission : permissions) {
            if (ContextCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED) {
                listPermissionsNeeded.add(permission);
            }
        }
        return listPermissionsNeeded.toArray(new String[0]);
    }
}

MainActivityにWebViewを表示するコードを追加する。
「ファイルを選択」ボタンが押された時、showFileChooser()メソッドを呼ぶ。

public class MainActivity extends AppCompatActivity {

    private ValueCallback<Uri[]> filePathCallback;

    @SuppressLint("SetJavaScriptEnabled")
    private void setupWebView() {
        WebView webView = new WebView(this);
        setContentView(webView);
        webView.loadUrl("http://xxx.xxx.xxx.xxx/");
        // JavaScriptを有効にする
        webView.getSettings().setJavaScriptEnabled(true);
        webView.setWebChromeClient(new WebChromeClient() {
            /**
             * 「ファイルを選択」ボタンが押された時
             * For Android > 5.0
             * @param webView
             * @param filePathCallback
             * @param fileChooserParams
             * @return
             */
            @TargetApi(Build.VERSION_CODES.LOLLIPOP)
            public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
                showFileChooser(filePathCallback, fileChooserParams);
                return true;
            }
        });
    }
}

「ファイルを選択」ボタンが押された時の処理。
権限がなければ権限を要求する。
カメラアプリとファイルアプリを選択するインテントを作成する。

private void showFileChooser(ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) {
    // 完了していない処理があれば完了する
    if (this.filePathCallback != null) {
        this.filePathCallback.onReceiveValue(null);
    }
    this.filePathCallback = filePathCallback;

    // 権限がないときは、権限を要求する
    if (!CameraPermission.checkAndRequestPermissions(this, REQUEST_PERMISSIONS)) {
        this.filePathCallback.onReceiveValue(null);
        this.filePathCallback = null;
        return;
    }

    // カメラとファイルのインテントを作成する
    Intent chooserIntent = Intent.createChooser(fileChooserParams.createIntent(), "写真の選択");
    try {
        mImageUri = createImageFile();
        Intent imageCaptureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        imageCaptureIntent.putExtra(MediaStore.EXTRA_OUTPUT, mImageUri);
        chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Parcelable[]{imageCaptureIntent});
    } catch (IOException ex) {
        mImageUri = null;
    }
    startActivityForResult(chooserIntent, REQUEST_SELECT_FILE_CODE);
}

撮影した写真を保存するファイルのURIを作成するメソッドを作成する。

private Uri createImageFile() throws IOException {
    File folder = getExternalFilesDir(Environment.DIRECTORY_DCIM);
    String date = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
    String fileName = String.format("MyApp_%s.jpg", date);
    File cameraFile = new File(folder, fileName);

    return FileProvider.getUriForFile(
            this,
            getApplicationContext().getPackageName() + ".fileprovider",
            cameraFile);
}

選択されたファイルをWebViewに返す。

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (requestCode == REQUEST_SELECT_FILE_CODE) {
        if (filePathCallback != null) {
            if (resultCode == RESULT_OK) {
                Uri[] result = WebChromeClient.FileChooserParams.parseResult(resultCode, data);
                if (result == null) {
                    result = new Uri[]{mImageUri};
                }
                filePathCallback.onReceiveValue(result);
            } else {
                filePathCallback.onReceiveValue(null);
            }
            filePathCallback = null;
        }
    }
}

2020年12月15日追記

動作確認用のサンプルコードを公開しました。

コメント

  1. 初めまして
    記事を見させていただきました

    お伺いしたいのですが、ここままのソースを試してみたのですがカメラ撮影後に撮影した画像が残らない(クラッシュ)します
    これはonActivityResultメソッドの部分またはshowFileChooserメソッドでカメラを選択した際の処理が足りないという認識で良いでしょうか
    またもしそうであればご教授いただきたいです

    恐れ入りますがよろしくお願いいたします

  2. Pingback: 【Android】WebView の input type file で カメラを起動する | プログラマーの1日

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください