michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko; michael@0: michael@0: import org.mozilla.gecko.mozglue.GeckoLoader; michael@0: import org.mozilla.gecko.util.ActivityResultHandler; michael@0: import org.mozilla.gecko.util.ThreadUtils; michael@0: michael@0: import android.app.Activity; michael@0: import android.content.ContentResolver; michael@0: import android.content.Context; michael@0: import android.content.Intent; michael@0: import android.database.Cursor; michael@0: import android.net.Uri; michael@0: import android.os.Bundle; michael@0: import android.os.Environment; michael@0: import android.provider.MediaStore; michael@0: import android.provider.OpenableColumns; michael@0: import android.support.v4.app.FragmentActivity; michael@0: import android.support.v4.app.LoaderManager; michael@0: import android.support.v4.app.LoaderManager.LoaderCallbacks; michael@0: import android.support.v4.content.CursorLoader; michael@0: import android.support.v4.content.Loader; michael@0: import android.text.TextUtils; michael@0: import android.text.format.Time; michael@0: import android.util.Log; michael@0: import android.os.Process; michael@0: michael@0: import java.io.File; michael@0: import java.io.FileOutputStream; michael@0: import java.io.InputStream; michael@0: import java.io.IOException; michael@0: michael@0: class FilePickerResultHandler implements ActivityResultHandler { michael@0: private static final String LOGTAG = "GeckoFilePickerResultHandler"; michael@0: private static final String UPLOADS_DIR = "uploads"; michael@0: michael@0: protected final FilePicker.ResultHandler handler; michael@0: private final int tabId; michael@0: private final File cacheDir; michael@0: michael@0: // this code is really hacky and doesn't belong anywhere so I'm putting it here for now michael@0: // until I can come up with a better solution. michael@0: private String mImageName = ""; michael@0: michael@0: /* Use this constructor to asynchronously listen for results */ michael@0: public FilePickerResultHandler(final FilePicker.ResultHandler handler, final Context context, final int tabId) { michael@0: this.tabId = tabId; michael@0: cacheDir = new File(context.getCacheDir(), UPLOADS_DIR); michael@0: this.handler = handler; michael@0: } michael@0: michael@0: private void sendResult(String res) { michael@0: if (handler != null) { michael@0: handler.gotFile(res); michael@0: } michael@0: } michael@0: michael@0: @Override michael@0: public void onActivityResult(int resultCode, Intent intent) { michael@0: if (resultCode != Activity.RESULT_OK) { michael@0: sendResult(""); michael@0: return; michael@0: } michael@0: michael@0: // Camera results won't return an Intent. Use the file name we passed to the original intent. michael@0: if (intent == null) { michael@0: if (mImageName != null) { michael@0: File file = new File(Environment.getExternalStorageDirectory(), mImageName); michael@0: sendResult(file.getAbsolutePath()); michael@0: } else { michael@0: sendResult(""); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: Uri uri = intent.getData(); michael@0: if (uri == null) { michael@0: sendResult(""); michael@0: return; michael@0: } michael@0: michael@0: // Some file pickers may return a file uri michael@0: if ("file".equals(uri.getScheme())) { michael@0: String path = uri.getPath(); michael@0: sendResult(path == null ? "" : path); michael@0: return; michael@0: } michael@0: michael@0: final FragmentActivity fa = (FragmentActivity) GeckoAppShell.getGeckoInterface().getActivity(); michael@0: final LoaderManager lm = fa.getSupportLoaderManager(); michael@0: // Finally, Video pickers and some file pickers may return a content provider. michael@0: Cursor cursor = null; michael@0: try { michael@0: // Try a query to make sure the expected columns exist michael@0: final ContentResolver cr = fa.getContentResolver(); michael@0: cursor = cr.query(uri, new String[] { MediaStore.Video.Media.DATA }, null, null, null); michael@0: michael@0: int index = cursor.getColumnIndex(MediaStore.Video.Media.DATA); michael@0: if (index >= 0) { michael@0: lm.initLoader(intent.hashCode(), null, new VideoLoaderCallbacks(uri)); michael@0: return; michael@0: } michael@0: } catch(Exception ex) { michael@0: // We'll try a different loader below michael@0: } finally { michael@0: if (cursor != null) { michael@0: cursor.close(); michael@0: } michael@0: } michael@0: michael@0: lm.initLoader(uri.hashCode(), null, new FileLoaderCallbacks(uri)); michael@0: return; michael@0: } michael@0: michael@0: public String generateImageName() { michael@0: Time now = new Time(); michael@0: now.setToNow(); michael@0: mImageName = now.format("%Y-%m-%d %H.%M.%S") + ".jpg"; michael@0: return mImageName; michael@0: } michael@0: michael@0: private class VideoLoaderCallbacks implements LoaderCallbacks { michael@0: final private Uri uri; michael@0: public VideoLoaderCallbacks(Uri uri) { michael@0: this.uri = uri; michael@0: } michael@0: michael@0: @Override michael@0: public Loader onCreateLoader(int id, Bundle args) { michael@0: final FragmentActivity fa = (FragmentActivity) GeckoAppShell.getGeckoInterface().getActivity(); michael@0: return new CursorLoader(fa, michael@0: uri, michael@0: new String[] { MediaStore.Video.Media.DATA }, michael@0: null, // selection michael@0: null, // selectionArgs michael@0: null); // sortOrder michael@0: } michael@0: michael@0: @Override michael@0: public void onLoadFinished(Loader loader, Cursor cursor) { michael@0: if (cursor.moveToFirst()) { michael@0: String res = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA)); michael@0: michael@0: // Some pickers (the KitKat Documents one for instance) won't return a temporary file here. michael@0: // Fall back to the normal FileLoader if we didn't find anything. michael@0: if (TextUtils.isEmpty(res)) { michael@0: tryFileLoaderCallback(); michael@0: return; michael@0: } michael@0: michael@0: sendResult(res); michael@0: } else { michael@0: tryFileLoaderCallback(); michael@0: } michael@0: } michael@0: michael@0: private void tryFileLoaderCallback() { michael@0: final FragmentActivity fa = (FragmentActivity) GeckoAppShell.getGeckoInterface().getActivity(); michael@0: final LoaderManager lm = fa.getSupportLoaderManager(); michael@0: lm.initLoader(uri.hashCode(), null, new FileLoaderCallbacks(uri)); michael@0: } michael@0: michael@0: @Override michael@0: public void onLoaderReset(Loader loader) { } michael@0: } michael@0: michael@0: private class FileLoaderCallbacks implements LoaderCallbacks, michael@0: Tabs.OnTabsChangedListener { michael@0: final private Uri uri; michael@0: private String tempFile; michael@0: michael@0: public FileLoaderCallbacks(Uri uri) { michael@0: this.uri = uri; michael@0: } michael@0: michael@0: @Override michael@0: public Loader onCreateLoader(int id, Bundle args) { michael@0: final FragmentActivity fa = (FragmentActivity) GeckoAppShell.getGeckoInterface().getActivity(); michael@0: return new CursorLoader(fa, michael@0: uri, michael@0: new String[] { OpenableColumns.DISPLAY_NAME }, michael@0: null, // selection michael@0: null, // selectionArgs michael@0: null); // sortOrder michael@0: } michael@0: michael@0: @Override michael@0: public void onLoadFinished(Loader loader, Cursor cursor) { michael@0: if (cursor.moveToFirst()) { michael@0: String name = cursor.getString(0); michael@0: // tmp filenames must be at least 3 characters long. Add a prefix to make sure that happens michael@0: String fileName = "tmp_" + Process.myPid() + "-"; michael@0: String fileExt = null; michael@0: int period; michael@0: michael@0: final FragmentActivity fa = (FragmentActivity) GeckoAppShell.getGeckoInterface().getActivity(); michael@0: final ContentResolver cr = fa.getContentResolver(); michael@0: michael@0: // Generate an extension if we don't already have one michael@0: if (name == null || (period = name.lastIndexOf('.')) == -1) { michael@0: String mimeType = cr.getType(uri); michael@0: fileExt = "." + GeckoAppShell.getExtensionFromMimeType(mimeType); michael@0: } else { michael@0: fileExt = name.substring(period); michael@0: fileName += name.substring(0, period); michael@0: } michael@0: michael@0: // Now write the data to the temp file michael@0: try { michael@0: cacheDir.mkdir(); michael@0: michael@0: File file = File.createTempFile(fileName, fileExt, cacheDir); michael@0: FileOutputStream fos = new FileOutputStream(file); michael@0: InputStream is = cr.openInputStream(uri); michael@0: byte[] buf = new byte[4096]; michael@0: int len = is.read(buf); michael@0: while (len != -1) { michael@0: fos.write(buf, 0, len); michael@0: len = is.read(buf); michael@0: } michael@0: fos.close(); michael@0: michael@0: tempFile = file.getAbsolutePath(); michael@0: sendResult((tempFile == null) ? "" : tempFile); michael@0: michael@0: if (tabId > -1 && !TextUtils.isEmpty(tempFile)) { michael@0: Tabs.registerOnTabsChangedListener(this); michael@0: } michael@0: } catch(IOException ex) { michael@0: Log.i(LOGTAG, "Error writing file", ex); michael@0: } michael@0: } else { michael@0: sendResult(""); michael@0: } michael@0: } michael@0: michael@0: @Override michael@0: public void onLoaderReset(Loader loader) { } michael@0: michael@0: /*Tabs.OnTabsChangedListener*/ michael@0: // This cleans up our temp file. If it doesn't run, we just hope that Android michael@0: // will eventually does the cleanup for us. michael@0: @Override michael@0: public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) { michael@0: if (tab.getId() != tabId) { michael@0: return; michael@0: } michael@0: michael@0: if (msg == Tabs.TabEvents.LOCATION_CHANGE || michael@0: msg == Tabs.TabEvents.CLOSED) { michael@0: ThreadUtils.postToBackgroundThread(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: File f = new File(tempFile); michael@0: f.delete(); michael@0: } michael@0: }); michael@0: michael@0: // Tabs' listener array is safe to modify during use: its michael@0: // iteration pattern is based on snapshots. michael@0: Tabs.unregisterOnTabsChangedListener(this); michael@0: } michael@0: } michael@0: } michael@0: michael@0: } michael@0: