package app.grapheneos.setupwizard.view.activity import android.app.Activity import android.content.Intent import android.content.pm.PackageManager import android.os.Bundle import android.os.ServiceManager import android.os.SystemClock import android.text.InputType import android.view.View import android.widget.Button import android.widget.EditText import android.widget.TextView import android.util.Slog import app.grapheneos.setupwizard.R import app.grapheneos.setupwizard.action.SetupWizard import com.android.secureline.ISecureLineManager import java.util.concurrent.Executors class SecureLineActivationActivity : Activity() { private val TAG = "SecureLineActivation" private lateinit var input: EditText private lateinit var button: Button private lateinit var status: TextView private val executor = Executors.newSingleThreadExecutor() private val serviceWaitTimeoutMs = 15000L override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 🔒 Lock system UI window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN setContentView(R.layout.activity_secureline_activation) input = findViewById(R.id.activation_code_input) button = findViewById(R.id.activate_button) status = findViewById(R.id.activation_status) val scanQrText: TextView = findViewById(R.id.scan_qr_text) scanQrText.setOnClickListener { startActivityForResult( Intent( this, SecureLineQrScanActivity::class.java ), 500 ) } button.setOnClickListener { val code = input.text.toString().trim() if (code.length < 8) { showError(getString(R.string.sl_invalid_activation_code)) return@setOnClickListener } activate(code) } } private fun activate(code: String) { lockUi() status.text = getString(R.string.sl_activation_in_progress) executor.execute { try { val binder = waitForSecureLineServiceOrNull( serviceWaitTimeoutMs ) ?: throw IllegalStateException( "SecureLine service not available" ) val service = ISecureLineManager.Stub .asInterface(binder) val ok = service.activate(code) Slog.d(TAG, "SecureLine activate() returned: $ok" ) if (!ok) { runOnUiThread { unlockUi() showError( getString(R.string.sl_activation_failed) ) } return@execute } runOnUiThread { status.text = getString(R.string.sl_activation_successful) SetupWizard.next(this) finish() } } catch (t: Throwable) { Slog.e(TAG, getString(R.string.sl_activation_error), t ) runOnUiThread { unlockUi() showError( if (t is IllegalStateException) getString(R.string.sl_service_unavailable) else getString(R.string.sl_activation_error) ) } } } } private fun waitForSecureLineServiceOrNull(timeoutMs: Long): android.os.IBinder? { val deadline = SystemClock.elapsedRealtime() + timeoutMs while (SystemClock.elapsedRealtime() < deadline) { val binder = ServiceManager.checkService("secureline") if (binder != null) { return binder } SystemClock.sleep(200) } Slog.e(TAG, "Timed out waiting for secureline service after ${timeoutMs}ms") return null } private fun lockUi() { button.isEnabled = false input.isEnabled = false } private fun unlockUi() { button.isEnabled = true input.isEnabled = true } private fun showError(msg: String) { status.text = msg } override fun onBackPressed() { // Activation cannot be skipped } override fun onActivityResult( requestCode: Int, resultCode: Int, data: android.content.Intent? ) { if (requestCode == 500 && resultCode == RESULT_OK) { val code = data?.getStringExtra("activation_code") if (!code.isNullOrBlank()) { input.setText(code) status.text = getString(R.string.sl_qr_scanned) } } } }