Wed, 31 Dec 2014 07:22:50 +0100
Correct previous dual key logic pending first delivery installment.
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
michael@0 | 3 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | package org.mozilla.gecko; |
michael@0 | 6 | |
michael@0 | 7 | import org.mozilla.gecko.GeckoAppShell; |
michael@0 | 8 | import org.mozilla.gecko.util.ThreadUtils; |
michael@0 | 9 | import org.mozilla.gecko.util.GeckoEventListener; |
michael@0 | 10 | |
michael@0 | 11 | import org.json.JSONException; |
michael@0 | 12 | import org.json.JSONObject; |
michael@0 | 13 | |
michael@0 | 14 | import android.app.Activity; |
michael@0 | 15 | import android.content.ComponentName; |
michael@0 | 16 | import android.content.Context; |
michael@0 | 17 | import android.content.Intent; |
michael@0 | 18 | import android.content.pm.PackageManager; |
michael@0 | 19 | import android.content.pm.ResolveInfo; |
michael@0 | 20 | import android.net.Uri; |
michael@0 | 21 | import android.os.Environment; |
michael@0 | 22 | import android.os.Parcelable; |
michael@0 | 23 | import android.provider.MediaStore; |
michael@0 | 24 | import android.util.Log; |
michael@0 | 25 | |
michael@0 | 26 | import java.io.File; |
michael@0 | 27 | import java.util.ArrayList; |
michael@0 | 28 | import java.util.Collection; |
michael@0 | 29 | import java.util.HashMap; |
michael@0 | 30 | import java.util.Iterator; |
michael@0 | 31 | import java.util.List; |
michael@0 | 32 | |
michael@0 | 33 | public class FilePicker implements GeckoEventListener { |
michael@0 | 34 | private static final String LOGTAG = "GeckoFilePicker"; |
michael@0 | 35 | private static FilePicker sFilePicker; |
michael@0 | 36 | private final Context context; |
michael@0 | 37 | |
michael@0 | 38 | public interface ResultHandler { |
michael@0 | 39 | public void gotFile(String filename); |
michael@0 | 40 | } |
michael@0 | 41 | |
michael@0 | 42 | public static void init(Context context) { |
michael@0 | 43 | if (sFilePicker == null) { |
michael@0 | 44 | sFilePicker = new FilePicker(context.getApplicationContext()); |
michael@0 | 45 | } |
michael@0 | 46 | } |
michael@0 | 47 | |
michael@0 | 48 | protected FilePicker(Context context) { |
michael@0 | 49 | this.context = context; |
michael@0 | 50 | GeckoAppShell.getEventDispatcher().registerEventListener("FilePicker:Show", this); |
michael@0 | 51 | } |
michael@0 | 52 | |
michael@0 | 53 | @Override |
michael@0 | 54 | public void handleMessage(String event, final JSONObject message) { |
michael@0 | 55 | if (event.equals("FilePicker:Show")) { |
michael@0 | 56 | String mimeType = "*/*"; |
michael@0 | 57 | final String mode = message.optString("mode"); |
michael@0 | 58 | final int tabId = message.optInt("tabId", -1); |
michael@0 | 59 | |
michael@0 | 60 | if ("mimeType".equals(mode)) |
michael@0 | 61 | mimeType = message.optString("mimeType"); |
michael@0 | 62 | else if ("extension".equals(mode)) |
michael@0 | 63 | mimeType = GeckoAppShell.getMimeTypeFromExtensions(message.optString("extensions")); |
michael@0 | 64 | |
michael@0 | 65 | showFilePickerAsync(mimeType, new ResultHandler() { |
michael@0 | 66 | public void gotFile(String filename) { |
michael@0 | 67 | try { |
michael@0 | 68 | message.put("file", filename); |
michael@0 | 69 | } catch (JSONException ex) { |
michael@0 | 70 | Log.i(LOGTAG, "Can't add filename to message " + filename); |
michael@0 | 71 | } |
michael@0 | 72 | |
michael@0 | 73 | |
michael@0 | 74 | GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent( |
michael@0 | 75 | "FilePicker:Result", message.toString())); |
michael@0 | 76 | } |
michael@0 | 77 | }, tabId); |
michael@0 | 78 | } |
michael@0 | 79 | } |
michael@0 | 80 | |
michael@0 | 81 | private void addActivities(Intent intent, HashMap<String, Intent> intents, HashMap<String, Intent> filters) { |
michael@0 | 82 | PackageManager pm = context.getPackageManager(); |
michael@0 | 83 | List<ResolveInfo> lri = pm.queryIntentActivities(intent, 0); |
michael@0 | 84 | for (ResolveInfo ri : lri) { |
michael@0 | 85 | ComponentName cn = new ComponentName(ri.activityInfo.applicationInfo.packageName, ri.activityInfo.name); |
michael@0 | 86 | if (filters != null && !filters.containsKey(cn.toString())) { |
michael@0 | 87 | Intent rintent = new Intent(intent); |
michael@0 | 88 | rintent.setComponent(cn); |
michael@0 | 89 | intents.put(cn.toString(), rintent); |
michael@0 | 90 | } |
michael@0 | 91 | } |
michael@0 | 92 | } |
michael@0 | 93 | |
michael@0 | 94 | private Intent getIntent(String mimeType) { |
michael@0 | 95 | Intent intent = new Intent(Intent.ACTION_GET_CONTENT); |
michael@0 | 96 | intent.setType(mimeType); |
michael@0 | 97 | intent.addCategory(Intent.CATEGORY_OPENABLE); |
michael@0 | 98 | return intent; |
michael@0 | 99 | } |
michael@0 | 100 | |
michael@0 | 101 | private List<Intent> getIntentsForFilePicker(final String mimeType, |
michael@0 | 102 | final FilePickerResultHandler fileHandler) { |
michael@0 | 103 | // The base intent to use for the file picker. Even if this is an implicit intent, Android will |
michael@0 | 104 | // still show a list of Activitiees that match this action/type. |
michael@0 | 105 | Intent baseIntent; |
michael@0 | 106 | // A HashMap of Activities the base intent will show in the chooser. This is used |
michael@0 | 107 | // to filter activities from other intents so that we don't show duplicates. |
michael@0 | 108 | HashMap<String, Intent> baseIntents = new HashMap<String, Intent>(); |
michael@0 | 109 | // A list of other activities to shwo in the picker (and the intents to launch them). |
michael@0 | 110 | HashMap<String, Intent> intents = new HashMap<String, Intent> (); |
michael@0 | 111 | |
michael@0 | 112 | if ("audio/*".equals(mimeType)) { |
michael@0 | 113 | // For audio the only intent is the mimetype |
michael@0 | 114 | baseIntent = getIntent(mimeType); |
michael@0 | 115 | addActivities(baseIntent, baseIntents, null); |
michael@0 | 116 | } else if ("image/*".equals(mimeType)) { |
michael@0 | 117 | // For images the base is a capture intent |
michael@0 | 118 | baseIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); |
michael@0 | 119 | baseIntent.putExtra(MediaStore.EXTRA_OUTPUT, |
michael@0 | 120 | Uri.fromFile(new File(Environment.getExternalStorageDirectory(), |
michael@0 | 121 | fileHandler.generateImageName()))); |
michael@0 | 122 | addActivities(baseIntent, baseIntents, null); |
michael@0 | 123 | |
michael@0 | 124 | // We also add the mimetype intent |
michael@0 | 125 | addActivities(getIntent(mimeType), intents, baseIntents); |
michael@0 | 126 | } else if ("video/*".equals(mimeType)) { |
michael@0 | 127 | // For videos the base is a capture intent |
michael@0 | 128 | baseIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); |
michael@0 | 129 | addActivities(baseIntent, baseIntents, null); |
michael@0 | 130 | |
michael@0 | 131 | // We also add the mimetype intent |
michael@0 | 132 | addActivities(getIntent(mimeType), intents, baseIntents); |
michael@0 | 133 | } else { |
michael@0 | 134 | baseIntent = getIntent("*/*"); |
michael@0 | 135 | addActivities(baseIntent, baseIntents, null); |
michael@0 | 136 | |
michael@0 | 137 | Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); |
michael@0 | 138 | intent.putExtra(MediaStore.EXTRA_OUTPUT, |
michael@0 | 139 | Uri.fromFile(new File(Environment.getExternalStorageDirectory(), |
michael@0 | 140 | fileHandler.generateImageName()))); |
michael@0 | 141 | addActivities(intent, intents, baseIntents); |
michael@0 | 142 | intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); |
michael@0 | 143 | addActivities(intent, intents, baseIntents); |
michael@0 | 144 | } |
michael@0 | 145 | |
michael@0 | 146 | // If we didn't find any activities, we fall back to the */* mimetype intent |
michael@0 | 147 | if (baseIntents.size() == 0 && intents.size() == 0) { |
michael@0 | 148 | intents.clear(); |
michael@0 | 149 | |
michael@0 | 150 | baseIntent = getIntent("*/*"); |
michael@0 | 151 | addActivities(baseIntent, baseIntents, null); |
michael@0 | 152 | } |
michael@0 | 153 | |
michael@0 | 154 | ArrayList<Intent> vals = new ArrayList<Intent>(intents.values()); |
michael@0 | 155 | vals.add(0, baseIntent); |
michael@0 | 156 | return vals; |
michael@0 | 157 | } |
michael@0 | 158 | |
michael@0 | 159 | private String getFilePickerTitle(String mimeType) { |
michael@0 | 160 | if (mimeType.equals("audio/*")) { |
michael@0 | 161 | return context.getString(R.string.filepicker_audio_title); |
michael@0 | 162 | } else if (mimeType.equals("image/*")) { |
michael@0 | 163 | return context.getString(R.string.filepicker_image_title); |
michael@0 | 164 | } else if (mimeType.equals("video/*")) { |
michael@0 | 165 | return context.getString(R.string.filepicker_video_title); |
michael@0 | 166 | } else { |
michael@0 | 167 | return context.getString(R.string.filepicker_title); |
michael@0 | 168 | } |
michael@0 | 169 | } |
michael@0 | 170 | |
michael@0 | 171 | private interface IntentHandler { |
michael@0 | 172 | public void gotIntent(Intent intent); |
michael@0 | 173 | } |
michael@0 | 174 | |
michael@0 | 175 | /* Gets an intent that can open a particular mimetype. Will show a prompt with a list |
michael@0 | 176 | * of Activities that can handle the mietype. Asynchronously calls the handler when |
michael@0 | 177 | * one of the intents is selected. If the caller passes in null for the handler, will still |
michael@0 | 178 | * prompt for the activity, but will throw away the result. |
michael@0 | 179 | */ |
michael@0 | 180 | private void getFilePickerIntentAsync(final String mimeType, |
michael@0 | 181 | final FilePickerResultHandler fileHandler, |
michael@0 | 182 | final IntentHandler handler) { |
michael@0 | 183 | List<Intent> intents = getIntentsForFilePicker(mimeType, fileHandler); |
michael@0 | 184 | |
michael@0 | 185 | if (intents.size() == 0) { |
michael@0 | 186 | Log.i(LOGTAG, "no activities for the file picker!"); |
michael@0 | 187 | handler.gotIntent(null); |
michael@0 | 188 | return; |
michael@0 | 189 | } |
michael@0 | 190 | |
michael@0 | 191 | Intent base = intents.remove(0); |
michael@0 | 192 | |
michael@0 | 193 | if (intents.size() == 0) { |
michael@0 | 194 | handler.gotIntent(base); |
michael@0 | 195 | return; |
michael@0 | 196 | } |
michael@0 | 197 | |
michael@0 | 198 | Intent chooser = Intent.createChooser(base, getFilePickerTitle(mimeType)); |
michael@0 | 199 | chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, intents.toArray(new Parcelable[]{})); |
michael@0 | 200 | handler.gotIntent(chooser); |
michael@0 | 201 | } |
michael@0 | 202 | |
michael@0 | 203 | /* Allows the user to pick an activity to load files from using a list prompt. Then opens the activity and |
michael@0 | 204 | * sends the file returned to the passed in handler. If a null handler is passed in, will still |
michael@0 | 205 | * pick and launch the file picker, but will throw away the result. |
michael@0 | 206 | */ |
michael@0 | 207 | protected void showFilePickerAsync(String mimeType, final ResultHandler handler, final int tabId) { |
michael@0 | 208 | final FilePickerResultHandler fileHandler = new FilePickerResultHandler(handler, context, tabId); |
michael@0 | 209 | getFilePickerIntentAsync(mimeType, fileHandler, new IntentHandler() { |
michael@0 | 210 | @Override |
michael@0 | 211 | public void gotIntent(Intent intent) { |
michael@0 | 212 | if (handler == null) { |
michael@0 | 213 | return; |
michael@0 | 214 | } |
michael@0 | 215 | |
michael@0 | 216 | if (intent == null) { |
michael@0 | 217 | handler.gotFile(""); |
michael@0 | 218 | return; |
michael@0 | 219 | } |
michael@0 | 220 | |
michael@0 | 221 | ActivityHandlerHelper.startIntent(intent, fileHandler); |
michael@0 | 222 | } |
michael@0 | 223 | }); |
michael@0 | 224 | } |
michael@0 | 225 | } |