@@ -11,7 +11,6 @@ import android.os.Looper
1111import android.view.Window
1212import android.widget.Button
1313import android.widget.EditText
14- import com.bugsnag.android.BugsnagInternals
1514import com.bugsnag.android.mazerunner.scenarios.Scenario
1615import org.json.JSONObject
1716import java.io.File
@@ -21,21 +20,23 @@ import java.net.URL
2120import kotlin.concurrent.thread
2221import kotlin.math.max
2322
24- const val CONFIG_FILE_TIMEOUT = 5000
23+ const val CONFIG_FILE_TIMEOUT = 15000
2524
2625class MainActivity : Activity () {
2726
2827 private val mainHandler = Handler (Looper .getMainLooper())
2928
3029 private val apiKeyKey = " BUGSNAG_API_KEY"
30+ private val commandUUIDKey = " MAZE_COMMAND_UUID"
3131 lateinit var prefs: SharedPreferences
3232
3333 var scenario: Scenario ? = null
34- var polling = false
34+ var isActivityRecreate = false
3535 var mazeAddress: String? = null
3636
3737 override fun onCreate (savedInstanceState : Bundle ? ) {
3838 super .onCreate(savedInstanceState)
39+ this .isActivityRecreate = savedInstanceState != null
3940 log(" MainActivity.onCreate called" )
4041 requestWindowFeature(Window .FEATURE_NO_TITLE )
4142 setContentView(R .layout.activity_main)
@@ -71,7 +72,9 @@ class MainActivity : Activity() {
7172 super .onResume()
7273 log(" MainActivity.onResume called" )
7374
74- if (! polling) {
75+ // Don't start the command runner again if the activity is being recreated,
76+ // as it results in two threads executing commands concurrently and causing flakes.
77+ if (! this .isActivityRecreate) {
7578 startCommandRunner()
7679 }
7780 log(" MainActivity.onResume complete" )
@@ -122,14 +125,33 @@ class MainActivity : Activity() {
122125 return jsonObject?.optString(key) ? : " "
123126 }
124127
128+ private fun setStoredCommandUUID (commandUUID : String ) {
129+ with (prefs.edit()) {
130+ putString(commandUUIDKey, commandUUID)
131+ commit()
132+ }
133+ CiLog .info(" lastCommandUUID set to: $commandUUID " )
134+ }
135+
136+ private fun clearStoredCommandUUID () {
137+ with (prefs.edit()) {
138+ remove(commandUUIDKey)
139+ commit()
140+ }
141+ CiLog .info(" lastCommandUUID set to empty" )
142+ }
143+
144+ private fun getStoredCommandUUID (): String? {
145+ return prefs.getString(commandUUIDKey, " " ).orEmpty()
146+ }
147+
125148 // Starts a thread to poll for Maze Runner actions to perform
126149 private fun startCommandRunner () {
127- // Get the next maze runner command
128- polling = true
129150 thread(start = true ) {
130151 if (mazeAddress == null ) setMazeRunnerAddress()
131152 checkNetwork()
132153
154+ var polling = true
133155 while (polling) {
134156 Thread .sleep(1000 )
135157 try {
@@ -142,17 +164,13 @@ class MainActivity : Activity() {
142164
143165 // Log the received command
144166 CiLog .info(" Received command: $commandStr " )
145- var command = JSONObject (commandStr)
167+ val command = JSONObject (commandStr)
146168 val action = getStringSafely(command, " action" )
147169 val scenarioName = getStringSafely(command, " scenario_name" )
148170 val scenarioMode = getStringSafely(command, " scenario_mode" )
149171 val sessionsUrl = getStringSafely(command, " sessions_endpoint" )
150172 val notifyUrl = getStringSafely(command, " notify_endpoint" )
151- log(" command.action: $action " )
152- log(" command.scenarioName: $scenarioName " )
153- log(" command.scenarioMode: $scenarioMode " )
154- log(" command.sessionsUrl: $sessionsUrl " )
155- log(" command.notifyUrl: $notifyUrl " )
173+ val commandUUID = getStringSafely(command, " uuid" )
156174
157175 // Stop polling once we have a scenario action
158176 if (" start_bugsnag" .equals(action) || " run_scenario" .equals(action)) {
@@ -172,13 +190,18 @@ class MainActivity : Activity() {
172190 CiLog .info(" No Maze Runner command queuing, continuing to poll" )
173191 }
174192 " start_bugsnag" -> {
193+ setStoredCommandUUID(commandUUID)
175194 startBugsnag(scenarioName, scenarioMode, sessionsUrl, notifyUrl)
176195 }
177196 " run_scenario" -> {
197+ setStoredCommandUUID(commandUUID)
178198 runScenario(scenarioName, scenarioMode, sessionsUrl, notifyUrl)
179199 }
180- " clear_persistent_data" -> clearPersistentData()
181- " flush" -> BugsnagInternals .flush()
200+ " clear_persistent_data" -> {
201+ setStoredCommandUUID(commandUUID)
202+ PersistentData (applicationContext).clear()
203+ }
204+ " reset_uuid" -> clearStoredCommandUUID()
182205 else -> throw IllegalArgumentException (" Unknown action: $action " )
183206 }
184207 }
@@ -190,7 +213,8 @@ class MainActivity : Activity() {
190213 }
191214
192215 private fun readCommand (): String {
193- val commandUrl = " http://$mazeAddress /command"
216+ val commandUrl = " http://$mazeAddress /idem-command?after=${getStoredCommandUUID()} "
217+ CiLog .info(" Requesting Maze Runner command from: $commandUrl " )
194218 val urlConnection = URL (commandUrl).openConnection() as HttpURLConnection
195219 try {
196220 return urlConnection.inputStream.use { it.reader().readText() }
@@ -246,52 +270,6 @@ class MainActivity : Activity() {
246270 }
247271 }
248272
249- // Clear persistent data (used to stop scenarios bleeding into each other)
250- private fun clearPersistentData () {
251- CiLog .info(" Clearing persistent data" )
252- clearCacheFolder(" bugsnag" )
253- clearCacheFolder(" StrictModeDiscScenarioFile" )
254- clearFilesFolder(" background-service-dir" )
255-
256- removeFile(" device-id" )
257- removeFile(" internal-device-id" )
258-
259- listFolders()
260- }
261-
262- // Recursively deletes the contents of a folder beneath /cache
263- private fun clearCacheFolder (name : String ) {
264- val folder = File (applicationContext.cacheDir, name)
265- log(" Clearing folder: ${folder.path} " )
266- folder.deleteRecursively()
267- }
268-
269- private fun clearFilesFolder (name : String ) {
270- val folder = File (applicationContext.filesDir, name)
271- log(" Clearing folder: ${folder.path} " )
272- folder.deleteRecursively()
273- }
274-
275- // Deletes a file beneath /files
276- private fun removeFile (name : String ) {
277- val file = File (applicationContext.filesDir, name)
278- log(" Removing file: ${file.path} " )
279- file.delete()
280- }
281-
282- // Logs out the contents of the /cache and /files folders
283- private fun listFolders () {
284- log(" Contents of: ${applicationContext.cacheDir} " )
285- applicationContext.cacheDir.walkTopDown().forEach {
286- log(it.absolutePath)
287- }
288-
289- log(" Contents of: ${applicationContext.filesDir} " )
290- applicationContext.filesDir.walkTopDown().forEach {
291- log(it.absolutePath)
292- }
293- }
294-
295273 private fun loadScenario (
296274 eventType : String ,
297275 mode : String ,
0 commit comments