Skip to content

Commit 56ee7ad

Browse files
Apikey rotation (#10)
* Add files via upload * Add files via upload * Add files via upload * Add files via upload
1 parent 4c3f327 commit 56ee7ad

3 files changed

Lines changed: 425 additions & 180 deletions

File tree

app/src/main/kotlin/com/google/ai/sample/ApiKeyManager.kt

Lines changed: 133 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class ApiKeyManager(context: Context) {
1212
private val PREFS_NAME = "api_key_prefs"
1313
private val API_KEYS = "api_keys"
1414
private val CURRENT_KEY_INDEX = "current_key_index"
15+
private val FAILED_KEYS = "failed_keys"
1516

1617
private val prefs: SharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
1718

@@ -73,6 +74,9 @@ class ApiKeyManager(context: Context) {
7374
setCurrentKeyIndex(0)
7475
}
7576

77+
// Clear this key from failed keys if it was previously marked as failed
78+
removeFailedKey(apiKey)
79+
7680
Log.d(TAG, "Added new API key, total keys: ${keys.size}")
7781
return true
7882
}
@@ -95,6 +99,9 @@ class ApiKeyManager(context: Context) {
9599
setCurrentKeyIndex(0)
96100
}
97101

102+
// Also remove from failed keys if present
103+
removeFailedKey(apiKey)
104+
98105
Log.d(TAG, "Removed API key, remaining keys: ${keys.size}")
99106
} else {
100107
Log.d(TAG, "API key not found for removal")
@@ -128,6 +135,122 @@ class ApiKeyManager(context: Context) {
128135
return prefs.getInt(CURRENT_KEY_INDEX, 0)
129136
}
130137

138+
/**
139+
* Mark an API key as failed (e.g., due to 503 error)
140+
* @param apiKey The API key to mark as failed
141+
*/
142+
fun markKeyAsFailed(apiKey: String) {
143+
val failedKeys = getFailedKeys().toMutableList()
144+
if (!failedKeys.contains(apiKey)) {
145+
failedKeys.add(apiKey)
146+
saveFailedKeys(failedKeys)
147+
Log.d(TAG, "Marked API key as failed: ${apiKey.take(5)}...")
148+
}
149+
}
150+
151+
/**
152+
* Remove an API key from the failed keys list
153+
* @param apiKey The API key to remove from failed keys
154+
*/
155+
fun removeFailedKey(apiKey: String) {
156+
val failedKeys = getFailedKeys().toMutableList()
157+
if (failedKeys.remove(apiKey)) {
158+
saveFailedKeys(failedKeys)
159+
Log.d(TAG, "Removed API key from failed keys: ${apiKey.take(5)}...")
160+
}
161+
}
162+
163+
/**
164+
* Get all failed API keys
165+
* @return List of failed API keys
166+
*/
167+
fun getFailedKeys(): List<String> {
168+
val keysString = prefs.getString(FAILED_KEYS, "") ?: ""
169+
return if (keysString.isEmpty()) {
170+
emptyList()
171+
} else {
172+
keysString.split(",")
173+
}
174+
}
175+
176+
/**
177+
* Check if an API key is marked as failed
178+
* @param apiKey The API key to check
179+
* @return True if the key is marked as failed, false otherwise
180+
*/
181+
fun isKeyFailed(apiKey: String): Boolean {
182+
return getFailedKeys().contains(apiKey)
183+
}
184+
185+
/**
186+
* Reset all failed keys
187+
*/
188+
fun resetFailedKeys() {
189+
prefs.edit().remove(FAILED_KEYS).apply()
190+
Log.d(TAG, "Reset all failed keys")
191+
}
192+
193+
/**
194+
* Check if all API keys are marked as failed
195+
* @return True if all keys are failed, false otherwise
196+
*/
197+
fun areAllKeysFailed(): Boolean {
198+
val keys = getApiKeys()
199+
val failedKeys = getFailedKeys()
200+
return keys.isNotEmpty() && failedKeys.size >= keys.size
201+
}
202+
203+
/**
204+
* Get the count of available API keys
205+
* @return The number of API keys
206+
*/
207+
fun getKeyCount(): Int {
208+
return getApiKeys().size
209+
}
210+
211+
/**
212+
* Switch to the next available API key that is not marked as failed
213+
* @return The new API key or null if no valid keys are available
214+
*/
215+
fun switchToNextAvailableKey(): String? {
216+
val keys = getApiKeys()
217+
if (keys.isEmpty()) {
218+
Log.d(TAG, "No API keys available to switch to")
219+
return null
220+
}
221+
222+
val failedKeys = getFailedKeys()
223+
val currentIndex = getCurrentKeyIndex()
224+
225+
// If all keys are failed, reset failed keys and start from the beginning
226+
if (failedKeys.size >= keys.size) {
227+
Log.d(TAG, "All keys are marked as failed, resetting failed keys")
228+
resetFailedKeys()
229+
setCurrentKeyIndex(0)
230+
return keys[0]
231+
}
232+
233+
// Find the next key that is not failed
234+
var nextIndex = (currentIndex + 1) % keys.size
235+
var attempts = 0
236+
237+
while (attempts < keys.size) {
238+
if (!failedKeys.contains(keys[nextIndex])) {
239+
setCurrentKeyIndex(nextIndex)
240+
Log.d(TAG, "Switched to next available key at index $nextIndex")
241+
return keys[nextIndex]
242+
}
243+
nextIndex = (nextIndex + 1) % keys.size
244+
attempts++
245+
}
246+
247+
// If we get here, all keys are failed (shouldn't happen due to earlier check)
248+
Log.d(TAG, "Could not find a non-failed key, resetting failed keys")
249+
resetFailedKeys()
250+
setCurrentKeyIndex(0)
251+
return keys[0]
252+
}
253+
131254
/**
132255
* Save the list of API keys to SharedPreferences
133256
* @param keys The list of API keys to save
@@ -137,11 +260,20 @@ class ApiKeyManager(context: Context) {
137260
prefs.edit().putString(API_KEYS, keysString).apply()
138261
}
139262

263+
/**
264+
* Save the list of failed API keys to SharedPreferences
265+
* @param keys The list of failed API keys to save
266+
*/
267+
private fun saveFailedKeys(keys: List<String>) {
268+
val keysString = keys.joinToString(",")
269+
prefs.edit().putString(FAILED_KEYS, keysString).apply()
270+
}
271+
140272
/**
141273
* Clear all stored API keys
142274
*/
143275
fun clearAllKeys() {
144-
prefs.edit().remove(API_KEYS).remove(CURRENT_KEY_INDEX).apply()
276+
prefs.edit().remove(API_KEYS).remove(CURRENT_KEY_INDEX).remove(FAILED_KEYS).apply()
145277
Log.d(TAG, "Cleared all API keys")
146278
}
147279

app/src/main/kotlin/com/google/ai/sample/GenerativeAiViewModelFactory.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.google.ai.sample
22

3+
import android.content.Context
34
import androidx.lifecycle.ViewModel
45
import androidx.lifecycle.ViewModelProvider
56
import androidx.lifecycle.viewmodel.CreationExtras
@@ -67,7 +68,9 @@ val GenerativeViewModelFactory = object : ViewModelProvider.Factory {
6768
apiKey = apiKey,
6869
generationConfig = config
6970
)
70-
PhotoReasoningViewModel(generativeModel)
71+
// Pass the ApiKeyManager to the ViewModel for key rotation
72+
val apiKeyManager = ApiKeyManager.getInstance(application)
73+
PhotoReasoningViewModel(generativeModel, apiKeyManager)
7174
}
7275

7376
isAssignableFrom(ChatViewModel::class.java) -> {

0 commit comments

Comments
 (0)