Skip to content

Commit 07582c0

Browse files
authored
Merge pull request #10 from algolia/feat/manualEnable
feat: Instructions for manual enabling of permission
2 parents e41d1b7 + 069a7cc commit 07582c0

File tree

8 files changed

+121
-36
lines changed

8 files changed

+121
-36
lines changed

app/src/main/kotlin/com/algolia/instantsearch/voice/demo/MainActivity.kt

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ package com.algolia.instantsearch.voice.demo
22

33
import android.os.Bundle
44
import android.support.v7.app.AppCompatActivity
5-
import com.algolia.instantsearch.voice.ui.Voice
5+
import android.view.View
66
import com.algolia.instantsearch.voice.VoiceSpeechRecognizer
7+
import com.algolia.instantsearch.voice.ui.Voice
78
import com.algolia.instantsearch.voice.ui.VoiceInputDialogFragment
89
import com.algolia.instantsearch.voice.ui.VoicePermissionDialogFragment
910
import kotlinx.android.synthetic.main.main.*
@@ -34,11 +35,23 @@ class MainActivity : AppCompatActivity(), VoiceSpeechRecognizer.ResultsListener
3435
}
3536
}
3637

37-
override fun onResults(results: Array<out String>) {
38-
main.results.text = results.firstOrNull()?.capitalize()
38+
override fun onResults(possibleTexts: Array<out String>) {
39+
main.results.text = possibleTexts.firstOrNull()?.capitalize()
40+
}
41+
42+
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
43+
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
44+
if (Voice.isRecordPermissionWithResults(requestCode, grantResults)) {
45+
when {
46+
Voice.isPermissionGranted(grantResults) -> showVoiceDialog()
47+
Voice.shouldExplainPermission(this) -> Voice.showPermissionRationale(getPermissionView(), this)
48+
else -> Voice.showPermissionManualInstructions(getPermissionView())
49+
}
50+
}
3951
}
4052

4153
private fun showVoiceDialog() {
54+
getPermissionDialog()?.dismiss()
4255
(getVoiceDialog() ?: VoiceInputDialogFragment()).let {
4356
it.setArguments(
4457
"Hey, I just met you",
@@ -54,13 +67,6 @@ class MainActivity : AppCompatActivity(), VoiceSpeechRecognizer.ResultsListener
5467

5568
private fun getPermissionDialog() = (supportFragmentManager.findFragmentByTag(Tag.Permission.name) as? VoicePermissionDialogFragment)
5669

57-
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
58-
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
59-
if (Voice.isRecordPermissionWithResults(requestCode, grantResults)) {
60-
if (Voice.isPermissionGranted(grantResults)) {
61-
getPermissionDialog()?.dismiss()
62-
showVoiceDialog()
63-
}
64-
}
65-
}
70+
private fun getPermissionView(): View = getPermissionDialog()!!.view!!.findViewById(R.id.positive)
71+
6672
}

ui/src/main/kotlin/com/algolia/instantsearch/voice/ui/Voice.kt

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,16 @@ import android.Manifest
44
import android.Manifest.permission.RECORD_AUDIO
55
import android.app.Activity
66
import android.content.Context
7+
import android.content.Intent
78
import android.content.pm.PackageManager
9+
import android.net.Uri
10+
import android.provider.Settings
11+
import android.support.design.widget.BaseTransientBottomBar
12+
import android.support.design.widget.Snackbar
813
import android.support.v4.app.ActivityCompat
914
import android.support.v4.content.ContextCompat
15+
import android.view.View
16+
import android.widget.TextView
1017

1118
/** Helper functions for voice permission handling. */
1219
object Voice {
@@ -41,6 +48,66 @@ object Voice {
4148
@JvmStatic
4249
fun shouldExplainPermission(activity: Activity) =
4350
ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.RECORD_AUDIO)
51+
52+
/**
53+
* Requests the [recording permission][RECORD_AUDIO] from your [activity].*/
54+
@JvmStatic
55+
fun requestPermission(activity: Activity) {
56+
ActivityCompat.requestPermissions(activity, arrayOf(RECORD_AUDIO), PermissionRequestRecordAudio)
57+
}
58+
59+
/** Opens the application's settings from a given [context], so the user can enable recording permission.*/
60+
@JvmStatic
61+
fun openAppSettings(context: Context) {
62+
context.startActivity(Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
63+
.setData(Uri.fromParts("package", context.packageName, null)))
64+
}
65+
66+
/** Displays the rationale behind requesting the recording permission via a [Snackbar].
67+
* @param anchor the view on which the SnackBar will be anchored.
68+
* @param activity the activity which would request the permission.
69+
* @param whyAllow a description of why the permission should be granted.
70+
* @param buttonAllow a call to action for granting the permission.
71+
* */
72+
@JvmStatic
73+
fun showPermissionRationale(
74+
anchor: View,
75+
activity: Activity,
76+
whyAllow: CharSequence? = null,
77+
buttonAllow: CharSequence? = null
78+
) {
79+
val whyText = whyAllow ?: activity.getString(R.string.permission_rationale)
80+
val buttonText = (buttonAllow ?: activity.getString(R.string.permission_button_again))
81+
Snackbar.make(anchor, whyText, Snackbar.LENGTH_LONG).setAction(buttonText) { requestPermission(activity) }.show()
82+
}
83+
84+
/** Guides the user to manually enable recording permission in the app's settings.
85+
* @param anchor the view on which the SnackBar will be anchored.
86+
* @param whyEnable a description of why the permission should be enabled.
87+
* @param buttonEnable a call to action for enabling the permission.
88+
* @param howEnable instructions to manually enable the permission in settings.
89+
* */
90+
@JvmStatic
91+
fun showPermissionManualInstructions(
92+
anchor: View,
93+
whyEnable: CharSequence? = null,
94+
buttonEnable: CharSequence? = null,
95+
howEnable: CharSequence? = null
96+
) {
97+
val context = anchor.context
98+
val whyText = (whyEnable ?: context.getText(R.string.permission_enable_rationale))
99+
val buttonText = (buttonEnable ?: context.getText(R.string.permission_button_enable))
100+
val howText = (howEnable ?: context.getText(R.string.permission_enable_instructions))
101+
102+
val snackbar = Snackbar.make(anchor, whyText, Snackbar.LENGTH_LONG).setAction(buttonText) {
103+
Snackbar.make(anchor, howText, Snackbar.LENGTH_SHORT)
104+
.addCallback(object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
105+
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) = openAppSettings(context)
106+
}).show()
107+
}
108+
(snackbar.view.findViewById(android.support.design.R.id.snackbar_text) as TextView).maxLines = 2
109+
snackbar.show()
110+
}
44111
}
45112

