Skip to content
This repository has been archived by the owner on Nov 1, 2022. It is now read-only.

Commit

Permalink
For #12060 - Add support for select address prompt request
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexandru2909 committed May 4, 2022
1 parent c277fe7 commit 68b7cae
Show file tree
Hide file tree
Showing 8 changed files with 260 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/* 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.browser.engine.gecko.ext

import mozilla.components.concept.storage.Address
import org.mozilla.geckoview.Autocomplete

/**
* Converts a GeckoView [Autocomplete.Address] to an Android Components [Address].
*/
fun Autocomplete.Address.toAddress() = Address(
guid = guid,
givenName = givenName,
additionalName = additionalName,
familyName = familyName,
organization = organization,
streetAddress = streetAddress,
addressLevel3 = addressLevel3,
addressLevel2 = addressLevel2,
addressLevel1 = addressLevel1,
postalCode = postalCode,
country = country,
tel = tel,
email = email
)

/**
* Converts an Android Components [Address] to a GeckoView [Autocomplete.Address].
*/
fun Address.toAutocompleteAddress() = Autocomplete.Address.Builder()
.guid(guid)
.givenName(givenName)
.additionalName(additionalName)
.familyName(familyName)
.organization(organization)
.streetAddress(streetAddress)
.addressLevel3(addressLevel3)
.addressLevel2(addressLevel2)
.addressLevel1(addressLevel1)
.postalCode(postalCode)
.country(country)
.tel(tel)
.email(email)
.build()
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import android.content.Context
import android.net.Uri
import androidx.annotation.VisibleForTesting
import mozilla.components.browser.engine.gecko.GeckoEngineSession
import mozilla.components.browser.engine.gecko.ext.toAddress
import mozilla.components.browser.engine.gecko.ext.toAutocompleteAddress
import mozilla.components.browser.engine.gecko.ext.toAutocompleteCreditCard
import mozilla.components.browser.engine.gecko.ext.toCreditCardEntry
import mozilla.components.browser.engine.gecko.ext.toLoginEntry
Expand All @@ -17,6 +19,7 @@ import mozilla.components.concept.engine.prompt.PromptRequest.MenuChoice
import mozilla.components.concept.engine.prompt.PromptRequest.MultipleChoice
import mozilla.components.concept.engine.prompt.PromptRequest.SingleChoice
import mozilla.components.concept.engine.prompt.ShareData
import mozilla.components.concept.storage.Address
import mozilla.components.concept.storage.CreditCardEntry
import mozilla.components.concept.storage.Login
import mozilla.components.concept.storage.LoginEntry
Expand Down Expand Up @@ -224,6 +227,39 @@ internal class GeckoPromptDelegate(private val geckoEngineSession: GeckoEngineSe
return geckoResult
}

override fun onAddressSelect(
session: GeckoSession,
request: AutocompleteRequest<Autocomplete.AddressSelectOption>
): GeckoResult<PromptResponse>? {
val geckoResult = GeckoResult<PromptResponse>()

val onConfirm: (Address) -> Unit = { address ->
if (!request.isComplete) {
geckoResult.complete(
request.confirm(
Autocomplete.AddressSelectOption(address.toAutocompleteAddress())
)
)
}
}

val onDismiss: () -> Unit = {
request.dismissSafely(geckoResult)
}

geckoEngineSession.notifyObservers {
onPromptRequest(
PromptRequest.SelectAddress(
addressList = request.options.map { it.value.toAddress() },
onConfirm = onConfirm,
onDismiss = onDismiss
)
)
}

return geckoResult
}

