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