A Journey Towards Android App Log Subscriptions

I’ve got an Android project going that uses FFmpeg to build short video files (more on this at a later date). Normally, you run FFmpeg in a terminal window or command prompt.  It updates you on the status of a task in that terminal window.  I’ve instrumented FFmpeg on Android with some C code and a JNI interface.  When I execute FFmpeg from an Android app those status updates are written to logcat.

There are some important details that I was to surface in my app, primarily the progress of an encoding process.  In order to do this, I needed a way to read my Android apps logging statements.  This is possible on Android and without needing to request the scary READ_LOGS Android permission.

Note: All of my examples are written in Kotlin because I ❤ Kotlin.

You can execute a process with the Runtime API:

val process = Runtime.getRuntime().exec("some_command_here")

You can then get an InputStream to the Process’s output:

val process = Runtime.getRuntime().exec("some_command_here")
val inputStream = process.inputStream

Putting it all together, this is how one would get your apps logging statements from logcat, within the scope of your app:

val process = Runtime.getRuntime().exec("logcat")
reader = BufferedReader(InputStreamReader(process.inputStream))
var line: String?

do {
    line = reader.readLine()

    if (line != null) {
        // do something with this logging statement
    }
} while (line != null)

reader.close()

This code essentially runs forever because, as far as I’ve seen, reader doesn’t return a null line.

I’m close, but I still needed to add some sophistication. I needed to read and parse logs while FFmpeg was running so I can update the user on the status of their job. I needed to start parsing logs when I wanted to update the user while FFmpeg was running and end parsing when FFmpeg was complete. This problem is perfectly solved using RxJava:

fun readLogs(): Observable = 
  return Observable.create ({ emitter ->
    var reader: BufferedReader? = null

    try {
      val process = Runtime.getRuntime().exec("logcat")
      reader = BufferedReader(InputStreamReader(process.inputStream))
      var line: String?

      do {
          line = reader.readLine()

          if (line.isNotBlank()) {
              emitter.onNext(line)
          }
      } while (line != null)
      emitter.onComplete()

    } catch (e: Exception) {
      emitter.onError(e)
    } finally {
      reader?.close()
    }
  })

With RxJava, I can emit a logging statement as it’s received by logcat.

Usage:

val disposable = readLogs()
  .subscribeOn(Schedulers.io())
  .observeOn(AndroidSchedulers.mainThread())
  .subscribe({ logLine -> 
    logTextView.setText(logLine)
  }, { error -> throw error })
  
// later, when you no longer need to subscribe to your logs
disposable.dispose()

Because it’s all based on RxJava, you can do more advanced things like filtering

val disposable = readLogs()
  .filter({ line -> line.startsWith("foo") })
  .subscribeOn(Schedulers.io())
  .observeOn(AndroidSchedulers.mainThread())
  .subscribe({ logLine -> 
    logTextView.setText(logLine)
  }, { error -> throw error })

This allows you to “subscribe” to your apps logs!

/* fini */

Chromecast, the Pixel 2, and MDNS

There have been widespread reports of some network issues for users with a certain brand of consumer router, Chromecast / Chromecast enabled devices, and Pixels.  I’ve been having sporadic WiFi issues for a few weeks to a month.  I finally but 2 and 2 together. I have a number of Google devices in my house (a lot..perhaps too many 🙂 ).

  • Google WiFi mesh router system
  • 2 Chromecast audios
  • 3 Chromecast dongles (2 1st gen, 1 2nd gen)
  • 2 Android TV devices, Nexus Player running Android Oreo and a Xiaomi MiBox running Android 6
  • 3 Google Homes, 2 normal, 1 mini

I also use a Google Pixel 2 XL as my main device. I have several test devices as well. A problem I’ve been having for a while now is when I wake my Pixel up I lose my WiFi connection. It happens very sporadically. It’s annoying. There are also times when my Nexus Player loses its WiFi connection. In both instances, the WiFi connection is re-established, but it makes things like streaming video or controlling streaming video (via Chromecast) a nuisance.

I’m also an Android developer by day and have ADB Logcat open at all times. I use a Nexus 6 running Android 7.1.1 as my primary test device. Periodically, I’ll see the following group of log statements printed in Logcat, for my Nexus 6.

I/DeviceScanner: [MDNS] Received response from "Cast Device 1" (-----------) with result "0 No change"
I/DeviceScanner: [MDNS] Received response from "Cast Device 2" (-----------) with result "0 No change"
I/DeviceScanner: [MDNS] Received response from "Cast Device 3" (-----------) with result "0 No change"
I/DeviceScanner: [MDNS] Received response from "Cast Device 4" (-----------) with result "0 No change"
I/DeviceScanner: [MDNS] Received response from "Cast Device 5" (-----------) with result "0 No change"
I/DeviceScanner: [MDNS] Received response from "Cast Device 6" (-----------) with result "0 No change"

