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 */

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.

TrafficFlow – Classifying Data

As a follow up to my initial TrafflicFlow post, I have built some more software to help me classify the dataset I collected over the past few weeks.

TrafficFlow is a project where I develop an algorithm that can “look” at a still image pulled from a traffic camera and determine whether or not traffic is congested.  I am using the deep learning framework, TensorFlow, to build the model that will house this algorithm.

Over the past few weeks, I have collected 4,966 still images from a NCDOT traffic camera.  I wrote a Python script that took a snapshot.  I cron’d that Python script to run every 4 minutes.  Now that I have all of this data, how can I efficiently classify it?  A few ideas came to mind:

  • Python script that loaded the image in a picture viewer and presented a question in terminal.  This worked, but the picture viewer grabbed focus. I also couldn’t close the picture viewer automatically.  I determined that the extra interaction involved would make classifying the data this way, inefficient.  This also limited me to classifying data on my MacBook Pro only.
  • AngularJS web app that allowed me to classify images in a desktop web browser.  This was interesting, but I didn’t know a ton of Angular and this limited me to classifying data on my MacBook Pro only.

I’m an Android developer by day (checkout RadioPublic 😉 ).  I figured I’d just build an Android app that would allow me to classify the data, so I did.  But first, I needed to gather the collected data into a format that is easily usable in this app.  So I wrote a Python script:

This script simply reads a list of files from a directory, creates an entry in a dictionary (saving some additional metadata in the process), and exports that object to JSON.

A snippet from the exported data looks like:

Next, I uploaded this JSON file to Firebase.  Firebase is a backend as a service that allows app developers to quickly build apps without needing to spin up servers or turn into “devops”.  Best of all, it’s free to get started and use.

Finally, I uploaded 4,966 images to my web server so that my app can access them.

Now on to the app.  It’s nothing special and particularly ugly, but it works.

It allows me to quickly classify an image as congested (1), not congested (0), or ditch/don’t include (-1).  Once I classify an image, it saves the result (and my progress) to Firebase, then automatically loads the next one.  It turns this exercise into a single tap adventure, well a 4,966 series-of-single-taps adventure.

