diff --git a/mobile/android/base/java/org/mozilla/gecko/FilePicker.java b/mobile/android/base/java/org/mozilla/gecko/FilePicker.java index 6cccb7bb9aa1..9c6eb302802b 100644 --- a/mobile/android/base/java/org/mozilla/gecko/FilePicker.java +++ b/mobile/android/base/java/org/mozilla/gecko/FilePicker.java @@ -22,11 +22,13 @@ import android.net.Uri; import android.os.Environment; import android.os.Parcelable; import android.provider.MediaStore; +import android.support.annotation.NonNull; import android.text.TextUtils; import android.util.Log; import java.io.File; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -82,13 +84,23 @@ public class FilePicker implements BundleEventListener { .andFallback(new Runnable() { @Override public void run() { - callback.sendError(null); + // In the fallback case, we still show the picker, just + // with the default file list. + // TODO: Figure out which permissions have been denied and use that + // knowledge for availPermissions. For now we assume we don't have any + // permissions at all (bug 1411014). + showFilePickerAsync(title, "*/*", new String[0], new ResultHandler() { + @Override + public void gotFile(final String filename) { + callback.sendSuccess(filename); + } + }, tabId); } }) .run(new Runnable() { @Override public void run() { - showFilePickerAsync(title, finalMimeType, new ResultHandler() { + showFilePickerAsync(title, finalMimeType, requiredPermission, new ResultHandler() { @Override public void gotFile(final String filename) { callback.sendSuccess(filename); @@ -110,6 +122,11 @@ public class FilePicker implements BundleEventListener { return new String[] { Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO, Manifest.permission.READ_EXTERNAL_STORAGE }; } + private static boolean hasPermissionsForMimeType(final String mimeType, final String[] availPermissions) { + return Arrays.asList(availPermissions) + .containsAll(Arrays.asList(getPermissionsForMimeType(mimeType))); + } + private void addActivities(Intent intent, HashMap intents, HashMap filters) { PackageManager pm = context.getPackageManager(); List lri = pm.queryIntentActivities(intent, 0); @@ -130,7 +147,8 @@ public class FilePicker implements BundleEventListener { return intent; } - private List getIntentsForFilePicker(final String mimeType, + private List getIntentsForFilePicker(final @NonNull String mimeType, + final @NonNull String[] availPermissions, final FilePickerResultHandler fileHandler) { // The base intent to use for the file picker. Even if this is an implicit intent, Android will // still show a list of Activities that match this action/type. @@ -138,41 +156,50 @@ public class FilePicker implements BundleEventListener { // A HashMap of Activities the base intent will show in the chooser. This is used // to filter activities from other intents so that we don't show duplicates. HashMap baseIntents = new HashMap(); - // A list of other activities to shwo in the picker (and the intents to launch them). + // A list of other activities to show in the picker (and the intents to launch them). HashMap intents = new HashMap (); - if ("audio/*".equals(mimeType)) { + if (mimeType.startsWith("audio/")) { // For audio the only intent is the mimetype baseIntent = getIntent(mimeType); addActivities(baseIntent, baseIntents, null); - } else if ("image/*".equals(mimeType)) { - // For images the base is a capture intent - baseIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); - baseIntent.putExtra(MediaStore.EXTRA_OUTPUT, - Uri.fromFile(new File(Environment.getExternalStorageDirectory(), - fileHandler.generateImageName()))); + } else if (mimeType.startsWith("image/")) { + baseIntent = getIntent(mimeType); addActivities(baseIntent, baseIntents, null); - // We also add the mimetype intent - addActivities(getIntent(mimeType), intents, baseIntents); - } else if ("video/*".equals(mimeType)) { - // For videos the base is a capture intent - baseIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); + if (mimeType.equals("image/*") && + hasPermissionsForMimeType(mimeType, availPermissions)) { + // We also add a capture intent + Intent intent = IntentHelper.getImageCaptureIntent( + new File(Environment.getExternalStorageDirectory(), + fileHandler.generateImageName())); + addActivities(intent, intents, baseIntents); + } + } else if (mimeType.startsWith("video/")) { + baseIntent = getIntent(mimeType); addActivities(baseIntent, baseIntents, null); - // We also add the mimetype intent - addActivities(getIntent(mimeType), intents, baseIntents); + if (mimeType.equals("video/*") && + hasPermissionsForMimeType(mimeType, availPermissions)) { + // We also add a capture intent + Intent intent = IntentHelper.getVideoCaptureIntent(); + addActivities(intent, intents, baseIntents); + } } else { baseIntent = getIntent("*/*"); addActivities(baseIntent, baseIntents, null); - Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); - intent.putExtra(MediaStore.EXTRA_OUTPUT, - Uri.fromFile(new File(Environment.getExternalStorageDirectory(), - fileHandler.generateImageName()))); - addActivities(intent, intents, baseIntents); - intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); - addActivities(intent, intents, baseIntents); + Intent intent; + if (hasPermissionsForMimeType("image/*", availPermissions)) { + intent = IntentHelper.getImageCaptureIntent( + new File(Environment.getExternalStorageDirectory(), + fileHandler.generateImageName())); + addActivities(intent, intents, baseIntents); + } + if (hasPermissionsForMimeType("video/*", availPermissions)) { + intent = IntentHelper.getVideoCaptureIntent(); + addActivities(intent, intents, baseIntents); + } } // If we didn't find any activities, we fall back to the */* mimetype intent @@ -206,9 +233,10 @@ public class FilePicker implements BundleEventListener { * prompt for the activity, but will throw away the result. */ private Intent getFilePickerIntent(String title, - final String mimeType, + final @NonNull String mimeType, + final @NonNull String[] availPermissions, final FilePickerResultHandler fileHandler) { - final List intents = getIntentsForFilePicker(mimeType, fileHandler); + final List intents = getIntentsForFilePicker(mimeType, availPermissions, fileHandler); if (intents.size() == 0) { Log.i(LOGTAG, "no activities for the file picker!"); @@ -234,11 +262,12 @@ public class FilePicker implements BundleEventListener { * sends the file returned to the passed in handler. If a null handler is passed in, will still * pick and launch the file picker, but will throw away the result. */ - protected void showFilePickerAsync(final String title, final String mimeType, + protected void showFilePickerAsync(final String title, final @NonNull String mimeType, + final @NonNull String[] availPermissions, final ResultHandler handler, final int tabId) { final FilePickerResultHandler fileHandler = new FilePickerResultHandler(handler, context, tabId); - final Intent intent = getFilePickerIntent(title, mimeType, fileHandler); + final Intent intent = getFilePickerIntent(title, mimeType, availPermissions, fileHandler); final Activity currentActivity = GeckoActivityMonitor.getInstance().getCurrentActivity(); diff --git a/mobile/android/base/java/org/mozilla/gecko/IntentHelper.java b/mobile/android/base/java/org/mozilla/gecko/IntentHelper.java index fccbd447c7f2..88511d4cc1cf 100644 --- a/mobile/android/base/java/org/mozilla/gecko/IntentHelper.java +++ b/mobile/android/base/java/org/mozilla/gecko/IntentHelper.java @@ -24,7 +24,9 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.net.Uri; +import android.os.Environment; import android.provider.Browser; +import android.provider.MediaStore; import android.support.annotation.Nullable; import android.support.v4.app.FragmentActivity; import android.text.TextUtils; @@ -237,6 +239,17 @@ public final class IntentHelper implements BundleEventListener { return intent; } + public static Intent getImageCaptureIntent(final File destinationFile) { + final Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + intent.putExtra(MediaStore.EXTRA_OUTPUT, + Uri.fromFile(destinationFile)); + return intent; + } + + public static Intent getVideoCaptureIntent() { + return new Intent(MediaStore.ACTION_VIDEO_CAPTURE); + } + /** * Given a URI, a MIME type, an Android intent "action", and a title, * produce an intent which can be used to start an activity to open