[fenix] Close https://github.com/mozilla-mobile/fenix/issues/19919: Migrate away from startActivityForResult

pull/600/head
Roger Yang 1 year ago committed by mergify[bot]
parent 360aa313e2
commit fa7e89df4f

@ -58,7 +58,6 @@ import mozilla.components.feature.media.ext.findActiveMediaTab
import mozilla.components.feature.privatemode.notification.PrivateNotificationFeature
import mozilla.components.feature.search.BrowserStoreSearchAdapter
import mozilla.components.service.fxa.sync.SyncReason
import mozilla.components.support.base.feature.ActivityResultHandler
import mozilla.components.support.base.feature.UserInteractionHandler
import mozilla.components.support.base.log.logger.Logger
import mozilla.components.support.ktx.android.arch.lifecycle.addObservers
@ -682,17 +681,6 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
super.getOnBackPressedDispatcher().onBackPressed()
}
@Suppress("DEPRECATION")
// https://github.com/mozilla-mobile/fenix/issues/19919
final override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
supportFragmentManager.primaryNavigationFragment?.childFragmentManager?.fragments?.forEach {
if (it is ActivityResultHandler && it.onActivityResult(requestCode, data, resultCode)) {
return
}
}
super.onActivityResult(requestCode, resultCode, data)
}
private fun shouldUseCustomBackLongPress(): Boolean {
val isAndroidN =
Build.VERSION.SDK_INT == Build.VERSION_CODES.N || Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1

@ -17,6 +17,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.accessibility.AccessibilityManager
import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.CallSuper
import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.AlertDialog
@ -87,7 +88,6 @@ import mozilla.components.lib.state.ext.flowScoped
import mozilla.components.service.glean.private.NoExtras
import mozilla.components.service.sync.autofill.DefaultCreditCardValidationDelegate
import mozilla.components.service.sync.logins.DefaultLoginValidationDelegate
import mozilla.components.support.base.feature.ActivityResultHandler
import mozilla.components.support.base.feature.PermissionsFeature
import mozilla.components.support.base.feature.UserInteractionHandler
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
@ -128,6 +128,7 @@ import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getPreferenceKey
import org.mozilla.fenix.ext.hideToolbar
import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.ext.registerForActivityResult
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.runIfFragmentIsAttached
import org.mozilla.fenix.ext.secure
@ -153,7 +154,6 @@ import mozilla.components.feature.session.behavior.ToolbarPosition as MozacToolb
abstract class BaseBrowserFragment :
Fragment(),
UserInteractionHandler,
ActivityResultHandler,
OnBackLongPressedListener,
AccessibilityManager.AccessibilityStateChangeListener {
@ -162,6 +162,7 @@ abstract class BaseBrowserFragment :
private lateinit var browserFragmentStore: BrowserFragmentStore
private lateinit var browserAnimator: BrowserAnimator
private lateinit var startForResult: ActivityResultLauncher<Intent>
private var _browserToolbarInteractor: BrowserToolbarInteractor? = null
protected val browserToolbarInteractor: BrowserToolbarInteractor
@ -246,6 +247,13 @@ abstract class BaseBrowserFragment :
)
}
startForResult = registerForActivityResult { result ->
listOf(
promptsFeature,
webAuthnFeature,
).any { it.onActivityResult(PIN_REQUEST, result.data, result.resultCode) }
}
// DO NOT MOVE ANYTHING BELOW THIS addMarker CALL!
requireComponents.core.engine.profiler?.addMarker(
MarkersFragmentLifecycleCallbacks.MARKER_NAME,
@ -856,13 +864,14 @@ abstract class BaseBrowserFragment :
/**
* Shows a pin request prompt. This is only used when BiometricPrompt is unavailable.
*/
@Suppress("Deprecation")
@Suppress("DEPRECATION")
private fun showPinVerification(manager: KeyguardManager) {
val intent = manager.createConfirmDeviceCredentialIntent(
getString(R.string.credit_cards_biometric_prompt_message_pin),
getString(R.string.credit_cards_biometric_prompt_unlock_message),
)
requireActivity().startActivityForResult(intent, PIN_REQUEST)
startForResult.launch(intent)
}
/**
@ -1186,16 +1195,6 @@ abstract class BaseBrowserFragment :
feature?.onPermissionsResult(permissions, grantResults)
}
/**
* Forwards activity results to the [ActivityResultHandler] features.
*/
override fun onActivityResult(requestCode: Int, data: Intent?, resultCode: Int): Boolean {
return listOf(
promptsFeature,
webAuthnFeature,
).any { it.onActivityResult(requestCode, data, resultCode) }
}
/**
* Removes the session if it was opened by an ACTION_VIEW intent
* or if it has a parent session and no more history

@ -4,7 +4,12 @@
package org.mozilla.fenix.ext
import android.app.Activity
import android.content.Intent
import android.view.WindowManager
import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.IdRes
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
@ -128,3 +133,19 @@ fun Fragment.removeSecure() {
WindowManager.LayoutParams.FLAG_SECURE,
)
}
/**
* Register a request to start an activity for result.
*/
fun Fragment.registerForActivityResult(
onFailure: (result: ActivityResult) -> Unit = {},
onSuccess: (result: ActivityResult) -> Unit,
): ActivityResultLauncher<Intent> {
return registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
onSuccess(result)
} else {
onFailure(result)
}
}
}

