Bug 1351739 - Part 2 - Convert CustomTabsActivity to SafeIntents. r?sebastian draft
authorJan Henning <jh+bugzilla@buttercookie.de>
Sun, 02 Apr 2017 14:09:36 +0200
changeset 559300 a984289df68676d6c2c3694e24d02d1be530c68f
parent 559299 ad3144e298c7801dd4b04605798ff7a8b08d94bd
child 559301 da6ff52373008a130dfee7c2b921db0a3ab7f4e3
push id53051
push usermozilla@buttercookie.de
push dateSun, 09 Apr 2017 17:35:56 +0000
reviewerssebastian
bugs1351739
milestone55.0a1
Bug 1351739 - Part 2 - Convert CustomTabsActivity to SafeIntents. r?sebastian These are potentially untrusted external intents, so we should use SafeIntents for interacting with them. MozReview-Commit-ID: 3nmjg85wbr1
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/TestCustomTabsActivity.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
@@ -41,16 +41,17 @@ import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.SnackbarBuilder;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.menu.GeckoMenu;
 import org.mozilla.gecko.menu.GeckoMenuInflater;
+import org.mozilla.gecko.mozglue.SafeIntent;
 import org.mozilla.gecko.util.Clipboard;
 import org.mozilla.gecko.util.ColorUtil;
 import org.mozilla.gecko.util.IntentUtils;
 import org.mozilla.gecko.widget.GeckoPopupMenu;
 import org.mozilla.gecko.util.GeckoBundle;
 
 import java.util.List;
 
@@ -62,27 +63,28 @@ public class CustomTabsActivity extends 
     private GeckoPopupMenu popupMenu;
     private ActionBarPresenter actionBarPresenter;
     private ProgressBar mProgressView;
     // A state to indicate whether this activity is finishing with customize animation
     private boolean usingCustomAnimation = false;
 
     // Bug 1351605 - getIntent() not always returns the intent which started this activity.
     // Therefore we make a copy in case of this Activity is re-created.
-    private Intent startIntent;
+    private SafeIntent startIntent;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
         if (savedInstanceState != null) {
-            startIntent = savedInstanceState.getParcelable(SAVED_START_INTENT);
+            Intent restoredIntent = savedInstanceState.getParcelable(SAVED_START_INTENT);
+            startIntent = new SafeIntent(restoredIntent);
         } else {
             Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT, "customtab");
-            startIntent = getIntent();
+            startIntent = new SafeIntent(getIntent());
             final String host = getReferrerHost();
             recordCustomTabUsage(host);
         }
 
         setThemeFromToolbarColor();
 
         mProgressView = (ProgressBar) findViewById(R.id.page_progress);
         final Toolbar toolbar = (Toolbar) findViewById(R.id.actionbar);
@@ -190,17 +192,17 @@ public class CustomTabsActivity extends 
         }
 
         updateMenuItemForward();
     }
 
     @Override
     protected void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
