1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/FilePicker.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,225 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +package org.mozilla.gecko; 1.9 + 1.10 +import org.mozilla.gecko.GeckoAppShell; 1.11 +import org.mozilla.gecko.util.ThreadUtils; 1.12 +import org.mozilla.gecko.util.GeckoEventListener; 1.13 + 1.14 +import org.json.JSONException; 1.15 +import org.json.JSONObject; 1.16 + 1.17 +import android.app.Activity; 1.18 +import android.content.ComponentName; 1.19 +import android.content.Context; 1.20 +import android.content.Intent; 1.21 +import android.content.pm.PackageManager; 1.22 +import android.content.pm.ResolveInfo; 1.23 +import android.net.Uri; 1.24 +import android.os.Environment; 1.25 +import android.os.Parcelable; 1.26 +import android.provider.MediaStore; 1.27 +import android.util.Log; 1.28 + 1.29 +import java.io.File; 1.30 +import java.util.ArrayList; 1.31 +import java.util.Collection; 1.32 +import java.util.HashMap; 1.33 +import java.util.Iterator; 1.34 +import java.util.List; 1.35 + 1.36 +public class FilePicker implements GeckoEventListener { 1.37 + private static final String LOGTAG = "GeckoFilePicker"; 1.38 + private static FilePicker sFilePicker; 1.39 + private final Context context; 1.40 + 1.41 + public interface ResultHandler { 1.42 + public void gotFile(String filename); 1.43 + } 1.44 + 1.45 + public static void init(Context context) { 1.46 + if (sFilePicker == null) { 1.47 + sFilePicker = new FilePicker(context.getApplicationContext()); 1.48 + } 1.49 + } 1.50 + 1.51 + protected FilePicker(Context context) { 1.52 + this.context = context; 1.53 + GeckoAppShell.getEventDispatcher().registerEventListener("FilePicker:Show", this); 1.54 + } 1.55 + 1.56 + @Override 1.57 + public void handleMessage(String event, final JSONObject message) { 1.58 + if (event.equals("FilePicker:Show")) { 1.59 + String mimeType = "*/*"; 1.60 + final String mode = message.optString("mode"); 1.61 + final int tabId = message.optInt("tabId", -1); 1.62 + 1.63 + if ("mimeType".equals(mode)) 1.64 + mimeType = message.optString("mimeType"); 1.65 + else if ("extension".equals(mode)) 1.66 + mimeType = GeckoAppShell.getMimeTypeFromExtensions(message.optString("extensions")); 1.67 + 1.68 + showFilePickerAsync(mimeType, new ResultHandler() { 1.69 + public void gotFile(String filename) { 1.70 + try { 1.71 + message.put("file", filename); 1.72 + } catch (JSONException ex) { 1.73 + Log.i(LOGTAG, "Can't add filename to message " + filename); 1.74 + } 1.75 + 1.76 + 1.77 + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent( 1.78 + "FilePicker:Result", message.toString())); 1.79 + } 1.80 + }, tabId); 1.81 + } 1.82 + } 1.83 + 1.84 + private void addActivities(Intent intent, HashMap<String, Intent> intents, HashMap<String, Intent> filters) { 1.85 + PackageManager pm = context.getPackageManager(); 1.86 + List<ResolveInfo> lri = pm.queryIntentActivities(intent, 0); 1.87 + for (ResolveInfo ri : lri) { 1.88 + ComponentName cn = new ComponentName(ri.activityInfo.applicationInfo.packageName, ri.activityInfo.name); 1.89 + if (filters != null && !filters.containsKey(cn.toString())) { 1.90 + Intent rintent = new Intent(intent); 1.91 + rintent.setComponent(cn); 1.92 + intents.put(cn.toString(), rintent); 1.93 + } 1.94 + } 1.95 + } 1.96 + 1.97 + private Intent getIntent(String mimeType) { 1.98 + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); 1.99 + intent.setType(mimeType); 1.100 + intent.addCategory(Intent.CATEGORY_OPENABLE); 1.101 + return intent; 1.102 + } 1.103 + 1.104 + private List<Intent> getIntentsForFilePicker(final String mimeType, 1.105 + final FilePickerResultHandler fileHandler) { 1.106 + // The base intent to use for the file picker. Even if this is an implicit intent, Android will 1.107 + // still show a list of Activitiees that match this action/type. 1.108 + Intent baseIntent; 1.109 + // A HashMap of Activities the base intent will show in the chooser. This is used 1.110 + // to filter activities from other intents so that we don't show duplicates. 1.111 + HashMap<String, Intent> baseIntents = new HashMap<String, Intent>(); 1.112 + // A list of other activities to shwo in the picker (and the intents to launch them). 1.113 + HashMap<String, Intent> intents = new HashMap<String, Intent> (); 1.114 + 1.115 + if ("audio/*".equals(mimeType)) { 1.116 + // For audio the only intent is the mimetype 1.117 + baseIntent = getIntent(mimeType); 1.118 + addActivities(baseIntent, baseIntents, null); 1.119 + } else if ("image/*".equals(mimeType)) { 1.120 + // For images the base is a capture intent 1.121 + baseIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); 1.122 + baseIntent.putExtra(MediaStore.EXTRA_OUTPUT, 1.123 + Uri.fromFile(new File(Environment.getExternalStorageDirectory(), 1.124 + fileHandler.generateImageName()))); 1.125 + addActivities(baseIntent, baseIntents, null); 1.126 + 1.127 + // We also add the mimetype intent 1.128 + addActivities(getIntent(mimeType), intents, baseIntents); 1.129 + } else if ("video/*".equals(mimeType)) { 1.130 + // For videos the base is a capture intent 1.131 + baseIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); 1.132 + addActivities(baseIntent, baseIntents, null); 1.133 + 1.134 + // We also add the mimetype intent 1.135 + addActivities(getIntent(mimeType), intents, baseIntents); 1.136 + } else { 1.137 + baseIntent = getIntent("*/*"); 1.138 + addActivities(baseIntent, baseIntents, null); 1.139 + 1.140 + Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); 1.141 + intent.putExtra(MediaStore.EXTRA_OUTPUT, 1.142 + Uri.fromFile(new File(Environment.getExternalStorageDirectory(), 1.143 + fileHandler.generateImageName()))); 1.144 + addActivities(intent, intents, baseIntents); 1.145 + intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); 1.146 + addActivities(intent, intents, baseIntents); 1.147 + } 1.148 + 1.149 + // If we didn't find any activities, we fall back to the */* mimetype intent 1.150 + if (baseIntents.size() == 0 && intents.size() == 0) { 1.151 + intents.clear(); 1.152 + 1.153 + baseIntent = getIntent("*/*"); 1.154 + addActivities(baseIntent, baseIntents, null); 1.155 + } 1.156 + 1.157 + ArrayList<Intent> vals = new ArrayList<Intent>(intents.values()); 1.158 + vals.add(0, baseIntent); 1.159 + return vals; 1.160 + } 1.161 + 1.162 + private String getFilePickerTitle(String mimeType) { 1.163 + if (mimeType.equals("audio/*")) { 1.164 + return context.getString(R.string.filepicker_audio_title); 1.165 + } else if (mimeType.equals("image/*")) { 1.166 + return context.getString(R.string.filepicker_image_title); 1.167 + } else if (mimeType.equals("video/*")) { 1.168 + return context.getString(R.string.filepicker_video_title); 1.169 + } else { 1.170 + return context.getString(R.string.filepicker_title); 1.171 + } 1.172 + } 1.173 + 1.174 + private interface IntentHandler { 1.175 + public void gotIntent(Intent intent); 1.176 + } 1.177 + 1.178 + /* Gets an intent that can open a particular mimetype. Will show a prompt with a list 1.179 + * of Activities that can handle the mietype. Asynchronously calls the handler when 1.180 + * one of the intents is selected. If the caller passes in null for the handler, will still 1.181 + * prompt for the activity, but will throw away the result. 1.182 + */ 1.183 + private void getFilePickerIntentAsync(final String mimeType, 1.184 + final FilePickerResultHandler fileHandler, 1.185 + final IntentHandler handler) { 1.186 + List<Intent> intents = getIntentsForFilePicker(mimeType, fileHandler); 1.187 + 1.188 + if (intents.size() == 0) { 1.189 + Log.i(LOGTAG, "no activities for the file picker!"); 1.190 + handler.gotIntent(null); 1.191 + return; 1.192 + } 1.193 + 1.194 + Intent base = intents.remove(0); 1.195 + 1.196 + if (intents.size() == 0) { 1.197 + handler.gotIntent(base); 1.198 + return; 1.199 + } 1.200 + 1.201 + Intent chooser = Intent.createChooser(base, getFilePickerTitle(mimeType)); 1.202 + chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, intents.toArray(new Parcelable[]{})); 1.203 + handler.gotIntent(chooser); 1.204 + } 1.205 + 1.206 + /* Allows the user to pick an activity to load files from using a list prompt. Then opens the activity and 1.207 + * sends the file returned to the passed in handler. If a null handler is passed in, will still 1.208 + * pick and launch the file picker, but will throw away the result. 1.209 + */ 1.210 + protected void showFilePickerAsync(String mimeType, final ResultHandler handler, final int tabId) { 1.211 + final FilePickerResultHandler fileHandler = new FilePickerResultHandler(handler, context, tabId); 1.212 + getFilePickerIntentAsync(mimeType, fileHandler, new IntentHandler() { 1.213 + @Override 1.214 + public void gotIntent(Intent intent) { 1.215 + if (handler == null) { 1.216 + return; 1.217 + } 1.218 + 1.219 + if (intent == null) { 1.220 + handler.gotFile(""); 1.221 + return; 1.222 + } 1.223 + 1.224 + ActivityHandlerHelper.startIntent(intent, fileHandler); 1.225 + } 1.226 + }); 1.227 + } 1.228 +}