Bug 1315937 - Add custom items to menu of CustomTabsActivity
If 3rd-party app provides any custom menu items, add them to PopupMenu
and handle corresponding pending intent.
MozReview-Commit-ID: 5STg49hsCWF
--- a/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
@@ -13,16 +13,17 @@ import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.StyleRes;
import android.support.annotation.VisibleForTesting;
+import android.support.v4.util.SparseArrayCompat;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
@@ -39,33 +40,32 @@ import org.mozilla.gecko.R;
import org.mozilla.gecko.Tab;
import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.menu.GeckoMenu;
import org.mozilla.gecko.menu.GeckoMenuInflater;
import org.mozilla.gecko.util.ColorUtil;
import org.mozilla.gecko.widget.GeckoPopupMenu;
import java.lang.reflect.Field;
+import java.util.List;
import static android.support.customtabs.CustomTabsIntent.EXTRA_TOOLBAR_COLOR;
public class CustomTabsActivity extends GeckoApp implements Tabs.OnTabsChangedListener {
private static final String LOGTAG = "CustomTabsActivity";
private static final String SAVED_TOOLBAR_COLOR = "SavedToolbarColor";
private static final String SAVED_TOOLBAR_TITLE = "SavedToolbarTitle";
private static final int NO_COLOR = -1;
-
+ private final SparseArrayCompat<PendingIntent> menuItemsIntent = new SparseArrayCompat<>();
private ActionBar actionBar;
private GeckoPopupMenu popupMenu;
private int tabId = -1;
private boolean useDomainTitle = true;
-
private int toolbarColor;
private String toolbarTitle;
-
// A state to indicate whether this activity is finishing with customize animation
private boolean usingCustomAnimation = false;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
@@ -263,16 +263,23 @@ public class CustomTabsActivity extends
return true;
case R.id.custom_tabs_menu_reload:
onReloadClicked();
return true;
case R.id.custom_tabs_menu_open_in:
onOpenInClicked();
return true;
}
+
+ final PendingIntent intent = menuItemsIntent.get(item.getItemId());
+ if (intent != null) {
+ performPendingIntent(intent);
+ return true;
+ }
+
return super.onOptionsItemSelected(item);
}
/**
* To insert a MenuItem (as an ActionButton) into Menu.
*
* @param menu The options menu in which to place items.
* @param intent which to launch this activity
@@ -344,16 +351,28 @@ public class CustomTabsActivity extends
popupMenu.setOnMenuItemClickListener(new GeckoPopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
return CustomTabsActivity.this.onMenuItemClick(item);
}
});
// to add Fennec default menu
+ final Intent intent = getIntent();
+
+ // to add custom menu items
+ final List<String> titles = IntentUtil.getMenuItemsTitle(intent);
+ final List<PendingIntent> intents = IntentUtil.getMenuItemsPendingIntent(intent);
+ menuItemsIntent.clear();
+ for (int i = 0; i < titles.size(); i++) {
+ final int menuId = Menu.FIRST + i;
+ geckoMenu.add(Menu.NONE, menuId, Menu.NONE, titles.get(i));
+ menuItemsIntent.put(menuId, intents.get(i));
+ }
+
final MenuInflater inflater = new GeckoMenuInflater(this);
inflater.inflate(R.menu.customtabs_menu, geckoMenu);
// insert default browser name to title of menu-item-Open-In
final MenuItem openItem = geckoMenu.findItem(R.id.custom_tabs_menu_open_in);
if (openItem != null) {
final Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://"));
final ResolveInfo info = getPackageManager()
--- a/mobile/android/base/java/org/mozilla/gecko/customtabs/IntentUtil.java
+++ b/mobile/android/base/java/org/mozilla/gecko/customtabs/IntentUtil.java
@@ -8,16 +8,19 @@ package org.mozilla.gecko.customtabs;
import android.app.PendingIntent;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.customtabs.CustomTabsIntent;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* A utility class for CustomTabsActivity to extract information from intent.
* For example, this class helps to extract exit-animation resource id.
*/
class IntentUtil {
public static final int NO_ANIMATION_RESOURCE = -1;
@@ -107,16 +110,51 @@ class IntentUtil {
* @return package name, if the intent defined extra exit-animation bundle. Otherwise, null.
*/
static String getAnimationPackageName(@NonNull Intent intent) {
final Bundle bundle = getAnimationBundle(intent);
return (bundle == null) ? null : bundle.getString(KEY_PACKAGE_NAME);
}
/**
+ * To get titles for Menu Items from an intent.
+ * 3rd-party-app is able to add and customize up to five menu items. This method helps to
+ * get titles for each menu items.
+ *
+ * @param intent which to launch a Custom-Tabs-Activity
+ * @return A list of string as title for each menu items
+ */
+ static List<String> getMenuItemsTitle(@NonNull Intent intent) {
+ final List<Bundle> bundles = getMenuItemsBundle(intent);
+ final List<String> titles = new ArrayList<>();
+ for (Bundle b : bundles) {
+ titles.add(b.getString(CustomTabsIntent.KEY_MENU_ITEM_TITLE));
+ }
+ return titles;
+ }
+
+ /**
+ * To get pending-intent for Menu Items from an intent.
+ * 3rd-party-app is able to add and customize up to five menu items. This method helps to
+ * get pending-intent for each menu items.
+ *
+ * @param intent which to launch a Custom-Tabs-Activity
+ * @return A list of pending-intent for each menu items
+ */
+ static List<PendingIntent> getMenuItemsPendingIntent(@NonNull Intent intent) {
+ final List<Bundle> bundles = getMenuItemsBundle(intent);
+ final List<PendingIntent> intents = new ArrayList<>();
+ for (Bundle b : bundles) {
+ PendingIntent p = b.getParcelable(CustomTabsIntent.KEY_PENDING_INTENT);
+ intents.add(p);
+ }
+ return intents;
+ }
+
+ /**
* To check whether the intent has necessary information to apply customize exit-animation.
*
* @param intent which to launch a Custom-Tabs-Activity
* @return true, if the intent has necessary information.
*/
static boolean hasExitAnimation(@NonNull Intent intent) {
final Bundle bundle = getAnimationBundle(intent);
return (bundle != null)
@@ -159,9 +197,23 @@ class IntentUtil {
* To extract extra exit-animation bundle from an intent.
*
* @param intent which to launch a Custom-Tabs-Activity
* @return bundle for extra exit-animation, if any. Otherwise, null.
*/
private static Bundle getAnimationBundle(@NonNull Intent intent) {
return intent.getBundleExtra(CustomTabsIntent.EXTRA_EXIT_ANIMATION_BUNDLE);
}
+
+ /**
+ * To extract bundles for Menu Items from an intent.
+ * 3rd-party-app is able to add and customize up to five menu items. This method helps to
+ * extract bundles to build menu items.
+ *
+ * @param intent which to launch a Custom-Tabs-Activity
+ * @return bundle for menu items, if any. Otherwise, an empty list.
+ */
+ private static List<Bundle> getMenuItemsBundle(@NonNull Intent intent) {
+ ArrayList<Bundle> extra = intent.getParcelableArrayListExtra(
+ CustomTabsIntent.EXTRA_MENU_ITEMS);
+ return (extra == null) ? new ArrayList<Bundle>() : extra;
+ }
}
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/customtabs/TestIntentUtil.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/customtabs/TestIntentUtil.java
@@ -3,27 +3,31 @@
package org.mozilla.gecko.customtabs;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.net.Uri;
import android.support.annotation.AnimRes;
+import android.support.annotation.Nullable;
import android.support.customtabs.CustomTabsIntent;
+import android.text.TextUtils;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.R;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.robolectric.RuntimeEnvironment;
+import java.util.List;
import java.util.Objects;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
@RunWith(TestRunner.class)
public class TestIntentUtil {
@@ -36,22 +40,17 @@ public class TestIntentUtil {
doReturn(THIRD_PARTY_PACKAGE_NAME).when(spyContext).getPackageName();
}
@Test
public void testIntentWithActionButton() {
// create properties for CustomTabsIntent
final String description = "Description";
final boolean tinted = true;
- final Intent actionIntent = new Intent(Intent.ACTION_VIEW);
- final int reqCode = 0x123;
- final PendingIntent pendingIntent = PendingIntent.getActivities(spyContext,
- reqCode,
- new Intent[]{actionIntent},
- PendingIntent.FLAG_CANCEL_CURRENT);
+ final PendingIntent pendingIntent = createPendingIntent(0x123, "https://mozilla.org");
final Bitmap bitmap = BitmapFactory.decodeResource(
spyContext.getResources(),
R.drawable.ic_action_settings); // arbitrary icon resource
final CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
builder.setActionButton(bitmap, description, pendingIntent, tinted);
@@ -98,9 +97,42 @@ public class TestIntentUtil {
Assert.assertEquals(false, IntentUtil.hasExitAnimation(i));
Assert.assertEquals(null, IntentUtil.getAnimationPackageName(i));
Assert.assertEquals(IntentUtil.NO_ANIMATION_RESOURCE,
IntentUtil.getEnterAnimationRes(i));
Assert.assertEquals(IntentUtil.NO_ANIMATION_RESOURCE,
IntentUtil.getExitAnimationRes(i));
}
+
+ @Test
+ public void testMenuItems() {
+ final CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
+ builder.addDefaultShareMenuItem(); // This should not effect menu-item method
+ PendingIntent intent0 = createPendingIntent(0x100, "http://mozilla.com/0");
+ PendingIntent intent1 = createPendingIntent(0x100, "http://mozilla.com/1");
+ PendingIntent intent2 = createPendingIntent(0x100, "http://mozilla.com/2");
+ builder.addMenuItem("Label 0", intent0);
+ builder.addMenuItem("Label 1", intent1);
+ builder.addMenuItem("Label 2", intent2);
+
+ final Intent intent = builder.build().intent;
+ List<String> titles = IntentUtil.getMenuItemsTitle(intent);
+ List<PendingIntent> intents = IntentUtil.getMenuItemsPendingIntent(intent);
+ Assert.assertEquals(3, titles.size());
+ Assert.assertEquals(3, intents.size());
+ Assert.assertEquals("Label 0", titles.get(0));
+ Assert.assertEquals("Label 1", titles.get(1));
+ Assert.assertEquals("Label 2", titles.get(2));
+ Assert.assertTrue(Objects.equals(intent0, intents.get(0)));
+ Assert.assertTrue(Objects.equals(intent1, intents.get(1)));
+ Assert.assertTrue(Objects.equals(intent2, intents.get(2)));
+ }
+
+ private PendingIntent createPendingIntent(int reqCode, @Nullable String uri) {
+ final Intent actionIntent = new Intent(Intent.ACTION_VIEW);
+ if (!TextUtils.isEmpty(uri)) {
+ actionIntent.setData(Uri.parse(uri));
+ }
+ return PendingIntent.getActivity(spyContext, reqCode, actionIntent,
+ PendingIntent.FLAG_CANCEL_CURRENT);
+ }
}