diff options
author | Ted Choc <tedchoc@google.com> | 2015-07-20 16:40:20 -0700 |
---|---|---|
committer | Ted Choc <tedchoc@google.com> | 2015-07-20 23:41:26 +0000 |
commit | 7c9ae5f380d65a8a4f52b31323905b3069dc3777 (patch) | |
tree | 26aaac30c36a06ee4000e1b7b5651e4d2513c3bf | |
parent | 2486cc4ecbdfb1226ad0fd9d3790eda59f5074ae (diff) | |
download | chromium_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.java | 5 | ||||
-rw-r--r-- | ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java | 152 |
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); } |