Simple Hardware Hacking: Auto "On Air" VC Indicator - Chapter 4
This post is part of a series on building an automatic busy/not busy light for your room/office/desk. If this is the first post in this series you're landing on, I recommend starting with Chapter 1. We'll be building on the groundwork that those posts laid.
As a quick summary, up until now, we've created a Chrome extension which listens for instances of Google Hangouts call windows and turns a light red or green according to whether or not I'm actively on a call.
This is useful by itself but me being on a video call is obviously not the only reason I might not want someone to barge into my room. I'm a big fan of naps and this could be the perfect thing to avoid people unintentionally waking me up.
You might be thinking, how in the world are we supposed to detect if we're sleeping by using an android app? This might be pretty specific to me, but I almost never take a nap without setting an alarm so there being an active alarm clock is a solid approximation of whether or not I'm napping. So our goal is to create an app that updates the sign to indicate busy whenever there is an upcoming alarm clock set.
To give a quick overview, the things we'll cover in this post are:
Getting a simple, single activity android app up and running
Reviewing the pertinent parts of the generated code
Adding Dependencies to an android project
Adding a button with some functionality
Making Network requests
Requesting permissions in our AndroidManifest file
Listen for Alarms Being Set?
If we take our Chrome extension as an example, it would be useful to have a listener for changes to the alarm clock. In my searches, such a thing doesn't seem to be possible. So we'll have to come up with another way.
Whenever it's not possible to use a push model (i.e. the system calls our callback when the time is right) you need to resort to a pull model. That is we'll need to set up a repeating task to check the current state of the alarm clock and update the status accordingly. Before we get into the nitty gritty of getting something to repeat regularly let's just take a look at what doing this once will look like.
Quick Tour of the Empty Activity Starter App
Let's start from the start with one of the shell android app options. To keep things simple, I suggest using Android Studio. Create a new Android Studio project by selecting "File" > "New" > "New Project …". The first page prompts you to pick a starter template. I recommend going with Empty Activity. That option gives a good balance of including just the stuff we need and nothing that we don't. On the second page of the New Project Wizard, be sure to select a minimum SDK version of at least 23. Then you should be able to click finish to create your new project.
If you're new to Android development, there's kind of an astonishing number of files and directories that it generates. In previous posts, I've tried to include all of the code in the post itself. Unfortunately, the amount of stuff auto-generated in the basic android app makes that undesirable. I'll be highlighting all the interesting bits of the implementation here, but rest assured, you can check out the full content of the app in the repository on GitHub.
Here are the files that are relevant to us:
java/com/example/myapplication/MainActivity.kt
This is the main entrypoint for this app. Right now, all this code is doing is saying "Use this layout file to render my app". Which brings us to:
res/layout/activity_main.xml
This file defines a layout. Specifically, it declares a "Constraint Layout" and has a TextView which displays the text "Hello World!".
manifests/AndroidManifest.xml
Finally, we have the Android Manifest. This is basically just a bunch of information about your app. This is where things like what permissions your app uses, what icon to use in the launcher etc are stored. In our case, the most pertinent thing to note is that this is where we're declaring that MainActivity is the one that should be run when the user taps on the icon in the launcher for this app.
Even though we haven't done anything yet, let's build the app and take a look at what this gets us:
Adding A Button
As described in the section above, we want to add a button to our activity which will check if there are any upcoming alarms and call our REST API accordingly. Let's update our manifest file to have a button that says "SCAN" instead of the text that says HelloWorld:
You should be able to rebuild and see your button in place of the "Hello World!" in the original version:
Defining the Click Listener
And now we'll add a click listener for that button to do the checking. In our click listener, we'll be making an HTTP call to our REST server. To do that we'll have to do a bit of setup. Namely:
Add a dependency to Volley (which is a utility for making http requests on Android)
Add a declaration to our Android Manifest that we need access to the internet
Add the Volley Dependency
In your project view, below the implementation of the app are the "Gradle Scripts". In that folder, you'll find two build.gradle files. One is for the overall project and the other is specifically for your app. That may be slightly confusing given the current state of our project because our project IS the app right now, but that would be useful in the case that you had more than one app in a single Android Studio project.
But I digress, the main point is just to make sure you're modifying the right build.gradle file. The easiest way I use is to check in the project outline on the left side. The one we'll modify is the one labelled "(Module: app)"
Once you've opened the right file, you should find a section labelled dependencies at the bottom:
Add the highlighted line to your dependencies section.
Add the INTERNET permission to AndroidManifest.xml
Open your AndroidManifest.xml and add the following line:
Now we can implement the click listener for our button:
To walk through this, after we set our content view to be our activity_main layout. We grab our button by id (this is the id declared in the layout file) and set an onclick listener.
In the onclick listener, we get the AlarmManager using the getSystemService utility, and we get the alarm clock info from the AlarmManager.
Now we have the information we were looking for. If nextAlarmClock returns null, that means there's not currently an alarm set, otherwise, there is. We use that information to construct which url we will be making our request to. Note that we declare our endpoint at the top of the file. This will be the local address of your Flask Server. The same one that your Chrome Extension should be configured to talk to (if you've followed along with that blog post).
Next, we construct our StringRequest Object and finally we make our request by constructing a one-off queue and adding our StringRequest to it.
When building our StringRequest, we provide two callbacks. One that should run if the request succeeded and one if it failed. In either case, we create a toast to make it easy to see whether or not our request was successful.
Okay Great! So now we have our button wired up so that it will actually do something.
So go ahead and run your app and try clicking on your button.
Most likely, you saw a toast that said. "Didn't work: java.io.IOException: Cleartext HTTP traffic to 192.168.1.6 not permitted"
It's complaining because, by default, Android doesn't allow your app to communicate over plain HTTP. Generally, it's super important to make all calls over HTTPS to make sure that third parties can't access your data. In this case though, we're sending non-sensitive data over our local network so we'll lift this restriction to our app. To do that, we'll need to go back to our AndroidManifest.xml and add an attribute to our application tag:
Now, if we go back to our app, (assuming that our python server is listening on the provided endpoint) we should see that our request has now gone through successfully.
Conclusion
As they say, the first step's a doozy! To recap what we've accomplished, we have an android app that observes the state of the device that it's on and makes a call to our REST endpoint accordingly. The main shortcoming of our current version is that we need to push the button every time we want it to check which definitely limits its usefulness.
In our next post, we'll dig into Android Alarms (not to be confused with AlarmClock) which will allow our app to repeat this check continually without us needing to push any buttons.
See you then!