override fun onAlertPrompt(
session: GeckoSession,
prompt: PromptDelegate.AlertPrompt
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import android.net.Uri
import android.os.Looper.getMainLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import mozilla.components.browser.engine.gecko.GeckoEngineSession
import mozilla.components.browser.engine.gecko.ext.toAutocompleteAddress
import mozilla.components.browser.engine.gecko.ext.toAutocompleteCreditCard
import mozilla.components.browser.engine.gecko.ext.toLoginEntry
import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.prompt.Choice
import mozilla.components.concept.engine.prompt.PromptRequest
import mozilla.components.concept.engine.prompt.PromptRequest.MultipleChoice
import mozilla.components.concept.engine.prompt.PromptRequest.SingleChoice
import mozilla.components.concept.storage.Address
import mozilla.components.concept.storage.CreditCardEntry
import mozilla.components.concept.storage.Login
import mozilla.components.concept.storage.LoginEntry
Expand Down Expand Up @@ -1436,6 +1438,96 @@ class GeckoPromptDelegateTest {
verify(geckoResult, never()).complete(any())
}

@Test
fun `WHEN onAddressSelect is called THEN SelectAddress prompt request must be provided with the correct callbacks`() {
val mockSession = GeckoEngineSession(runtime)

var isOnConfirmCalled = false
var isOnDismissCalled = false

var selectAddressPrompt: PromptRequest.SelectAddress = mock()

val promptDelegate = spy(GeckoPromptDelegate(mockSession))

// Capture the SelectAddress prompt request
mockSession.register(object : EngineSession.Observer {
override fun onPromptRequest(promptRequest: PromptRequest) {
selectAddressPrompt = promptRequest as PromptRequest.SelectAddress
}
})

val address = Address(
guid = "1",
givenName = "Firefox",
additionalName = "-",
familyName = "-",
organization = "-",
streetAddress = "street",
addressLevel3 = "address3",
addressLevel2 = "address2",
addressLevel1 = "address1",
postalCode = "1",
country = "Country",
tel = "1",
email = "@"
)
val addressSelectOption =
Autocomplete.AddressSelectOption(address.toAutocompleteAddress())

var geckoPrompt =
geckoSelectAddressPrompt(arrayOf(addressSelectOption))

var geckoResult = promptDelegate.onAddressSelect(
mock(),
geckoPrompt
)

// Verify that the onDismiss callback was called
geckoResult!!.accept {
isOnDismissCalled = true
}

selectAddressPrompt.onDismiss()
shadowOf(getMainLooper()).idle()
assertTrue(isOnDismissCalled)

// Verify that the onConfirm callback was called
geckoPrompt =
geckoSelectAddressPrompt(arrayOf(addressSelectOption))

geckoResult = promptDelegate.onAddressSelect(
mock(),
geckoPrompt
)

geckoResult!!.accept {
isOnConfirmCalled = true
}

selectAddressPrompt.onConfirm(selectAddressPrompt.addressList.first())
shadowOf(getMainLooper()).idle()
assertTrue(isOnConfirmCalled)

// Verify that when the request is already completed, the onConfirm callback is called
// but the request is not confirmed
isOnConfirmCalled = false
geckoPrompt =
geckoSelectAddressPrompt(arrayOf(addressSelectOption), true)

geckoResult = promptDelegate.onAddressSelect(
mock(),
geckoPrompt
)

geckoResult!!.accept {
isOnConfirmCalled = true
}

selectAddressPrompt.onConfirm(selectAddressPrompt.addressList.first())
shadowOf(getMainLooper()).idle()
assertFalse(isOnConfirmCalled)
}

private fun geckoChoicePrompt(
title: String,
message: String,
Expand Down Expand Up @@ -1595,4 +1687,15 @@ class GeckoPromptDelegateTest {
ReflectionUtils.setField(prompt, "options", creditCards)
return prompt
}

private fun geckoSelectAddressPrompt(
addresses: Array<Autocomplete.AddressSelectOption>,
isComplete: Boolean = false
): GeckoSession.PromptDelegate.AutocompleteRequest<Autocomplete.AddressSelectOption> {
val prompt: GeckoSession.PromptDelegate.AutocompleteRequest<Autocomplete.AddressSelectOption> =
mock()
whenever(prompt.isComplete).thenReturn(isComplete)
ReflectionUtils.setField(prompt, "options", addresses)
return prompt
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import android.net.Uri
import mozilla.components.concept.engine.prompt.PromptRequest.Authentication.Level
import mozilla.components.concept.engine.prompt.PromptRequest.Authentication.Method
import mozilla.components.concept.engine.prompt.PromptRequest.TimeSelection.Type
import mozilla.components.concept.storage.Address
import mozilla.components.concept.storage.CreditCardEntry
import mozilla.components.concept.storage.Login
import mozilla.components.concept.storage.LoginEntry
Expand Down Expand Up @@ -346,6 +347,21 @@ sealed class PromptRequest(
override val onDismiss: () -> Unit
) : PromptRequest(), Dismissible

/**
* Value type that represents a request for a select address prompt.
*
* This prompt is triggered by the user focusing on an address field.
*
* @property addressList List of addresses for the user to choose from.
* @property onConfirm Callback used to confirm the selected address.
* @property onDismiss Callback used to dismiss the address prompt.
*/
data class SelectAddress(
val addressList: List<Address>,
val onConfirm: (Address) -> Unit,
override val onDismiss: () -> Unit
) : PromptRequest(), Dismissible

interface Dismissible {
val onDismiss: () -> Unit
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import mozilla.components.concept.engine.prompt.PromptRequest.SingleChoice
import mozilla.components.concept.engine.prompt.PromptRequest.TextPrompt
import mozilla.components.concept.engine.prompt.PromptRequest.TimeSelection
import mozilla.components.concept.engine.prompt.PromptRequest.TimeSelection.Type
import mozilla.components.concept.storage.Address
import mozilla.components.concept.storage.CreditCardEntry
import mozilla.components.concept.storage.Login
import mozilla.components.concept.storage.LoginEntry
Expand Down Expand Up @@ -312,4 +313,52 @@ class PromptRequestTest {
assertFalse(onConfirmCalled)
assertNull(confirmedCreditCard)
}

@Test
fun `WHEN calling confirm or dismiss on SelectAddress THEN the respective callback is invoked`() {
val address = Address(
guid = "1",
givenName = "Firefox",
additionalName = "-",
familyName = "-",
organization = "-",
streetAddress = "street",
addressLevel3 = "address3",
addressLevel2 = "address2",
addressLevel1 = "address1",
postalCode = "1",
country = "Country",
tel = "1",
email = "@"
)
var onDismissCalled = false
var onConfirmCalled = false
var confirmedAddress: Address? = null

val selectAddresPromptRequest = PromptRequest.SelectAddress(
addressList = listOf(address),
onDismiss = {
onDismissCalled = true
},
onConfirm = {
confirmedAddress = it
onConfirmCalled = true
}
)

assertEquals(selectAddresPromptRequest.addressList, listOf(address))

selectAddresPromptRequest.onConfirm(address)

assertTrue(onConfirmCalled)
assertFalse(onDismissCalled)
assertEquals(address, confirmedAddress)

onConfirmCalled = false

selectAddresPromptRequest.onDismiss()

assertTrue(onDismissCalled)
assertFalse(onConfirmCalled)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ data class UpdatableCreditCardFields(
@SuppressLint("ParcelCreator")
@Parcelize
data class Address(
val guid: String,
val guid: String? = null,
val givenName: String,
val additionalName: String,
val familyName: String,
Expand All @@ -328,10 +328,10 @@ data class Address(
val country: String,
val tel: String,
val email: String,
val timeCreated: Long,
val timeLastUsed: Long?,
val timeLastModified: Long,
val timesUsed: Long
val timeCreated: Long = 0L,
val timeLastUsed: Long? = 0L,
val timeLastModified: Long = 0L,
val timesUsed: Long = 0L
) : Parcelable

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import mozilla.components.concept.engine.prompt.PromptRequest.MultipleChoice
import mozilla.components.concept.engine.prompt.PromptRequest.Popup
import mozilla.components.concept.engine.prompt.PromptRequest.Repost
import mozilla.components.concept.engine.prompt.PromptRequest.SaveLoginPrompt
import mozilla.components.concept.engine.prompt.PromptRequest.SelectAddress
import mozilla.components.concept.engine.prompt.PromptRequest.SelectCreditCard
import mozilla.components.concept.engine.prompt.PromptRequest.SelectLoginPrompt
import mozilla.components.concept.engine.prompt.PromptRequest.Share
Expand Down Expand Up @@ -791,6 +792,7 @@ class PromptFeature private constructor(
is SelectLoginPrompt,
is SelectCreditCard,
is Share -> true
is SelectAddress -> false
is Alert, is TextPrompt, is Confirm, is Repost, is Popup -> promptAbuserDetector.shouldShowMoreDialogs
}
}
Expand Down
3 changes: 3 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ permalink: /changelog/
* [Gecko](https://github.com/mozilla-mobile/android-components/blob/main/buildSrc/src/main/java/Gecko.kt)
* [Configuration](https://github.com/mozilla-mobile/android-components/blob/main/.config.yml)

* **concept-engine**:
* Added support for `SelectAddress` prompt request.

* **feature-contextmenu**
* 🌟 Add new `additionalValidation` parameter to context menu options builders allowing clients to know when these options to be shown and potentially block showing them.

Expand Down

1 comment on commit 68b7cae

@firefoxci-taskcluster
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uh oh! Looks like an error! Details

Failed to fetch task artifact public/github/customCheckRunText.md for GitHub integration.
Make sure the artifact exists on the worker or other location.

Please sign in to comment.