michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko; michael@0: michael@0: import org.mozilla.gecko.GeckoAppShell; michael@0: import org.mozilla.gecko.util.ThreadUtils; michael@0: import org.mozilla.gecko.util.GeckoEventListener; michael@0: michael@0: import org.json.JSONException; michael@0: import org.json.JSONObject; michael@0: michael@0: import android.app.Activity; michael@0: import android.content.ComponentName; michael@0: import android.content.Context; michael@0: import android.content.Intent; michael@0: import android.content.pm.PackageManager; michael@0: import android.content.pm.ResolveInfo; michael@0: import android.net.Uri; michael@0: import android.os.Environment; michael@0: import android.os.Parcelable; michael@0: import android.provider.MediaStore; michael@0: import android.util.Log; michael@0: michael@0: import java.io.File; michael@0: import java.util.ArrayList; michael@0: import java.util.Collection; michael@0: import java.util.HashMap; michael@0: import java.util.Iterator; michael@0: import java.util.List; michael@0: michael@0: public class FilePicker implements GeckoEventListener { michael@0: private static final String LOGTAG = "GeckoFilePicker"; michael@0: private static FilePicker sFilePicker; michael@0: private final Context context; michael@0: michael@0: public interface ResultHandler { michael@0: public void gotFile(String filename); michael@0: } michael@0: michael@0: public static void init(Context context) { michael@0: if (sFilePicker == null) { michael@0: sFilePicker = new FilePicker(context.getApplicationContext()); michael@0: } michael@0: } michael@0: michael@0: protected FilePicker(Context context) { michael@0: this.context = context; michael@0: GeckoAppShell.getEventDispatcher().registerEventListener("FilePicker:Show", this); michael@0: } michael@0: michael@0: @Override michael@0: public void handleMessage(String event, final JSONObject message) { michael@0: if (event.equals("FilePicker:Show")) { michael@0: String mimeType = "*/*"; michael@0: final String mode = message.optString("mode"); michael@0: final int tabId = message.optInt("tabId", -1); michael@0: michael@0: if ("mimeType".equals(mode)) michael@0: mimeType = message.optString("mimeType"); michael@0: else if ("extension".equals(mode)) michael@0: mimeType = GeckoAppShell.getMimeTypeFromExtensions(message.optString("extensions")); michael@0: michael@0: showFilePickerAsync(mimeType, new ResultHandler() { michael@0: public void gotFile(String filename) { michael@0: try { michael@0: message.put("file", filename); michael@0: } catch (JSONException ex) { michael@0: Log.i(LOGTAG, "Can't add filename to message " + filename); michael@0: } michael@0: michael@0: michael@0: GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent( michael@0: "FilePicker:Result", message.toString())); michael@0: } michael@0: }, tabId); michael@0: } michael@0: } michael@0: michael@0: private void addActivities(Intent intent, HashMap intents, HashMap filters) { michael@0: PackageManager pm = context.getPackageManager(); michael@0: List lri = pm.queryIntentActivities(intent, 0); michael@0: for (ResolveInfo ri : lri) { michael@0: ComponentName cn = new ComponentName(ri.activityInfo.applicationInfo.packageName, ri.activityInfo.name); michael@0: if (filters != null && !filters.containsKey(cn.toString())) { michael@0: Intent rintent = new Intent(intent); michael@0: rintent.setComponent(cn); michael@0: intents.put(cn.toString(), rintent); michael@0: } michael@0: } michael@0: } michael@0: michael@0: private Intent getIntent(String mimeType) { michael@0: Intent intent = new Intent(Intent.ACTION_GET_CONTENT); michael@0: intent.setType(mimeType); michael@0: intent.addCategory(Intent.CATEGORY_OPENABLE); michael@0: return intent; michael@0: } michael@0: michael@0: private List getIntentsForFilePicker(final String mimeType, michael@0: final FilePickerResultHandler fileHandler) { michael@0: // The base intent to use for the file picker. Even if this is an implicit intent, Android will michael@0: // still show a list of Activitiees that match this action/type. michael@0: Intent baseIntent; michael@0: // A HashMap of Activities the base intent will show in the chooser. This is used michael@0: // to filter activities from other intents so that we don't show duplicates. michael@0: HashMap baseIntents = new HashMap(); michael@0: // A list of other activities to shwo in the picker (and the intents to launch them). michael@0: HashMap intents = new HashMap (); michael@0: michael@0: if ("audio/*".equals(mimeType)) { michael@0: // For audio the only intent is the mimetype michael@0: baseIntent = getIntent(mimeType); michael@0: addActivities(baseIntent, baseIntents, null); michael@0: } else if ("image/*".equals(mimeType)) { michael@0: // For images the base is a capture intent michael@0: baseIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); michael@0: baseIntent.putExtra(MediaStore.EXTRA_OUTPUT, michael@0: Uri.fromFile(new File(Environment.getExternalStorageDirectory(), michael@0: fileHandler.generateImageName()))); michael@0: addActivities(baseIntent, baseIntents, null); michael@0: michael@0: // We also add the mimetype intent michael@0: addActivities(getIntent(mimeType), intents, baseIntents); michael@0: } else if ("video/*".equals(mimeType)) { michael@0: // For videos the base is a capture intent michael@0: baseIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); michael@0: addActivities(baseIntent, baseIntents, null); michael@0: michael@0: // We also add the mimetype intent michael@0: addActivities(getIntent(mimeType), intents, baseIntents); michael@0: } else { michael@0: baseIntent = getIntent("*/*"); michael@0: addActivities(baseIntent, baseIntents, null); michael@0: michael@0: Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); michael@0: intent.putExtra(MediaStore.EXTRA_OUTPUT, michael@0: Uri.fromFile(new File(Environment.getExternalStorageDirectory(), michael@0: fileHandler.generateImageName()))); michael@0: addActivities(intent, intents, baseIntents); michael@0: intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); michael@0: addActivities(intent, intents, baseIntents); michael@0: } michael@0: michael@0: // If we didn't find any activities, we fall back to the */* mimetype intent michael@0: if (baseIntents.size() == 0 && intents.size() == 0) { michael@0: intents.clear(); michael@0: michael@0: baseIntent = getIntent("*/*"); michael@0: addActivities(baseIntent, baseIntents, null); michael@0: } michael@0: michael@0: ArrayList vals = new ArrayList(intents.values()); michael@0: vals.add(0, baseIntent); michael@0: return vals; michael@0: } michael@0: michael@0: private String getFilePickerTitle(String mimeType) { michael@0: if (mimeType.equals("audio/*")) { michael@0: return context.getString(R.string.filepicker_audio_title); michael@0: } else if (mimeType.equals("image/*")) { michael@0: return context.getString(R.string.filepicker_image_title); michael@0: } else if (mimeType.equals("video/*")) { michael@0: return context.getString(R.string.filepicker_video_title); michael@0: } else { michael@0: return context.getString(R.string.filepicker_title); michael@0: } michael@0: } michael@0: michael@0: private interface IntentHandler { michael@0: public void gotIntent(Intent intent); michael@0: } michael@0: michael@0: /* Gets an intent that can open a particular mimetype. Will show a prompt with a list michael@0: * of Activities that can handle the mietype. Asynchronously calls the handler when michael@0: * one of the intents is selected. If the caller passes in null for the handler, will still michael@0: * prompt for the activity, but will throw away the result. michael@0: */ michael@0: private void getFilePickerIntentAsync(final String mimeType, michael@0: final FilePickerResultHandler fileHandler, michael@0: final IntentHandler handler) { michael@0: List intents = getIntentsForFilePicker(mimeType, fileHandler); michael@0: michael@0: if (intents.size() == 0) { michael@0: Log.i(LOGTAG, "no activities for the file picker!"); michael@0: handler.gotIntent(null); michael@0: return; michael@0: } michael@0: michael@0: Intent base = intents.remove(0); michael@0: michael@0: if (intents.size() == 0) { michael@0: handler.gotIntent(base); michael@0: return; michael@0: } michael@0: michael@0: Intent chooser = Intent.createChooser(base, getFilePickerTitle(mimeType)); michael@0: chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, intents.toArray(new Parcelable[]{})); michael@0: handler.gotIntent(chooser); michael@0: } michael@0: michael@0: /* Allows the user to pick an activity to load files from using a list prompt. Then opens the activity and michael@0: * sends the file returned to the passed in handler. If a null handler is passed in, will still michael@0: * pick and launch the file picker, but will throw away the result. michael@0: */ michael@0: protected void showFilePickerAsync(String mimeType, final ResultHandler handler, final int tabId) { michael@0: final FilePickerResultHandler fileHandler = new FilePickerResultHandler(handler, context, tabId); michael@0: getFilePickerIntentAsync(mimeType, fileHandler, new IntentHandler() { michael@0: @Override michael@0: public void gotIntent(Intent intent) { michael@0: if (handler == null) { michael@0: return; michael@0: } michael@0: michael@0: if (intent == null) { michael@0: handler.gotFile(""); michael@0: return; michael@0: } michael@0: michael@0: ActivityHandlerHelper.startIntent(intent, fileHandler); michael@0: } michael@0: }); michael@0: } michael@0: }