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

Commit

Permalink
For #12565 Implement the common part of search widget in Android Comp…
Browse files Browse the repository at this point in the history
…onents
  • Loading branch information
iorgamgabriel committed Aug 2, 2022
2 parents fe60cf3 + 3696994 commit ea140eb
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ import androidx.core.graphics.drawable.toBitmap
import mozilla.components.feature.searchwidget.VoiceSearchActivity.Companion.SPEECH_PROCESSING
import mozilla.components.support.utils.PendingIntentUtils

/**
* It contains all the Gui and behaviour for AppWidgetProvider.
* Needs to be extended in client app.
*/
abstract class AppSearchWidgetProvider : AppWidgetProvider() {

override fun onUpdate(
Expand Down Expand Up @@ -208,6 +212,9 @@ abstract class AppSearchWidgetProvider : AppWidgetProvider() {
private const val DP_LARGE = 256
private const val REQUEST_CODE_VOICE = 1

/**
* It updates AppSearchWidgetProvider size and microphone icon visibility
*/
fun updateAllWidgets(context: Context, widgetClassNameApp: AppSearchWidgetProvider) {
val widgetManager = AppWidgetManager.getInstance(context)
val widgetIds = widgetManager.getAppWidgetIds(
Expand Down Expand Up @@ -266,7 +273,7 @@ abstract class AppSearchWidgetProvider : AppWidgetProvider() {
}
}

enum class SearchWidgetProviderSize {
internal enum class SearchWidgetProviderSize {
EXTRA_SMALL_V1,
EXTRA_SMALL_V2,
SMALL,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.speech.RecognizerIntent
import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.VisibleForTesting
Expand Down Expand Up @@ -75,18 +76,23 @@ abstract class VoiceSearchActivity : AppCompatActivity() {
abstract fun startIntentAfterVoiceSearch(spokenText: String?)

@VisibleForTesting
internal fun activityResultImplementation(activityResult: ActivityResult) {
if (activityResult.resultCode == Activity.RESULT_OK) {
val spokenText =
activityResult.data?.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)
?.first()
previousIntent?.apply {
startIntentAfterVoiceSearch(spokenText)
}
}
finish()
}

private fun getActivityResultLauncher(): ActivityResultLauncher<Intent> {
return registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
if (it.resultCode == Activity.RESULT_OK) {
val spokenText =
it.data?.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)?.first()
previousIntent?.apply {
startIntentAfterVoiceSearch(spokenText)
}
}
finish()
activityResultImplementation(it)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/* 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.searchwidget

import java.util.Locale

class VoiceSearchActivityExtendedForTests : VoiceSearchActivity() {

override fun getCurrentLocale(): Locale {
return Locale.getDefault()
}

override fun recordVoiceButtonTelemetry() {
}

override fun startIntentAfterVoiceSearch(spokenText: String?) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/* 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.searchwidget

import android.app.Activity
import android.app.Activity.RESULT_OK
import android.content.ComponentName
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import android.speech.RecognizerIntent.ACTION_RECOGNIZE_SPEECH
import android.speech.RecognizerIntent.EXTRA_RESULTS
import androidx.activity.result.ActivityResult
import mozilla.components.feature.searchwidget.VoiceSearchActivity.Companion.PREVIOUS_INTENT
import mozilla.components.feature.searchwidget.VoiceSearchActivity.Companion.SPEECH_PROCESSING
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.Shadows.shadowOf
import org.robolectric.android.controller.ActivityController
import org.robolectric.shadows.ShadowActivity

@RunWith(RobolectricTestRunner::class)
class VoiceSearchActivityTest {

private lateinit var controller: ActivityController<VoiceSearchActivityExtendedForTests>
private lateinit var activity: VoiceSearchActivityExtendedForTests
private lateinit var shadow: ShadowActivity

@Before
fun setup() {
val intent = Intent()
intent.putExtra(SPEECH_PROCESSING, true)

controller = Robolectric.buildActivity(VoiceSearchActivityExtendedForTests::class.java, intent)
activity = controller.get()
shadow = shadowOf(activity)
}

private fun allowVoiceIntentToResolveActivity() {
val shadowPackageManager = shadowOf(testContext.packageManager)
val component = ComponentName("com.test", "Test")
shadowPackageManager.addActivityIfNotPresent(component)
shadowPackageManager.addIntentFilterForActivity(
component,
IntentFilter(ACTION_RECOGNIZE_SPEECH).apply { addCategory(Intent.CATEGORY_DEFAULT) }
)
}

@Test
fun `process intent with speech processing set to true`() {
val intent = Intent()
intent.putStringArrayListExtra(EXTRA_RESULTS, ArrayList<String>(listOf("hello world")))
val activityResult = ActivityResult(RESULT_OK, intent)
controller.get().activityResultImplementation(activityResult)

assertTrue(activity.isFinishing)
}

@Test
fun `process intent with speech processing set to false`() {
allowVoiceIntentToResolveActivity()
val intent = Intent()
intent.putExtra(SPEECH_PROCESSING, false)

val controller = Robolectric.buildActivity(VoiceSearchActivityExtendedForTests::class.java, intent)
val activity = controller.get()

controller.create()

assertTrue(activity.isFinishing)
}

@Test
fun `process null intent`() {
allowVoiceIntentToResolveActivity()
val controller = Robolectric.buildActivity(VoiceSearchActivityExtendedForTests::class.java, null)
val activity = controller.get()

controller.create()

assertTrue(activity.isFinishing)
}

@Test
fun `save previous intent to instance state`() {
allowVoiceIntentToResolveActivity()
val previousIntent = Intent().apply {
putExtra(SPEECH_PROCESSING, true)
}
val savedInstanceState = Bundle().apply {
putParcelable(PREVIOUS_INTENT, previousIntent)
}
val outState = Bundle()

controller.create(savedInstanceState)
controller.saveInstanceState(outState)

assertEquals(previousIntent, outState.getParcelable<Intent>(PREVIOUS_INTENT))
}

@Test
fun `process intent with speech processing in previous intent set to true`() {
allowVoiceIntentToResolveActivity()
val savedInstanceState = Bundle()
val previousIntent = Intent().apply {
putExtra(SPEECH_PROCESSING, true)
}
savedInstanceState.putParcelable(PREVIOUS_INTENT, previousIntent)

controller.create(savedInstanceState)

assertFalse(activity.isFinishing)
assertNull(shadow.peekNextStartedActivityForResult())
}

@Test
fun `handle invalid result code`() {
val activityResult = ActivityResult(Activity.RESULT_CANCELED, Intent())
controller.get().activityResultImplementation(activityResult)

assertTrue(activity.isFinishing)
}

@Test
fun `handle no activity able to resolve voice intent`() {
controller.create()
assertTrue(activity.isFinishing)
}
}

0 comments on commit ea140eb

Please sign in to comment.