diff --git a/android/app/src/main/cpp/chiaki-jni.c b/android/app/src/main/cpp/chiaki-jni.c index 6c29f17..bb426ff 100644 --- a/android/app/src/main/cpp/chiaki-jni.c +++ b/android/app/src/main/cpp/chiaki-jni.c @@ -104,6 +104,7 @@ typedef struct android_chiaki_session_t ChiakiSession session; jobject java_session; jclass java_session_class; + jmethodID java_session_event_connected_meth; jmethodID java_session_event_login_pin_request_meth; jmethodID java_session_event_quit_meth; jfieldID java_controller_state_buttons; @@ -143,6 +144,10 @@ static void android_chiaki_event_cb(ChiakiEvent *event, void *user) 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: E->CallVoidMethod(env, session->java_session, 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_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_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_y = (int16_t)E->GetShortField(env, controller_state_java, session->java_controller_state_right_y); chiaki_session_set_controller_state(&session->session, &controller_state); -} \ No newline at end of file +} + +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); +} diff --git a/android/app/src/main/java/com/metallic/chiaki/StreamSession.kt b/android/app/src/main/java/com/metallic/chiaki/StreamSession.kt index f5db53f..f0413ac 100644 --- a/android/app/src/main/java/com/metallic/chiaki/StreamSession.kt +++ b/android/app/src/main/java/com/metallic/chiaki/StreamSession.kt @@ -25,9 +25,12 @@ import android.view.TextureView import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import com.metallic.chiaki.lib.* +import java.util.stream.Stream sealed class StreamState object StreamStateIdle: StreamState() +object StreamStateConnecting: StreamState() +object StreamStateConnected: StreamState() data class StreamStateCreateError(val error: SessionCreateError): StreamState() data class StreamStateQuit(val reason: QuitReason, val reasonString: String?): StreamState() data class StreamStateLoginPinRequest(val pinIncorrect: Boolean): StreamState() @@ -51,6 +54,7 @@ class StreamSession(val connectInfo: ConnectInfo) session?.stop() session?.dispose() session = null + _state.value = StreamStateIdle //surfaceTexture?.release() } @@ -66,6 +70,7 @@ class StreamSession(val connectInfo: ConnectInfo) try { val session = Session(connectInfo) + _state.value = StreamStateConnecting session.eventCallback = this::eventCallback session.start() val surfaceTexture = surfaceTexture @@ -83,6 +88,7 @@ class StreamSession(val connectInfo: ConnectInfo) { when(event) { + is ConnectedEvent -> _state.postValue(StreamStateConnected) is QuitEvent -> _state.postValue(StreamStateQuit(event.reason, event.reasonString)) is LoginPinRequestEvent -> _state.postValue(StreamStateLoginPinRequest(event.pinIncorrect)) } @@ -114,6 +120,11 @@ class StreamSession(val connectInfo: ConnectInfo) textureView.surfaceTexture = surfaceTexture } + fun setLoginPin(pin: String) + { + session?.setLoginPin(pin) + } + @ExperimentalUnsignedTypes fun dispatchKeyEvent(event: KeyEvent): Boolean { diff --git a/android/app/src/main/java/com/metallic/chiaki/lib/Chiaki.kt b/android/app/src/main/java/com/metallic/chiaki/lib/Chiaki.kt index 4084e5b..145f7d2 100644 --- a/android/app/src/main/java/com/metallic/chiaki/lib/Chiaki.kt +++ b/android/app/src/main/java/com/metallic/chiaki/lib/Chiaki.kt @@ -40,6 +40,7 @@ class ChiakiNative @JvmStatic external fun sessionJoin(ptr: Long): Int @JvmStatic external fun sessionSetSurface(ptr: Long, surface: Surface) @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 +object ConnectedEvent: Event() data class LoginPinRequestEvent(val pinIncorrect: Boolean): Event() data class QuitEvent(val reason: QuitReason, val reasonString: String?): Event() @@ -140,6 +142,11 @@ class Session(connectInfo: ConnectInfo) eventCallback?.let { it(event) } } + private fun eventConnected() + { + event(ConnectedEvent) + } + private fun eventLoginPinRequest(pinIncorrect: Boolean) { event(LoginPinRequestEvent(pinIncorrect)) @@ -159,4 +166,9 @@ class Session(connectInfo: ConnectInfo) { ChiakiNative.sessionSetControllerState(nativePtr, controllerState) } + + fun setLoginPin(pin: String) + { + ChiakiNative.sessionSetLoginPin(nativePtr, pin) + } } \ No newline at end of file diff --git a/android/app/src/main/java/com/metallic/chiaki/stream/StreamActivity.kt b/android/app/src/main/java/com/metallic/chiaki/stream/StreamActivity.kt index b6cce7e..e1810b0 100644 --- a/android/app/src/main/java/com/metallic/chiaki/stream/StreamActivity.kt +++ b/android/app/src/main/java/com/metallic/chiaki/stream/StreamActivity.kt @@ -17,19 +17,30 @@ package com.metallic.chiaki.stream +import android.app.AlertDialog +import android.app.Dialog +import android.content.DialogInterface import android.graphics.Matrix import android.os.Bundle +import android.util.Log import android.view.KeyEvent import android.view.View +import android.widget.EditText import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import androidx.lifecycle.* +import com.metallic.chiaki.* import com.metallic.chiaki.R -import com.metallic.chiaki.StreamStateIdle import com.metallic.chiaki.lib.ConnectInfo +import com.metallic.chiaki.lib.LoginPinRequestEvent import com.metallic.chiaki.touchcontrols.TouchControlsFragment import kotlinx.android.synthetic.main.activity_stream.* +private sealed class DialogContents +private object StreamQuitDialog: DialogContents() +private object CreateErrorDialog: DialogContents() +private object PinRequestDialog: DialogContents() + @ExperimentalUnsignedTypes class StreamActivity : AppCompatActivity() { @@ -60,9 +71,7 @@ class StreamActivity : AppCompatActivity() viewModel.session.attachToTextureView(textureView) - viewModel.session.state.observe(this, Observer { - stateTextView.text = if(it != StreamStateIdle) "$it" else "" - }) + viewModel.session.state.observe(this, Observer { this.stateChanged(it) }) textureView.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> adjustTextureViewAspect() @@ -89,6 +98,12 @@ class StreamActivity : AppCompatActivity() viewModel.session.pause() } + private fun reconnect() + { + viewModel.session.shutdown() + viewModel.session.resume() + } + override fun onWindowFocusChanged(hasFocus: Boolean) { super.onWindowFocusChanged(hasFocus) @@ -106,6 +121,92 @@ class StreamActivity : AppCompatActivity() 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(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() { diff --git a/android/app/src/main/java/com/metallic/chiaki/stream/StreamViewModel.kt b/android/app/src/main/java/com/metallic/chiaki/stream/StreamViewModel.kt index aa0300d..9e69579 100644 --- a/android/app/src/main/java/com/metallic/chiaki/stream/StreamViewModel.kt +++ b/android/app/src/main/java/com/metallic/chiaki/stream/StreamViewModel.kt @@ -21,6 +21,7 @@ import androidx.lifecycle.ViewModel import com.metallic.chiaki.StreamSession import com.metallic.chiaki.lib.* +@ExperimentalUnsignedTypes class StreamViewModel: ViewModel() { private var connectInfo: ConnectInfo? = null diff --git a/android/app/src/main/res/layout/activity_stream.xml b/android/app/src/main/res/layout/activity_stream.xml index aec07ee..69d5e99 100644 --- a/android/app/src/main/res/layout/activity_stream.xml +++ b/android/app/src/main/res/layout/activity_stream.xml @@ -11,10 +11,13 @@ android:layout_width="match_parent" android:layout_height="match_parent"/> - + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 20eac75..6c56835 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -1,3 +1,10 @@ Chiaki + Session has quit: %s + Failed to create Session: %s + Login PIN: + Entered PIN was incorrect!\nLogin PIN: + Reconnect + Quit + Connect diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index d0481c0..b6460af 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -10,6 +10,5 @@ @color/primary_dark @color/accent @color/stream_background - @color/stream_text diff --git a/lib/include/chiaki/session.h b/lib/include/chiaki/session.h index 45cb596..0cd52c0 100644 --- a/lib/include/chiaki/session.h +++ b/lib/include/chiaki/session.h @@ -111,6 +111,7 @@ typedef struct chiaki_audio_stream_info_event_t typedef enum { + CHIAKI_EVENT_CONNECTED, CHIAKI_EVENT_LOGIN_PIN_REQUEST, CHIAKI_EVENT_QUIT } ChiakiEventType; diff --git a/lib/src/session.c b/lib/src/session.c index 83d3cf1..af7a1d9 100644 --- a/lib/src/session.c +++ b/lib/src/session.c @@ -276,7 +276,7 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_session_set_login_pin(ChiakiSession *sessio 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) return; @@ -376,7 +376,7 @@ static void *session_thread_func(void *arg) ChiakiEvent event = { 0 }; event.type = CHIAKI_EVENT_LOGIN_PIN_REQUEST; event.login_pin_request.pin_incorrect = pin_incorrect; - session_send_event(session, &event); + chiaki_session_send_event(session, &event); pin_incorrect = true; 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.quit.reason = session->quit_reason; quit_event.quit.reason_str = session->quit_reason_str; - session_send_event(session, &quit_event); + chiaki_session_send_event(session, &quit_event); return NULL; #undef CHECK_STOP diff --git a/lib/src/streamconnection.c b/lib/src/streamconnection.c index 8b3e404..fde546c 100644 --- a/lib/src/streamconnection.c +++ b/lib/src/streamconnection.c @@ -55,6 +55,7 @@ typedef enum { STATE_EXPECT_STREAMINFO } 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_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_finished = 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) { err = chiaki_cond_timedwait_pred(&stream_connection->state_cond, &stream_connection->state_mutex, HEARTBEAT_INTERVAL_MS, state_finished_cond_check, stream_connection);