I’ve uploaded the Python script and Classify Android app to GitHub (https://github.com/emuneee/trafficflow).  I hope to make my dataset available soon as well.

Now onto classification.

New Android Stuff Part 1 😍

I’m barely into the things that were released or announced at Google I/O 2017.  I’ve already got a list of stuff that I need to watch and review.  It’s really a lot of stuff and it’s only day 1!

What’s New In Android

After watching the Google I/O Keynote, this is normally the video I watch next.

Kotlin is Officially Support for Android Development

I’ve been holding off on doing anything major in Kotlin until it was blessed with official support from the Android team.  Well, I’m out of excuses.  Kotlin is an officially supported language for Android development.  It’s necessary dependencies and plugins are being integrated into Android Studio, beginning with version 3.0.

Kotlin and Android | Android Developers

New Android Studio Profilers

There are a ton of re-designed profilers for CPU, memory, and network operations in Android Studio 3.0.  I’ll let the pictures do the talking (all taken from Android Developers).

I’m especially pumped about the network profiler!

Android Studio 3.0 Canary 1 | Android Developers

Android O Beta

The next version of the Android O Beta was released today.  If you have a Nexus 5X, 6P, Pixel, Pixel XL, Nexus Player, or Pixel C, you can enroll your device at android.com/beta.  I’ve been using it for a few hours.   The only issues I’ve seen are Android Pay doesn’t work (it politely lets you know with a splash screen) and the Google Play Music playback notification just re-appears from time to time.

Android O Developer Preview | Android Developers

Android Architecture Components

The Android team has started putting together new tools and guidelines to help Android developers properly architect their app to prevent memory leaks, make lifecycle management easier (!), and reduce boiler plate code.

A new SQLite object mapper from the Android team, called Room.

Screenshot from the Architecture Components Talk

 

Android Architecture Components | Android Developers

These are just a few of the things that immediately stood out to me as an Android Developer.  I’m looking forward to doing a deeper dive into all of it.

Share the Cache

A lot of Android apps make viewing images a core part of their user experience.  Many of these apps use image caching libraries, like Glide, to make image caching easy, robust, and configurable.  Sharing these cached images can be a bit tricky.  A few questions arose when I tried:

  • How do I access files in the cache?
  • Do other apps have access to these files?
  • What are the needed permissions if I need to manually copy the file somewhere else?

Those are just a few of the questions that came up, each with a pretty simple answer.  A first pass at sharing images in my cache involved, re-caching the image to a public directory (ie. root of the external storage device), generating a URI, and passing that URI to an Intent to share with other apps.  This sounds simple, but it is complicated by the fact that I needed to ask the user for permission to read and write to the external storage on their device, if they were running Android 6.0 Marshmallow or above.  This flow worked, but I was looking for something much simpler for the user and myself, the developer.  Enter the FileProvider.

The FileProvider API

The FileProvider API, added to the Android Support libraries in version 22.0.0, is a ContentProvider-like API that allows URI specific sharing of files relevant to your application.  It can, temporarily, enable access (read and/or write) to the file at the URI.  You also do not need to copy the file to a more accessible location on the user’s device.  Setting up the FileProvider is very straightforward.

Setting up a File Provider

Setting up the FileProvider is a three step process.

First, like we would do with a ContentProvider, we added a <provider> entry to our AndroidManifest.xml file.


<provider 
    android:name="android.support.v4.content.FileProvider" 
    android:authorities="com.yourdomain.android.fileprovider" 
    android:grantUriPermissions="true" 
    android:exported="false">

    <meta-data 
        android:name="android.support.FILE_PROVIDER_PATHS" 
        android:resource="@xml/filepaths" />
</provider>

This provider entry has a <meta-data> element that points to an XML file, that defines what paths, within our application file structure, we want to expose with our FileProvider.  This is important.  You can only share files in the paths contained in this XML file.  I created an XML file in the “res/xml” folder.


<paths>
    <external-files-path name="image_files" path="." />
</paths>

In my particular instance, I am caching images to the External Files directory (which can be located on the SD card or a portion of internal memory, simulating an SD Card).  Other tags you can use here:

The name attribute is a string that will be used as a URI-path component in the URI that’s generated by FileProvider.  The path attribute, is the relative path to folder containing the files you would like share.  In my case, I’m fine with sharing the root because it only contains cached images, but you should probably be as granular as possible in the case where you have multiple directories or file types in this directory.

Now that my FileProvider is set up and configured, how do I share files out of my cache.

Sharing Cached Files

The specifics on sharing cached image files is highly dependant on the Image caching library used and how it’s configured.  At a high level, the process is:

  1. Cache an image to a location (done by an image caching library) that falls in the location specified in XML configuration file given to the FileProvider.
  2. Get a reference to that cached image using the java.io.File API.
  3. Pass the File reference to FileProvider.getUriForFile() to get a URI you can pass to other apps.
  4. Pack that URI into an Android Intent to start sharing with other apps.

Very straightforward.  Now this is how I did it with Glide.

My first step was to specify a location for Glide to store cached images.  I needed to subclass DiskLruCacheFactory to make this happen (Note: you don’t need to subclass DiskLruCacheFactory if you want to use the Cache directory).


private static class DiskCacheFactory extends DiskLruCacheFactory {

    DiskCacheFactory(final Context context, final String diskCacheName, long diskCacheSize) {
        super(new CacheDirectoryGetter() {
            File cacheDirectory = new File(context.getExternalFilesDir(null), diskCacheName);
            return cacheDirectory;
        }, (int) diskCacheSize);
    }
}

As you can see from the code snippet, I pass in a reference to the External Files directory.  By default, Glide uses the Cache directory.

I implement a GlideModule so that I can customize caching behavior in my application.  I specify my subclassed DiskLruCacheFactory class in my GlideModule.


public class MyGlideModule implements GlideModule {
    private static final int IMAGE_CACHE_SIZE = 200_000_000;

    @Override
    public void applyOptions(Context context, GlideBuilder builder) {
        builder.setDiskCache(new DiskCacheFactory(context, “.”, IMAGE_CACHE_SIZE));
    }

    @Override
    public void registerComponents(Context context, Glide glide) {
        // ...
    }
}

Now, I instruct Glide where it can find my GlideModule by adding a <meta-data> tag to the AndroidManifest.xml file, in <application> element.


<meta-data android:name="com.yourdomain.android.MyGlideModule" android:value="GlideModule"/>

Next, I invoke a manual file caching with Glide.


File file = Glide.with(context)
    .load(uri) // uri to the location on the web where the image originates
    .downloadOnly(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
    .get();

This is specific to Glide and worth mentioning.  When Glide caches a file to disk, the file name is comprised of generated key with an integer appended to it.  There is no extension.  This is important because sharing an extension-less image will make it difficult for the apps you are sharing with to determine how this file should be handled (despite the MIME type being sent along with the image in the Intent).

Contents of a Glide image cache folder

 

I worked around this issue by simply copying that file, appending an extension to the duplicate in the process.  Since I know I am always handling JPEGs, I give these files the “.jpg” extension.  This may not work in cases where you may be dealing with different types of images like, GIFs, PNGs, WebP, etc.

Finally, I can get a URI from the FileProvider for my cached image by calling FileProvider.getUriForFile().

// be sure to use the authority given to your FileProvider in the AndroidManifest.xml file
String authority = “com.yourdomain.android.fileprovider”;
Uri uri = FileProvider.getUriForFile(context, authority, file);
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_STREAM, uri);
intent.setType("image/jpeg");
context.startActivity(Intent.createChooser(intent, “Share via”);

The Uri generated by the FileProvider looks like:

content://com.yourdomain.android.fileprovider/external_files/bb65e6a364264255b4833d34e____some_key.0.jpg

Once the Intent makes its way to the target app, things should look just like you are sharing a picture you’ve just taken.

Sharing an image from Traffcams to Gmail

#Dassit