diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 75889c32d3..6e01b1c790 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -36,6 +36,15 @@
+
+
+
diff --git a/android/app/src/main/java/com/openai/codex/agent/CreateSessionActivity.kt b/android/app/src/main/java/com/openai/codex/agent/CreateSessionActivity.kt
new file mode 100644
index 0000000000..95c9a4cafc
--- /dev/null
+++ b/android/app/src/main/java/com/openai/codex/agent/CreateSessionActivity.kt
@@ -0,0 +1,470 @@
+package com.openai.codex.agent
+
+import android.app.Activity
+import android.app.agent.AgentManager
+import android.app.agent.AgentSessionInfo
+import android.content.Context
+import android.content.Intent
+import android.graphics.drawable.Drawable
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ArrayAdapter
+import android.widget.Button
+import android.widget.EditText
+import android.widget.ImageView
+import android.widget.Spinner
+import android.widget.TextView
+import android.widget.Toast
+import com.openai.codex.bridge.SessionExecutionSettings
+import kotlin.concurrent.thread
+
+class CreateSessionActivity : Activity() {
+ companion object {
+ private const val TAG = "CodexCreateSession"
+ private const val EXTRA_EXISTING_SESSION_ID = "existingSessionId"
+ private const val EXTRA_TARGET_PACKAGE = "targetPackage"
+ private const val EXTRA_LOCK_TARGET = "lockTarget"
+ private const val EXTRA_INITIAL_MODEL = "initialModel"
+ private const val EXTRA_INITIAL_REASONING_EFFORT = "initialReasoningEffort"
+
+ fun newSessionIntent(
+ context: Context,
+ initialSettings: SessionExecutionSettings,
+ ): Intent {
+ return Intent(context, CreateSessionActivity::class.java).apply {
+ putExtra(EXTRA_INITIAL_MODEL, initialSettings.model)
+ putExtra(EXTRA_INITIAL_REASONING_EFFORT, initialSettings.reasoningEffort)
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ }
+ }
+
+ fun existingHomeSessionIntent(
+ context: Context,
+ sessionId: String,
+ targetPackage: String,
+ initialSettings: SessionExecutionSettings,
+ ): Intent {
+ return newSessionIntent(context, initialSettings).apply {
+ putExtra(EXTRA_EXISTING_SESSION_ID, sessionId)
+ putExtra(EXTRA_TARGET_PACKAGE, targetPackage)
+ putExtra(EXTRA_LOCK_TARGET, true)
+ }
+ }
+ }
+
+ private val sessionController by lazy { AgentSessionController(this) }
+ private var availableModels: List = emptyList()
+ @Volatile
+ private var modelsRefreshInFlight = false
+ private val pendingModelCallbacks = mutableListOf<() -> Unit>()
+
+ private var existingSessionId: String? = null
+ private var selectedPackage: InstalledApp? = null
+ private var targetLocked = false
+
+ private lateinit var promptInput: EditText
+ private lateinit var packageSummary: TextView
+ private lateinit var packageButton: Button
+ private lateinit var clearPackageButton: Button
+ private lateinit var modelSpinner: Spinner
+ private lateinit var effortSpinner: Spinner
+ private lateinit var titleView: TextView
+ private lateinit var statusView: TextView
+ private lateinit var startButton: Button
+
+ private var selectedReasoningOptions = emptyList()
+ private lateinit var effortLabelAdapter: ArrayAdapter
+ private var initialSettings = SessionExecutionSettings.default
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_create_session)
+ setFinishOnTouchOutside(true)
+ bindViews()
+ loadInitialState()
+ refreshModelsIfNeeded(force = true)
+ }
+
+ private fun bindViews() {
+ titleView = findViewById(R.id.create_session_title)
+ statusView = findViewById(R.id.create_session_status)
+ promptInput = findViewById(R.id.create_session_prompt)
+ packageSummary = findViewById(R.id.create_session_target_summary)
+ packageButton = findViewById(R.id.create_session_pick_target_button)
+ clearPackageButton = findViewById(R.id.create_session_clear_target_button)
+ modelSpinner = findViewById(R.id.create_session_model_spinner)
+ effortSpinner = findViewById(R.id.create_session_effort_spinner)
+ startButton = findViewById(R.id.create_session_start_button)
+
+ effortLabelAdapter = ArrayAdapter(
+ this,
+ android.R.layout.simple_spinner_item,
+ mutableListOf(),
+ ).also {
+ it.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
+ effortSpinner.adapter = it
+ }
+ modelSpinner.adapter = ArrayAdapter(
+ this,
+ android.R.layout.simple_spinner_item,
+ mutableListOf(),
+ ).also { it.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) }
+ modelSpinner.onItemSelectedListener = SimpleItemSelectedListener { updateEffortOptions(null) }
+
+ packageButton.setOnClickListener {
+ showInstalledAppPicker { app ->
+ selectedPackage = app
+ updatePackageSummary()
+ }
+ }
+ clearPackageButton.setOnClickListener {
+ selectedPackage = null
+ updatePackageSummary()
+ }
+ findViewById