|
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.mozglue.GeckoLoader; |
|
8 import org.mozilla.gecko.util.ActivityResultHandler; |
|
9 import org.mozilla.gecko.util.ThreadUtils; |
|
10 |
|
11 import android.app.Activity; |
|
12 import android.content.ContentResolver; |
|
13 import android.content.Context; |
|
14 import android.content.Intent; |
|
15 import android.database.Cursor; |
|
16 import android.net.Uri; |
|
17 import android.os.Bundle; |
|
18 import android.os.Environment; |
|
19 import android.provider.MediaStore; |
|
20 import android.provider.OpenableColumns; |
|
21 import android.support.v4.app.FragmentActivity; |
|
22 import android.support.v4.app.LoaderManager; |
|
23 import android.support.v4.app.LoaderManager.LoaderCallbacks; |
|
24 import android.support.v4.content.CursorLoader; |
|
25 import android.support.v4.content.Loader; |
|
26 import android.text.TextUtils; |
|
27 import android.text.format.Time; |
|
28 import android.util.Log; |
|
29 import android.os.Process; |
|
30 |
|
31 import java.io.File; |
|
32 import java.io.FileOutputStream; |
|
33 import java.io.InputStream; |
|
34 import java.io.IOException; |
|
35 |
|
36 class FilePickerResultHandler implements ActivityResultHandler { |
|
37 private static final String LOGTAG = "GeckoFilePickerResultHandler"; |
|
38 private static final String UPLOADS_DIR = "uploads"; |
|
39 |
|
40 protected final FilePicker.ResultHandler handler; |
|
41 private final int tabId; |
|
42 private final File cacheDir; |
|
43 |
|
44 // this code is really hacky and doesn't belong anywhere so I'm putting it here for now |
|
45 // until I can come up with a better solution. |
|
46 private String mImageName = ""; |
|
47 |
|
48 /* Use this constructor to asynchronously listen for results */ |
|
49 public FilePickerResultHandler(final FilePicker.ResultHandler handler, final Context context, final int tabId) { |
|
50 this.tabId = tabId; |
|
51 cacheDir = new File(context.getCacheDir(), UPLOADS_DIR); |
|
52 this.handler = handler; |
|
53 } |
|
54 |
|
55 private void sendResult(String res) { |
|
56 if (handler != null) { |
|
57 handler.gotFile(res); |
|
58 } |
|
59 } |
|
60 |
|
61 @Override |
|
62 public void onActivityResult(int resultCode, Intent intent) { |
|
63 if (resultCode != Activity.RESULT_OK) { |
|
64 sendResult(""); |
|
65 return; |
|
66 } |
|
67 |
|
68 // Camera results won't return an Intent. Use the file name we passed to the original intent. |
|
69 if (intent == null) { |
|
70 if (mImageName != null) { |
|
71 File file = new File(Environment.getExternalStorageDirectory(), mImageName); |
|
72 sendResult(file.getAbsolutePath()); |
|
73 } else { |
|
74 sendResult(""); |
|
75 } |
|
76 return; |
|
77 } |
|
78 |
|
79 Uri uri = intent.getData(); |
|
80 if (uri == null) { |
|
81 sendResult(""); |
|
82 return; |
|
83 } |
|
84 |
|
85 // Some file pickers may return a file uri |
|
86 if ("file".equals(uri.getScheme())) { |
|
87 String path = uri.getPath(); |
|
88 sendResult(path == null ? "" : path); |
|
89 return; |
|
90 } |
|
91 |
|
92 final FragmentActivity fa = (FragmentActivity) GeckoAppShell.getGeckoInterface().getActivity(); |
|
93 final LoaderManager lm = fa.getSupportLoaderManager(); |
|
94 // Finally, Video pickers and some file pickers may return a content provider. |
|
95 Cursor cursor = null; |
|
96 try { |
|
97 // Try a query to make sure the expected columns exist |
|
98 final ContentResolver cr = fa.getContentResolver(); |
|
99 cursor = cr.query(uri, new String[] { MediaStore.Video.Media.DATA }, null, null, null); |
|
100 |
|
101 int index = cursor.getColumnIndex(MediaStore.Video.Media.DATA); |
|
102 if (index >= 0) { |
|
103 lm.initLoader(intent.hashCode(), null, new VideoLoaderCallbacks(uri)); |
|
104 return; |
|
105 } |
|
106 } catch(Exception ex) { |
|
107 // We'll try a different loader below |
|
108 } finally { |
|
109 if (cursor != null) { |
|
110 cursor.close(); |
|
111 } |
|
112 } |
|
113 |
|
114 lm.initLoader(uri.hashCode(), null, new FileLoaderCallbacks(uri)); |
|
115 return; |
|
116 } |
|
117 |
|
118 public String generateImageName() { |
|
119 Time now = new Time(); |
|
120 now.setToNow(); |
|
121 mImageName = now.format("%Y-%m-%d %H.%M.%S") + ".jpg"; |
|
122 return mImageName; |
|
123 } |
|
124 |
|
125 private class VideoLoaderCallbacks implements LoaderCallbacks<Cursor> { |
|
126 final private Uri uri; |
|
127 public VideoLoaderCallbacks(Uri uri) { |
|
128 this.uri = uri; |
|
129 } |
|
130 |
|
131 @Override |
|
132 public Loader<Cursor> onCreateLoader(int id, Bundle args) { |
|
133 final FragmentActivity fa = (FragmentActivity) GeckoAppShell.getGeckoInterface().getActivity(); |
|
134 return new CursorLoader(fa, |
|
135 uri, |
|
136 new String[] { MediaStore.Video.Media.DATA }, |
|
137 null, // selection |
|
138 null, // selectionArgs |
|
139 null); // sortOrder |
|
140 } |
|
141 |
|
142 @Override |
|
143 public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { |
|
144 if (cursor.moveToFirst()) { |
|
145 String res = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA)); |
|
146 |
|
147 // Some pickers (the KitKat Documents one for instance) won't return a temporary file here. |
|
148 // Fall back to the normal FileLoader if we didn't find anything. |
|
149 if (TextUtils.isEmpty(res)) { |
|
150 tryFileLoaderCallback(); |
|
151 return; |
|
152 } |
|
153 |
|
154 sendResult(res); |
|
155 } else { |
|
156 tryFileLoaderCallback(); |
|
157 } |
|
158 } |
|
159 |
|
160 private void tryFileLoaderCallback() { |
|
161 final FragmentActivity fa = (FragmentActivity) GeckoAppShell.getGeckoInterface().getActivity(); |
|
162 final LoaderManager lm = fa.getSupportLoaderManager(); |
|
163 lm.initLoader(uri.hashCode(), null, new FileLoaderCallbacks(uri)); |
|
164 } |
|
165 |
|
166 @Override |
|
167 public void onLoaderReset(Loader<Cursor> loader) { } |
|
168 } |
|
169 |
|
170 private class FileLoaderCallbacks implements LoaderCallbacks<Cursor>, |
|
171 Tabs.OnTabsChangedListener { |
|
172 final private Uri uri; |
|
173 private String tempFile; |
|
174 |
|
175 public FileLoaderCallbacks(Uri uri) { |
|
176 this.uri = uri; |
|
177 } |
|
178 |
|
179 @Override |
|
180 public Loader<Cursor> onCreateLoader(int id, Bundle args) { |
|
181 final FragmentActivity fa = (FragmentActivity) GeckoAppShell.getGeckoInterface().getActivity(); |
|
182 return new CursorLoader(fa, |
|
183 uri, |
|
184 new String[] { OpenableColumns.DISPLAY_NAME }, |
|
185 null, // selection |
|
186 null, // selectionArgs |
|
187 null); // sortOrder |
|
188 } |
|
189 |
|
190 @Override |
|
191 public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { |
|
192 if (cursor.moveToFirst()) { |
|
193 String name = cursor.getString(0); |
|
194 // tmp filenames must be at least 3 characters long. Add a prefix to make sure that happens |
|
195 String fileName = "tmp_" + Process.myPid() + "-"; |
|
196 String fileExt = null; |
|
197 int period; |
|
198 |
|
199 final FragmentActivity fa = (FragmentActivity) GeckoAppShell.getGeckoInterface().getActivity(); |
|
200 final ContentResolver cr = fa.getContentResolver(); |
|
201 |
|
202 // Generate an extension if we don't already have one |
|
203 if (name == null || (period = name.lastIndexOf('.')) == -1) { |
|
204 String mimeType = cr.getType(uri); |
|
205 fileExt = "." + GeckoAppShell.getExtensionFromMimeType(mimeType); |
|
206 } else { |
|
207 fileExt = name.substring(period); |
|
208 fileName += name.substring(0, period); |
|
209 } |
|
210 |
|
211 // Now write the data to the temp file |
|
212 try { |
|
213 cacheDir.mkdir(); |
|
214 |
|
215 File file = File.createTempFile(fileName, fileExt, cacheDir); |
|
216 FileOutputStream fos = new FileOutputStream(file); |
|
217 InputStream is = cr.openInputStream(uri); |
|
218 byte[] buf = new byte[4096]; |
|
219 int len = is.read(buf); |
|
220 while (len != -1) { |
|
221 fos.write(buf, 0, len); |
|
222 len = is.read(buf); |
|
223 } |
|
224 fos.close(); |
|
225 |
|
226 tempFile = file.getAbsolutePath(); |
|
227 sendResult((tempFile == null) ? "" : tempFile); |
|
228 |
|
229 if (tabId > -1 && !TextUtils.isEmpty(tempFile)) { |
|
230 Tabs.registerOnTabsChangedListener(this); |
|
231 } |
|
232 } catch(IOException ex) { |
|
233 Log.i(LOGTAG, "Error writing file", ex); |
|
234 } |
|
235 } else { |
|
236 sendResult(""); |
|
237 } |
|
238 } |
|
239 |
|
240 @Override |
|
241 public void onLoaderReset(Loader<Cursor> loader) { } |
|
242 |
|
243 /*Tabs.OnTabsChangedListener*/ |
|
244 // This cleans up our temp file. If it doesn't run, we just hope that Android |
|
245 // will eventually does the cleanup for us. |
|
246 @Override |
|
247 public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) { |
|
248 if (tab.getId() != tabId) { |
|
249 return; |
|
250 } |
|
251 |
|
252 if (msg == Tabs.TabEvents.LOCATION_CHANGE || |
|
253 msg == Tabs.TabEvents.CLOSED) { |
|
254 ThreadUtils.postToBackgroundThread(new Runnable() { |
|
255 @Override |
|
256 public void run() { |
|
257 File f = new File(tempFile); |
|
258 f.delete(); |
|
259 } |
|
260 }); |
|
261 |
|
262 // Tabs' listener array is safe to modify during use: its |
|
263 // iteration pattern is based on snapshots. |
|
264 Tabs.unregisterOnTabsChangedListener(this); |
|
265 } |
|
266 } |
|
267 } |
|
268 |
|
269 } |
|
270 |