46113
//TODO: Expose Activity extension methods instead?

ui/src/main/kotlin/com/algolia/instantsearch/voice/ui/VoiceAndroidView.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class VoiceAndroidView(
1313

1414
private val context = view.context
1515

16-
override val formatterSuggestion = { suggestion: String -> context.getString(R.string.voice_suggestion_html_format, suggestion) }
16+
override val formatterSuggestion = { suggestion: String -> context.getString(R.string.format_voice_suggestion_html, suggestion) }
1717

1818
override fun setOnClickListenerClose(onClickListener: View.OnClickListener) {
1919
view.voiceInput.close.setOnClickListener(onClickListener)

ui/src/main/kotlin/com/algolia/instantsearch/voice/ui/VoiceUI.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ interface VoiceUI {
88
val formatterSuggestion: (String) -> String
99

1010
enum class Title(val resource: Int) {
11-
Listen(R.string.voice_title_listening),
12-
Error(R.string.voice_title_error)
11+
Listen(R.string.input_title_listening),
12+
Error(R.string.input_title_error)
1313
}
1414

1515
enum class Subtitle(val resource: Int) {
16-
Error(R.string.voice_subtitle_error),
17-
Listen(R.string.voice_subtitle_listening)
16+
Error(R.string.input_subtitle_error),
17+
Listen(R.string.input_subtitle_listening)
1818
}
1919

2020
fun setOnClickListenerClose(onClickListener: View.OnClickListener)

ui/src/main/res/layout/voice_input.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
android:layout_width="0dp"
6363
app:layout_constraintBottom_toTopOf="@id/subtitle"
6464
android:layout_height="wrap_content"
65-
android:text="@string/voice_title_listening"
65+
android:text="@string/input_title_listening"
6666
android:textAppearance="?attr/textAppearanceHeadline6"
6767
app:layout_constraintEnd_toEndOf="parent"
6868
android:layout_marginStart="24dp"
@@ -77,7 +77,7 @@
7777
android:textAppearance="?attr/textAppearanceBody1"
7878
android:layout_height="wrap_content"
7979
android:layout_marginTop="24dp"
80-
android:text="@string/voice_subtitle_listening"
80+
android:text="@string/input_subtitle_listening"
8181
app:layout_constraintBottom_toTopOf="@id/microphone"
8282
app:layout_constraintEnd_toEndOf="@id/title"
8383
app:layout_constraintStart_toStartOf="@id/title"
@@ -110,7 +110,7 @@
110110
android:textAppearance="?attr/textAppearanceSubtitle2"
111111
android:textSize="16sp"
112112
android:visibility="invisible"
113-
android:text="@string/voice_hint_error"
113+
android:text="@string/input_hint_error"
114114
app:layout_constraintTop_toBottomOf="@id/microphone"
115115
app:layout_constraintBottom_toBottomOf="parent"
116116
android:layout_width="wrap_content"

ui/src/main/res/layout/voice_permission.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
android:id="@+id/positive"
6767
app:layout_constraintStart_toStartOf="parent"
6868
app:layout_constraintEnd_toEndOf="parent"
69-
android:text="@string/permission_allow_text"
69+
android:text="@string/permission_button_allow"
7070
android:background="@drawable/button_positive_background_ripple"
7171
android:layout_marginStart="24dp"
7272
android:textSize="16sp"
@@ -82,7 +82,7 @@
8282
<android.support.v7.widget.AppCompatButton
8383
android:id="@+id/negative"
8484
android:layout_marginTop="16dp"
85-
android:text="@string/permission_reject_text"
85+
android:text="@string/permission_button_reject"
8686
android:background="@drawable/button_negative_background_ripple"
8787
android:textSize="16sp"
8888
android:textAllCaps="false"

ui/src/main/res/values/strings.xml

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,31 @@
11
<resources>
2-
<!--TODO: Rename voice_ to input_ -->
3-
<string name="voice_title_listening">Listening…</string>
4-
<string name="voice_subtitle_listening">Say something like:</string>
5-
<string name="voice_title_error">Sorry, we didn\'t quite get that.</string>
6-
<string name="voice_subtitle_error">Try repeating your request.</string>
7-
<string name="voice_hint_error">Try again</string>
2+
<!-- region VoiceInputDialogFragment -->
3+
<string name="input_title_listening">Listening…</string>
4+
<string name="input_subtitle_listening">Say something like:</string>
5+
<string name="input_title_error">Sorry, we didn\'t quite get that.</string>
6+
<string name="input_subtitle_error">Try repeating your request.</string>
7+
<string name="input_hint_error">Try again</string>
8+
<!-- endregion -->
89

10+
<!-- region VoicePermissionDialogFragment -->
911
<string name="permission_title">You can use voice search to find products.</string>
1012
<string name="permission_subtitle">May we access your device’s microphone to enable voice search?</string>
11-
<string name="permission_allow_text">Allow microphone access</string>
12-
<string name="permission_reject_text">No</string>
13+
<string name="permission_button_allow">Allow microphone access</string>
14+
<string name="permission_button_reject">No</string>
1315

14-
<string name="voice_search_disabled_rationale">To use voice search you need to allow recording.</string>
15-
<string name="permission_enable_text">Give permission</string>
16-
<string name="voice_search_disabled_instructions">On the next screen, tap Permissions then Microphone.</string>
17-
<string name="voice_search_rationale">Voice search requires this permission.</string>
16+
<!-- region Rationale/Try Again -->
17+
<string name="permission_rationale">Voice search requires this permission.</string>
18+
<string name="permission_button_again">Request again?</string>
19+
<!-- endregion -->
20+
21+
<!-- region Manual Instructions -->
22+
<string name="permission_enable_rationale">Permission denied, allow it to use voice search.</string>
23+
<string name="permission_button_enable">Allow recording</string>
24+
<string name="permission_enable_instructions">On the next screen, tap Permissions then Microphone.</string>
25+
<!-- endregion -->
26+
27+
<!-- endregion -->
1828

1929
<!-- Not intended to be overridden by user app. -->
20-
<string name="voice_suggestion_html_format"><![CDATA[&#8226;\t\t\t%s<br/>]]></string>
30+
<string name="format_voice_suggestion_html"><![CDATA[&#8226;\t\t\t%s<br/>]]></string>
2131
</resources>

voice/src/main/kotlin/com/algolia/instantsearch/voice/VoiceSpeechRecognizer.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ class VoiceSpeechRecognizer(
2020

2121
interface ResultsListener {
2222

23-
fun onResults(results: Array<out String>)
23+
//TODO: Document
24+
//TODO: Also see if can be vararg
25+
fun onResults(possibleTexts: Array<out String>)
2426
}
2527

2628
private val speechRecognizer = SpeechRecognizer.createSpeechRecognizer(context)

0 commit comments

Comments
 (0)