Skip to content

Commit

Permalink
client start and audio record permission request improvement
Browse files Browse the repository at this point in the history
  • Loading branch information
igorpolt committed Feb 18, 2021
1 parent 8228c51 commit 2b09f5c
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 56 deletions.
30 changes: 15 additions & 15 deletions client-test/src/main/kotlin/com/speechly/clienttest/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.speechly.clienttest

import android.app.Activity
import android.os.Bundle
import android.view.MotionEvent
import android.view.View
Expand Down Expand Up @@ -34,7 +33,11 @@ val repoList = listOf(

class MainActivity : AppCompatActivity() {

private var speechlyClient: Client? = null
private var speechlyClient: Client = Client.fromActivity(
activity = getActivity(),
appId = UUID.randomUUID()
)

private var button: SpeechlyButton? = null
private var textView: TextView? = null
private var recyclerView: RecyclerView? = null
Expand All @@ -48,7 +51,7 @@ class MainActivity : AppCompatActivity() {
MotionEvent.ACTION_DOWN -> {
textView?.visibility = View.VISIBLE
textView?.text = ""
speechlyClient!!.startContext()
speechlyClient?.startContext()
}
MotionEvent.ACTION_UP -> {
speechlyClient!!.stopContext()
Expand All @@ -62,24 +65,21 @@ class MainActivity : AppCompatActivity() {
}
}

fun getActivity(): Activity {
fun getActivity(): AppCompatActivity {
return this
}

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
this.button = findViewById<SpeechlyButton>(R.id.speechly)
this.button = findViewById(R.id.speechly)
this.recyclerView = findViewById(R.id.recycler_view)
this.textView = findViewById(R.id.textView)
textView?.visibility = View.INVISIBLE


GlobalScope.launch(Dispatchers.Default) {
speechlyClient = Client.fromActivity(
activity = getActivity(),
appId = UUID.fromString(UUID.randomUUID().toString())
)
speechlyClient?.onSegmentChange { segment: Segment ->
val transcript: String = segment.words.values.map{it.value}.joinToString(" ")

Expand All @@ -89,15 +89,15 @@ class MainActivity : AppCompatActivity() {
if (segment.intent != null) {
when(segment.intent?.intent) {
"filter" -> {
lunguageFilter = segment.getEntityByType("language")?.value
languageFilter = segment.getEntityByType("language")?.value
updateList()
}
"sort" -> {
sortField = segment.getEntityByType("sort_field")?.value
updateList()
}
"reset" -> {
lunguageFilter = null
languageFilter = null
updateList()
}
}
Expand All @@ -118,11 +118,11 @@ class MainActivity : AppCompatActivity() {

fun updateList() {
val list = repoList.filter { repo: Repo ->
lunguageFilter == null || repo.language == lunguageFilter
languageFilter == null || repo.language == languageFilter
}.sortedWith(Comparator { r1: Repo, r2: Repo ->
when(sortField) {
"NAME" -> if (r1.name > r2.name) 1 else -1
"LANGUAGE" -> if (r1.language > r2.language) 1 else -1
when (sortField) {
"NAME" -> if (r1.name > r2.name) 1 else -1
"LANGUAGE" -> if (r1.language > r2.language) 1 else -1
"FOLLOWERS" -> r2.followers - r1.followers
"STARS" -> r2.stars - r1.stars
"FORKS" -> r2.forks - r1.forks
Expand Down
4 changes: 0 additions & 4 deletions client-test/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">


<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
Expand All @@ -21,7 +19,6 @@
tools:listitem="@layout/repo" />

</LinearLayout>

<com.speechly.ui.SpeechlyButton
android:id="@+id/speechly"
android:layout_width="100dp"
Expand All @@ -38,5 +35,4 @@
android:textSize="20dp"
android:padding="5dp"
android:text="TextView" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>
6 changes: 3 additions & 3 deletions client/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,11 @@ android {
}

dependencies {
def activity_version = "1.2.0-rc01"
def activity_version = "1.2.0"

implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.1.0'

implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
Expand All @@ -64,6 +64,6 @@ dependencies {

androidTestImplementation "com.android.support.test:runner:1.0.2"
androidTestImplementation "com.android.support.test.espresso:espresso-core:3.0.2"

implementation 'androidx.fragment:fragment:1.3.0'
implementation "androidx.activity:activity-ktx:$activity_version"
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,19 @@ class RandomIdProvider : DeviceIdProvider {
* By default it uses a random id provider for generating a new id in case of a cache miss.
*/
class CachingIdProvider(
private val cacheService: CacheService,
private val baseProvider: DeviceIdProvider = RandomIdProvider()
) : DeviceIdProvider {

var cacheService: CacheService? = null

private val cacheKey = "speechly-device-id"

override fun getDeviceId(): DeviceId {
return this.loadFromCache() ?: this.storeAndReturn()
}

private fun loadFromCache(): DeviceId? {
val cached = this.cacheService.loadString(this.cacheKey) ?: return null
val cached = this.cacheService?.loadString(this.cacheKey) ?: return null

return try {
UUID.fromString(cached)
Expand All @@ -63,7 +65,7 @@ class CachingIdProvider(

// `storeString` returns false if the write operation has failed.
// Current we choose to ignore failed writes and instead re-generate the id on the next call.
this.cacheService.storeString(this.cacheKey, id.toString())
this.cacheService?.storeString(this.cacheKey, id.toString())

return id
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,30 +70,31 @@ class BasicIdentityService(
* @param cacheService an implementation of PersistentCache that is used for storing cached tokens
*/
class CachingIdentityService(
private val baseService: IdentityService,
private val cacheService: CacheService
private val baseService: IdentityService
) : IdentityService {

// cacheService an implementation of PersistentCache that is used for storing cached tokens
var cacheService: CacheService? = null

companion object {
/**
* Creates a new identity service using default gRPC client implementation.
*
* @param cacheService an implementation of PersistentCache that is used for storing cached tokens
* @param target the address of the API endpoint to connect to, e.g. "api.speechly.com"
* @param secure whether to use secured (TLS) or plaintext connection
*/
fun forTarget(
cacheService: CacheService,
target: String = "api.speechly.com",
secure: Boolean = true
): CachingIdentityService {
return CachingIdentityService(
BasicIdentityService.forTarget(target, secure),
cacheService
BasicIdentityService.forTarget(target, secure)
)
}
}

override suspend fun authenticate(appId: UUID, deviceId: UUID): AuthToken {
println("\n\n\n***** appId ${appId} deviceId ${deviceId} ***** \n\n\n")
// Try to load the token from the cache.
val token = this.loadToken(appId, deviceId)

Expand All @@ -112,7 +113,7 @@ class CachingIdentityService(
}

private fun loadToken(appId: UUID, deviceId: UUID): AuthToken? {
val cacheValue = this.cacheService.loadString(this.makeCacheKey(appId, deviceId)) ?: return null
val cacheValue = this.cacheService?.loadString(this.makeCacheKey(appId, deviceId)) ?: return null

return try {
AuthToken.fromJWT(cacheValue)
Expand All @@ -124,7 +125,7 @@ class CachingIdentityService(
private suspend fun reloadToken(appId: UUID, deviceId: UUID): AuthToken {
val token = this.baseService.authenticate(appId, deviceId)

this.cacheService.storeString(this.makeCacheKey(token.appId, token.deviceId), token.tokenString)
this.cacheService?.storeString(this.makeCacheKey(token.appId, token.deviceId), token.tokenString)

return token
}
Expand Down
34 changes: 18 additions & 16 deletions client/src/main/kotlin/com/speechly/client/speech/AudioRecorder.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package com.speechly.client.speech

import android.Manifest
import android.media.AudioRecord
import android.content.pm.PackageManager
import android.media.AudioFormat
import android.media.AudioRecord
import android.media.MediaRecorder
import android.content.pm.PackageManager
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat.requestPermissions
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
Expand All @@ -13,24 +16,23 @@ import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch

val REQUEST_AUDIO = 1

class AudioRecorder(var activity: android.app.Activity, val sampleRate: Int) {
class AudioRecorder(var activity: AppCompatActivity, val sampleRate: Int) {

private var recording = false
private var recorder: AudioRecord? = null
val channelMask = AudioFormat.CHANNEL_IN_MONO
val bufferSize = AudioRecord.getMinBufferSize(sampleRate, channelMask, AudioFormat.ENCODING_PCM_16BIT) * 4

private var permissionGranted: Boolean =
activity.checkSelfPermission(Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED

private var bufferSize: Int? = null

init {
val channelMask = AudioFormat.CHANNEL_IN_MONO

bufferSize = AudioRecord.getMinBufferSize(sampleRate, channelMask, AudioFormat.ENCODING_PCM_16BIT) * 4
val requestPermissionLauncher = activity.registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
if (isGranted) {
buildRecorder()
} else {
throw Exception("Need access to microphone")
}
}

if (permissionGranted) {
fun buildRecorder() {
if (activity.checkSelfPermission(Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) {
recorder = AudioRecord.Builder()
.setAudioSource(MediaRecorder.AudioSource.MIC)
.setAudioFormat(AudioFormat.Builder()
Expand All @@ -41,7 +43,7 @@ class AudioRecorder(var activity: android.app.Activity, val sampleRate: Int) {
.setBufferSizeInBytes(bufferSize!!)
.build()
} else {
activity.requestPermissions(arrayOf<String>(Manifest.permission.RECORD_AUDIO), REQUEST_AUDIO)
requestPermissionLauncher.launch(Manifest.permission.RECORD_AUDIO)
}
}

Expand Down
28 changes: 21 additions & 7 deletions client/src/main/kotlin/com/speechly/client/speech/Client.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package com.speechly.client.speech

import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import com.speechly.client.cache.SharedPreferencesCache
import com.speechly.client.device.CachingIdProvider
import com.speechly.client.device.DeviceIdProvider
Expand Down Expand Up @@ -48,21 +52,32 @@ class Client (

companion object {
fun fromActivity(
activity: android.app.Activity,
activity: AppCompatActivity,
appId: UUID,
language: StreamConfig.LanguageCode = StreamConfig.LanguageCode.EN_US,
target: String = "api.speechly.com",
secure: Boolean = true
): Client {
val cache = SharedPreferencesCache.fromContext(activity.getApplicationContext())
val cachingIdProvider = CachingIdProvider()
val cachingIdentityService = CachingIdentityService.forTarget(target, secure)
val audioRecorder = AudioRecorder(activity, 16000)
activity.lifecycle.addObserver(object : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun connectListener() {
val cache = SharedPreferencesCache.fromContext(activity.getApplicationContext())
cachingIdProvider.cacheService = cache
cachingIdentityService.cacheService = cache
audioRecorder.buildRecorder()
}
})

return Client(
appId,
language,
CachingIdProvider(cache),
CachingIdentityService.forTarget(cache, target, secure),
cachingIdProvider,
cachingIdentityService,
GrpcSluClient.forTarget(target, secure),
AudioRecorder(activity, 16000)
audioRecorder
)
}
}
Expand Down Expand Up @@ -147,8 +162,7 @@ class Client (
streams.add(stream)

var segment: Segment? = null
var contextId: String? = null
GlobalScope.launch(Dispatchers.IO) {
GlobalScope.launch(Dispatchers.Default) {
try {
stream.responseFlow?.collect { response: SLUResponse ->
if (segment == null) {
Expand Down

0 comments on commit 2b09f5c

Please sign in to comment.