Show Stream States nicely on Android

This commit is contained in:
Florian Märkl 2019-09-29 22:24:36 +02:00
commit 88db8c8840
No known key found for this signature in database
GPG key ID: 125BC8A5A6A1E857
12 changed files with 185 additions and 12 deletions

View file

@ -104,6 +104,7 @@ typedef struct android_chiaki_session_t
ChiakiSession session; ChiakiSession session;
jobject java_session; jobject java_session;
jclass java_session_class; jclass java_session_class;
jmethodID java_session_event_connected_meth;
jmethodID java_session_event_login_pin_request_meth; jmethodID java_session_event_login_pin_request_meth;
jmethodID java_session_event_quit_meth; jmethodID java_session_event_quit_meth;
jfieldID java_controller_state_buttons; jfieldID java_controller_state_buttons;
@ -143,6 +144,10 @@ static void android_chiaki_event_cb(ChiakiEvent *event, void *user)
switch(event->type) switch(event->type)
{ {
case CHIAKI_EVENT_CONNECTED:
E->CallVoidMethod(env, session->java_session,
session->java_session_event_connected_meth);
break;
case CHIAKI_EVENT_LOGIN_PIN_REQUEST: case CHIAKI_EVENT_LOGIN_PIN_REQUEST:
E->CallVoidMethod(env, session->java_session, E->CallVoidMethod(env, session->java_session,
session->java_session_event_login_pin_request_meth, session->java_session_event_login_pin_request_meth,
@ -253,6 +258,7 @@ JNIEXPORT void JNICALL Java_com_metallic_chiaki_lib_ChiakiNative_sessionCreate(J
session->java_session = E->NewGlobalRef(env, java_session); session->java_session = E->NewGlobalRef(env, java_session);
session->java_session_class = E->GetObjectClass(env, session->java_session); session->java_session_class = E->GetObjectClass(env, session->java_session);
session->java_session_event_connected_meth = E->GetMethodID(env, session->java_session_class, "eventConnected", "()V");
session->java_session_event_login_pin_request_meth = E->GetMethodID(env, session->java_session_class, "eventLoginPinRequest", "(Z)V"); session->java_session_event_login_pin_request_meth = E->GetMethodID(env, session->java_session_class, "eventLoginPinRequest", "(Z)V");
session->java_session_event_quit_meth = E->GetMethodID(env, session->java_session_class, "eventQuit", "(ILjava/lang/String;)V"); session->java_session_event_quit_meth = E->GetMethodID(env, session->java_session_class, "eventQuit", "(ILjava/lang/String;)V");
@ -332,4 +338,12 @@ JNIEXPORT void JNICALL Java_com_metallic_chiaki_lib_ChiakiNative_sessionSetContr
controller_state.right_x = (int16_t)E->GetShortField(env, controller_state_java, session->java_controller_state_right_x); controller_state.right_x = (int16_t)E->GetShortField(env, controller_state_java, session->java_controller_state_right_x);
controller_state.right_y = (int16_t)E->GetShortField(env, controller_state_java, session->java_controller_state_right_y); controller_state.right_y = (int16_t)E->GetShortField(env, controller_state_java, session->java_controller_state_right_y);
chiaki_session_set_controller_state(&session->session, &controller_state); chiaki_session_set_controller_state(&session->session, &controller_state);
} }
JNIEXPORT void JNICALL Java_com_metallic_chiaki_lib_ChiakiNative_sessionSetLoginPin(JNIEnv *env, jobject obj, jlong ptr, jstring pin_java)
{
AndroidChiakiSession *session = (AndroidChiakiSession *)ptr;
const char *pin = E->GetStringUTFChars(env, pin_java, NULL);
chiaki_session_set_login_pin(&session->session, (const uint8_t *)pin, strlen(pin));
E->ReleaseStringUTFChars(env, pin_java, pin);
}

View file

@ -25,9 +25,12 @@ import android.view.TextureView
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.metallic.chiaki.lib.* import com.metallic.chiaki.lib.*
import java.util.stream.Stream
sealed class StreamState sealed class StreamState
object StreamStateIdle: StreamState() object StreamStateIdle: StreamState()
object StreamStateConnecting: StreamState()
object StreamStateConnected: StreamState()
data class StreamStateCreateError(val error: SessionCreateError): StreamState() data class StreamStateCreateError(val error: SessionCreateError): StreamState()
data class StreamStateQuit(val reason: QuitReason, val reasonString: String?): StreamState() data class StreamStateQuit(val reason: QuitReason, val reasonString: String?): StreamState()
data class StreamStateLoginPinRequest(val pinIncorrect: Boolean): StreamState() data class StreamStateLoginPinRequest(val pinIncorrect: Boolean): StreamState()
@ -51,6 +54,7 @@ class StreamSession(val connectInfo: ConnectInfo)
session?.stop() session?.stop()
session?.dispose() session?.dispose()
session = null session = null
_state.value = StreamStateIdle
//surfaceTexture?.release() //surfaceTexture?.release()
} }
@ -66,6 +70,7 @@ class StreamSession(val connectInfo: ConnectInfo)
try try
{ {
val session = Session(connectInfo) val session = Session(connectInfo)
_state.value = StreamStateConnecting
session.eventCallback = this::eventCallback session.eventCallback = this::eventCallback
session.start() session.start()
val surfaceTexture = surfaceTexture val surfaceTexture = surfaceTexture
@ -83,6 +88,7 @@ class StreamSession(val connectInfo: ConnectInfo)
{ {
when(event) when(event)
{ {
is ConnectedEvent -> _state.postValue(StreamStateConnected)
is QuitEvent -> _state.postValue(StreamStateQuit(event.reason, event.reasonString)) is QuitEvent -> _state.postValue(StreamStateQuit(event.reason, event.reasonString))
is LoginPinRequestEvent -> _state.postValue(StreamStateLoginPinRequest(event.pinIncorrect)) is LoginPinRequestEvent -> _state.postValue(StreamStateLoginPinRequest(event.pinIncorrect))
} }
@ -114,6 +120,11 @@ class StreamSession(val connectInfo: ConnectInfo)
textureView.surfaceTexture = surfaceTexture textureView.surfaceTexture = surfaceTexture
} }
fun setLoginPin(pin: String)
{
session?.setLoginPin(pin)
}
@ExperimentalUnsignedTypes @ExperimentalUnsignedTypes
fun dispatchKeyEvent(event: KeyEvent): Boolean fun dispatchKeyEvent(event: KeyEvent): Boolean
{ {

View file

@ -40,6 +40,7 @@ class ChiakiNative
@JvmStatic external fun sessionJoin(ptr: Long): Int @JvmStatic external fun sessionJoin(ptr: Long): Int
@JvmStatic external fun sessionSetSurface(ptr: Long, surface: Surface) @JvmStatic external fun sessionSetSurface(ptr: Long, surface: Surface)
@JvmStatic external fun sessionSetControllerState(ptr: Long, controllerState: ControllerState) @JvmStatic external fun sessionSetControllerState(ptr: Long, controllerState: ControllerState)
@JvmStatic external fun sessionSetLoginPin(ptr: Long, pin: String)
} }
} }
@ -98,6 +99,7 @@ class QuitReason(val value: Int)
} }
sealed class Event sealed class Event
object ConnectedEvent: Event()
data class LoginPinRequestEvent(val pinIncorrect: Boolean): Event() data class LoginPinRequestEvent(val pinIncorrect: Boolean): Event()
data class QuitEvent(val reason: QuitReason, val reasonString: String?): Event() data class QuitEvent(val reason: QuitReason, val reasonString: String?): Event()
@ -140,6 +142,11 @@ class Session(connectInfo: ConnectInfo)
eventCallback?.let { it(event) } eventCallback?.let { it(event) }
} }
private fun eventConnected()
{
event(ConnectedEvent)
}
private fun eventLoginPinRequest(pinIncorrect: Boolean) private fun eventLoginPinRequest(pinIncorrect: Boolean)
{ {
event(LoginPinRequestEvent(pinIncorrect)) event(LoginPinRequestEvent(pinIncorrect))
@ -159,4 +166,9 @@ class Session(connectInfo: ConnectInfo)
{ {
ChiakiNative.sessionSetControllerState(nativePtr, controllerState) ChiakiNative.sessionSetControllerState(nativePtr, controllerState)
} }
fun setLoginPin(pin: String)
{
ChiakiNative.sessionSetLoginPin(nativePtr, pin)
}
} }

