Skip to content

Commit

Permalink
Implement flutter#3286
Browse files Browse the repository at this point in the history
  • Loading branch information
phamnhuvu-dev committed Jan 12, 2021
1 parent 71a8317 commit fb0c292
Show file tree
Hide file tree
Showing 9 changed files with 370 additions and 2 deletions.
25 changes: 23 additions & 2 deletions packages/webview_flutter/android/src/main/AndroidManifest.xml
@@ -1,2 +1,23 @@
<manifest package="io.flutter.plugins.webviewflutter">
</manifest>
<manifest package="io.flutter.plugins.webviewflutter"
xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity
android:name="io.flutter.plugins.webviewflutter.RequestCameraPermissionActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen" />
<activity
android:name="io.flutter.plugins.webviewflutter.FileChooserActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen" />

<provider
android:name="io.flutter.plugins.webviewflutter.GenericFileProvider"
android:authorities="${applicationId}.generic.provider"
android:exported="false"
android:grantUriPermissions="true"
>
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"
/>
</provider>
</application>
</manifest>
@@ -0,0 +1,18 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugins.webviewflutter;

public class Constants {
static final String ACTION_REQUEST_CAMERA_PERMISSION_FINISHED =
"action_request_camera_permission_denied";
static final String ACTION_FILE_CHOOSER_FINISHED = "action_file_chooser_completed";

static final String EXTRA_TITLE = "extra_title";
static final String EXTRA_TYPE = "extra_type";
static final String EXTRA_SHOW_CAMERA_OPTION = "extra_show_camera_option";
static final String EXTRA_FILE_URI = "extra_file_uri";

static final String WEBVIEW_STORAGE_DIRECTORY = "storage";
}
@@ -0,0 +1,169 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugins.webviewflutter;

import static io.flutter.plugins.webviewflutter.Constants.ACTION_FILE_CHOOSER_FINISHED;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_FILE_URI;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_SHOW_CAMERA_OPTION;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_TITLE;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_TYPE;
import static io.flutter.plugins.webviewflutter.Constants.WEBVIEW_STORAGE_DIRECTORY;

import android.app.Activity;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.provider.OpenableColumns;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.core.content.FileProvider;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;

public class FileChooserActivity extends Activity {

private static final int FILE_CHOOSER_REQUEST_CODE = 12322;
private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");

private Uri cameraImageUri;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
showFileChooser(getIntent().getBooleanExtra(EXTRA_SHOW_CAMERA_OPTION, false));
}

private void showFileChooser(boolean enableCamera) {
Intent galleryIntent = createGalleryIntent();
Intent takePictureIntent = enableCamera ? createCameraIntent() : null;
if (galleryIntent == null && takePictureIntent == null) {
// cannot open anything: cancel file chooser
sendBroadcast(new Intent(ACTION_FILE_CHOOSER_FINISHED));
finish();
} else {
Intent[] intentArray =
takePictureIntent != null ? new Intent[] {takePictureIntent} : new Intent[] {};

Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
chooserIntent.putExtra(
Intent.EXTRA_INTENT, galleryIntent != null ? galleryIntent : takePictureIntent);
chooserIntent.putExtra(Intent.EXTRA_TITLE, getIntent().getStringExtra(EXTRA_TITLE));
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);

startActivityForResult(chooserIntent, FILE_CHOOSER_REQUEST_CODE);
}
}

private Intent createGalleryIntent() {
Intent filesIntent = new Intent(Intent.ACTION_GET_CONTENT);
filesIntent.setType(getIntent().getStringExtra(EXTRA_TYPE));
return (filesIntent.resolveActivity(getPackageManager()) != null) ? filesIntent : null;
}

private Intent createCameraIntent() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(getPackageManager()) == null) {
return null;
}
// Create the File where the photo should go
cameraImageUri = getTempImageUri();
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, cameraImageUri);

return takePictureIntent;
}

private File getStorageDirectory() {
File imageDirectory = new File(getCacheDir(), WEBVIEW_STORAGE_DIRECTORY);
if (!imageDirectory.exists() && !imageDirectory.mkdir()) {
Log.e("WEBVIEW", "Unable to create storage directory");
}
return imageDirectory;
}

private Uri getTempImageUri() {
String imageFileName = "IMG-" + simpleDateFormat.format(new Date()) + ".jpg";
File imageFile = new File(getStorageDirectory(), imageFileName);
return FileProvider.getUriForFile(
this, getApplicationContext().getPackageName() + ".generic.provider", imageFile);
}

private String getFileNameFromUri(Uri uri) {
Cursor returnCursor = getContentResolver().query(uri, null, null, null, null);
assert returnCursor != null;
int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
returnCursor.moveToFirst();
String name = returnCursor.getString(nameIndex);
returnCursor.close();
return name;
}

