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