-        outState.putParcelable(SAVED_START_INTENT, startIntent);
+        outState.putParcelable(SAVED_START_INTENT, startIntent.getUnsafe());
     }
 
     @Override
     public void onResume() {
         if (lastSelectedTabId >= 0) {
             final Tabs tabs = Tabs.getInstance();
             final Tab tab = tabs.getTab(lastSelectedTabId);
             if (tab == null) {
@@ -281,17 +283,17 @@ public class CustomTabsActivity extends 
      *
      * @param menu   The options menu in which to place items.
      * @param intent which to launch this activity
      * @param tintColor color to tint action-button
      * @return the MenuItem which be created and inserted into menu. Otherwise, null.
      */
     @VisibleForTesting
     MenuItem insertActionButton(final Menu menu,
-                                final Intent intent,
+                                final SafeIntent intent,
                                 @ColorInt final int tintColor) {
         if (!IntentUtil.hasActionButton(intent)) {
             return null;
         }
 
         MenuItem item = menu.add(Menu.NONE,
                 R.id.action_button,
                 Menu.NONE,
--- a/mobile/android/base/java/org/mozilla/gecko/customtabs/IntentUtil.java
+++ b/mobile/android/base/java/org/mozilla/gecko/customtabs/IntentUtil.java
@@ -1,25 +1,26 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * 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.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.ColorInt;
 import android.support.annotation.NonNull;
 import android.support.annotation.VisibleForTesting;
 import android.support.customtabs.CustomTabsIntent;
 
+import org.mozilla.gecko.mozglue.SafeIntent;
+
 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 {
@@ -39,214 +40,214 @@ class IntentUtil {
     private static final String KEY_ANIM_EXIT_RES_ID = PREFIX + "animExitRes";
 
     /**
      * To determine whether the intent has necessary information to build an Action-Button.
      *
      * @param intent which to launch a Custom-Tabs-Activity
      * @return true, if intent has all necessary information.
      */
-    static boolean hasActionButton(@NonNull Intent intent) {
+    static boolean hasActionButton(@NonNull SafeIntent intent) {
         return (getActionButtonBundle(intent) != null)
                 && (getActionButtonIcon(intent) != null)
                 && (getActionButtonDescription(intent) != null)
                 && (getActionButtonPendingIntent(intent) != null);
     }
 
     /**
      * To determine whether the intent requires to add share action to menu item.
      *
      * @param intent which to launch a Custom-Tabs-Activity
      * @return true, if intent requires to add share action to menu item.
      */
-    static boolean hasShareItem(@NonNull Intent intent) {
+    static boolean hasShareItem(@NonNull SafeIntent intent) {
         return intent.getBooleanExtra(CustomTabsIntent.EXTRA_DEFAULT_SHARE_MENU_ITEM, false);
     }
 
     /**
      * To extract bitmap icon from intent for Action-Button.
      *
      * @param intent which to launch a Custom-Tabs-Activity
      * @return bitmap icon, if any. Otherwise, null.
      */
-    static Bitmap getActionButtonIcon(@NonNull Intent intent) {
+    static Bitmap getActionButtonIcon(@NonNull SafeIntent intent) {
         final Bundle bundle = getActionButtonBundle(intent);
         return (bundle == null) ? null : (Bitmap) bundle.getParcelable(CustomTabsIntent.KEY_ICON);
     }
 
     /**
      * To extract color code from intent for top toolbar.
      * It also ensure the color is not translucent.
      *
      * @param intent which to launch a Custom-Tabs-Activity
      * @return color code in integer type.
      */
     @ColorInt
-    static int getToolbarColor(@NonNull Intent intent) {
+    static int getToolbarColor(@NonNull SafeIntent intent) {
         @ColorInt int toolbarColor = intent.getIntExtra(CustomTabsIntent.EXTRA_TOOLBAR_COLOR,
                 DEFAULT_ACTION_BAR_COLOR);
 
         // Translucent color does not make sense for toolbar color. Ensure it is 0xFF.
         toolbarColor = 0xFF000000 | toolbarColor;
         return toolbarColor;
     }
 
     /**
      * To extract description from intent for Action-Button. This description is used for
      * accessibility.
      *
      * @param intent which to launch a Custom-Tabs-Activity
      * @return description, if any. Otherwise, null.
      */
-    static String getActionButtonDescription(@NonNull Intent intent) {
+    static String getActionButtonDescription(@NonNull SafeIntent intent) {
         final Bundle bundle = getActionButtonBundle(intent);
         return (bundle == null) ? null : bundle.getString(CustomTabsIntent.KEY_DESCRIPTION);
     }
 
     /**
      * To extract pending-intent from intent for Action-Button.
      *
      * @param intent which to launch a Custom-Tabs-Activity
      * @return PendingIntent, if any. Otherwise, null.
      */
-    static PendingIntent getActionButtonPendingIntent(@NonNull Intent intent) {
+    static PendingIntent getActionButtonPendingIntent(@NonNull SafeIntent intent) {
         final Bundle bundle = getActionButtonBundle(intent);
         return (bundle == null)
                 ? null
                 : (PendingIntent) bundle.getParcelable(CustomTabsIntent.KEY_PENDING_INTENT);
     }
 
     /**
      * To know whether the Action-Button should be tinted.
      *
      * @param intent which to launch a Custom-Tabs-Activity
      * @return true, if Action-Button should be tinted. Default value is false.
      */
-    static boolean isActionButtonTinted(@NonNull Intent intent) {
+    static boolean isActionButtonTinted(@NonNull SafeIntent intent) {
         return intent.getBooleanExtra(CustomTabsIntent.EXTRA_TINT_ACTION_BUTTON, false);
     }
 
     /**
      * To extract extra Action-button bundle from an intent.
      *
      * @param intent which to launch a Custom-Tabs-Activity
      * @return bundle for Action-Button, if any. Otherwise, null.
      */
-    private static Bundle getActionButtonBundle(@NonNull Intent intent) {
+    private static Bundle getActionButtonBundle(@NonNull SafeIntent intent) {
         return intent.getBundleExtra(CustomTabsIntent.EXTRA_ACTION_BUTTON_BUNDLE);
     }
 
     /**
      * To get package name of 3rd-party-app from an intent.
      * If the app defined extra exit-animation to use, it should also provide its package name
      * to get correct animation resource.
      *
      * @param intent which to launch a Custom-Tabs-Activity
      * @return package name, if the intent defined extra exit-animation bundle. Otherwise, null.
      */
-    static String getAnimationPackageName(@NonNull Intent intent) {
+    static String getAnimationPackageName(@NonNull SafeIntent 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) {
+    static List<String> getMenuItemsTitle(@NonNull SafeIntent 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) {
+    static List<PendingIntent> getMenuItemsPendingIntent(@NonNull SafeIntent 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) {
+    static boolean hasExitAnimation(@NonNull SafeIntent intent) {
         final Bundle bundle = getAnimationBundle(intent);
         return (bundle != null)
                 && (getAnimationPackageName(intent) != null)
                 && (getEnterAnimationRes(intent) != NO_ANIMATION_RESOURCE)
                 && (getExitAnimationRes(intent) != NO_ANIMATION_RESOURCE);
     }
 
     /**
      * To get enter-animation resource id of 3rd-party-app from an intent.
      * If the app defined extra exit-animation to use, it should also provide its animation resource
      * id.
      *
      * @param intent which to launch a Custom-Tabs-Activity
      * @return animation resource id if any; otherwise, NO_ANIMATION_RESOURCE;
      */
-    static int getEnterAnimationRes(@NonNull Intent intent) {
+    static int getEnterAnimationRes(@NonNull SafeIntent intent) {
         final Bundle bundle = getAnimationBundle(intent);
         return (bundle == null)
                 ? NO_ANIMATION_RESOURCE
                 : bundle.getInt(KEY_ANIM_ENTER_RES_ID, NO_ANIMATION_RESOURCE);
     }
 
     /**
      * To get exit-animation resource id of 3rd-party-app from an intent.
      * If the app defined extra exit-animation to use, it should also provide its animation resource
      * id.
      *
      * @param intent which to launch a Custom-Tabs-Activity
      * @return animation resource id if any; otherwise, NO_ANIMATION_RESOURCE.
      */
-    static int getExitAnimationRes(@NonNull Intent intent) {
+    static int getExitAnimationRes(@NonNull SafeIntent intent) {
         final Bundle bundle = getAnimationBundle(intent);
         return (bundle == null)
                 ? NO_ANIMATION_RESOURCE
                 : bundle.getInt(KEY_ANIM_EXIT_RES_ID, NO_ANIMATION_RESOURCE);
     }
 
     /**
      * 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) {
+    private static Bundle getAnimationBundle(@NonNull SafeIntent 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(
+    private static List<Bundle> getMenuItemsBundle(@NonNull SafeIntent intent) {
+        ArrayList<Bundle> extra = intent.getUnsafe().getParcelableArrayListExtra(
                 CustomTabsIntent.EXTRA_MENU_ITEMS);
         return (extra == null) ? new ArrayList<Bundle>() : extra;
     }
 }
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/customtabs/TestCustomTabsActivity.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/customtabs/TestCustomTabsActivity.java
@@ -17,16 +17,17 @@ import android.view.MenuItem;
 import junit.framework.Assert;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.internal.util.reflection.Whitebox;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.background.testhelpers.TestRunner;
+import org.mozilla.gecko.mozglue.SafeIntent;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.fakes.RoboMenu;
 
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
@@ -58,47 +59,47 @@ public class TestCustomTabsActivity {
     }
 
     /**
      * Activity should not call overridePendingTransition if custom animation does not exist.
      */
     @Test
     public void testFinishWithoutCustomAnimation() {
         final CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
-        final Intent i = builder.build().intent;
+        final SafeIntent i = new SafeIntent(builder.build().intent);
 
         Whitebox.setInternalState(spyActivity, "startIntent", i);
 
         spyActivity.finish();
         verify(spyActivity, times(0)).overridePendingTransition(anyInt(), anyInt());
     }
 
     /**
      * Activity should call overridePendingTransition if custom animation exists.
      */
     @Test
     public void testFinish() {
         final CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
         builder.setExitAnimations(spyContext, enterRes, exitRes);
-        final Intent i = builder.build().intent;
+        final SafeIntent i = new SafeIntent(builder.build().intent);
 
         Whitebox.setInternalState(spyActivity, "startIntent", i);
 
         spyActivity.finish();
         verify(spyActivity, times(1)).overridePendingTransition(eq(enterRes), eq(exitRes));
     }
 
     /**
      * To get 3rd party app's package name, if custom animation exists.
      */
     @Test
     public void testGetPackageName() {
         final CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
         builder.setExitAnimations(spyContext, enterRes, exitRes);
-        final Intent i = builder.build().intent;
+        final SafeIntent i = new SafeIntent(builder.build().intent);
 
         Whitebox.setInternalState(spyActivity, "usingCustomAnimation", true);
         Whitebox.setInternalState(spyActivity, "startIntent", i);
 
         Assert.assertEquals(THIRD_PARTY_PACKAGE_NAME, spyActivity.getPackageName());
     }
 
     @Test
@@ -116,15 +117,15 @@ public class TestCustomTabsActivity {
                 R.drawable.ic_action_settings); // arbitrary icon resource
 
         // To create a CustomTabsIntent which is asking for ActionButton.
         final CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
         builder.setActionButton(bitmap, description, pendingIntent, true);
 
         // CustomTabsActivity should return a MenuItem with corresponding attributes.
         Menu menu = new RoboMenu(spyContext);
-        MenuItem item = spyActivity.insertActionButton(menu, builder.build().intent, 0xFF0000);
+        MenuItem item = spyActivity.insertActionButton(menu, new SafeIntent(builder.build().intent), 0xFF0000);
         Assert.assertNotNull(item);
         Assert.assertEquals(item.getTitle(), description);
         Assert.assertEquals(0, item.getOrder()); // should be the first one
         Assert.assertTrue(item.isVisible());
     }
 }
\ No newline at end of file
--- 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
@@ -15,16 +15,17 @@ import android.support.customtabs.Custom
 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.mozilla.gecko.mozglue.SafeIntent;
 import org.robolectric.RuntimeEnvironment;
 
 import java.util.List;
 import java.util.Objects;
 
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
 
@@ -49,56 +50,56 @@ public class TestIntentUtil {
 
         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);
 
-        Intent intent = builder.build().intent;
+        SafeIntent intent = new SafeIntent(builder.build().intent);
         Assert.assertTrue(IntentUtil.hasActionButton(intent));
         Assert.assertEquals(tinted, IntentUtil.isActionButtonTinted(intent));
         Assert.assertEquals(bitmap, IntentUtil.getActionButtonIcon(intent));
         Assert.assertEquals(description, IntentUtil.getActionButtonDescription(intent));
         Assert.assertTrue(
                 Objects.equals(pendingIntent, IntentUtil.getActionButtonPendingIntent(intent)));
     }
 
     @Test
     public void testIntentWithoutActionButton() {
         final CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
 
-        Intent intent = builder.build().intent;
+        SafeIntent intent = new SafeIntent(builder.build().intent);
         Assert.assertFalse(IntentUtil.hasActionButton(intent));
         Assert.assertFalse(IntentUtil.isActionButtonTinted(intent));
         Assert.assertNull(IntentUtil.getActionButtonIcon(intent));
         Assert.assertNull(IntentUtil.getActionButtonDescription(intent));
         Assert.assertNull(IntentUtil.getActionButtonPendingIntent(intent));
     }
 
     @Test
     public void testIntentWithCustomAnimation() {
         @AnimRes final int enterRes = 0x123; // arbitrary number as animation resource id
         @AnimRes final int exitRes = 0x456; // arbitrary number as animation resource id
 
         final CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
         builder.setExitAnimations(spyContext, enterRes, exitRes);
-        final Intent i = builder.build().intent;
+        final SafeIntent i = new SafeIntent(builder.build().intent);
 
         Assert.assertEquals(true, IntentUtil.hasExitAnimation(i));
         Assert.assertEquals(THIRD_PARTY_PACKAGE_NAME, IntentUtil.getAnimationPackageName(i));
         Assert.assertEquals(enterRes, IntentUtil.getEnterAnimationRes(i));
         Assert.assertEquals(exitRes, IntentUtil.getExitAnimationRes(i));
     }
 
     @Test
     public void testIntentWithoutCustomAnimation() {
         final CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
-        final Intent i = builder.build().intent;
+        final SafeIntent i = new SafeIntent(builder.build().intent);
 
         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));
     }
@@ -109,55 +110,55 @@ public class TestIntentUtil {
         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;
+        final SafeIntent intent = new SafeIntent(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)));
     }
 
     @Test
     public void testToolbarColor() {
         final CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
 
-        Assert.assertEquals(IntentUtil.getToolbarColor(builder.build().intent),
+        Assert.assertEquals(IntentUtil.getToolbarColor(new SafeIntent(builder.build().intent)),
                 IntentUtil.DEFAULT_ACTION_BAR_COLOR);
 
         // Test red color
         builder.setToolbarColor(0xFF0000);
-        Assert.assertEquals(IntentUtil.getToolbarColor(builder.build().intent), 0xFFFF0000);
+        Assert.assertEquals(IntentUtil.getToolbarColor(new SafeIntent(builder.build().intent)), 0xFFFF0000);
         builder.setToolbarColor(0xFFFF0000);
-        Assert.assertEquals(IntentUtil.getToolbarColor(builder.build().intent), 0xFFFF0000);
+        Assert.assertEquals(IntentUtil.getToolbarColor(new SafeIntent(builder.build().intent)), 0xFFFF0000);
 
         // Test translucent green color, it should force alpha value to be 0xFF
         builder.setToolbarColor(0x0000FF00);
-        Assert.assertEquals(IntentUtil.getToolbarColor(builder.build().intent), 0xFF00FF00);
+        Assert.assertEquals(IntentUtil.getToolbarColor(new SafeIntent(builder.build().intent)), 0xFF00FF00);
     }
 
     @Test
     public void testMenuShareItem() {
         final CustomTabsIntent.Builder builderNoShareItem = new CustomTabsIntent.Builder();
-        Assert.assertFalse(IntentUtil.hasShareItem(builderNoShareItem.build().intent));
+        Assert.assertFalse(IntentUtil.hasShareItem(new SafeIntent(builderNoShareItem.build().intent)));
 
         final CustomTabsIntent.Builder builderHasShareItem = new CustomTabsIntent.Builder();
         builderHasShareItem.addDefaultShareMenuItem();
-        Assert.assertTrue(IntentUtil.hasShareItem(builderHasShareItem.build().intent));
+        Assert.assertTrue(IntentUtil.hasShareItem(new SafeIntent(builderHasShareItem.build().intent)));
     }
 
     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,