private Uri copyToLocalUri(Uri uri) {
InputStream in = null;
OutputStream out = null;
try {
File destination = new File(getStorageDirectory(), getFileNameFromUri(uri));
in = getContentResolver().openInputStream(uri);
out = new FileOutputStream(destination);

int cnt = 0;
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
cnt += len;
}

return FileProvider.getUriForFile(
this, getApplicationContext().getPackageName() + ".generic.provider", destination);
} catch (Exception e) {
Log.e("WEBVIEW", "Unable to copy selected image", e);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

return null;
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == FILE_CHOOSER_REQUEST_CODE) {
Intent intent = new Intent(ACTION_FILE_CHOOSER_FINISHED);
if (resultCode == Activity.RESULT_OK) {
if (data != null && data.getDataString() != null) {
// result from file browser
final Uri uri = copyToLocalUri(data.getData());
intent.putExtra(EXTRA_FILE_URI, uri.toString());
} else {
// result from camera
intent.putExtra(EXTRA_FILE_URI, cameraImageUri.toString());
}
}
sendBroadcast(intent);
finish();
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
}
@@ -0,0 +1,89 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugins.webviewflutter;

import static io.flutter.plugins.webviewflutter.Constants.ACTION_FILE_CHOOSER_FINISHED;
import static io.flutter.plugins.webviewflutter.Constants.ACTION_REQUEST_CAMERA_PERMISSION_FINISHED;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_FILE_URI;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_SHOW_CAMERA_OPTION;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_TITLE;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_TYPE;

import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.webkit.ValueCallback;
import androidx.core.content.ContextCompat;

public class FileChooserLauncher extends BroadcastReceiver {

private Context context;
private String title;
private String type;
private boolean showCameraOption;
private ValueCallback<Uri[]> filePathCallback;

public FileChooserLauncher(
Context context,
String title,
String type,
boolean showCameraOption,
ValueCallback<Uri[]> filePathCallback) {
this.context = context;
this.title = title;
this.type = type;
this.showCameraOption = showCameraOption;
this.filePathCallback = filePathCallback;
}

private boolean hasCameraPermission() {
return ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA)
== PackageManager.PERMISSION_GRANTED;
}

public void start() {
if (!showCameraOption || hasCameraPermission()) {
showFileChooser();
} else {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ACTION_REQUEST_CAMERA_PERMISSION_FINISHED);
context.registerReceiver(this, intentFilter);

Intent intent = new Intent(context, RequestCameraPermissionActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
}

private void showFileChooser() {
IntentFilter intentFilter = new IntentFilter(ACTION_FILE_CHOOSER_FINISHED);
context.registerReceiver(this, intentFilter);

Intent intent = new Intent(context, FileChooserActivity.class);
intent.putExtra(EXTRA_TITLE, title);
intent.putExtra(EXTRA_TYPE, type);
intent.putExtra(EXTRA_SHOW_CAMERA_OPTION, showCameraOption && hasCameraPermission());
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}

@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(ACTION_REQUEST_CAMERA_PERMISSION_FINISHED)) {
context.unregisterReceiver(this);
showFileChooser();
} else if (intent.getAction().equals(ACTION_FILE_CHOOSER_FINISHED)) {
String uriString = intent.getStringExtra(EXTRA_FILE_URI);
Uri[] result = uriString != null ? new Uri[] {Uri.parse(uriString)} : null;
filePathCallback.onReceiveValue(result);
context.unregisterReceiver(this);
filePathCallback = null;
}
}
}
Expand Up @@ -7,10 +7,12 @@
import android.annotation.TargetApi;
import android.content.Context;
import android.hardware.display.DisplayManager;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceRequest;
import android.webkit.WebStorage;
Expand Down Expand Up @@ -74,6 +76,18 @@ public boolean shouldOverrideUrlLoading(WebView view, String url) {
}
}

@Override
public boolean onShowFileChooser(
WebView webView,
ValueCallback<Uri[]> filePathCallback,
FileChooserParams fileChooserParams) {
final Context context = webView.getContext();
final String title = context.getResources().getString(R.string.webview_file_chooser_title);
final String type = context.getResources().getString(R.string.webview_file_chooser_type);
new FileChooserLauncher(context, title, type, true, filePathCallback).start();
return true;
}

@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
@SuppressWarnings("unchecked")
FlutterWebView(
Expand Down
@@ -0,0 +1,9 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugins.webviewflutter;

import androidx.core.content.FileProvider;

public class GenericFileProvider extends FileProvider {}
@@ -0,0 +1,39 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugins.webviewflutter;

import static io.flutter.plugins.webviewflutter.Constants.ACTION_REQUEST_CAMERA_PERMISSION_FINISHED;

import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;

public class RequestCameraPermissionActivity extends Activity {

private static final int CAMERA_PERMISSION_REQUEST_CODE = 12321;

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

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

@Override
public void onRequestPermissionsResult(
int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) {
sendBroadcast(new Intent(ACTION_REQUEST_CAMERA_PERMISSION_FINISHED));
finish();
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
}
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="webview_file_chooser_title">Choose a file</string>
<string name="webview_file_chooser_type">image/*</string>
</resources>

0 comments on commit fb0c292

Please sign in to comment.