Skip to content

Commit 824545b

Browse files
Update TrialTimerService.kt
1 parent b7dff2c commit 824545b

1 file changed

Lines changed: 38 additions & 28 deletions

File tree

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

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,8 @@ import java.net.HttpURLConnection
1717
import java.net.MalformedURLException
1818
import java.net.SocketTimeoutException
1919
import java.net.URL
20-
import java.time.LocalDateTime // Added
21-
import java.time.OffsetDateTime
22-
import java.time.ZoneOffset // Added
20+
import java.time.LocalDateTime
21+
import java.time.ZoneOffset
2322
import java.time.format.DateTimeParseException
2423

2524
class TrialTimerService : Service() {
@@ -37,12 +36,11 @@ class TrialTimerService : Service() {
3736
const val EXTRA_CURRENT_UTC_TIME_MS = "extra_current_utc_time_ms"
3837
private const val TAG = "TrialTimerService"
3938
private const val CHECK_INTERVAL_MS = 60 * 1000L // 1 minute
40-
// Changed API URL to timeapi.io for UTC time
4139
private const val TIME_API_URL = "https://timeapi.io/api/time/current/zone?timeZone=Etc/UTC"
42-
private const val CONNECTION_TIMEOUT_MS = 30000 // 30 seconds
43-
private const val READ_TIMEOUT_MS = 30000 // 30 seconds
40+
private const val CONNECTION_TIMEOUT_MS = 30000
41+
private const val READ_TIMEOUT_MS = 30000
4442
private const val MAX_RETRIES = 3
45-
private val RETRY_DELAYS_MS = listOf(5000L, 15000L, 30000L) // 5s, 15s, 30s
43+
private val RETRY_DELAYS_MS = listOf(5000L, 15000L, 30000L)
4644
}
4745

4846
override fun onCreate() {
@@ -91,11 +89,11 @@ class TrialTimerService : Service() {
9189
connection.connectTimeout = CONNECTION_TIMEOUT_MS
9290
connection.readTimeout = READ_TIMEOUT_MS
9391
Log.d(TAG, "startTimerLogic: Connection configured. Timeout: $CONNECTION_TIMEOUT_MS ms. About to connect.")
94-
connection.connect() // Explicit connect call
92+
connection.connect()
9593
Log.d(TAG, "startTimerLogic: Connection established.")
9694

9795
val responseCode = connection.responseCode
98-
val responseMessage = connection.responseMessage // Get response message for logging
96+
val responseMessage = connection.responseMessage
9997
Log.i(TAG, "Time API response code: $responseCode, Message: $responseMessage")
10098

10199
if (responseCode == HttpURLConnection.HTTP_OK) {
@@ -108,14 +106,12 @@ class TrialTimerService : Service() {
108106
Log.d(TAG, "startTimerLogic: Connection disconnected.")
109107

110108
val jsonObject = JSONObject(result)
111-
// Updated to parse "dateTime" field from timeapi.io
112109
val currentDateTimeStr = jsonObject.getString("dateTime")
113110
Log.d(TAG, "startTimerLogic: Parsed dateTime string: $currentDateTimeStr")
114-
Log.d(TAG, "Attempting to parse dateTime string $currentDateTimeStr as LocalDateTime and applying UTC offset.") // New Log
115-
// Parse ISO 8601 string to milliseconds since epoch
116-
val currentUtcTimeMs = LocalDateTime.parse(currentDateTimeStr).atOffset(ZoneOffset.UTC).toInstant().toEpochMilli() // Modified Line
111+
Log.d(TAG, "Attempting to parse dateTime string $currentDateTimeStr as LocalDateTime and applying UTC offset.")
112+
val currentUtcTimeMs = LocalDateTime.parse(currentDateTimeStr).atOffset(ZoneOffset.UTC).toInstant().toEpochMilli()
117113

118-
Log.i(TAG, "Successfully parsed dateTime string $currentDateTimeStr to UTC milliseconds: $currentUtcTimeMs using LocalDateTime.parse().atOffset(ZoneOffset.UTC)") // Modified Log
114+
Log.i(TAG, "Successfully parsed dateTime string $currentDateTimeStr to UTC milliseconds: $currentUtcTimeMs using LocalDateTime.parse().atOffset(ZoneOffset.UTC)")
119115

120116
val trialState = TrialManager.getTrialState(applicationContext, currentUtcTimeMs)
121117
Log.i(TAG, "Current trial state from TrialManager: $trialState (based on time $currentUtcTimeMs)")
@@ -132,20 +128,22 @@ class TrialTimerService : Service() {
132128
TrialManager.TrialState.EXPIRED_INTERNET_TIME_CONFIRMED -> {
133129
Log.i(TAG, "TrialState: EXPIRED_INTERNET_TIME_CONFIRMED. Trial expired based on internet time. Broadcasting ACTION_TRIAL_EXPIRED and stopping timer.")
134130
sendBroadcast(Intent(ACTION_TRIAL_EXPIRED))
135-
stopTimerLogic()
131+
stopTimerLogic() // MODIFIED: Stop the service after confirming expiry
136132
}
137133
TrialManager.TrialState.PURCHASED -> {
138134
Log.i(TAG, "TrialState: PURCHASED. App is purchased. Stopping timer.")
139135
stopTimerLogic()
140136
}
141137
TrialManager.TrialState.INTERNET_UNAVAILABLE_CANNOT_VERIFY -> {
138+
// This case should ideally not be returned by TrialManager if currentUtcTimeMs is available.
139+
// However, if it does occur, it implies an issue. We broadcast available time.
142140
Log.w(TAG, "TrialState: INTERNET_UNAVAILABLE_CANNOT_VERIFY from TrialManager, but we just fetched time. This is unexpected. Broadcasting ACTION_INTERNET_TIME_AVAILABLE anyway.")
143141
sendBroadcast(Intent(ACTION_INTERNET_TIME_AVAILABLE).putExtra(EXTRA_CURRENT_UTC_TIME_MS, currentUtcTimeMs))
144142
}
145143
}
146144
success = true
147145
Log.d(TAG, "startTimerLogic: Time fetch successful. success = true. Resetting attempt counter.")
148-
attempt = 0 // Reset attempts on success
146+
attempt = 0
149147
} else {
150148
Log.e(TAG, "Failed to fetch internet time. HTTP Response code: $responseCode - $responseMessage")
151149
connection.disconnect()
@@ -159,22 +157,22 @@ class TrialTimerService : Service() {
159157
} catch (e: SocketTimeoutException) {
160158
Log.e(TAG, "Failed to fetch internet time: Socket Timeout after $CONNECTION_TIMEOUT_MS ms (connect) or $READ_TIMEOUT_MS ms (read). Attempt ${attempt + 1}", e)
161159
} catch (e: MalformedURLException) {
162-
Log.e(TAG, "Failed to fetch internet time: Malformed URL \t$TIME_API_URL\t. Stopping timer logic.", e)
163-
stopTimerLogic() // URL is wrong, no point in retrying
160+
Log.e(TAG, "Failed to fetch internet time: Malformed URL $TIME_API_URL. Stopping timer logic.", e)
161+
stopTimerLogic()
164162
return@launch
165163
} catch (e: IOException) {
166164
Log.e(TAG, "Failed to fetch internet time: IO Exception (e.g., network issue, connection reset). Attempt ${attempt + 1}", e)
167165
} catch (e: JSONException) {
168166
Log.e(TAG, "Failed to parse JSON response from time API. Response might not be valid JSON. Attempt ${attempt + 1}", e)
169-
} catch (e: DateTimeParseException) { // This catch block will now likely not be hit for this specific issue, but good to keep for other parsing issues.
167+
} catch (e: DateTimeParseException) {
170168
Log.e(TAG, "Failed to parse date/time string from time API response. API format might have changed. Attempt ${attempt + 1}", e)
171169
} catch (e: Exception) {
172170
Log.e(TAG, "An unexpected error occurred while fetching or processing internet time. Attempt ${attempt + 1}", e)
173171
}
174172

175173
if (!isTimerRunning || !isActive) {
176174
Log.d(TAG, "startTimerLogic: Loop condition check: isTimerRunning=$isTimerRunning, isActive=$isActive. Breaking loop.")
177-
break // Exit loop if timer stopped or coroutine cancelled
175+
break
178176
}
179177

180178
if (!success) {
@@ -190,15 +188,24 @@ class TrialTimerService : Service() {
190188
Log.e(TAG, "Failed to fetch internet time after $MAX_RETRIES attempts. Broadcasting ACTION_INTERNET_TIME_UNAVAILABLE.")
191189
sendBroadcast(Intent(ACTION_INTERNET_TIME_UNAVAILABLE))
192190
Log.d(TAG, "Resetting attempt counter to 0 and waiting for CHECK_INTERVAL_MS (${CHECK_INTERVAL_MS / 1000}s) before next cycle of attempts.")
193-
attempt = 0 // Reset attempts for next full CHECK_INTERVAL_MS cycle
194-
delay(CHECK_INTERVAL_MS) // Wait for the normal check interval after max retries failed
191+
attempt = 0
192+
delay(CHECK_INTERVAL_MS)
195193
}
196194
} else {
197-
Log.d(TAG, "startTimerLogic: Time fetch was successful. Waiting for CHECK_INTERVAL_MS (${CHECK_INTERVAL_MS / 1000}s) before next check.")
198-
delay(CHECK_INTERVAL_MS)
195+
// Check if timer is still supposed to be running after a successful fetch (e.g. not expired/purchased)
196+
if (isTimerRunning && isActive) {
197+
Log.d(TAG, "startTimerLogic: Time fetch was successful. Waiting for CHECK_INTERVAL_MS (${CHECK_INTERVAL_MS / 1000}s) before next check.")
198+
delay(CHECK_INTERVAL_MS)
199+
} else {
200+
Log.d(TAG, "startTimerLogic: Time fetch was successful, but timer is no longer running (e.g., expired/purchased). Exiting loop.")
201+
break // Exit if stopTimerLogic was called during the process
202+
}
199203
}
200204
}
201205
Log.i(TAG, "Timer coroutine ended. isTimerRunning: $isTimerRunning, isActive: $isActive")
206+
// If the loop exited because isTimerRunning became false (e.g., due to stopTimerLogic),
207+
// the service should already be in the process of stopping.
208+
// If it exited due to job cancellation (!isActive), it's also handled.
202209
}
203210
}
204211

@@ -208,9 +215,9 @@ class TrialTimerService : Service() {
208215
Log.i(TAG, "Stopping timer logic...")
209216
isTimerRunning = false
210217
Log.d(TAG, "Cancelling job: $job")
211-
job.cancel() // Cancel all coroutines started by this scope
218+
job.cancel()
212219
Log.d(TAG, "Calling stopSelf() to stop the service.")
213-
stopSelf() // Stop the service itself
220+
stopSelf()
214221
Log.i(TAG, "Timer stopped and service is stopping.")
215222
} else {
216223
Log.d(TAG, "stopTimerLogic: Timer was not running.")
@@ -225,7 +232,10 @@ class TrialTimerService : Service() {
225232
override fun onDestroy() {
226233
super.onDestroy()
227234
Log.i(TAG, "onDestroy: Service Destroyed. Ensuring timer is stopped via stopTimerLogic().")
228-
stopTimerLogic()
235+
// Call stopTimerLogic again to ensure job is cancelled if onDestroy is called unexpectedly.
236+
// If stopSelf() was called, isTimerRunning would be false, and job.cancel() is idempotent.
237+
isTimerRunning = false // Explicitly set to ensure job cancellation logic runs if needed
238+
job.cancel()
239+
Log.d(TAG, "onDestroy: Job cancelled explicitly in onDestroy.")
229240
}
230241
}
231-

0 commit comments

Comments
 (0)