1 package com.android.onboarding.bedsteadonboarding.logcat 2 3 import android.os.ParcelFileDescriptor 4 import android.util.Log 5 import androidx.test.platform.app.InstrumentationRegistry 6 import java.io.BufferedReader 7 import java.io.FileInputStream 8 import java.io.InputStreamReader 9 import java.util.concurrent.ExecutorService 10 import java.util.concurrent.Executors 11 12 /** 13 * Reads the logs continuously until stopped explicitly. It filters logs with tag [filterTag] from 14 * logs and stores them in-memory. Logs are streamed on a separate thread. 15 */ 16 class LogcatReader(private val startClean: Boolean = true, private val filterTag: String) { 17 18 private val LOGCAT_CLEAR_COMMAND = "logcat -c" 19 private val LOGCAT_COMMAND = "logcat" 20 private val TAG = "LogcatReader" 21 private val filteredLogs = arrayListOf<String>() 22 private val logsLock = Any() // Lock to provide thread safe access to [filteredLogs]. 23 private val logcatReaderExecutorService: ExecutorService = Executors.newSingleThreadExecutor() 24 25 private var shouldReadLogs = false 26 private var hasLogcatStarted = false 27 28 private lateinit var logcatFileDescriptor: ParcelFileDescriptor 29 <lambda>null30 private val logcatReaderRunnable = Runnable { 31 synchronized(logsLock) { 32 filteredLogs.clear() 33 } 34 if (startClean) { 35 try { 36 InstrumentationRegistry.getInstrumentation() 37 .uiAutomation 38 .executeShellCommand(LOGCAT_CLEAR_COMMAND) 39 } catch (t: Throwable) { 40 // Clearing is best effort - don't disrupt the test because we can't 41 Log.e(TAG, "Error clearing logcat $t") 42 } 43 } 44 logcatFileDescriptor = 45 InstrumentationRegistry.getInstrumentation().uiAutomation.executeShellCommand(LOGCAT_COMMAND) 46 val fis: FileInputStream = ParcelFileDescriptor.AutoCloseInputStream(logcatFileDescriptor) 47 val logcatReader = BufferedReader(InputStreamReader(fis)) 48 shouldReadLogs = true 49 hasLogcatStarted = true 50 Log.i(TAG, "Started Reading Logs") 51 while (shouldReadLogs) { 52 logcatReader 53 .readLine() 54 ?.takeIf { it.contains(filterTag) } 55 ?.also { synchronized(logsLock) { filteredLogs.add(it) } } 56 } 57 } 58 59 /** 60 * Submits [logcatReaderRunnable] on separate thread which in turn would perform the task of 61 * reading logs. It will block until logs have started to be read. 62 */ startnull63 fun start() { 64 var unused = logcatReaderExecutorService.submit(logcatReaderRunnable) 65 // Wait until logs have started to be read. 66 while (!hasLogcatStarted) { 67 Thread.sleep(100) 68 } 69 } 70 71 /** 72 * Closes the file-descriptor pointing to the logs. Clears the filtered log lines stored 73 * in-memory. No more logs would be read until the runnable [LogcatReader] is started again. 74 */ stopReadingLogsnull75 fun stopReadingLogs() { 76 shouldReadLogs = false 77 synchronized(logsLock) { 78 filteredLogs.clear() 79 } 80 Log.i(TAG, "Closing logcat file descriptor") 81 logcatFileDescriptor.close() 82 logcatReaderExecutorService.shutdown() 83 } 84 85 /** Returns the immutable set of log lines with tag = [filterTag] from logs */ getFilteredLogsnull86 fun getFilteredLogs(): Set<String> { 87 synchronized(logsLock) { 88 return filteredLogs.toSet() 89 } 90 } 91 92 /** Returns if the logs have started to be read. */ hasLogcatStartednull93 fun hasLogcatStarted() = hasLogcatStarted 94 } 95