View file

@ -17,19 +17,30 @@
package com.metallic.chiaki.stream package com.metallic.chiaki.stream
import android.app.AlertDialog
import android.app.Dialog
import android.content.DialogInterface
import android.graphics.Matrix import android.graphics.Matrix
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.KeyEvent import android.view.KeyEvent
import android.view.View import android.view.View
import android.widget.EditText
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.* import androidx.lifecycle.*
import com.metallic.chiaki.*
import com.metallic.chiaki.R import com.metallic.chiaki.R
import com.metallic.chiaki.StreamStateIdle
import com.metallic.chiaki.lib.ConnectInfo import com.metallic.chiaki.lib.ConnectInfo
import com.metallic.chiaki.lib.LoginPinRequestEvent
import com.metallic.chiaki.touchcontrols.TouchControlsFragment import com.metallic.chiaki.touchcontrols.TouchControlsFragment
import kotlinx.android.synthetic.main.activity_stream.* import kotlinx.android.synthetic.main.activity_stream.*
private sealed class DialogContents
private object StreamQuitDialog: DialogContents()
private object CreateErrorDialog: DialogContents()
private object PinRequestDialog: DialogContents()
@ExperimentalUnsignedTypes @ExperimentalUnsignedTypes
class StreamActivity : AppCompatActivity() class StreamActivity : AppCompatActivity()
{ {
@ -60,9 +71,7 @@ class StreamActivity : AppCompatActivity()
viewModel.session.attachToTextureView(textureView) viewModel.session.attachToTextureView(textureView)
viewModel.session.state.observe(this, Observer { viewModel.session.state.observe(this, Observer { this.stateChanged(it) })
stateTextView.text = if(it != StreamStateIdle) "$it" else ""
})
textureView.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> textureView.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
adjustTextureViewAspect() adjustTextureViewAspect()
@ -89,6 +98,12 @@ class StreamActivity : AppCompatActivity()
viewModel.session.pause() viewModel.session.pause()
} }
private fun reconnect()
{
viewModel.session.shutdown()
viewModel.session.resume()
}
override fun onWindowFocusChanged(hasFocus: Boolean) override fun onWindowFocusChanged(hasFocus: Boolean)
{ {
super.onWindowFocusChanged(hasFocus) super.onWindowFocusChanged(hasFocus)
@ -106,6 +121,92 @@ class StreamActivity : AppCompatActivity()
or View.SYSTEM_UI_FLAG_FULLSCREEN) or View.SYSTEM_UI_FLAG_FULLSCREEN)
} }
private var dialogContents: DialogContents? = null
private var dialog: AlertDialog? = null
set(value)
{
field = value
if(value == null)
dialogContents = null
}
private fun stateChanged(state: StreamState)
{
progressBar.visibility = if(state == StreamStateConnecting) View.VISIBLE else View.GONE
when(state)
{
is StreamStateQuit ->
{
if(dialogContents != StreamQuitDialog)
{
dialog?.dismiss()
val reasonStr = state.reasonString
val dialog = AlertDialog.Builder(this)
.setMessage(getString(R.string.alert_message_session_quit, state.reason.toString())
+ (if(reasonStr != null) "\n$reasonStr" else ""))
.setPositiveButton(R.string.action_reconnect) { _, _ ->
dialog = null
reconnect()
}
.setNegativeButton(R.string.action_quit_session) { _, _ ->
dialog = null
finish()
}
.create()
dialogContents = StreamQuitDialog
dialog.show()
}
}
is StreamStateCreateError ->
{
if(dialogContents != CreateErrorDialog)
{
dialog?.dismiss()
val dialog = AlertDialog.Builder(this)
.setMessage(getString(R.string.alert_message_session_create_error, state.error.errorCode.toString()))
.setNegativeButton(R.string.action_quit_session) { _, _ ->
dialog = null
finish()
}
.create()
dialogContents = CreateErrorDialog
dialog.show()
}
}
is StreamStateLoginPinRequest ->
{
if(dialogContents != PinRequestDialog)
{
dialog?.dismiss()
val view = layoutInflater.inflate(R.layout.dialog_login_pin, null)
val pinEditText = view.findViewById<EditText>(R.id.pinEditText)
val dialog = AlertDialog.Builder(this)
.setMessage(
if(state.pinIncorrect)
R.string.alert_message_login_pin_request_incorrect
else
R.string.alert_message_login_pin_request)
.setView(view)
.setPositiveButton(R.string.action_login_pin_connect) { _, _ ->
this.dialog = null
viewModel.session.setLoginPin(pinEditText.text.toString())
}
.setNegativeButton(R.string.action_quit_session) { _, _ ->
this.dialog = null
finish()
}
.create()
dialogContents = PinRequestDialog
dialog.show()
}
}
}
}
private fun adjustTextureViewAspect() private fun adjustTextureViewAspect()
{ {

View file

@ -21,6 +21,7 @@ import androidx.lifecycle.ViewModel
import com.metallic.chiaki.StreamSession import com.metallic.chiaki.StreamSession
import com.metallic.chiaki.lib.* import com.metallic.chiaki.lib.*
@ExperimentalUnsignedTypes
class StreamViewModel: ViewModel() class StreamViewModel: ViewModel()
{ {
private var connectInfo: ConnectInfo? = null private var connectInfo: ConnectInfo? = null

View file

@ -11,10 +11,13 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"/> android:layout_height="match_parent"/>
<TextView <ProgressBar
android:id="@+id/stateTextView" android:id="@+id/progressBar"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/> app:layout_constraintBottom_toBottomOf="parent"/>
<fragment <fragment

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp">
<EditText
android:id="@+id/pinEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="numberPassword"
android:singleLine="false" />
</LinearLayout>

View file

@ -1,3 +1,10 @@
<resources> <resources>
<string name="app_name">Chiaki</string> <string name="app_name">Chiaki</string>
<string name="alert_message_session_quit">Session has quit: %s</string>
<string name="alert_message_session_create_error">Failed to create Session: %s</string>
<string name="alert_message_login_pin_request">Login PIN:</string>
<string name="alert_message_login_pin_request_incorrect">Entered PIN was incorrect!\nLogin PIN:</string>
<string name="action_reconnect">Reconnect</string>
<string name="action_quit_session">Quit</string>
<string name="action_login_pin_connect">Connect</string>
</resources> </resources>

View file

@ -10,6 +10,5 @@
<item name="colorPrimaryDark">@color/primary_dark</item> <item name="colorPrimaryDark">@color/primary_dark</item>
<item name="colorAccent">@color/accent</item> <item name="colorAccent">@color/accent</item>
<item name="android:windowBackground">@color/stream_background</item> <item name="android:windowBackground">@color/stream_background</item>
<item name="android:textColor">@color/stream_text</item>
</style> </style>
</resources> </resources>

View file

@ -111,6 +111,7 @@ typedef struct chiaki_audio_stream_info_event_t
typedef enum { typedef enum {
CHIAKI_EVENT_CONNECTED,
CHIAKI_EVENT_LOGIN_PIN_REQUEST, CHIAKI_EVENT_LOGIN_PIN_REQUEST,
CHIAKI_EVENT_QUIT CHIAKI_EVENT_QUIT
} ChiakiEventType; } ChiakiEventType;

View file

@ -276,7 +276,7 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_session_set_login_pin(ChiakiSession *sessio
return CHIAKI_ERR_SUCCESS; return CHIAKI_ERR_SUCCESS;
} }
static void session_send_event(ChiakiSession *session, ChiakiEvent *event) void chiaki_session_send_event(ChiakiSession *session, ChiakiEvent *event)
{ {
if(!session->event_cb) if(!session->event_cb)
return; return;
@ -376,7 +376,7 @@ static void *session_thread_func(void *arg)
ChiakiEvent event = { 0 }; ChiakiEvent event = { 0 };
event.type = CHIAKI_EVENT_LOGIN_PIN_REQUEST; event.type = CHIAKI_EVENT_LOGIN_PIN_REQUEST;
event.login_pin_request.pin_incorrect = pin_incorrect; event.login_pin_request.pin_incorrect = pin_incorrect;
session_send_event(session, &event); chiaki_session_send_event(session, &event);
pin_incorrect = true; pin_incorrect = true;
chiaki_cond_timedwait_pred(&session->state_cond, &session->state_mutex, UINT64_MAX, session_check_state_pred_pin, session); chiaki_cond_timedwait_pred(&session->state_cond, &session->state_mutex, UINT64_MAX, session_check_state_pred_pin, session);
@ -502,7 +502,7 @@ quit:
quit_event.type = CHIAKI_EVENT_QUIT; quit_event.type = CHIAKI_EVENT_QUIT;
quit_event.quit.reason = session->quit_reason; quit_event.quit.reason = session->quit_reason;
quit_event.quit.reason_str = session->quit_reason_str; quit_event.quit.reason_str = session->quit_reason_str;
session_send_event(session, &quit_event); chiaki_session_send_event(session, &quit_event);
return NULL; return NULL;
#undef CHECK_STOP #undef CHECK_STOP

View file

@ -55,6 +55,7 @@ typedef enum {
STATE_EXPECT_STREAMINFO STATE_EXPECT_STREAMINFO
} StreamConnectionState; } StreamConnectionState;
void chiaki_session_send_event(ChiakiSession *session, ChiakiEvent *event);
static void stream_connection_takion_cb(ChiakiTakionEvent *event, void *user); static void stream_connection_takion_cb(ChiakiTakionEvent *event, void *user);
static void stream_connection_takion_data(ChiakiStreamConnection *stream_connection, ChiakiTakionMessageDataType data_type, uint8_t *buf, size_t buf_size); static void stream_connection_takion_data(ChiakiStreamConnection *stream_connection, ChiakiTakionMessageDataType data_type, uint8_t *buf, size_t buf_size);
@ -243,6 +244,14 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_stream_connection_run(ChiakiStreamConnectio
stream_connection->state = STATE_IDLE; stream_connection->state = STATE_IDLE;
stream_connection->state_finished = false; stream_connection->state_finished = false;
stream_connection->state_failed = false; stream_connection->state_failed = false;
ChiakiEvent event = { 0 };
event.type = CHIAKI_EVENT_CONNECTED;
chiaki_mutex_unlock(&stream_connection->state_mutex);
chiaki_session_send_event(session, &event);
err = chiaki_mutex_lock(&stream_connection->state_mutex);
assert(err == CHIAKI_ERR_SUCCESS);
while(true) while(true)
{ {
err = chiaki_cond_timedwait_pred(&stream_connection->state_cond, &stream_connection->state_mutex, HEARTBEAT_INTERVAL_MS, state_finished_cond_check, stream_connection); err = chiaki_cond_timedwait_pred(&stream_connection->state_cond, &stream_connection->state_mutex, HEARTBEAT_INTERVAL_MS, state_finished_cond_check, stream_connection);