Skip to content

Commit c464183

Browse files
Add files via upload
1 parent 29d71f6 commit c464183

2 files changed

Lines changed: 101 additions & 46 deletions

File tree

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

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,8 @@ fun MenuScreen(
8181
modifier = Modifier.weight(1f)
8282
)
8383
Button(
84-
onClick = {
85-
if (isTrialExpired) {
86-
Toast.makeText(context, "Bitte abonnieren Sie die App, um fortzufahren.", Toast.LENGTH_LONG).show()
87-
} else {
88-
onApiKeyButtonClicked()
89-
}
90-
},
91-
enabled = !isTrialExpired, // Disable button if trial is expired
84+
onClick = { onApiKeyButtonClicked() },
85+
enabled = true, // Always enabled
9286
modifier = Modifier.padding(start = 8.dp)
9387
) {
9488
Text(text = "Change API Key")
@@ -130,20 +124,14 @@ fun MenuScreen(
130124
Spacer(modifier = Modifier.weight(1f))
131125

132126
Button(
133-
onClick = {
134-
if (isTrialExpired) {
135-
Toast.makeText(context, "Bitte abonnieren Sie die App, um fortzufahren.", Toast.LENGTH_LONG).show()
136-
} else {
137-
expanded = true
138-
}
139-
},
140-
enabled = !isTrialExpired // Disable button if trial is expired
127+
onClick = { expanded = true },
128+
enabled = true // Always enabled
141129
) {
142130
Text("Change Model")
143131
}
144132

145133
DropdownMenu(
146-
expanded = expanded && !isTrialExpired,
134+
expanded = expanded,
147135
onDismissRequest = { expanded = false }
148136
) {
149137
val orderedModels = listOf(
@@ -161,7 +149,7 @@ fun MenuScreen(
161149
GenerativeAiViewModelFactory.setModel(modelOption)
162150
expanded = false
163151
},
164-
enabled = !isTrialExpired // Disable menu item if trial is expired
152+
enabled = true // Always enabled
165153
)
166154
}
167155
}
Lines changed: 95 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
11
package com.google.ai.sample
22

33
import android.app.Service
4-
import android.content.Context
54
import android.content.Intent
6-
import android.os.Handler
75
import android.os.IBinder
8-
import android.os.Looper
96
import android.util.Log
107
import kotlinx.coroutines.CoroutineScope
118
import kotlinx.coroutines.Dispatchers
129
import kotlinx.coroutines.Job
1310
import kotlinx.coroutines.delay
11+
import kotlinx.coroutines.isActive
1412
import kotlinx.coroutines.launch
13+
import org.json.JSONException
1514
import org.json.JSONObject
15+
import java.io.IOException
1616
import java.net.HttpURLConnection
17+
import java.net.MalformedURLException
18+
import java.net.SocketTimeoutException
1719
import java.net.URL
20+
import java.time.OffsetDateTime
21+
import java.time.format.DateTimeParseException
1822

1923
class TrialTimerService : Service() {
2024

@@ -31,7 +35,11 @@ class TrialTimerService : Service() {
3135
const val EXTRA_CURRENT_UTC_TIME_MS = "extra_current_utc_time_ms"
3236
private const val TAG = "TrialTimerService"
3337
private const val CHECK_INTERVAL_MS = 60 * 1000L // 1 minute
34-
private const val WORLD_TIME_API_URL = "https://worldtimeapi.org/api/timezone/Etc/UTC"
38+
private const val TIME_API_URL = "http://worldclockapi.com/api/json/utc/now" // Changed API URL
39+
private const val CONNECTION_TIMEOUT_MS = 15000 // 15 seconds
40+
private const val READ_TIMEOUT_MS = 15000 // 15 seconds
41+
private const val MAX_RETRIES = 3
42+
private val RETRY_DELAYS_MS = listOf(5000L, 15000L, 30000L) // 5s, 15s, 30s
3543
}
3644

3745
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
@@ -46,79 +54,138 @@ class TrialTimerService : Service() {
4654
stopTimerLogic()
4755
}
4856
}
49-
return START_STICKY // Keep service running if killed by system
57+
return START_STICKY
5058
}
5159

