Skip to content

Commit

Permalink
Working Wear remote PTT (mic/speaker is currently still phone only)
Browse files Browse the repository at this point in the history
Add wear/AlfredAiApp, consistent ViewModel init in App.onCreate
  • Loading branch information
paulpv committed Jan 26, 2025
1 parent d7d9a81 commit ff50836
Show file tree
Hide file tree
Showing 13 changed files with 241 additions and 46 deletions.
3 changes: 3 additions & 0 deletions mobile/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AlfredAI"
android:enableOnBackInvokedCallback="true"
>

<activity
android:name=".MobileActivity"
android:exported="true"
Expand All @@ -28,6 +30,7 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

</application>

</manifest>
31 changes: 30 additions & 1 deletion mobile/src/main/java/com/swooby/alfredai/AlfredAiApp.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,42 @@
package com.swooby.alfredai

import android.app.Application
import androidx.activity.ComponentActivity
import androidx.annotation.MainThread
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelLazy
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner

@MainThread
inline fun <reified VM : ViewModel> ComponentActivity.appViewModels(): Lazy<VM> {
return ViewModelLazy(
VM::class,
{ (application as AlfredAiApp).viewModelStore },
{ ViewModelProvider.AndroidViewModelFactory.getInstance(application) },
{ defaultViewModelCreationExtras }
)
}

