mobile/android/base/FilePickerResultHandler.java

branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
equal deleted inserted replaced
-1:000000000000 0:79feb3100cb8
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

mercurial