5260
private fun startTimerLogic() {
5361
isTimerRunning = true
5462
scope.launch {
55-
while (isTimerRunning) {
63+
var attempt = 0
64+
while (isTimerRunning && isActive) {
65+
var success = false
5666
try {
57-
val url = URL(WORLD_TIME_API_URL)
67+
Log.d(TAG, "Attempting to fetch internet time (attempt ${attempt + 1}/$MAX_RETRIES). URL: $TIME_API_URL")
68+
val url = URL(TIME_API_URL)
5869
val connection = url.openConnection() as HttpURLConnection
5970
connection.requestMethod = "GET"
60-
connection.connect()
71+
connection.connectTimeout = CONNECTION_TIMEOUT_MS
72+
connection.readTimeout = READ_TIMEOUT_MS
73+
connection.connect() // Explicit connect call
6174

62-
if (connection.responseCode == HttpURLConnection.HTTP_OK) {
75+
val responseCode = connection.responseCode
76+
Log.d(TAG, "Time API response code: $responseCode")
77+
78+
if (responseCode == HttpURLConnection.HTTP_OK) {
6379
val inputStream = connection.inputStream
6480
val result = inputStream.bufferedReader().use { it.readText() }
6581
inputStream.close()
82+
connection.disconnect()
83+
6684
val jsonObject = JSONObject(result)
67-
val currentUtcTimeMs = jsonObject.getLong("unixtime") * 1000L
68-
Log.d(TAG, "Successfully fetched internet time: $currentUtcTimeMs")
85+
val currentDateTimeStr = jsonObject.getString("currentDateTime")
86+
// Parse ISO 8601 string to milliseconds since epoch
87+
val currentUtcTimeMs = OffsetDateTime.parse(currentDateTimeStr).toInstant().toEpochMilli()
88+
89+
Log.d(TAG, "Successfully fetched and parsed internet time: $currentUtcTimeMs ($currentDateTimeStr)")
6990

7091
val trialState = TrialManager.getTrialState(applicationContext, currentUtcTimeMs)
92+
Log.d(TAG, "Current trial state from TrialManager: $trialState")
7193
when (trialState) {
7294
TrialManager.TrialState.NOT_YET_STARTED_AWAITING_INTERNET -> {
7395
TrialManager.startTrialIfNecessaryWithInternetTime(applicationContext, currentUtcTimeMs)
7496
sendBroadcast(Intent(ACTION_INTERNET_TIME_AVAILABLE).putExtra(EXTRA_CURRENT_UTC_TIME_MS, currentUtcTimeMs))
7597
}
7698
TrialManager.TrialState.ACTIVE_INTERNET_TIME_CONFIRMED -> {
77-
// Trial is active, continue checking
7899
sendBroadcast(Intent(ACTION_INTERNET_TIME_AVAILABLE).putExtra(EXTRA_CURRENT_UTC_TIME_MS, currentUtcTimeMs))
79100
}
80101
TrialManager.TrialState.EXPIRED_INTERNET_TIME_CONFIRMED -> {
81102
Log.d(TAG, "Trial expired based on internet time.")
82103
sendBroadcast(Intent(ACTION_TRIAL_EXPIRED))
83-
stopTimerLogic() // Stop further checks if expired
104+
stopTimerLogic()
84105
}
85106
TrialManager.TrialState.PURCHASED -> {
86107
Log.d(TAG, "App is purchased. Stopping timer.")
87108
stopTimerLogic()
88109
}
89-
else -> {
90-
// Should not happen if logic is correct
91-
Log.w(TAG, "Unhandled trial state: $trialState")
110+
TrialManager.TrialState.INTERNET_UNAVAILABLE_CANNOT_VERIFY -> {
111+
// This case might occur if TrialManager was called with null time before,
112+
// but now we have time. So we should re-broadcast available time.
113+
Log.w(TAG, "TrialManager reported INTERNET_UNAVAILABLE, but we just fetched time. Broadcasting available.")
114+
sendBroadcast(Intent(ACTION_INTERNET_TIME_AVAILABLE).putExtra(EXTRA_CURRENT_UTC_TIME_MS, currentUtcTimeMs))
92115
}
93116
}
117+
success = true
118+
attempt = 0 // Reset attempts on success
94119
} else {
95-
Log.e(TAG, "Failed to fetch internet time. Response code: ${connection.responseCode}")
96-
sendBroadcast(Intent(ACTION_INTERNET_TIME_UNAVAILABLE))
120+
Log.e(TAG, "Failed to fetch internet time. HTTP Response code: $responseCode - ${connection.responseMessage}")
121+
connection.disconnect()
122+
// For server-side errors (5xx), retry is useful. For client errors (4xx), less so unless temporary.
123+
if (responseCode >= 500) {
124+
// Retry for server errors
125+
} else {
126+
// For other errors (e.g. 404), might not be worth retrying indefinitely the same way
127+
// but we will follow the general retry logic for now.
128+
}
97129
}
130+
} catch (e: SocketTimeoutException) {
131+
Log.e(TAG, "Failed to fetch internet time: Socket Timeout", e)
132+
} catch (e: MalformedURLException) {
133+
Log.e(TAG, "Failed to fetch internet time: Malformed URL", e)
134+
stopTimerLogic() // URL is wrong, no point in retrying
135+
return@launch
136+
} catch (e: IOException) {
137+
Log.e(TAG, "Failed to fetch internet time: IO Exception (e.g., network issue)", e)
138+
} catch (e: JSONException) {
139+
Log.e(TAG, "Failed to parse JSON response from time API", e)
140+
// API might have changed format or returned error HTML, don't retry indefinitely for this specific error on this attempt.
141+
} catch (e: DateTimeParseException) {
142+
Log.e(TAG, "Failed to parse date/time string from time API response", e)
98143
} catch (e: Exception) {
99-
Log.e(TAG, "Error fetching internet time or processing trial state", e)
100-
sendBroadcast(Intent(ACTION_INTERNET_TIME_UNAVAILABLE))
144+
Log.e(TAG, "An unexpected error occurred while fetching or processing internet time", e)
145+
}
146+
147+
if (!isTimerRunning || !isActive) break // Exit loop if timer stopped
148+
149+
if (!success) {
150+
attempt++
151+
if (attempt < MAX_RETRIES) {
152+
val delayMs = RETRY_DELAYS_MS.getOrElse(attempt -1) { RETRY_DELAYS_MS.last() }
153+
Log.d(TAG, "Time fetch failed. Retrying in ${delayMs / 1000}s...")
154+
sendBroadcast(Intent(ACTION_INTERNET_TIME_UNAVAILABLE)) // Notify UI about current unavailability before retry
155+
delay(delayMs)
156+
} else {
157+
Log.e(TAG, "Failed to fetch internet time after $MAX_RETRIES attempts. Broadcasting unavailability.")
158+
sendBroadcast(Intent(ACTION_INTERNET_TIME_UNAVAILABLE))
159+
attempt = 0 // Reset attempts for next full CHECK_INTERVAL_MS cycle
160+
delay(CHECK_INTERVAL_MS) // Wait for the normal check interval after max retries failed
161+
}
162+
} else {
163+
// Success, wait for the normal check interval
164+
delay(CHECK_INTERVAL_MS)
101165
}
102-
delay(CHECK_INTERVAL_MS)
103166
}
167+
Log.d(TAG, "Timer coroutine ended.")
104168
}
105169
}
106170

107171
private fun stopTimerLogic() {
108-
isTimerRunning = false
109-
job.cancel() // Cancel all coroutines started by this scope
110-
stopSelf() // Stop the service itself
111-
Log.d(TAG, "Timer stopped and service is stopping.")
172+
if (isTimerRunning) {
173+
Log.d(TAG, "Stopping timer logic...")
174+
isTimerRunning = false
175+
job.cancel() // Cancel all coroutines started by this scope
176+
stopSelf() // Stop the service itself
177+
Log.d(TAG, "Timer stopped and service is stopping.")
178+
}
112179
}
113180

114181
override fun onBind(intent: Intent?): IBinder? {
115-
return null // We are not using binding
182+
return null
116183
}
117184

118185
override fun onDestroy() {
119186
super.onDestroy()
120-
stopTimerLogic() // Ensure timer is stopped when service is destroyed
121-
Log.d(TAG, "Service Destroyed")
187+
Log.d(TAG, "Service Destroyed. Ensuring timer is stopped.")
188+
stopTimerLogic()
122189
}
123190
}
124191

0 commit comments

Comments
 (0)