summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTed Choc <tedchoc@google.com>2015-07-20 16:40:20 -0700
committerTed Choc <tedchoc@google.com>2015-07-20 23:41:26 +0000
commit7c9ae5f380d65a8a4f52b31323905b3069dc3777 (patch)
tree26aaac30c36a06ee4000e1b7b5651e4d2513c3bf
parent2486cc4ecbdfb1226ad0fd9d3790eda59f5074ae (diff)
downloadchromium_src-7c9ae5f380d65a8a4f52b31323905b3069dc3777.zip
chromium_src-7c9ae5f380d65a8a4f52b31323905b3069dc3777.tar.gz
chromium_src-7c9ae5f380d65a8a4f52b31323905b3069dc3777.tar.bz2
Ask for necessary android permissions when selecting a file.
TEST: Go to: http://www.w3schools.com/tags/att_input_accept.asp Click "Try it yourself" Click "Choose file" and see it prompts for camera permission Change accept attribute to either "video/*" or "audio/*" and see that it requests camera or microphone accordingly. If there is no capture attirubte in the input tag, declining the permission should still allow the user to select a file but the direct intent to camera / sound recording should not be present. BUG=510998 Review URL: https://codereview.chromium.org/1238903007 Cr-Commit-Position: refs/heads/master@{#339511} Review URL: https://codereview.chromium.org/1241343002 . Cr-Commit-Position: refs/branch-heads/2403@{#536} Cr-Branched-From: f54b8097a9c45ed4ad308133d49f05325d6c5070-refs/heads/master@{#330231}
-rw-r--r--chrome/android/javatests_shell/src/org/chromium/chrome/browser/input/SelectFileDialogTest.java5
-rw-r--r--ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java152
2 files changed, 117 insertions, 40 deletions
diff --git a/chrome/android/javatests_shell/src/org/chromium/chrome/browser/input/SelectFileDialogTest.java b/chrome/android/javatests_shell/src/org/chromium/chrome/browser/input/SelectFileDialogTest.java
index 3ddd7ae..5c964f8 100644
--- a/chrome/android/javatests_shell/src/org/chromium/chrome/browser/input/SelectFileDialogTest.java
+++ b/chrome/android/javatests_shell/src/org/chromium/chrome/browser/input/SelectFileDialogTest.java
@@ -57,6 +57,11 @@ public class SelectFileDialogTest extends ChromeShellTestBase {
lastCallback = callback;
return 1;
}
+
+ @Override
+ public boolean canResolveActivity(Intent intent) {
+ return true;
+ }
}
private class IntentSentCriteria implements Criteria {
diff --git a/ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java b/ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java
index 9dec89d..88ab747 100644
--- a/ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java
+++ b/ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java
@@ -4,12 +4,14 @@
package org.chromium.ui.base;
+import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.ClipData;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
@@ -34,7 +36,7 @@ import java.util.List;
* a set of accepted file types. The path of the selected file is passed to the native dialog.
*/
@JNINamespace("ui")
-class SelectFileDialog implements WindowAndroid.IntentCallback {
+class SelectFileDialog implements WindowAndroid.IntentCallback, WindowAndroid.PermissionCallback {
private static final String TAG = "SelectFileDialog";
private static final String IMAGE_TYPE = "image/";
private static final String VIDEO_TYPE = "video/";
@@ -47,7 +49,13 @@ class SelectFileDialog implements WindowAndroid.IntentCallback {
private final long mNativeSelectFileDialog;
private List<String> mFileTypes;
private boolean mCapture;
+ private boolean mAllowMultiple;
private Uri mCameraOutputUri;
+ private WindowAndroid mWindowAndroid;
+
+ private boolean mSupportsImageCapture;
+ private boolean mSupportsVideoCapture;
+ private boolean mSupportsAudioCapture;
private SelectFileDialog(long nativeSelectFileDialog) {
mNativeSelectFileDialog = nativeSelectFileDialog;
@@ -66,48 +74,78 @@ class SelectFileDialog implements WindowAndroid.IntentCallback {
String[] fileTypes, boolean capture, boolean multiple, WindowAndroid window) {
mFileTypes = new ArrayList<String>(Arrays.asList(fileTypes));
mCapture = capture;
+ mAllowMultiple = multiple;
+ mWindowAndroid = window;
+
+ mSupportsImageCapture =
+ mWindowAndroid.canResolveActivity(new Intent(MediaStore.ACTION_IMAGE_CAPTURE));
+ mSupportsVideoCapture =
+ mWindowAndroid.canResolveActivity(new Intent(MediaStore.ACTION_VIDEO_CAPTURE));
+ mSupportsAudioCapture =
+ mWindowAndroid.canResolveActivity(
+ new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION));
+
+ List<String> missingPermissions = new ArrayList<>();
+ if (((mSupportsImageCapture && shouldShowImageTypes())
+ || (mSupportsVideoCapture && shouldShowVideoTypes()))
+ && !window.hasPermission(Manifest.permission.CAMERA)) {
+ missingPermissions.add(Manifest.permission.CAMERA);
+ }
+ if (mSupportsAudioCapture && shouldShowAudioTypes()
+ && !window.hasPermission(Manifest.permission.RECORD_AUDIO)) {
+ missingPermissions.add(Manifest.permission.RECORD_AUDIO);
+ }
- Intent chooser = new Intent(Intent.ACTION_CHOOSER);
- Intent camera = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
- Context context = window.getApplicationContext();
- camera.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
- | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
- try {
- mCameraOutputUri =
- UiUtils.getUriForImageCaptureFile(context, getFileForImageCapture(context));
- } catch (IOException e) {
- Log.e(TAG, "Cannot retrieve content uri from file", e);
+ if (missingPermissions.isEmpty()) {
+ launchSelectFileIntent();
+ } else {
+ window.requestPermissions(
+ missingPermissions.toArray(new String[missingPermissions.size()]), this);
+ }
+ }
+
+ private void launchSelectFileIntent() {
+ boolean hasCameraPermission = mWindowAndroid.hasPermission(Manifest.permission.CAMERA);
+ boolean hasAudioPermission =
+ mWindowAndroid.hasPermission(Manifest.permission.RECORD_AUDIO);
+
+ Intent camera = null;
+ if (mSupportsImageCapture && hasCameraPermission) {
+ camera = getCameraIntent(mWindowAndroid.getApplicationContext());
+ // The camera intent can be null if we are unable to generate the output URI. If this
+ // occurs while we are in camera capture mode, early exit as there is nothing we can
+ // do at this point.
+ if (camera == null && captureCamera()) {
+ onFileNotSelected();
+ return;
+ }
}
- if (mCameraOutputUri == null) {
- onFileNotSelected();
- return;
+ Intent camcorder = null;
+ if (mSupportsVideoCapture && hasCameraPermission) {
+ camcorder = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
}
- camera.putExtra(MediaStore.EXTRA_OUTPUT, mCameraOutputUri);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
- camera.setClipData(ClipData.newUri(
- context.getContentResolver(), UiUtils.IMAGE_FILE_PATH, mCameraOutputUri));
+ Intent soundRecorder = null;
+ if (mSupportsAudioCapture && hasAudioPermission) {
+ soundRecorder = new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION);
}
- Intent camcorder = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
- Intent soundRecorder = new Intent(
- MediaStore.Audio.Media.RECORD_SOUND_ACTION);
// Quick check - if the |capture| parameter is set and |fileTypes| has the appropriate MIME
- // type, we should just launch the appropriate intent. Otherwise build up a chooser based on
- // the accept type and then display that to the user.
- if (captureCamera()) {
- if (window.showIntent(camera, this, R.string.low_memory_error)) return;
- } else if (captureCamcorder()) {
- if (window.showIntent(camcorder, this, R.string.low_memory_error)) return;
- } else if (captureMicrophone()) {
- if (window.showIntent(soundRecorder, this, R.string.low_memory_error)) return;
+ // type, we should just launch the appropriate intent. Otherwise build up a chooser based
+ // on the accept type and then display that to the user.
+ if (captureCamera() && camera != null) {
+ if (mWindowAndroid.showIntent(camera, this, R.string.low_memory_error)) return;
+ } else if (captureCamcorder() && camcorder != null) {
+ if (mWindowAndroid.showIntent(camcorder, this, R.string.low_memory_error)) return;
+ } else if (captureMicrophone() && soundRecorder != null) {
+ if (mWindowAndroid.showIntent(soundRecorder, this, R.string.low_memory_error)) return;
}
Intent getContentIntent = new Intent(Intent.ACTION_GET_CONTENT);
getContentIntent.addCategory(Intent.CATEGORY_OPENABLE);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 && multiple) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 && mAllowMultiple) {
getContentIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
}
@@ -117,13 +155,13 @@ class SelectFileDialog implements WindowAndroid.IntentCallback {
// that if the web page specified multiple accept types, we will have built a generic
// chooser above.
if (shouldShowImageTypes()) {
- extraIntents.add(camera);
+ if (camera != null) extraIntents.add(camera);
getContentIntent.setType(ALL_IMAGE_TYPES);
} else if (shouldShowVideoTypes()) {
- extraIntents.add(camcorder);
+ if (camcorder != null) extraIntents.add(camcorder);
getContentIntent.setType(ALL_VIDEO_TYPES);
} else if (shouldShowAudioTypes()) {
- extraIntents.add(soundRecorder);
+ if (soundRecorder != null) extraIntents.add(soundRecorder);
getContentIntent.setType(ALL_AUDIO_TYPES);
}
}
@@ -131,21 +169,44 @@ class SelectFileDialog implements WindowAndroid.IntentCallback {
if (extraIntents.isEmpty()) {
// We couldn't resolve an accept type, so fallback to a generic chooser.
getContentIntent.setType(ANY_TYPES);
- extraIntents.add(camera);
- extraIntents.add(camcorder);
- extraIntents.add(soundRecorder);
+ if (camera != null) extraIntents.add(camera);
+ if (camcorder != null) extraIntents.add(camcorder);
+ if (soundRecorder != null) extraIntents.add(soundRecorder);
}
- chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS,
- extraIntents.toArray(new Intent[] { }));
-
+ Intent chooser = new Intent(Intent.ACTION_CHOOSER);
+ if (!extraIntents.isEmpty()) {
+ chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS,
+ extraIntents.toArray(new Intent[] { }));
+ }
chooser.putExtra(Intent.EXTRA_INTENT, getContentIntent);
- if (!window.showIntent(chooser, this, R.string.low_memory_error)) {
+ if (!mWindowAndroid.showIntent(chooser, this, R.string.low_memory_error)) {
onFileNotSelected();
}
}
+ private Intent getCameraIntent(Context context) {
+ Intent camera = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
+ camera.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ try {
+ mCameraOutputUri =
+ UiUtils.getUriForImageCaptureFile(context, getFileForImageCapture(context));
+ } catch (IOException e) {
+ Log.e(TAG, "Cannot retrieve content uri from file", e);
+ }
+
+ if (mCameraOutputUri == null) return null;
+
+ camera.putExtra(MediaStore.EXTRA_OUTPUT, mCameraOutputUri);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+ camera.setClipData(ClipData.newUri(
+ context.getContentResolver(), UiUtils.IMAGE_FILE_PATH, mCameraOutputUri));
+ }
+ return camera;
+ }
+
/**
* Get a file for the image capture operation. For devices with JB MR2 or
* latter android versions, the file is put under IMAGE_FILE_PATH directory.
@@ -236,6 +297,17 @@ class SelectFileDialog implements WindowAndroid.IntentCallback {
window.showError(R.string.opening_file_error);
}
+ @Override
+ public void onRequestPermissionsResult(String[] permissions, int[] grantResults) {
+ for (int i = 0; i < grantResults.length; i++) {
+ if (grantResults[i] == PackageManager.PERMISSION_DENIED && mCapture) {
+ onFileNotSelected();
+ return;
+ }
+ }
+ launchSelectFileIntent();
+ }
+
private void onFileNotSelected() {
nativeOnFileNotSelected(mNativeSelectFileDialog);
}