|
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/. */ |
|
4 |
|
5 package org.mozilla.gecko; |
|
6 |
|
7 import org.mozilla.gecko.GeckoAppShell; |
|
8 import org.mozilla.gecko.util.ThreadUtils; |
|
9 import org.mozilla.gecko.util.GeckoEventListener; |
|
10 |
|
11 import org.json.JSONException; |
|
12 import org.json.JSONObject; |
|
13 |
|
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; |
|
25 |
|
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; |
|
32 |
|
33 public class FilePicker implements GeckoEventListener { |
|
34 private static final String LOGTAG = "GeckoFilePicker"; |
|
35 private static FilePicker sFilePicker; |
|
36 private final Context context; |
|
37 |
|
38 public interface ResultHandler { |
|
39 public void gotFile(String filename); |
|
40 } |
|
41 |
|
42 public static void init(Context context) { |
|
43 if (sFilePicker == null) { |
|
44 sFilePicker = new FilePicker(context.getApplicationContext()); |
|
45 } |
|
46 } |
|
47 |
|
48 protected FilePicker(Context context) { |
|
49 this.context = context; |
|
50 GeckoAppShell.getEventDispatcher().registerEventListener("FilePicker:Show", this); |
|
51 } |
|
52 |
|
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); |
|
59 |
|
60 if ("mimeType".equals(mode)) |
|
61 mimeType = message.optString("mimeType"); |
|
62 else if ("extension".equals(mode)) |
|
63 mimeType = GeckoAppShell.getMimeTypeFromExtensions(message.optString("extensions")); |
|
64 |
|
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 } |
|
72 |
|
73 |
|
74 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent( |
|
75 "FilePicker:Result", message.toString())); |
|
76 } |
|
77 }, tabId); |
|
78 } |
|
79 } |
|
80 |
|
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 } |
|
93 |
|
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 } |
|
100 |
|
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> (); |
|
111 |
|
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); |
|
123 |
|
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); |
|
130 |
|
131 // We also add the mimetype intent |
|
132 addActivities(getIntent(mimeType), intents, baseIntents); |
|
133 } else { |
|
134 baseIntent = getIntent("*/*"); |
|
135 addActivities(baseIntent, baseIntents, null); |
|
136 |
|
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 } |
|
145 |
|
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(); |
|
149 |
|
150 baseIntent = getIntent("*/*"); |
|
151 addActivities(baseIntent, baseIntents, null); |
|
152 } |
|
153 |
|
154 ArrayList<Intent> vals = new ArrayList<Intent>(intents.values()); |
|
155 vals.add(0, baseIntent); |
|
156 return vals; |
|
157 } |
|
158 |
|
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 } |
|
170 |
|
171 private interface IntentHandler { |
|
172 public void gotIntent(Intent intent); |
|
173 } |
|
174 |
|
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); |
|
184 |
|
185 if (intents.size() == 0) { |
|
186 Log.i(LOGTAG, "no activities for the file picker!"); |
|
187 handler.gotIntent(null); |
|
188 return; |
|
189 } |
|
190 |
|
191 Intent base = intents.remove(0); |
|
192 |
|
193 if (intents.size() == 0) { |
|
194 handler.gotIntent(base); |
|
195 return; |
|
196 } |
|
197 |
|
198 Intent chooser = Intent.createChooser(base, getFilePickerTitle(mimeType)); |
|
199 chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, intents.toArray(new Parcelable[]{})); |
|
200 handler.gotIntent(chooser); |
|
201 } |
|
202 |
|
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 } |
|
215 |
|
216 if (intent == null) { |
|
217 handler.gotFile(""); |
|
218 return; |
|
219 } |
|
220 |
|
221 ActivityHandlerHelper.startIntent(intent, fileHandler); |
|
222 } |
|
223 }); |
|
224 } |
|
225 } |