mobile/android/base/FilePicker.java

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/mobile/android/base/FilePicker.java	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,225 @@
     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.GeckoAppShell;
    1.11 +import org.mozilla.gecko.util.ThreadUtils;
    1.12 +import org.mozilla.gecko.util.GeckoEventListener;
    1.13 +
    1.14 +import org.json.JSONException;
    1.15 +import org.json.JSONObject;
    1.16 +
    1.17 +import android.app.Activity;
    1.18 +import android.content.ComponentName;
    1.19 +import android.content.Context;
    1.20 +import android.content.Intent;
    1.21 +import android.content.pm.PackageManager;
    1.22 +import android.content.pm.ResolveInfo;
    1.23 +import android.net.Uri;
    1.24 +import android.os.Environment;
    1.25 +import android.os.Parcelable;
    1.26 +import android.provider.MediaStore;
    1.27 +import android.util.Log;
    1.28 +
    1.29 +import java.io.File;
    1.30 +import java.util.ArrayList;
    1.31 +import java.util.Collection;
    1.32 +import java.util.HashMap;
    1.33 +import java.util.Iterator;
    1.34 +import java.util.List;
    1.35 +
    1.36 +public class FilePicker implements GeckoEventListener {
    1.37 +    private static final String LOGTAG = "GeckoFilePicker";
    1.38 +    private static FilePicker sFilePicker;
    1.39 +    private final Context context;
    1.40 +
    1.41 +    public interface ResultHandler {
    1.42 +        public void gotFile(String filename);
    1.43 +    }
    1.44 +
    1.45 +    public static void init(Context context) {
    1.46 +        if (sFilePicker == null) {
    1.47 +            sFilePicker = new FilePicker(context.getApplicationContext());
    1.48 +        }
    1.49 +    }
    1.50 +
    1.51 +    protected FilePicker(Context context) {
    1.52 +        this.context = context;
    1.53 +        GeckoAppShell.getEventDispatcher().registerEventListener("FilePicker:Show", this);
    1.54 +    }
    1.55 +
    1.56 +    @Override
    1.57 +    public void handleMessage(String event, final JSONObject message) {
    1.58 +        if (event.equals("FilePicker:Show")) {
    1.59 +            String mimeType = "*/*";
    1.60 +            final String mode = message.optString("mode");
    1.61 +            final int tabId = message.optInt("tabId", -1);
    1.62 +
    1.63 +            if ("mimeType".equals(mode))
    1.64 +                mimeType = message.optString("mimeType");
    1.65 +            else if ("extension".equals(mode))
    1.66 +                mimeType = GeckoAppShell.getMimeTypeFromExtensions(message.optString("extensions"));
    1.67 +
    1.68 +            showFilePickerAsync(mimeType, new ResultHandler() {
    1.69 +                public void gotFile(String filename) {
    1.70 +                    try {
    1.71 +                        message.put("file", filename);
    1.72 +                    } catch (JSONException ex) {
    1.73 +                        Log.i(LOGTAG, "Can't add filename to message " + filename);
    1.74 +                    }
    1.75 +
    1.76 +
    1.77 +                    GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(
    1.78 +                        "FilePicker:Result", message.toString()));
    1.79 +                }
    1.80 +            }, tabId);
    1.81 +        }
    1.82 +    }
    1.83 +
    1.84 +    private void addActivities(Intent intent, HashMap<String, Intent> intents, HashMap<String, Intent> filters) {
    1.85 +        PackageManager pm = context.getPackageManager();
    1.86 +        List<ResolveInfo> lri = pm.queryIntentActivities(intent, 0);
    1.87 +        for (ResolveInfo ri : lri) {
    1.88 +            ComponentName cn = new ComponentName(ri.activityInfo.applicationInfo.packageName, ri.activityInfo.name);
    1.89 +            if (filters != null && !filters.containsKey(cn.toString())) {
    1.90 +                Intent rintent = new Intent(intent);
    1.91 +                rintent.setComponent(cn);
    1.92 +                intents.put(cn.toString(), rintent);
    1.93 +            }
    1.94 +        }
    1.95 +    }
    1.96 +
    1.97 +    private Intent getIntent(String mimeType) {
    1.98 +        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
    1.99 +        intent.setType(mimeType);
   1.100 +        intent.addCategory(Intent.CATEGORY_OPENABLE);
   1.101 +        return intent;
   1.102 +    }
   1.103 +
   1.104 +    private List<Intent> getIntentsForFilePicker(final String mimeType,
   1.105 +                                                       final FilePickerResultHandler fileHandler) {
   1.106 +        // The base intent to use for the file picker. Even if this is an implicit intent, Android will
   1.107 +        // still show a list of Activitiees that match this action/type.
   1.108 +        Intent baseIntent;
   1.109 +        // A HashMap of Activities the base intent will show in the chooser. This is used
   1.110 +        // to filter activities from other intents so that we don't show duplicates.
   1.111 +        HashMap<String, Intent> baseIntents = new HashMap<String, Intent>();
   1.112 +        // A list of other activities to shwo in the picker (and the intents to launch them).
   1.113 +        HashMap<String, Intent> intents = new HashMap<String, Intent> ();
   1.114 +
   1.115 +        if ("audio/*".equals(mimeType)) {
   1.116 +            // For audio the only intent is the mimetype
   1.117 +            baseIntent = getIntent(mimeType);
   1.118 +            addActivities(baseIntent, baseIntents, null);
   1.119 +        } else if ("image/*".equals(mimeType)) {
   1.120 +            // For images the base is a capture intent
   1.121 +            baseIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
   1.122 +            baseIntent.putExtra(MediaStore.EXTRA_OUTPUT,
   1.123 +                            Uri.fromFile(new File(Environment.getExternalStorageDirectory(),
   1.124 +                                                  fileHandler.generateImageName())));
   1.125 +            addActivities(baseIntent, baseIntents, null);
   1.126 +
   1.127 +            // We also add the mimetype intent
   1.128 +            addActivities(getIntent(mimeType), intents, baseIntents);
   1.129 +        } else if ("video/*".equals(mimeType)) {
   1.130 +            // For videos the base is a capture intent
   1.131 +            baseIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
   1.132 +            addActivities(baseIntent, baseIntents, null);
   1.133 +
   1.134 +            // We also add the mimetype intent
   1.135 +            addActivities(getIntent(mimeType), intents, baseIntents);
   1.136 +        } else {
   1.137 +            baseIntent = getIntent("*/*");
   1.138 +            addActivities(baseIntent, baseIntents, null);
   1.139 +
   1.140 +            Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
   1.141 +            intent.putExtra(MediaStore.EXTRA_OUTPUT,
   1.142 +                            Uri.fromFile(new File(Environment.getExternalStorageDirectory(),
   1.143 +                                                  fileHandler.generateImageName())));
   1.144 +            addActivities(intent, intents, baseIntents);
   1.145 +            intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
   1.146 +            addActivities(intent, intents, baseIntents);
   1.147 +        }
   1.148 +
   1.149 +        // If we didn't find any activities, we fall back to the */* mimetype intent
   1.150 +        if (baseIntents.size() == 0 && intents.size() == 0) {
   1.151 +            intents.clear();
   1.152 +
   1.153 +            baseIntent = getIntent("*/*");
   1.154 +            addActivities(baseIntent, baseIntents, null);
   1.155 +        }
   1.156 +
   1.157 +        ArrayList<Intent> vals = new ArrayList<Intent>(intents.values());
   1.158 +        vals.add(0, baseIntent);
   1.159 +        return vals;
   1.160 +    }
   1.161 +
   1.162 +    private String getFilePickerTitle(String mimeType) {
   1.163 +        if (mimeType.equals("audio/*")) {
   1.164 +            return context.getString(R.string.filepicker_audio_title);
   1.165 +        } else if (mimeType.equals("image/*")) {
   1.166 +            return context.getString(R.string.filepicker_image_title);
   1.167 +        } else if (mimeType.equals("video/*")) {
   1.168 +            return context.getString(R.string.filepicker_video_title);
   1.169 +        } else {
   1.170 +            return context.getString(R.string.filepicker_title);
   1.171 +        }
   1.172 +    }
   1.173 +
   1.174 +    private interface IntentHandler {
   1.175 +        public void gotIntent(Intent intent);
   1.176 +    }
   1.177 +
   1.178 +    /* Gets an intent that can open a particular mimetype. Will show a prompt with a list
   1.179 +     * of Activities that can handle the mietype. Asynchronously calls the handler when
   1.180 +     * one of the intents is selected. If the caller passes in null for the handler, will still
   1.181 +     * prompt for the activity, but will throw away the result.
   1.182 +     */
   1.183 +    private void getFilePickerIntentAsync(final String mimeType,
   1.184 +                                          final FilePickerResultHandler fileHandler,
   1.185 +                                          final IntentHandler handler) {
   1.186 +        List<Intent> intents = getIntentsForFilePicker(mimeType, fileHandler);
   1.187 +
   1.188 +        if (intents.size() == 0) {
   1.189 +            Log.i(LOGTAG, "no activities for the file picker!");
   1.190 +            handler.gotIntent(null);
   1.191 +            return;
   1.192 +        }
   1.193 +
   1.194 +        Intent base = intents.remove(0);
   1.195 +
   1.196 +        if (intents.size() == 0) {
   1.197 +            handler.gotIntent(base);
   1.198 +            return;
   1.199 +        }
   1.200 +
   1.201 +        Intent chooser = Intent.createChooser(base, getFilePickerTitle(mimeType));
   1.202 +        chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, intents.toArray(new Parcelable[]{}));
   1.203 +        handler.gotIntent(chooser);
   1.204 +    }
   1.205 +
   1.206 +    /* Allows the user to pick an activity to load files from using a list prompt. Then opens the activity and
   1.207 +     * sends the file returned to the passed in handler. If a null handler is passed in, will still
   1.208 +     * pick and launch the file picker, but will throw away the result.
   1.209 +     */
   1.210 +    protected void showFilePickerAsync(String mimeType, final ResultHandler handler, final int tabId) {
   1.211 +        final FilePickerResultHandler fileHandler = new FilePickerResultHandler(handler, context, tabId);
   1.212 +        getFilePickerIntentAsync(mimeType, fileHandler, new IntentHandler() {
   1.213 +            @Override
   1.214 +            public void gotIntent(Intent intent) {
   1.215 +                if (handler == null) {
   1.216 +                    return;
   1.217 +                }
   1.218 +
   1.219 +                if (intent == null) {
   1.220 +                    handler.gotFile("");
   1.221 +                    return;
   1.222 +                }
   1.223 +
   1.224 +                ActivityHandlerHelper.startIntent(intent, fileHandler);
   1.225 +            }
   1.226 +        });
   1.227 +    }
   1.228 +}

mercurial