This is the third blog in the series on building custom MDM (Mobile Device Management) solution for Android tablets. The first blog gives a general overview of the MDM solution and talks about the server architecture. The second one is about the Android MDM agent architecture.
The high level idea is that we have an MDM agent that runs on the tablet, fetches ‘operations’ from the MDM server, executes them and returns back the status to the server. In this blog, I will be talking about some implementation specific details on the Android MDM agent, specifically around serializing the processing of ‘operations’, which required us to implement multi-threaded synchronization in Android.
To start with, what are operations?
Operations are tasks that the server wants the tablet to execute, for example, set password policy, install an application, block settings on the tablet, et al.
So, what’s the problem?
The problem is making sure, during the operation processing on the tablet, only one operation of a given type is executed at any given point in time. This means that two PasswordPolicy operations should not be executed at the same time. It is ok though, to have a PasswordPolicy operation execute in parallel with InstallApp operation. In fact, that is desirable. Executing operations of the same type in parallel has a serious drawback.
Lets say the operation being executed in parallel is the PasswordPolicy operation. One of the operation wants to apply the password policy as Normal (4 digit password) whereas the other wants it to be Strong (6 alphanumeric password). Now based on which one gets executed last, the password policy will be set on the tablet and not necessarily the one that the server generated last. Worse the server could get out of sync with the tablet, based on which operation sent its status last.
So, what’s the solution?
Enter Android. As mentioned before, the MDM agent is an Android component. Android provides the IntentService that can solve a similar problem. Its basically a Service that processes one Intent (asynchronous request) at a time. It maintains a queue for the incoming Intents and spawns a single worker thread to process them sequentially. It provides a method for handling the Intent. Once the method is executed, it will pick up the next Intent waiting on the queue. Sounds like what we want, right? All we do is create an IntentService for each of the operation types and just keep firing Intents at the appropriate IntentService, as and when the operations arrive. Here’s how it would look.
But wait! There is always a but, in’it?
What’s the problem now?
Enter multi-threading. Some of the operations have to jump to another thread to finish their processing, viz., the Main thread, also known as the UI thread. Android mandates that all UI activities be performed on the Main thread. So, our PasswordPolicy operation would go on to the Main thread to show the dialog box for changing the password to confirm to the new password policy. When the control is passed to the Main thread, the worker thread thinks that its done, without waiting for the response from the Main thread. It happily continues processing the next operation, while the previous one could still be waiting for a user response on the Main thread. Damn! Serialization in multi-threaded environment is not easy.
So now, what do we do?
Now what we do is, block the worker thread until the Main thread has finished doing what its doing. We provide a callback when we jump off of the worker thread. When the main thread finishes, the callback is called which unblocks the worker thread, performs finish up activities for the operation (sending status to the server) and then releases control. Android will pick up the next Intent and process the next operation. Mission accomplished! 🙂 Here is a pseudo sequence diagram for doing it.
We could have implemented this in a purely non-blocking way, by having the finishing operation trigger the next one. This way though its hard to have expiration time on the operation processing since it moves between threads. Blocking makes it very easy to do that. We use the Java CountDownLatch with a timeout on it for blocking the thread. Also conceptually its easier to understand. And since its a background/worker thread its not such a big deal blocking it.
Thanks for reading! Comments/suggestions welcome.