Skip to content

Commit

Permalink
Fixes mozilla-mobile#1884 - Intent code to feature-customtabs
Browse files Browse the repository at this point in the history
  • Loading branch information
NotWoods committed Jul 23, 2019
1 parent 6bca40a commit d01cab7
Show file tree
Hide file tree
Showing 15 changed files with 519 additions and 122 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,60 @@ import android.app.PendingIntent
import android.graphics.Bitmap
import android.os.Bundle
import android.os.Parcelable
import androidx.browser.customtabs.CustomTabsIntent
import android.util.DisplayMetrics
import androidx.annotation.ColorInt

import androidx.browser.customtabs.CustomTabsIntent
import mozilla.components.support.utils.SafeBundle
import mozilla.components.support.utils.SafeIntent
import java.util.ArrayList
import java.util.Collections
import java.util.UUID

/**
* Holds configuration data for a Custom Tab. Use [createFromIntent] to
* create instances.
* Holds configuration data for a Custom Tab.
*
* @property toolbarColor Background color for the toolbar.
* @property closeButtonIcon Custom icon of the back button on the toolbar.
* @property enableUrlbarHiding Enables the toolbar to hide as the user scrolls down on the page.
* @property actionButtonConfig Custom action button on the toolbar.
* @property showShareMenuItem Specifies whether a default share button will be shown in the menu.
* @property menuItems Custom overflow menu items.
* @property exitAnimations Bundle containing custom exit animations for the tab.
* @property titleVisible Whether the title should be shown in the custom tab.
*/
class CustomTabConfig internal constructor(
data class CustomTabConfig(
val id: String,
@ColorInt val toolbarColor: Int?,
val closeButtonIcon: Bitmap?,
val disableUrlbarHiding: Boolean,
val enableUrlbarHiding: Boolean,
val actionButtonConfig: CustomTabActionButtonConfig?,
val showShareMenuItem: Boolean,
val menuItems: List<CustomTabMenuItem>,
val options: List<String>
val menuItems: List<CustomTabMenuItem> = emptyList(),
val exitAnimations: Bundle? = null,
val titleVisible: Boolean = false
) {
inline val disableUrlbarHiding
get() = !enableUrlbarHiding

val options: List<String> by lazy { generateOptions() }

@Suppress("ComplexMethod")
private fun generateOptions(): List<String> {
val options = mutableListOf<String>()

if (toolbarColor != null) options.add(TOOLBAR_COLOR_OPTION)
if (closeButtonIcon != null) options.add(CLOSE_BUTTON_OPTION)
if (enableUrlbarHiding) options.add(DISABLE_URLBAR_HIDING_OPTION)
if (actionButtonConfig != null) options.add(ACTION_BUTTON_OPTION)
if (showShareMenuItem) options.add(SHARE_MENU_ITEM_OPTION)
if (menuItems.isNotEmpty()) options.add(CUSTOMIZED_MENU_OPTION)
if (actionButtonConfig?.tint == true) options.add(ACTION_BUTTON_TINT_OPTION)

if (exitAnimations != null) options.add(EXIT_ANIMATION_OPTION)
if (titleVisible) options.add(PAGE_TITLE_OPTION)

return options
}

companion object {
internal const val TOOLBAR_COLOR_OPTION = "hasToolbarColor"
internal const val CLOSE_BUTTON_OPTION = "hasCloseButton"
Expand All @@ -57,9 +87,6 @@ class CustomTabConfig internal constructor(
private val EXTRA_DEFAULT_SHARE_MENU_ITEM = StringBuilder("support.customtabs.extra.SHARE_MENU_ITEM").toExtra()
private val EXTRA_MENU_ITEMS = StringBuilder("support.customtabs.extra.MENU_ITEMS").toExtra()
private val KEY_MENU_ITEM_TITLE = StringBuilder("support.customtabs.customaction.MENU_ITEM_TITLE").toExtra()
private val EXTRA_TINT_ACTION_BUTTON = StringBuilder("support.customtabs.extra.TINT_ACTION_BUTTON").toExtra()
private val EXTRA_REMOTEVIEWS = StringBuilder("support.customtabs.extra.EXTRA_REMOTEVIEWS").toExtra()
private val EXTRA_TOOLBAR_ITEMS = StringBuilder("support.customtabs.extra.TOOLBAR_ITEMS").toExtra()

/**
* TODO remove when fixed: https://github.com/mozilla-mobile/android-components/issues/1884
Expand All @@ -72,6 +99,8 @@ class CustomTabConfig internal constructor(
* @param intent the intent to check, wrapped as a SafeIntent.
* @return true if the intent is a custom tab intent, otherwise false.
*/
@Deprecated("Use isCustomTabIntent in feature-customtabs",
ReplaceWith("isCustomTabIntent(intent.unsafe)", "mozilla.components.feature.customtabs.isCustomTabIntent"))
fun isCustomTabIntent(intent: SafeIntent): Boolean {
return intent.hasExtra(EXTRA_SESSION)
}
Expand All @@ -84,14 +113,16 @@ class CustomTabConfig internal constructor(
* @param displayMetrics needed in-order to verify that icons of a max size are only provided.
* @return the CustomTabConfig instance.
*/
@Suppress("ComplexMethod")
@Deprecated("Use createCustomTabConfigFromIntent in feature-customtabs",
ReplaceWith(
"createCustomTabConfigFromIntent(intent.unsafe)",
"mozilla.components.feature.customtabs.createCustomTabConfigFromIntent"
))
@Suppress("LongMethod", "ComplexMethod")
fun createFromIntent(intent: SafeIntent, displayMetrics: DisplayMetrics? = null): CustomTabConfig {
val id = UUID.randomUUID().toString()

val options = mutableListOf<String>()

val toolbarColor = if (intent.hasExtra(EXTRA_TOOLBAR_COLOR)) {
options.add(TOOLBAR_COLOR_OPTION)
intent.getIntExtra(EXTRA_TOOLBAR_COLOR, -1)
} else {
null
Expand All @@ -104,57 +135,25 @@ class CustomTabConfig internal constructor(
icon.width / density <= MAX_CLOSE_BUTTON_SIZE_DP &&
icon.height / density <= MAX_CLOSE_BUTTON_SIZE_DP
) {
options.add(CLOSE_BUTTON_OPTION)
icon
} else {
null
}
}

val disableUrlbarHiding = !intent.getBooleanExtra(EXTRA_ENABLE_URLBAR_HIDING, true)
if (!disableUrlbarHiding) {
options.add(DISABLE_URLBAR_HIDING_OPTION)
}
val enableUrlbarHiding = intent.getBooleanExtra(EXTRA_ENABLE_URLBAR_HIDING, true)

val actionButtonConfig = getActionButtonConfig(intent)
if (actionButtonConfig != null) {
options.add(ACTION_BUTTON_OPTION)
}

val showShareMenuItem = intent.getBooleanExtra(EXTRA_DEFAULT_SHARE_MENU_ITEM, false)
if (showShareMenuItem) {
options.add(SHARE_MENU_ITEM_OPTION)
}

val menuItems = getMenuItems(intent)
if (menuItems.isNotEmpty()) {
options.add(CUSTOMIZED_MENU_OPTION)
}

if (intent.hasExtra(EXTRA_TINT_ACTION_BUTTON)) {
options.add(ACTION_BUTTON_TINT_OPTION)
}

if (intent.hasExtra(EXTRA_REMOTEVIEWS) || intent.hasExtra(EXTRA_TOOLBAR_ITEMS)) {
options.add(BOTTOM_TOOLBAR_OPTION)
}

if (intent.hasExtra(CustomTabsIntent.EXTRA_EXIT_ANIMATION_BUNDLE)) {
options.add(EXIT_ANIMATION_OPTION)
}

if (intent.hasExtra(CustomTabsIntent.EXTRA_TITLE_VISIBILITY_STATE)) {
val titleVisibility = intent.getIntExtra(CustomTabsIntent.EXTRA_TITLE_VISIBILITY_STATE, 0)
if (titleVisibility == CustomTabsIntent.SHOW_PAGE_TITLE) {
options.add(PAGE_TITLE_OPTION)
}
}

// We are currently ignoring EXTRA_SECONDARY_TOOLBAR_COLOR and EXTRA_ENABLE_INSTANT_APPS
// due to https://github.com/mozilla-mobile/focus-android/issues/629

return CustomTabConfig(id, toolbarColor, closeButtonIcon, disableUrlbarHiding, actionButtonConfig,
showShareMenuItem, menuItems, Collections.unmodifiableList(options))
return CustomTabConfig(id, toolbarColor, closeButtonIcon, enableUrlbarHiding, actionButtonConfig,
showShareMenuItem, menuItems)
}

private fun getActionButtonConfig(intent: SafeIntent): CustomTabActionButtonConfig? {
Expand Down Expand Up @@ -193,5 +192,15 @@ class CustomTabConfig internal constructor(
}
}

data class CustomTabActionButtonConfig(val description: String, val icon: Bitmap, val pendingIntent: PendingIntent)
data class CustomTabMenuItem(val name: String, val pendingIntent: PendingIntent)
data class CustomTabActionButtonConfig(
val description: String,
val icon: Bitmap,
val pendingIntent: PendingIntent,
val id: Int = CustomTabsIntent.TOOLBAR_ACTION_BUTTON_ID,
val tint: Boolean = false
)

data class CustomTabMenuItem(
val name: String,
val pendingIntent: PendingIntent
)
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,14 @@ class SessionTest {

assertNull(session.customTabConfig)

val customTabConfig = CustomTabConfig("id", null, null, true, null, true, listOf(), listOf())
val customTabConfig = CustomTabConfig(
"id",
toolbarColor = null,
closeButtonIcon = null,
enableUrlbarHiding = true,
actionButtonConfig = null,
showShareMenuItem = true
)
session.customTabConfig = customTabConfig

assertEquals(customTabConfig, session.customTabConfig)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock

@Suppress("Deprecation")
@RunWith(AndroidJUnit4::class)
class CustomTabConfigTest {

Expand Down Expand Up @@ -185,40 +186,4 @@ class CustomTabConfigTest {
assertNotNull(customTabConfig)
assertNull(customTabConfig.actionButtonConfig)
}

@Test
fun createFromIntentWithActionButtonTint() {
val customTabsIntent = CustomTabsIntent.Builder().build()
customTabsIntent.intent.putExtra(CustomTabsIntent.EXTRA_TINT_ACTION_BUTTON, true)

val customTabConfig = CustomTabConfig.createFromIntent(SafeIntent((customTabsIntent.intent)))
assertTrue(customTabConfig.options.contains(CustomTabConfig.ACTION_BUTTON_TINT_OPTION))
}

@Test
fun createFromIntentWithBottomToolbarOption() {
val customTabsIntent = CustomTabsIntent.Builder().build()
customTabsIntent.intent.putExtra(CustomTabsIntent.EXTRA_TOOLBAR_ITEMS, Bundle())

val customTabConfig = CustomTabConfig.createFromIntent(SafeIntent((customTabsIntent.intent)))
assertTrue(customTabConfig.options.contains(CustomTabConfig.BOTTOM_TOOLBAR_OPTION))
}

@Test
fun createFromIntentWithExitAnimationOption() {
val customTabsIntent = CustomTabsIntent.Builder().build()
customTabsIntent.intent.putExtra(CustomTabsIntent.EXTRA_EXIT_ANIMATION_BUNDLE, Bundle())

val customTabConfig = CustomTabConfig.createFromIntent(SafeIntent((customTabsIntent.intent)))
assertTrue(customTabConfig.options.contains(CustomTabConfig.EXIT_ANIMATION_OPTION))
}

@Test
fun createFromIntentWithPageTitleOption() {
val customTabsIntent = CustomTabsIntent.Builder().build()
customTabsIntent.intent.putExtra(CustomTabsIntent.EXTRA_TITLE_VISIBILITY_STATE, CustomTabsIntent.SHOW_PAGE_TITLE)

val customTabConfig = CustomTabConfig.createFromIntent(SafeIntent((customTabsIntent.intent)))
assertTrue(customTabConfig.options.contains(CustomTabConfig.PAGE_TITLE_OPTION))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/* 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 mozilla.components.feature.customtabs

import android.app.PendingIntent
import android.content.Intent
import android.content.res.Resources
import android.graphics.Bitmap
import android.os.Bundle
import android.os.Parcelable
import androidx.annotation.ColorInt
import androidx.browser.customtabs.CustomTabsIntent.EXTRA_ACTION_BUTTON_BUNDLE
import androidx.browser.customtabs.CustomTabsIntent.EXTRA_CLOSE_BUTTON_ICON
import androidx.browser.customtabs.CustomTabsIntent.EXTRA_DEFAULT_SHARE_MENU_ITEM
import androidx.browser.customtabs.CustomTabsIntent.EXTRA_ENABLE_URLBAR_HIDING
import androidx.browser.customtabs.CustomTabsIntent.EXTRA_EXIT_ANIMATION_BUNDLE
import androidx.browser.customtabs.CustomTabsIntent.EXTRA_MENU_ITEMS
import androidx.browser.customtabs.CustomTabsIntent.EXTRA_SESSION
import androidx.browser.customtabs.CustomTabsIntent.EXTRA_TINT_ACTION_BUTTON
import androidx.browser.customtabs.CustomTabsIntent.EXTRA_TITLE_VISIBILITY_STATE
import androidx.browser.customtabs.CustomTabsIntent.EXTRA_TOOLBAR_COLOR
import androidx.browser.customtabs.CustomTabsIntent.KEY_DESCRIPTION
import androidx.browser.customtabs.CustomTabsIntent.KEY_ICON
import androidx.browser.customtabs.CustomTabsIntent.KEY_ID
import androidx.browser.customtabs.CustomTabsIntent.KEY_MENU_ITEM_TITLE
import androidx.browser.customtabs.CustomTabsIntent.KEY_PENDING_INTENT
import androidx.browser.customtabs.CustomTabsIntent.NO_TITLE
import androidx.browser.customtabs.CustomTabsIntent.SHOW_PAGE_TITLE
import androidx.browser.customtabs.CustomTabsIntent.TOOLBAR_ACTION_BUTTON_ID
import mozilla.components.browser.session.tab.CustomTabActionButtonConfig
import mozilla.components.browser.session.tab.CustomTabConfig
import mozilla.components.browser.session.tab.CustomTabMenuItem
import mozilla.components.support.utils.SafeIntent
import mozilla.components.support.utils.toSafeBundle
import mozilla.components.support.utils.toSafeIntent
import java.util.UUID
import kotlin.math.max

/**
* Checks if the provided intent is a custom tab intent.
*
* @param intent the intent to check.
* @return true if the intent is a custom tab intent, otherwise false.
*/
fun isCustomTabIntent(intent: Intent) = isCustomTabIntent(intent.toSafeIntent())

/**
* Checks if the provided intent is a custom tab intent.
*
* @param intent the intent to check, wrapped as a SafeIntent.
* @return true if the intent is a custom tab intent, otherwise false.
*/
fun isCustomTabIntent(safeIntent: SafeIntent) = safeIntent.hasExtra(EXTRA_SESSION)

/**
* Creates a [CustomTabConfig] instance based on the provided intent.
*
* @param intent the intent, wrapped as a SafeIntent, which is processed to extract configuration data.
* @param resources needed in-order to verify that icons of a max size are only provided.
* @return the CustomTabConfig instance.
*/
fun createCustomTabConfigFromIntent(
intent: Intent,
resources: Resources
): CustomTabConfig {
val safeIntent = intent.toSafeIntent()

return CustomTabConfig(
id = UUID.randomUUID().toString(),
toolbarColor = safeIntent.getColorExtra(EXTRA_TOOLBAR_COLOR),
closeButtonIcon = getCloseButtonIcon(safeIntent, resources),
enableUrlbarHiding = safeIntent.getBooleanExtra(EXTRA_ENABLE_URLBAR_HIDING, false),
actionButtonConfig = getActionButtonConfig(safeIntent),
showShareMenuItem = safeIntent.getBooleanExtra(EXTRA_DEFAULT_SHARE_MENU_ITEM, false),
menuItems = getMenuItems(safeIntent),
exitAnimations = safeIntent.getBundleExtra(EXTRA_EXIT_ANIMATION_BUNDLE)?.unsafe,
titleVisible = safeIntent.getIntExtra(EXTRA_TITLE_VISIBILITY_STATE, NO_TITLE) == SHOW_PAGE_TITLE
)
}

@ColorInt
private fun SafeIntent.getColorExtra(name: String): Int? =
if (hasExtra(name)) getIntExtra(name, 0) else null

private fun getCloseButtonIcon(intent: SafeIntent, resources: Resources): Bitmap? {
val icon = intent.getParcelableExtra(EXTRA_CLOSE_BUTTON_ICON) as? Bitmap
val maxSize = resources.getDimension(R.dimen.mozac_feature_customtabs_max_close_button_size)

return if (icon != null && max(icon.width, icon.height) <= maxSize) {
icon
} else {
null
}
}

private fun getActionButtonConfig(intent: SafeIntent): CustomTabActionButtonConfig? {
val actionButtonBundle = intent.getBundleExtra(EXTRA_ACTION_BUTTON_BUNDLE) ?: return null
val description = actionButtonBundle.getString(KEY_DESCRIPTION)
val icon = actionButtonBundle.getParcelable(KEY_ICON) as? Bitmap
val pendingIntent = actionButtonBundle.getParcelable(KEY_PENDING_INTENT) as? PendingIntent
val id = actionButtonBundle.getInt(KEY_ID, TOOLBAR_ACTION_BUTTON_ID)
val tint = intent.getBooleanExtra(EXTRA_TINT_ACTION_BUTTON, false)

return if (description != null && icon != null && pendingIntent != null) {
CustomTabActionButtonConfig(
id = id,
description = description,
icon = icon,
pendingIntent = pendingIntent,
tint = tint
)
} else {
null
}
}

private fun getMenuItems(intent: SafeIntent): List<CustomTabMenuItem> =
intent.getParcelableArrayListExtra<Parcelable>(EXTRA_MENU_ITEMS).orEmpty()
.mapNotNull { menuItemBundle ->
val bundle = (menuItemBundle as? Bundle)?.toSafeBundle()
val name = bundle?.getString(KEY_MENU_ITEM_TITLE)
val pendingIntent = bundle?.getParcelable(KEY_PENDING_INTENT) as? PendingIntent

if (name != null && pendingIntent != null) {
CustomTabMenuItem(
name = name,
pendingIntent = pendingIntent
)
} else {
null
}
}
Loading

0 comments on commit d01cab7

Please sign in to comment.