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

For #12060 - Add support for select address prompt request #12101

Merged
merged 2 commits into from
May 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(
addresses = 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.addresses.first())
shadowOf(getMainLooper()).idle()
assertTrue(isOnConfirmCalled)

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

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

geckoResult.accept {
isOnConfirmCalled = true
}

selectAddressPrompt.onConfirm(selectAddressPrompt.addresses.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 @@ -130,6 +131,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 addresses 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 addresses: List<Address>,
val onConfirm: (Address) -> Unit,
override val onDismiss: () -> Unit
) : PromptRequest(), Dismissible

/**
* Value type that represents a request for an alert prompt to enter a message.
* @property title title of the dialog.
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 the SelectAddress prompt request 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(
addresses = listOf(address),
onDismiss = {
onDismissCalled = true
},
onConfirm = {
confirmedAddress = it
onConfirmCalled = true
}
)

assertEquals(selectAddresPromptRequest.addresses, 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 @@ -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. See [issue #12060](https://github.com/mozilla-mobile/android-components/issues/12060)

* **feature-autofill**
* 🚒 Bug fixed [issue #11893](https://github.com/mozilla-mobile/android-components/issues/11893) - Fix issue with autofilling in 3rd party applications not being immediately available after unlocking the autofill service.

Expand Down