diff --git a/mobile/android/base/moz.build b/mobile/android/base/moz.build index c887c52990f..eff7d0279fd 100644 --- a/mobile/android/base/moz.build +++ b/mobile/android/base/moz.build @@ -297,6 +297,8 @@ gbjar.sources += [ 'PrivateTab.java', 'prompts/ColorPickerInput.java', 'prompts/IconGridInput.java', + 'prompts/IntentChooserPrompt.java', + 'prompts/IntentHandler.java', 'prompts/Prompt.java', 'prompts/PromptInput.java', 'prompts/PromptListAdapter.java', diff --git a/mobile/android/base/prompts/IntentChooserPrompt.java b/mobile/android/base/prompts/IntentChooserPrompt.java new file mode 100644 index 00000000000..8ca4681be9a --- /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 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 getItems(final Context context, Intent[] intents) { + final ArrayList items = new ArrayList(); + + // 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 getItems(final Context context, final GeckoActionProvider provider) { + final ArrayList items = new ArrayList(); + + // Add any intents from the provider. + final PackageManager packageManager = context.getPackageManager(); + final ArrayList 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 getItemsForIntent(Context context, Intent intent) { + ArrayList items = new ArrayList(); + PackageManager pm = context.getPackageManager(); + List 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; + } +} diff --git a/mobile/android/base/prompts/IntentHandler.java b/mobile/android/base/prompts/IntentHandler.java new file mode 100644 index 00000000000..1509ab6264b --- /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(); +} diff --git a/mobile/android/base/prompts/PromptListItem.java b/mobile/android/base/prompts/PromptListItem.java index 3448dc76820..31d40599ce1 100644 --- a/mobile/android/base/prompts/PromptListItem.java +++ b/mobile/android/base/prompts/PromptListItem.java @@ -4,7 +4,9 @@ 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; @@ -17,6 +19,7 @@ public class PromptListItem { public final boolean disabled; public final int id; public boolean selected; + public Intent intent; public boolean isParent; public Drawable icon; diff --git a/mobile/android/base/widget/GeckoActionProvider.java b/mobile/android/base/widget/GeckoActionProvider.java index 7eb8ff95340..85245d96cf0 100644 --- a/mobile/android/base/widget/GeckoActionProvider.java +++ b/mobile/android/base/widget/GeckoActionProvider.java @@ -19,6 +19,8 @@ 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; @@ -125,6 +127,20 @@ public class GeckoActionProvider extends ActionProvider { mOnTargetListener = listener; } + public ArrayList getSortedActivites() { + ArrayList infos = new ArrayList(); + + 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. */