@ -6,7 +6,6 @@ package org.mozilla.fenix.search
import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.app.Dialog
import android.content.Context
import android.content.DialogInterface
@ -27,6 +26,7 @@ import android.view.ViewStub
import android.view.WindowManager
import android.view.accessibility.AccessibilityEvent
import android.view.inputmethod.InputMethodManager
import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatDialogFragment
@ -87,6 +87,7 @@ import org.mozilla.fenix.databinding.SearchSuggestionsHintBinding
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getRectWithScreenLocation
import org.mozilla.fenix.ext.increaseTapArea
import org.mozilla.fenix.ext.registerForActivityResult
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.search.awesomebar.AwesomeBarView
@ -96,7 +97,6 @@ import org.mozilla.fenix.search.toolbar.SearchSelectorMenu
import org.mozilla.fenix.search.toolbar.SearchSelectorToolbarAction
import org.mozilla.fenix.search.toolbar.ToolbarView
import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.widget.VoiceSearchActivity
typealias SearchDialogFragmentStore = SearchFragmentStore
@ -110,6 +110,7 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler {
private lateinit var toolbarView: ToolbarView
private lateinit var inlineAutocompleteEditText: InlineAutocompleteEditText
private lateinit var awesomeBarView: AwesomeBarView
private lateinit var startForResult: ActivityResultLauncher<Intent>
private val searchSelectorMenu by lazy {
SearchSelectorMenu(
@ -155,6 +156,14 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, R.style.SearchDialogStyle)
startForResult = registerForActivityResult { result ->
result.data?.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)?.first()?.also {
toolbarView.view.edit.updateUrl(url = it, shouldHighlight = true)
interactor.onTextChanged(it)
toolbarView.view.edit.focus()
}
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
@ -620,16 +629,6 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler {
hideDeviceKeyboard()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
if (requestCode == VoiceSearchActivity.SPEECH_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
intent?.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)?.first()?.also {
toolbarView.view.edit.updateUrl(url = it, shouldHighlight = true)
interactor.onTextChanged(it)
toolbarView.view.edit.focus()
}
}
}
override fun onBackPressed(): Boolean {
return when {
qrFeature.onBackPressed() -> {
@ -851,8 +850,6 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler {
}
}
@Suppress("DEPRECATION")
// https://github.com/mozilla-mobile/fenix/issues/19919
private fun launchVoiceSearch() {
// Note if a user disables speech while the app is on the search fragment
// the voice button will still be available and *will* cause a crash if tapped,
@ -865,7 +862,8 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler {
putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM)
putExtra(RecognizerIntent.EXTRA_PROMPT, requireContext().getString(R.string.voice_search_explainer))
}
startActivityForResult(speechIntent, VoiceSearchActivity.SPEECH_REQUEST_CODE)
startForResult.launch(speechIntent)
}
private fun updateQrButton(searchFragmentState: SearchFragmentState) {

@ -305,7 +305,8 @@ class AutofillSettingFragment : BiometricPromptPreferenceFragment() {
getString(R.string.credit_cards_biometric_prompt_message_pin),
getString(R.string.credit_cards_biometric_prompt_message),
)
startActivityForResult(intent, PIN_REQUEST)
startForResult.launch(intent)
}
private fun navigateToCreditCardManagementFragment() {

@ -4,15 +4,17 @@
package org.mozilla.fenix.settings.biometric
import android.app.Activity.RESULT_OK
import android.app.KeyguardManager
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.activity.result.ActivityResultLauncher
import androidx.core.content.getSystemService
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import org.mozilla.fenix.ext.registerForActivityResult
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.settings.requirePreference
@ -23,6 +25,15 @@ import org.mozilla.fenix.settings.requirePreference
abstract class BiometricPromptPreferenceFragment : PreferenceFragmentCompat() {
private val biometricPromptFeature = ViewBoundFeatureWrapper<BiometricPromptFeature>()
internal lateinit var startForResult: ActivityResultLauncher<Intent>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
startForResult = registerForActivityResult {
navigateOnSuccess()
}
}
/**
* Gets the string to be used for [BiometricPromptFeature.requestAuthentication] prompting to
@ -107,14 +118,4 @@ abstract class BiometricPromptPreferenceFragment : PreferenceFragmentCompat() {
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == PIN_REQUEST && resultCode == RESULT_OK) {
navigateOnSuccess()
}
}
companion object {
const val PIN_REQUEST = 303
}
}

@ -4,7 +4,6 @@
package org.mozilla.fenix.settings.logins.fragment
import android.app.Activity.RESULT_OK
import android.app.KeyguardManager
import android.content.Context
import android.content.DialogInterface
@ -12,6 +11,7 @@ import android.content.Intent
import android.os.Bundle
import android.provider.Settings.ACTION_SECURITY_SETTINGS
import android.view.View
import androidx.activity.result.ActivityResultLauncher
import androidx.appcompat.app.AlertDialog
import androidx.core.content.getSystemService
import androidx.lifecycle.lifecycleScope
@ -29,6 +29,7 @@ import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import org.mozilla.fenix.GleanMetrics.Logins
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.registerForActivityResult
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.runIfFragmentIsAttached
import org.mozilla.fenix.ext.secure
@ -42,6 +43,15 @@ import org.mozilla.fenix.settings.requirePreference
@Suppress("TooManyFunctions")
class SavedLoginsAuthFragment : PreferenceFragmentCompat() {
private val biometricPromptFeature = ViewBoundFeatureWrapper<BiometricPromptFeature>()
private lateinit var startForResult: ActivityResultLauncher<Intent>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
startForResult = registerForActivityResult {
navigateToSavedLoginsFragment()
}
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.logins_preferences, rootKey)
@ -205,19 +215,14 @@ class SavedLoginsAuthFragment : PreferenceFragmentCompat() {
context.settings().incrementSecureWarningCount()
}
@Suppress("Deprecation") // This is only used when BiometricPrompt is unavailable
@Suppress("Deprecation")
private fun showPinVerification(manager: KeyguardManager) {
val intent = manager.createConfirmDeviceCredentialIntent(
getString(R.string.logins_biometric_prompt_message_pin),
getString(R.string.logins_biometric_prompt_message),
)
startActivityForResult(intent, PIN_REQUEST)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == PIN_REQUEST && resultCode == RESULT_OK) {
navigateToSavedLoginsFragment()
}
startForResult.launch(intent)
}
/**

@ -9,7 +9,11 @@ import android.content.Intent
import android.os.Bundle
import android.os.StrictMode
import android.speech.RecognizerIntent
import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import com.google.android.gms.common.util.VisibleForTesting
import mozilla.components.support.locale.LocaleManager
import mozilla.components.support.utils.ext.getParcelableCompat
import mozilla.telemetry.glean.private.NoExtras
@ -28,6 +32,24 @@ class VoiceSearchActivity : AppCompatActivity() {
* so that it can persist through the speech activity.
*/
private var previousIntent: Intent? = null
private lateinit var startForResult: ActivityResultLauncher<Intent>
@VisibleForTesting
internal fun handleActivityResult(result: ActivityResult) {
if (result.resultCode == RESULT_OK) {
val spokenText = result.data?.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)?.first()
val context = this
previousIntent?.apply {
component = ComponentName(context, IntentReceiverActivity::class.java)
putExtra(SPEECH_PROCESSING, spokenText)
putExtra(HomeActivity.OPEN_TO_BROWSER_AND_LOAD, true)
startActivity(this)
}
}
finish()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
@ -37,6 +59,10 @@ class VoiceSearchActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
startForResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
handleActivityResult(it)
}
if (Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).resolveActivity(packageManager) == null) {
finish()
return
@ -63,8 +89,6 @@ class VoiceSearchActivity : AppCompatActivity() {
/**
* Displays a speech recognizer popup that listens for input from the user.
*/
@Suppress("DEPRECATION")
// https://github.com/mozilla-mobile/fenix/issues/19919
private fun displaySpeechRecognizer() {
val intentSpeech = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply {
putExtra(
@ -80,27 +104,7 @@ class VoiceSearchActivity : AppCompatActivity() {
}
SearchWidget.voiceButton.record(NoExtras())
startActivityForResult(intentSpeech, SPEECH_REQUEST_CODE)
}
@Suppress("DEPRECATION")
// https://github.com/mozilla-mobile/fenix/issues/19919
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == SPEECH_REQUEST_CODE && resultCode == RESULT_OK) {
val spokenText = data?.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)?.first()
val context = this
previousIntent?.apply {
component = ComponentName(context, IntentReceiverActivity::class.java)
putExtra(SPEECH_PROCESSING, spokenText)
putExtra(HomeActivity.OPEN_TO_BROWSER_AND_LOAD, true)
startActivity(this)
}
}
finish()
startForResult.launch(intentSpeech)
}
/**
@ -111,7 +115,6 @@ class VoiceSearchActivity : AppCompatActivity() {
this?.getBooleanExtra(SPEECH_PROCESSING, false) == true
companion object {
internal const val SPEECH_REQUEST_CODE = 0
internal const val PREVIOUS_INTENT = "org.mozilla.fenix.previous_intent"
/**

@ -4,7 +4,8 @@
package org.mozilla.fenix.widget
import android.app.Activity
import android.app.Activity.RESULT_CANCELED
import android.app.Activity.RESULT_OK
import android.content.ComponentName
import android.content.Intent
import android.content.IntentFilter
@ -13,7 +14,7 @@ import android.speech.RecognizerIntent.ACTION_RECOGNIZE_SPEECH
import android.speech.RecognizerIntent.EXTRA_LANGUAGE_MODEL
import android.speech.RecognizerIntent.EXTRA_RESULTS
import android.speech.RecognizerIntent.LANGUAGE_MODEL_FREE_FORM
import androidx.appcompat.app.AppCompatActivity.RESULT_OK
import androidx.activity.result.ActivityResult
import androidx.test.core.app.ApplicationProvider
import io.mockk.every
import io.mockk.mockk
@ -33,7 +34,6 @@ import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.helpers.perf.TestStrictModeManager
import org.mozilla.fenix.widget.VoiceSearchActivity.Companion.PREVIOUS_INTENT
import org.mozilla.fenix.widget.VoiceSearchActivity.Companion.SPEECH_PROCESSING
import org.mozilla.fenix.widget.VoiceSearchActivity.Companion.SPEECH_REQUEST_CODE
import org.robolectric.Robolectric
import org.robolectric.Shadows.shadowOf
import org.robolectric.android.controller.ActivityController
@ -75,7 +75,6 @@ class VoiceSearchActivityTest {
controller.create()
val intentForResult = shadow.peekNextStartedActivityForResult()
assertEquals(SPEECH_REQUEST_CODE, intentForResult.requestCode)
assertEquals(ACTION_RECOGNIZE_SPEECH, intentForResult.intent.action)
assertEquals(
LANGUAGE_MODEL_FREE_FORM,
@ -141,6 +140,12 @@ class VoiceSearchActivityTest {
assertNull(shadow.peekNextStartedActivityForResult())
}
@Test
fun `handle no activity able to resolve voice intent`() {
controller.create()
assertTrue(activity.isFinishing)
}
@Test
fun `handle speech result`() {
every { testContext.components.analytics } returns mockk(relaxed = true)
@ -151,11 +156,8 @@ class VoiceSearchActivityTest {
val resultIntent = Intent().apply {
putStringArrayListExtra(EXTRA_RESULTS, arrayListOf("hello world"))
}
shadow.receiveResult(
shadow.peekNextStartedActivityForResult().intent,
RESULT_OK,
resultIntent,
)
val result = ActivityResult(RESULT_OK, resultIntent)
activity.handleActivityResult(result)
val browserIntent = shadow.peekNextStartedActivity()
@ -176,18 +178,9 @@ class VoiceSearchActivityTest {
controller.create()
val resultIntent = Intent()
shadow.receiveResult(
shadow.peekNextStartedActivityForResult().intent,
Activity.RESULT_CANCELED,
resultIntent,
)
assertTrue(activity.isFinishing)
}
val result = ActivityResult(RESULT_CANCELED, resultIntent)
activity.handleActivityResult(result)
@Test
fun `handle no activity able to resolve voice intent`() {
controller.create()
assertTrue(activity.isFinishing)
}
}

Loading…
Cancel
Save