Note: I did mask my device names and MAC addresses in this snippet.

These are MDNS requests being logged by my Nexus 6 as they are received. I don’t have significant network experience or a deep understanding on how Chromecast really works at the network level. It appears, to me, that every 20 seconds or so, all of my Chromecast and Chromecast enabled devices send an MDNS packet announcing their presence on my network to devices that can utilize them, like Android phones and Google Home.

In fact, TP-Link, a router manufacturer, has come to the following conclusion:

This issue stems from these devices’ “Cast” feature, which sends MDNS multicast discovery packets in order to discover and keep a live connection with Google products such as Google Home. These packets normally sent in a 20-second interval. However, we have discovered that the devices will sometimes broadcast a large amount of these packets at a very high speed in a short amount of time. This occurs when the device is awakened from its “sleep” state, and could exceed more than 100,000 packets. The longer your device is in “sleep”, the larger this packet burst will be. This issue may eventually cause some of router’s primary features to shut down – including wireless connectivity.

So obviously there’s a problem here. Today I noticed, when I power on my Pixel 2 XL running Android 8.1 (w/ January security update), things get pretty crazy. My Nexus 6 seems to log over 1,000 MDNS responses in a 40 second timespan. With my limited networking knowledge, I don’t really know if anything is wrong, but it seems that something is out of wack. It also convenient that I tend to drop my WiFi connection, on my Pixel 2 XL at this time (when my Pixel is powered on from sleeping).

Running Wireshark on a computer verifies what I see in Logcat.  A gargantuan amount of MDNS activity upon powering on my Pixel can be seen.  It also stops when I put my Pixel back to sleep.  Waking my Nexus 6 up doesn’t result in the same amount of MDNS activity.

Something is probably wrong here.  I can’t identify it specifically, but this is just another data point.

A Factoid: The Enforcing-Inheritance-Generic-Thing-in-Kotlin

I learn a Kotlin thing everyday.  Today’s thing, “Enforcing-Inheritance-Generic-Thing-in-Kotlin” (I’m terrible with names and terms, there’s an official term for this that I cannot remember).

I have a function that starts an activity for a result.  I want to limit this function to only be able to start activities extending an abstract class, FooActivity.

In Java:

private void start(Activity activity, Class<? extends FooActivity> cls) {
   // some stuff with intents
}

In Kotlin:

fun start(activity: Activity, cls: Class<out FooActivity>) {
    // some stuff with intents
}

That’s it.  Read more about generics in Kotlin here.

Traffcams, now serving Georgia and Washington

Beginning today, Traffcams now has traffic cameras from Washington (WSDOT) and Georgia (GDOT).  Traffcams is a powerful app that puts the traffic cameras around you in your hand.  Traffcams now contains over 3,700 traffic cameras in 3 states.  More locations are on the way.

Get Traffcams free, from the Google Play Store today.

Get it on Google Play

Traffcams

Almost 5 years ago, I built an app that put every NC DOT traffic camera in your hand.  At that time, there was no such thing as looking up traffic conditions in Google Maps.  I was also in the early days of being a professional Android developer and used this app as a way to learn the ins and outs of building, marketing, and publishing an app on Google Play.

Today, the app is still used by a handful of users, but I noticed it’s usage is very seasonal.  People were using NC Traffic Cams (the name at the time) to check not only traffic congestion, but road and travel conditions.  It did not surprise me to see spikes in usage around snow storms, tropical storms, or hurricanes.  Things have stalled in recent years, but I am revisiting this app in hopes of building a really cool app and platform to view images from traffic cameras.  The first step of this starts today.  I’m re-launching NC Traffic Cams as simply, Traffcams.

Traffcams

Traffcams has been completely rebuilt and redesigned from the ground up.  It is built on modern technology and given a more modern design.  Traffcams contains all of the same features people came to love, plus a few more:

  • View cameras around you using your location
  • View cameras on a map
  • Favorite cameras for easy access
  • Search for traffic cameras by city, street, highway, or interstate name
  • Pin your searches for easy access

  

Traffcams is launching with traffic camera data availability for North Carolina.  I hope to quickly add support for other states as soon as I can.  You can get Traffcams, for free, on Google Play today.
Get it on Google Play
I have a roadmap of features to implement that are going to be really useful for commuters.  As with all side projects I work, Traffcams will allow me to learn new patterns and technologies for mobile development and really dive into using machine and deep learning.