Bug 1315937 - Add custom items to menu of CustomTabsActivity draft
authorJulian_Chu <walkingice0204@gmail.com>
Thu, 02 Mar 2017 16:44:56 +0800
changeset 497290 f309c59a70ae4f2498eb24c5e2b9402d6ef18d85
parent 497289 5071e2e45489726fd51b8efb3b850df1776635e5
child 497291 dcb1a685d25f002573ccfd322b04ef52e991171d
push id48857
push userbmo:walkingice0204@gmail.com
push dateMon, 13 Mar 2017 04:50:52 +0000
bugs1315937
milestone54.0a1
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
mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
mobile/android/base/java/org/mozilla/gecko/customtabs/IntentUtil.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/customtabs/TestIntentUtil.java
--- 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);
+    }
 }