mobile/android/base/FilePickerResultHandler.java

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/mobile/android/base/FilePickerResultHandler.java	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,270 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +package org.mozilla.gecko;
     1.9 +
    1.10 +import org.mozilla.gecko.mozglue.GeckoLoader;
    1.11 +import org.mozilla.gecko.util.ActivityResultHandler;
    1.12 +import org.mozilla.gecko.util.ThreadUtils;
    1.13 +
    1.14 +import android.app.Activity;
    1.15 +import android.content.ContentResolver;
    1.16 +import android.content.Context;
    1.17 +import android.content.Intent;
    1.18 +import android.database.Cursor;
    1.19 +import android.net.Uri;
    1.20 +import android.os.Bundle;
    1.21 +import android.os.Environment;
    1.22 +import android.provider.MediaStore;
    1.23 +import android.provider.OpenableColumns;
    1.24 +import android.support.v4.app.FragmentActivity;
    1.25 +import android.support.v4.app.LoaderManager;
    1.26 +import android.support.v4.app.LoaderManager.LoaderCallbacks;
    1.27 +import android.support.v4.content.CursorLoader;
    1.28 +import android.support.v4.content.Loader;
    1.29 +import android.text.TextUtils;
    1.30 +import android.text.format.Time;
    1.31 +import android.util.Log;
    1.32 +import android.os.Process;
    1.33 +
    1.34 +import java.io.File;
    1.35 +import java.io.FileOutputStream;
    1.36 +import java.io.InputStream;
    1.37 +import java.io.IOException;
    1.38 +
    1.39 +class FilePickerResultHandler implements ActivityResultHandler {
    1.40 +    private static final String LOGTAG = "GeckoFilePickerResultHandler";
    1.41 +    private static final String UPLOADS_DIR = "uploads";
    1.42 +
    1.43 +    protected final FilePicker.ResultHandler handler;
    1.44 +    private final int tabId;
    1.45 +    private final File cacheDir;
    1.46 +
    1.47 +    // this code is really hacky and doesn't belong anywhere so I'm putting it here for now
    1.48 +    // until I can come up with a better solution.
    1.49 +    private String mImageName = "";
    1.50 +
    1.51 +    /* Use this constructor to asynchronously listen for results */
    1.52 +    public FilePickerResultHandler(final FilePicker.ResultHandler handler, final Context context, final int tabId) {
    1.53 +        this.tabId = tabId;
    1.54 +        cacheDir = new File(context.getCacheDir(), UPLOADS_DIR);
    1.55 +        this.handler = handler;
    1.56 +    }
    1.57 +
    1.58 +    private void sendResult(String res) {
    1.59 +        if (handler != null) {
    1.60 +            handler.gotFile(res);
    1.61 +        }
    1.62 +    }
    1.63 +
    1.64 +    @Override
    1.65 +    public void onActivityResult(int resultCode, Intent intent) {
    1.66 +        if (resultCode != Activity.RESULT_OK) {
    1.67 +            sendResult("");
    1.68 +            return;
    1.69 +        }
    1.70 +
    1.71 +        // Camera results won't return an Intent. Use the file name we passed to the original intent.
    1.72 +        if (intent == null) {
    1.73 +            if (mImageName != null) {
    1.74 +                File file = new File(Environment.getExternalStorageDirectory(), mImageName);
    1.75 +                sendResult(file.getAbsolutePath());
    1.76 +            } else {
    1.77 +                sendResult("");
    1.78 +            }
    1.79 +            return;
    1.80 +        }
    1.81 +
    1.82 +        Uri uri = intent.getData();
    1.83 +        if (uri == null) {
    1.84 +            sendResult("");
    1.85 +            return;
    1.86 +        }
    1.87 +
    1.88 +        // Some file pickers may return a file uri
    1.89 +        if ("file".equals(uri.getScheme())) {
    1.90 +            String path = uri.getPath();
    1.91 +            sendResult(path == null ? "" : path);
    1.92 +            return;
    1.93 +        }
    1.94 +
    1.95 +        final FragmentActivity fa = (FragmentActivity) GeckoAppShell.getGeckoInterface().getActivity();
    1.96 +        final LoaderManager lm = fa.getSupportLoaderManager();
    1.97 +        // Finally, Video pickers and some file pickers may return a content provider.
    1.98 +        Cursor cursor = null;
    1.99 +        try {
   1.100 +            // Try a query to make sure the expected columns exist
   1.101 +            final ContentResolver cr = fa.getContentResolver();
   1.102 +            cursor = cr.query(uri, new String[] { MediaStore.Video.Media.DATA }, null, null, null);
   1.103 +
   1.104 +            int index = cursor.getColumnIndex(MediaStore.Video.Media.DATA);
   1.105 +            if (index >= 0) {
   1.106 +                lm.initLoader(intent.hashCode(), null, new VideoLoaderCallbacks(uri));
   1.107 +                return;
   1.108 +            }
   1.109 +        } catch(Exception ex) {
   1.110 +            // We'll try a different loader below
   1.111 +        } finally {
   1.112 +            if (cursor != null) {
   1.113 +                cursor.close();
   1.114 +            }
   1.115 +        }
   1.116 +
   1.117 +        lm.initLoader(uri.hashCode(), null, new FileLoaderCallbacks(uri));
   1.118 +        return;
   1.119 +    }
   1.120 +
   1.121 +    public String generateImageName() {
   1.122 +        Time now = new Time();
   1.123 +        now.setToNow();
   1.124 +        mImageName = now.format("%Y-%m-%d %H.%M.%S") + ".jpg";
   1.125 +        return mImageName;
   1.126 +    }
   1.127 +
   1.128 +    private class VideoLoaderCallbacks implements LoaderCallbacks<Cursor> {
   1.129 +        final private Uri uri;
   1.130 +        public VideoLoaderCallbacks(Uri uri) {
   1.131 +            this.uri = uri;
   1.132 +        }
   1.133 +
   1.134 +        @Override
   1.135 +        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
   1.136 +            final FragmentActivity fa = (FragmentActivity) GeckoAppShell.getGeckoInterface().getActivity();
   1.137 +            return new CursorLoader(fa,
   1.138 +                                    uri,
   1.139 +                                    new String[] { MediaStore.Video.Media.DATA },
   1.140 +                                    null,  // selection
   1.141 +                                    null,  // selectionArgs
   1.142 +                                    null); // sortOrder
   1.143 +        }
   1.144 +
   1.145 +        @Override
   1.146 +        public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
   1.147 +            if (cursor.moveToFirst()) {
   1.148 +                String res = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA));
   1.149 +
   1.150 +                // Some pickers (the KitKat Documents one for instance) won't return a temporary file here.
   1.151 +                // Fall back to the normal FileLoader if we didn't find anything.
   1.152 +                if (TextUtils.isEmpty(res)) {
   1.153 +                    tryFileLoaderCallback();
   1.154 +                    return;
   1.155 +                }
   1.156 +
   1.157 +                sendResult(res);
   1.158 +            } else {
   1.159 +                tryFileLoaderCallback();
   1.160 +            }
   1.161 +        }
   1.162 +
   1.163 +        private void tryFileLoaderCallback() {
   1.164 +            final FragmentActivity fa = (FragmentActivity) GeckoAppShell.getGeckoInterface().getActivity();
   1.165 +            final LoaderManager lm = fa.getSupportLoaderManager();
   1.166 +            lm.initLoader(uri.hashCode(), null, new FileLoaderCallbacks(uri));
   1.167 +        }
   1.168 +
   1.169 +        @Override
   1.170 +        public void onLoaderReset(Loader<Cursor> loader) { }
   1.171 +    }
   1.172 +
   1.173 +    private class FileLoaderCallbacks implements LoaderCallbacks<Cursor>,
   1.174 +                                                 Tabs.OnTabsChangedListener {
   1.175 +        final private Uri uri;
   1.176 +        private String tempFile;
   1.177 +
   1.178 +        public FileLoaderCallbacks(Uri uri) {
   1.179 +            this.uri = uri;
   1.180 +        }
   1.181 +
   1.182 +        @Override
   1.183 +        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
   1.184 +            final FragmentActivity fa = (FragmentActivity) GeckoAppShell.getGeckoInterface().getActivity();
   1.185 +            return new CursorLoader(fa,
   1.186 +                                    uri,
   1.187 +                                    new String[] { OpenableColumns.DISPLAY_NAME },
   1.188 +                                    null,  // selection
   1.189 +                                    null,  // selectionArgs
   1.190 +                                    null); // sortOrder
   1.191 +        }
   1.192 +
   1.193 +        @Override
   1.194 +        public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
   1.195 +            if (cursor.moveToFirst()) {
   1.196 +                String name = cursor.getString(0);
   1.197 +                // tmp filenames must be at least 3 characters long. Add a prefix to make sure that happens
   1.198 +                String fileName = "tmp_" + Process.myPid() + "-";
   1.199 +                String fileExt = null;
   1.200 +                int period;
   1.201 +
   1.202 +                final FragmentActivity fa = (FragmentActivity) GeckoAppShell.getGeckoInterface().getActivity();
   1.203 +                final ContentResolver cr = fa.getContentResolver();
   1.204 +
   1.205 +                // Generate an extension if we don't already have one
   1.206 +                if (name == null || (period = name.lastIndexOf('.')) == -1) {
   1.207 +                    String mimeType = cr.getType(uri);
   1.208 +                    fileExt = "." + GeckoAppShell.getExtensionFromMimeType(mimeType);
   1.209 +                } else {
   1.210 +                    fileExt = name.substring(period);
   1.211 +                    fileName += name.substring(0, period);
   1.212 +                }
   1.213 +
   1.214 +                // Now write the data to the temp file
   1.215 +                try {
   1.216 +                    cacheDir.mkdir();
   1.217 +
   1.218 +                    File file = File.createTempFile(fileName, fileExt, cacheDir);
   1.219 +                    FileOutputStream fos = new FileOutputStream(file);
   1.220 +                    InputStream is = cr.openInputStream(uri);
   1.221 +                    byte[] buf = new byte[4096];
   1.222 +                    int len = is.read(buf);
   1.223 +                    while (len != -1) {
   1.224 +                        fos.write(buf, 0, len);
   1.225 +                        len = is.read(buf);
   1.226 +                    }
   1.227 +                    fos.close();
   1.228 +
   1.229 +                    tempFile = file.getAbsolutePath();
   1.230 +                    sendResult((tempFile == null) ? "" : tempFile);
   1.231 +
   1.232 +                    if (tabId > -1 && !TextUtils.isEmpty(tempFile)) {
   1.233 +                        Tabs.registerOnTabsChangedListener(this);
   1.234 +                    }
   1.235 +                } catch(IOException ex) {
   1.236 +                    Log.i(LOGTAG, "Error writing file", ex);
   1.237 +                }
   1.238 +            } else {
   1.239 +                sendResult("");
   1.240 +            }
   1.241 +        }
   1.242 +
   1.243 +        @Override
   1.244 +        public void onLoaderReset(Loader<Cursor> loader) { }
   1.245 +
   1.246 +        /*Tabs.OnTabsChangedListener*/
   1.247 +        // This cleans up our temp file. If it doesn't run, we just hope that Android
   1.248 +        // will eventually does the cleanup for us.
   1.249 +        @Override
   1.250 +        public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) {
   1.251 +            if (tab.getId() != tabId) {
   1.252 +                return;
   1.253 +            }
   1.254 +
   1.255 +            if (msg == Tabs.TabEvents.LOCATION_CHANGE ||
   1.256 +                msg == Tabs.TabEvents.CLOSED) {
   1.257 +                ThreadUtils.postToBackgroundThread(new Runnable() {
   1.258 +                    @Override
   1.259 +                    public void run() {
   1.260 +                        File f = new File(tempFile);
   1.261 +                        f.delete();
   1.262 +                    }
   1.263 +                });
   1.264 +
   1.265 +                // Tabs' listener array is safe to modify during use: its
   1.266 +                // iteration pattern is based on snapshots.
   1.267 +                Tabs.unregisterOnTabsChangedListener(this);
   1.268 +            }
   1.269 +        }
   1.270 +    }
   1.271 +
   1.272 +}
   1.273 +

mercurial