ai-chat-android-auto / commit
Add phone setup screen for API key and microphone permission
app/src/main/AndroidManifest.xml | 26 +++++++ .../main/java/net/fuzzyworld/claudeauto/Prefs.kt | 27 ++++++++ .../net/fuzzyworld/claudeauto/SetupActivity.kt | 80 ++++++++++++++++++++++ app/src/main/res/layout/activity_setup.xml | 64 +++++++++++++++++ app/src/main/res/values/colors.xml | 4 ++ app/src/main/res/values/strings.xml | 4 ++ app/src/main/res/values/themes.xml | 4 ++ 7 files changed, 209 insertions(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..4a71f4f --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> + + <uses-permission android:name="android.permission.RECORD_AUDIO" /> + + <application + android:allowBackup="true" + android:icon="@mipmap/ic_launcher" + android:roundIcon="@mipmap/ic_launcher_round" + android:label="@string/app_name" + android:supportsRtl="true"> + + <activity + android:name=".SetupActivity" + android:exported="true" + android:label="@string/app_name" + android:theme="@style/Theme.ClaudeAuto"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + + </application> + +</manifest> diff --git a/app/src/main/java/net/fuzzyworld/claudeauto/Prefs.kt b/app/src/main/java/net/fuzzyworld/claudeauto/Prefs.kt new file mode 100644 index 0000000..ab34d61 --- /dev/null +++ b/app/src/main/java/net/fuzzyworld/claudeauto/Prefs.kt @@ -0,0 +1,27 @@ +package net.fuzzyworld.claudeauto + +import android.content.Context +import android.content.SharedPreferences +import androidx.security.crypto.EncryptedSharedPreferences +import androidx.security.crypto.MasterKey + +object Prefs { + private const val FILE = "claude_prefs" + private const val KEY = "api_key" + + private fun prefs(context: Context): SharedPreferences { + val master = MasterKey.Builder(context) + .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) + .build() + return EncryptedSharedPreferences.create( + context, FILE, master, + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM + ) + } + + fun apiKey(context: Context): String = prefs(context).getString(KEY, "") ?: "" + fun hasApiKey(context: Context): Boolean = apiKey(context).isNotBlank() + fun saveApiKey(context: Context, key: String) = prefs(context).edit().putString(KEY, key).apply() + fun clearApiKey(context: Context) = prefs(context).edit().remove(KEY).apply() +} diff --git a/app/src/main/java/net/fuzzyworld/claudeauto/SetupActivity.kt b/app/src/main/java/net/fuzzyworld/claudeauto/SetupActivity.kt new file mode 100644 index 0000000..145109d --- /dev/null +++ b/app/src/main/java/net/fuzzyworld/claudeauto/SetupActivity.kt @@ -0,0 +1,80 @@ +package net.fuzzyworld.claudeauto + +import android.Manifest +import android.content.pm.PackageManager +import android.os.Bundle +import android.view.View +import android.widget.Button +import android.widget.EditText +import android.widget.LinearLayout +import android.widget.TextView +import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.ContextCompat + +class SetupActivity : AppCompatActivity() { + + private lateinit var enterKeyGroup: LinearLayout + private lateinit var keyInput: EditText + private lateinit var clearBtn: Button + private lateinit var statusView: TextView + + private val requestMic = registerForActivityResult( + ActivityResultContracts.RequestPermission() + ) { _ -> refreshUI() } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_setup) + + enterKeyGroup = findViewById(R.id.enterKeyGroup) + keyInput = findViewById(R.id.keyInput) + clearBtn = findViewById(R.id.clearBtn) + statusView = findViewById(R.id.status) + + findViewById<Button>(R.id.saveBtn).setOnClickListener { + val key = keyInput.text.toString().trim() + if (key.isBlank()) { + Toast.makeText(this, "Paste your API key first.", Toast.LENGTH_SHORT).show() + return@setOnClickListener + } + Prefs.saveApiKey(this, key) + keyInput.setText("") + Toast.makeText(this, "API key saved.", Toast.LENGTH_SHORT).show() + refreshUI() + } + + clearBtn.setOnClickListener { + Prefs.clearApiKey(this) + Toast.makeText(this, "API key cleared.", Toast.LENGTH_SHORT).show() + refreshUI() + } + + if (!hasMic()) requestMic.launch(Manifest.permission.RECORD_AUDIO) + refreshUI() + } + + private fun refreshUI() { + val hasKey = Prefs.hasApiKey(this) + enterKeyGroup.visibility = if (hasKey) View.GONE else View.VISIBLE + clearBtn.visibility = if (hasKey) View.VISIBLE else View.GONE + + val micOk = hasMic() + statusView.text = buildString { + appendLine(if (hasKey) "✅ API key saved" else "❌ No API key — paste one above and tap Save") + appendLine(if (micOk) "✅ Microphone permission granted" else "❌ Microphone permission denied") + appendLine() + appendLine( + if (hasKey && micOk) + "Ready. Connect Android Auto and open Claude Auto from the car launcher." + else + "Fix the issues above before using the car app." + ) + } + } + + private fun hasMic() = ContextCompat.checkSelfPermission( + this, Manifest.permission.RECORD_AUDIO + ) == PackageManager.PERMISSION_GRANTED +} diff --git a/app/src/main/res/layout/activity_setup.xml b/app/src/main/res/layout/activity_setup.xml new file mode 100644 index 0000000..26c804d --- /dev/null +++ b/app/src/main/res/layout/activity_setup.xml @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:padding="32dp" + android:gravity="top"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Claude Auto" + android:textSize="26sp" + android:textStyle="bold" + android:layout_marginBottom="8dp" /> + + <!-- Shown only when no key is saved --> + <LinearLayout + android:id="@+id/enterKeyGroup" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Paste your API key from console.anthropic.com" + android:textSize="14sp" + android:layout_marginBottom="20dp" /> + + <EditText + android:id="@+id/keyInput" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="sk-ant-..." + android:inputType="textPassword" + android:fontFamily="monospace" + android:layout_marginBottom="12dp" /> + + <Button + android:id="@+id/saveBtn" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Save API Key" + android:layout_marginBottom="24dp" /> + + </LinearLayout> + + <!-- Shown only when a key is already saved --> + <Button + android:id="@+id/clearBtn" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Clear Saved Key" + android:layout_marginBottom="24dp" /> + + <TextView + android:id="@+id/status" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textSize="14sp" + android:lineSpacingMultiplier="1.4" /> + +</LinearLayout> diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..f42ada6 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <color name="ic_launcher_background">#FFFFFF</color> +</resources> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..dfdf895 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="app_name">Claude Auto</string> +</resources> diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..9bac402 --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <style name="Theme.ClaudeAuto" parent="Theme.AppCompat.Light.NoActionBar" /> +</resources>