class AlfredAiApp : Application(), ViewModelStoreOwner
{
override val viewModelStore = ViewModelStore()

val mobileViewModel by lazy {
ViewModelProvider(
this,
ViewModelProvider.AndroidViewModelFactory.getInstance(this)
)[MobileViewModel::class.java]
}

class AlfredAiApp : Application() {
override fun onCreate() {
super.onCreate()
mobileViewModel.init()
}

override fun onTerminate() {
super.onTerminate()
mobileViewModel.close()
}
}
28 changes: 21 additions & 7 deletions mobile/src/main/java/com/swooby/alfredai/MobileActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
Expand Down Expand Up @@ -122,15 +123,14 @@ class MobileActivity : ComponentActivity() {
private const val TAG = "PushToTalkActivity"
}

private val mobileViewModel: MobileViewModel by viewModels()
private val mobileViewModel: MobileViewModel by appViewModels()

override fun onCreate(savedInstanceState: Bundle?) {
Log.d(TAG, "onCreate()")
super.onCreate(savedInstanceState)

enableEdgeToEdge()
setContent {
PushToTalkScreen(mobileViewModel)
MobileApp(mobileViewModel)
}
}

Expand All @@ -150,9 +150,9 @@ enum class ConversationSpeaker {

@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable
fun PushToTalkScreen(mobileViewModel: MobileViewModel? = null) {
fun MobileApp(mobileViewModel: MobileViewModel? = null) {
@Suppress("LocalVariableName")
val TAG = "PushToTalkScreen"
val TAG = "MobileApp"

@Suppress(
"SimplifyBooleanWithConstants",
Expand Down Expand Up @@ -973,6 +973,9 @@ fun PushToTalkScreen(mobileViewModel: MobileViewModel? = null) {
,
contentAlignment = Alignment.Center
) {
if (isConnected) {
KeepScreenOnComposable()
}
Box {
when {
isConnected -> {
Expand Down Expand Up @@ -1086,13 +1089,24 @@ fun PushToTalkScreen(mobileViewModel: MobileViewModel? = null) {
//
}

@Composable
fun KeepScreenOnComposable() {
val view = LocalView.current
DisposableEffect(Unit) {
view.keepScreenOn = true
onDispose {
view.keepScreenOn = false
}
}
}

@Preview(
uiMode = Configuration.UI_MODE_NIGHT_NO,
showBackground = true
)
@Composable
fun PushToTalkButtonActivityPreviewLight() {
PushToTalkScreen()
MobileApp()
}

@Preview(
Expand All @@ -1101,5 +1115,5 @@ fun PushToTalkButtonActivityPreviewLight() {
)
@Composable
fun PushToTalkButtonActivityPreviewDark() {
PushToTalkScreen()
MobileApp()
}
35 changes: 33 additions & 2 deletions mobile/src/main/java/com/swooby/alfredai/MobileViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class MobileViewModel(application: Application) :
override val remoteTypeName: String
get() = "MOBILE"
override val remoteCapabilityName: String
get() = "verify_remote_example_wear_app"
get() = "verify_remote_alfredai_wear_app"

private val prefs = PushToTalkPreferences(application)

Expand Down Expand Up @@ -231,7 +231,38 @@ class MobileViewModel(application: Application) :
}

override fun pushToTalk(on: Boolean, sourceNodeId: String?) {
TODO("Not yet implemented")
Log.i(TAG, "pushToTalk(on=$on)")
if (on) {
if (pushToTalkState.value != PttState.Pressed) {
setPushToTalkState(PttState.Pressed)
playAudioResourceOnce(getApplication(), R.raw.quindar_nasa_apollo_intro)
//provideHapticFeedback(context)
}
} else {
if (pushToTalkState.value != PttState.Idle) {
setPushToTalkState(PttState.Idle)
playAudioResourceOnce(getApplication(), R.raw.quindar_nasa_apollo_outro)
//provideHapticFeedback(context)
}
}

if (sourceNodeId == null) {
// request from local/mobile
Log.d(TAG, "pushToTalk: PTT $on **from** local/mobile...")
val remoteAppNodeId = remoteAppNodeId.value
if (remoteAppNodeId != null) {
// tell remote/wear app that we are PTTing...
sendPushToTalkCommand(remoteAppNodeId, on)
}
//...
} else {
// request from remote/wear
//_remoteAppNodeId.value = sourceNodeId
Log.d(TAG, "pushToTalk: PTT $on **from** remote/wear...")
//...
}

pushToTalkLocal(on)
}

override fun pushToTalkLocal(on: Boolean) {
Expand Down
2 changes: 1 addition & 1 deletion mobile/src/main/res/values/wear.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
tools:keep="@array/android_wear_capabilities"
>
<string-array name="android_wear_capabilities">
<item>verify_remote_example_phone_app</item>
<item>verify_remote_alfredai_mobile_app</item>
</string-array>
</resources>
35 changes: 22 additions & 13 deletions shared/src/main/java/com/swooby/alfredai/SharedViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,18 @@ abstract class SharedViewModel(application: Application) :
protected abstract val remoteTypeName: String
protected abstract val remoteCapabilityName: String

protected var _remoteAppNodeId = MutableStateFlow<String?>(null) // private mutable
private fun setRemoteAppNodeId(nodeId: String?) {
Log.i(TAG, "setRemoteAppNodeId(nodeId=${quote(nodeId)})")
_remoteAppNodeId.value = nodeId
}
private var _remoteAppNodeId = MutableStateFlow<String?>(null)
val remoteAppNodeId = _remoteAppNodeId.asStateFlow() // public readonly

protected var _pushToTalkState = MutableStateFlow(PttState.Idle) // private mutable
protected fun setPushToTalkState(state: PttState) {
Log.i(TAG, "setPushToTalkState(state=$state)")
_pushToTalkState.value = state
}
private var _pushToTalkState = MutableStateFlow(PttState.Idle)
val pushToTalkState = _pushToTalkState.asStateFlow() // public readonly

private val capabilityClient by lazy { Wearable.getCapabilityClient(application) }
Expand Down Expand Up @@ -79,8 +87,8 @@ abstract class SharedViewModel(application: Application) :
return messageClient
.sendMessage(nodeId, path, data)
.addOnFailureListener { e ->
Log.e(TAG, "sendMessageToNode: Message failed.", e)
_remoteAppNodeId.value = null
Log.e(TAG, "sendMessageToNode: Message failed", e)
setRemoteAppNodeId(null)
}
}

Expand All @@ -99,8 +107,8 @@ abstract class SharedViewModel(application: Application) :

private fun handlePingCommand(messageEvent: MessageEvent) {
val nodeId = messageEvent.sourceNodeId
_remoteAppNodeId.value = nodeId
Log.i(TAG, "handlePingCommand: Got ping request from $remoteTypeName app nodeId=${quote(nodeId)}; Responding pong...")
Log.i(TAG, "handlePingCommand: Ping request from $remoteTypeName app nodeId=${quote(nodeId)}; Responding pong...")
setRemoteAppNodeId(nodeId)
sendPongCommand(nodeId)
}

Expand All @@ -110,8 +118,8 @@ abstract class SharedViewModel(application: Application) :

private fun handlePongCommand(messageEvent: MessageEvent) {
val nodeId = messageEvent.sourceNodeId
_remoteAppNodeId.value = nodeId
Log.i(TAG, "handlePongCommand: Got pong response from MOBILE app nodeId=${quote(nodeId)}")
Log.i(TAG, "handlePongCommand: Pong response from $remoteTypeName app nodeId=${quote(nodeId)}")
setRemoteAppNodeId(nodeId)
}

protected fun sendPushToTalkCommand(nodeId: String, on: Boolean): Task<Int> {
Expand All @@ -121,12 +129,13 @@ abstract class SharedViewModel(application: Application) :
}

private fun handlePushToTalkCommand(messageEvent: MessageEvent) {
val payload = messageEvent.data
val payloadString = String(payload)
Log.i(TAG, "handlePushToTalkCommand: PushToTalk command received! payloadString=${quote(payloadString)}")
val nodeId = messageEvent.sourceNodeId
val payloadString = String(messageEvent.data)
Log.i(TAG, "handlePushToTalkCommand: PushToTalk request from $remoteTypeName app nodeId=${quote(nodeId)}! payloadString=${quote(payloadString)}")
setRemoteAppNodeId(nodeId)
when (payloadString) {
"on" -> pushToTalk(true, sourceNodeId = messageEvent.sourceNodeId)
"off" -> pushToTalk(false, sourceNodeId = messageEvent.sourceNodeId)
"on" -> pushToTalk(true, sourceNodeId = nodeId)
"off" -> pushToTalk(false, sourceNodeId = nodeId)
}
}

Expand Down
11 changes: 7 additions & 4 deletions wear/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@
<application
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:name=".AlfredAiApp"
android:supportsRtl="true"
android:theme="@android:style/Theme.DeviceDefault">
android:theme="@android:style/Theme.DeviceDefault"
>

<service
android:name=".complication.MainComplicationService"
android:exported="true"
Expand All @@ -21,7 +24,6 @@
<intent-filter>
<action android:name="android.support.wearable.complications.ACTION_COMPLICATION_UPDATE_REQUEST" />
</intent-filter>

<meta-data
android:name="android.support.wearable.complications.SUPPORTED_TYPES"
android:value="SHORT_TEXT" />
Expand All @@ -37,7 +39,6 @@
<intent-filter>
<action android:name="androidx.wear.tiles.action.BIND_TILE_PROVIDER" />
</intent-filter>

<meta-data
android:name="androidx.wear.tiles.PREVIEW"
android:resource="@drawable/tile_preview" />
Expand All @@ -58,12 +59,14 @@
android:name=".presentation.WearActivity"
android:exported="true"
android:taskAffinity=""
android:theme="@style/MainActivityTheme.Starting">
android:theme="@style/MainActivityTheme.Starting"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

</application>

</manifest>
41 changes: 41 additions & 0 deletions wear/src/main/java/com/swooby/alfredai/AlfredAiApp.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.swooby.alfredai

import android.app.Application
import androidx.activity.ComponentActivity
import androidx.annotation.MainThread
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelLazy
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner

@MainThread
inline fun <reified VM : ViewModel> ComponentActivity.appViewModels(): Lazy<VM> {
return ViewModelLazy(
VM::class,
{ (application as AlfredAiApp).viewModelStore },
{ ViewModelProvider.AndroidViewModelFactory.getInstance(application) },
{ defaultViewModelCreationExtras }
)
}

class AlfredAiApp : Application(), ViewModelStoreOwner {
override val viewModelStore = ViewModelStore()

val wearViewModel by lazy {
ViewModelProvider(
this,
ViewModelProvider.AndroidViewModelFactory.getInstance(this)
)[WearViewModel::class.java]
}

override fun onCreate() {
super.onCreate()
wearViewModel.init()
}

override fun onTerminate() {
super.onTerminate()
wearViewModel.close()
}
}
Loading

0 comments on commit ff50836

Please sign in to comment.