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