Bug 973036 - Create an intent chooser dialog class. r=bnicholson
authorWesley Johnston <wjohnston>
Thu, 06 Feb 2014 16:21:00 -0800
changeset 170949 566ecc34854ea08d80424d2bdc6458211b0f7a65
parent 170892 4f3ea4de80fccd03966b3798ddc804c8e0dbf34e
child 170950 4881a4e25a3eabb247fac3ac85bc3db84bde9817
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewersbnicholson
bugs973036
milestone30.0a1
Bug 973036 - Create an intent chooser dialog class. r=bnicholson
mobile/android/base/moz.build
mobile/android/base/prompts/IntentChooserPrompt.java
mobile/android/base/prompts/IntentHandler.java
mobile/android/base/prompts/PromptListItem.java
mobile/android/base/widget/GeckoActionProvider.java
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -292,16 +292,18 @@ gbjar.sources += [
     'preferences/PrivateDataPreference.java',
     'preferences/SearchEnginePreference.java',
     'preferences/SearchPreferenceCategory.java',
     'preferences/SyncPreference.java',
     'PrefsHelper.java',
     'PrivateTab.java',
     'prompts/ColorPickerInput.java',
     'prompts/IconGridInput.java',
+    'prompts/IntentChooserPrompt.java',
+    'prompts/IntentHandler.java',
     'prompts/Prompt.java',
     'prompts/PromptInput.java',
     'prompts/PromptListAdapter.java',
     'prompts/PromptListItem.java',
     'prompts/PromptService.java',
     'ReaderModeUtils.java',
     'ReferrerReceiver.java',
     'RemoteTabs.java',
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/prompts/IntentChooserPrompt.java
@@ -0,0 +1,157 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.prompts;
+
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.util.ThreadUtils;
+import org.mozilla.gecko.widget.GeckoActionProvider;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.widget.ListView;
+import android.util.Log;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Shows a prompt letting the user pick from a list of intent handlers for a set of Intents or
+ * for a GeckoActionProvider.  Basic usage:
+ *   IntentChooserPrompt prompt = new IntentChooserPrompt(context, new Intent[] {
+ *      ... // some intents
+ *    });
+ *    prompt.show("Title", context, new IntentHandler() {
+ *        public void onIntentSelected(Intent intent, int position) { }
+ *        public void onCancelled() { }
+ *    });
+ **/
+public class IntentChooserPrompt {
+    private static final String LOGTAG = "GeckoIntentChooser";
+
+    private final ArrayList<PromptListItem> mItems;
+
+    public IntentChooserPrompt(Context context, Intent[] intents) {
+        mItems = getItems(context, intents);
+    }
+
+    public IntentChooserPrompt(Context context, GeckoActionProvider provider) {
+        mItems = getItems(context, provider);
+    }
+
+    /* If an IntentHandler is passed in, will asynchronously call the handler when the dialog is closed
+     * Otherwise, will return the Intent that was chosen by the user. Must be called on the UI thread.
+     */
+    public void show(final String title, final Context context, final IntentHandler handler) {
+        ThreadUtils.assertOnUiThread();
+
+        if (mItems.isEmpty()) {
+            Log.i(LOGTAG, "No activities for the intent chooser!");
+            handler.onCancelled();
+            return;
+        }
+
+        // If there's only one item in the intent list, just return it
+        if (mItems.size() == 1) {
+            handler.onIntentSelected(mItems.get(0).intent, 0);
+            return;
+        }
+
+        final Prompt prompt = new Prompt(context, new Prompt.PromptCallback() {
+            @Override
+            public void onPromptFinished(String promptServiceResult) {
+                if (handler == null) {
+                    return;
+                }
+
+                int itemId = -1;
+                try {
+                    itemId = new JSONObject(promptServiceResult).getInt("button");
+                } catch (JSONException e) {
+                    Log.e(LOGTAG, "result from promptservice was invalid: ", e);
+                }
+
+                if (itemId == -1) {
+                    handler.onCancelled();
+                } else {
+                    handler.onIntentSelected(mItems.get(itemId).intent, itemId);
+                }
+            }
+        });
+
+        PromptListItem[] arrays = new PromptListItem[mItems.size()];
+        mItems.toArray(arrays);
+        prompt.show(title, "", arrays, ListView.CHOICE_MODE_NONE);
+
+        return;
+    }
+
+    // Whether or not any activities were found. Useful for checking if you should try a different Intent set
+    public boolean hasActivities(Context context) {
+        return mItems.isEmpty();
+    }
+
+    // Gets a list of PromptListItems for an Intent array
+    private ArrayList<PromptListItem> getItems(final Context context, Intent[] intents) {
+        final ArrayList<PromptListItem> items = new ArrayList<PromptListItem>();
+
+        // If we have intents, use them to build the initial list
+        for (final Intent intent : intents) {
+            items.addAll(getItemsForIntent(context, intent));
+        }
+
+        return items;
+    }
+
+    // Gets a list of PromptListItems for a GeckoActionProvider
+    private ArrayList<PromptListItem> getItems(final Context context, final GeckoActionProvider provider) {
+        final ArrayList<PromptListItem> items = new ArrayList<PromptListItem>();
+
+        // Add any intents from the provider.
+        final PackageManager packageManager = context.getPackageManager();
+        final ArrayList<ResolveInfo> infos = provider.getSortedActivites();
+
+        for (final ResolveInfo info : infos) {
+            items.add(getItemForResolveInfo(info, packageManager, provider.getIntent()));
+        }
+
+        return items;
+    }
+
+    private PromptListItem getItemForResolveInfo(ResolveInfo info, PackageManager pm, Intent intent) {
+        PromptListItem item = new PromptListItem(info.loadLabel(pm).toString());
+        item.icon = info.loadIcon(pm);
+        item.intent = new Intent(intent);
+
+        // These intents should be implicit.
+        item.intent.setComponent(new ComponentName(info.activityInfo.applicationInfo.packageName,
+                                                   info.activityInfo.name));
+        return item;
+    }
+
+    private ArrayList<PromptListItem> getItemsForIntent(Context context, Intent intent) {
+        ArrayList<PromptListItem> items = new ArrayList<PromptListItem>();
+        PackageManager pm = context.getPackageManager();
+        List<ResolveInfo> lri = pm.queryIntentActivityOptions(GeckoAppShell.getGeckoInterface().getActivity().getComponentName(), null, intent, 0);
+
+        // If we didn't find any activities, just return the empty list
+        if (lri == null) {
+            return items;
+        }
+
+        // Otherwise, convert the ResolveInfo. Note we don't currently check for duplicates here.
+        for (ResolveInfo ri : lri) {
+            items.add(getItemForResolveInfo(ri, pm, intent));
+        }
+
+        return items;
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/prompts/IntentHandler.java
@@ -0,0 +1,12 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.prompts;
+
+import android.content.Intent;
+
+public interface IntentHandler {
+    public void onIntentSelected(Intent intent, int position);
+    public void onCancelled();
+}
--- a/mobile/android/base/prompts/PromptListItem.java
+++ b/mobile/android/base/prompts/PromptListItem.java
@@ -1,27 +1,30 @@
 package org.mozilla.gecko.prompts;
 
 import org.json.JSONArray;
 import org.json.JSONObject;
 import org.json.JSONException;
 
+import android.content.Intent;
 import android.graphics.drawable.Drawable;
+
 import java.util.List;
 import java.util.ArrayList;
 
 // This class should die and be replaced with normal menu items
 public class PromptListItem {
     private static final String LOGTAG = "GeckoPromptListItem";
     public final String label;
     public final boolean isGroup;
     public final boolean inGroup;
     public final boolean disabled;
     public final int id;
     public boolean selected;
+    public Intent intent;
 
     public boolean isParent;
     public Drawable icon;
 
     PromptListItem(JSONObject aObject) {
         label = aObject.optString("label");
         isGroup = aObject.optBoolean("isGroup");
         inGroup = aObject.optBoolean("inGroup");
--- a/mobile/android/base/widget/GeckoActionProvider.java
+++ b/mobile/android/base/widget/GeckoActionProvider.java
@@ -14,16 +14,18 @@ import android.content.pm.ResolveInfo;
 import android.graphics.drawable.Drawable;
 import android.view.ActionProvider;
 import android.view.MenuItem;
 import android.view.MenuItem.OnMenuItemClickListener;
 import android.view.SubMenu;
 import android.view.View;
 import android.view.View.OnClickListener;
 
+import java.util.ArrayList;
+
 public class GeckoActionProvider extends ActionProvider {
     private static int MAX_HISTORY_SIZE = 2;
 
     /**
      * A listener to know when a target was selected.
      * When setting a provider, the activity can listen to this,
      * to close the menu.
      */
@@ -120,16 +122,30 @@ public class GeckoActionProvider extends
             mOnTargetListener.onTargetSelected();
         }
     }
 
     public void setOnTargetSelectedListener(OnTargetSelectedListener listener) {
         mOnTargetListener = listener;
     }
 
+    public ArrayList<ResolveInfo> getSortedActivites() {
+        ArrayList<ResolveInfo> infos = new ArrayList<ResolveInfo>();
+
+        ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mHistoryFileName);
+        PackageManager packageManager = mContext.getPackageManager();
+
+        // Populate the sub-menu with a sub set of the activities.
+        final int count = dataModel.getActivityCount();
+        for (int i = 0; i < count; i++) {
+            infos.add(dataModel.getActivity(i));
+        }
+        return infos;
+    }
+
     /**
      * Listener for handling default activity / menu item clicks.
      */
     private class Callbacks implements OnMenuItemClickListener,
                                        OnClickListener {
         private void chooseActivity(int index) { 
             ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mHistoryFileName);
             Intent launchIntent = dataModel.chooseActivity(index);