For quick background, I’m an iOS development consultant, and I work primarily with funded startups who are building and growing their MVP app. Most of my clients are consumer-facing startups, so most commonly, the apps I help my clients build are destined for the App Store. However, I occasionally have a client who needs to deploy an application via Apple’s enterprise distribution model. This is a case study about just such an occasion.
In this instance, my client needed to develop a custom iPad application, deploy it to hundreds of iPads around the country, and then remotely manage the device and app, including periodic updates. Crucially, the iPads were installed in a kiosk configuration, and we needed to be able to lock the iPads to our custom app. Finally, we needed to be able to manage and update the device and the app without any physical access to the devices or any kind of user intervention being required.
That might not sound like a challenge, but it is, for reasons I’ll explain.
By the way, this is a bit of a technical post and really gets into the nitty gritty details, so bear with me if you’re not in the market for this kind of solution. However, I believe it also serves to demonstrate a lot of the types of decisions and tradeoffs you must make as a startup when trying to build out your products with limited information, budget, etc. I have run into these types of quagmires more times than I can count as a software developer, where you’re trying to navigate a thousand tiny obscure details to get things working the way you need them to. As a result, I have a fair bit of experience helping my clients navigate them, which is why they hire me 🙂
One of my clients is in the mobile payments space, and while we have a version of their application that we distribute via the App Store, we also have an internal version that is designed for tablet devices. My client needed a version of the app that could run on tablets installed at hundreds of locations across the country. Unlike their iPhone app that’s distributed via the App Store, this tablet version would be distributed via the enterprise distribution model, for various reasons, mostly related to control, security, and speed of updates.
However, there were a few other requirements for the tablet app:
- We needed to be able to fully manage the device and the apps that would be on it.
- That management needed needed to be 100% remote, with no physical access to the device after initial provisioning. While the iPads are owned by this company and under their physical control, they don’t have technical staff on hand at every location to manage any issues. So we needed to be able to remotely manage them and deal with any problems that came up.
- We needed to be able to push updates to the app and other changes and have them install with no user intervention needed.
- We needed to be able to lock the device into a mode where only our custom tablet app could be used.
Each of these is easy to accomplish by itself, but getting all of them to work together was a complete nightmare.
For #1, Apple has a concept called Mobile Device Management (MDM) for exactly this situation. You use an MDM provider (we chose Meraki) and enroll your devices in their program. From there you can change almost any setting on the device and install or uninstall apps across all your devices.
However, can you do it remotely, with no user intervention? That’s our requirement for #2 and #3, and that’s where we started to see issues.
See, the primary use case for MDM isn’t devices sitting somewhere that you are remotely managing. Instead, it’s where your employees have devices (either their own iOS devices or ones you provide) and you can remotely manage them within your organization. However, users have a role to play in this model, from approving apps that are installed, choosing when to install updates, etc.
After some sleuthing, we found that Apple offers a “Supervised mode”, which you can apparently ONLY put your devices into by either buying them from Apple in that mode, or by physically plugging them into your computer and using Apple Configurator. OK, fine, we’ll do that during provisioning. The great thing about Supervised mode is that you have absolute control over the device, and you can install, uninstall, and update apps without user intervention at all.
There’s one problem, and it relates to point #4 above.
In order to update an app, remotely or otherwise, that app can’t be running. When it’s time to update an app, iOS needs to shut it down first, do the update, and then start it back up.
However, just shutting down the app while the user is using it without any warning isn’t a great experience, so Apple pops an alert asking if the user wants to install the update now, or later. That’s not great, because it breaks the requirement #3 above (no user intervention). But all in all, it’s probably not the end of the world.
The problem comes with requirement #4: locking the iPad to just our app, meaning that a user can’t exit the app without a code of some kind.
There are a few different ways to “lock” the device to a single app, but they all share a common problem: if the app is running and can’t be shut down, but it must be shut down to install an update, then there’s no way to install updates at all.
And that’s exactly what we found: if we pushed an update from our MDM server to our app while the app was running but the device was NOT locked to our app, then the user got a little alert about the update (not great).
But if we DID have the device locked to the app, which was a requirement, then the MDM app update push just failed, silently and completely (terrible).
So now what? Well, we needed a way to take the app out of being locked to our app, shut our app down, update the app, restart the app, and then put it back into single app mode. All remotely.
How best to accomplish this? Well, we started with the most straightforward approach, which was something called “Single App mode”. MDM servers can push a special profile to a device that locks that device to a particular app, which is what we wanted. While in Single App mode, the home button is not responsive, and hard-restarting the device results in the app being immediately relaunched after device startup. In fact, the only way that we could discover to get out of Single App mode was to disable it remotely from the MDM server, or plug the device into a computer and erase it completely.
So under the Single App mode approach, our update process looked like this from Meraki, our MDM server:
- Put latest build of the app on Meraki
- Disable Single App mode on all devices
- Delete the app from all devices (so we don’t get the little popup when app is running) 
- Install our app (which installs the latest version that we put on Meraki in version 1)
- Enable Single App mode for our app on all devices
- Note: There’s no way to shut down an app remotely from an MDM server other than by deleting it. Our app was designed to be able to be deleted and reinstalled with no data loss or reconfiguration needed.
If you were looking at the device in Single App mode while all this happened you would see it shut down and disappear from the home screen (when we deleted it), install on the home screen (when we installed it again), and open back up (when we enabled Single App mode in the last step). We do this process outside of business hours when no one is around to mess with the devices.
OK, so that’s a huge pain, but whatever, it works right?
Well, yes. It works great. But there’s a huge problem with this method.
Remember when I said that the only way to get the app out of Single App mode was to either plug it into a computer (which violates one of our requirements of no physical access) or to disable it from our MDM server?
What happens if something goes wrong with the internet connection on that device?
Yeah. If something happens so that the device can no longer connect to Wifi, we can’t send commands to it from our MDM server so we can no longer disable Single App mode. Making things worse, a local staff member (if we even had one available) can’t exit the app to go to Settings and fix the Wifi connection, because it’s locked in Single App mode. The device is effectively useless at that point, and if they aren’t able to plug it in to a computer to wipe it and re-enroll with Meraki (which isn’t an option for the locations in our case), then the only option is to ship the device back to us at HQ so we can erase it.
That’s insane, we can’t do that. These devices ARE going to lose connectivity at some point. Their Wifi hotspot will change its name, or its password, or something. Guaranteed. And when that happens, it can’t effectively brick the devices and require them to be shipped back to us.
So this whole “Single App mode” thing appears to be a total rabbit hole / waste of time. It works too well, and thus is just too risky to deploy in any remote environment where we don’t have easy physical access to the device.
OK, so that’s a non-starter. What other options do we have?
Well, there’s another option for locking the device to a single app other than Single App mode, and that’s “Guided Access”. Like Single App mode, Guided Access also locks the device to a particular app, but with two important differences:
- A user with the device passcode can triple click the home button or hard-reset the device to break out of Guided Access mode (good)
- It can’t be set remotely; a local user has to go into Settings on the device and set it into Guided Access mode. (bad)
That second one isn’t the end of the world though; we can just put into Guided Access when we initially provision the device, right?
Yes, but what about updates? If the device locked into our app and we push an update, it will fail silently, as described above. And remember, we can’t remotely enable or disable Guided Access mode. So that means that we can only push an update if we have someone with physical access to the device who can unlock it, wait for the update to be pushed and installed, and relock it.
That won’t work for our purposes. We have hundreds of these devices around the country, and may only have someone on-site that we can coordinate with to perform these updates once or twice a month. It would be a full-time job for someone to sit on Meraki and do phone calls with these staff members all over the country to coordinate these updates.
So Guided Access was a non-starter too, it appeared. It gives us the ability to let people break out with the passcode, but since we can’t enable or disable it remotely, coordinating it with our updates on hundreds of devices all over the country is nigh impossible.
Here’s another possibility for an easy fix from Apple: make Guided Access a setting that MDM servers can set remotely. Then we get the best of both worlds.
At this point, we were at a dead end. Neither Single App Mode nor Guided Access Mode offered us what we needed for our requirements. This had been a frustrating exercise to this point, because this feels like something that should be possible without this many issues.
Around this point in our journey, I stumbled across an obscure API call that Apple offers:
Here’s what the docs have to say about it:
You can use this method to lock your app into Single App mode and to release it from that mode later. For example, a test-taking app might enter this mode at the beginning of a test and exit it when the user completes the test. Entering Single App mode is supported only for devices that are supervised using Mobile Device Management (MDM), and the app itself must be enabled for this mode by MDM.
This turns out to be exactly what we need, with a little extra work on our end. The fact that this is only available for Supervised devices that have had this capability specifically enabled for that app by an MDM server is probably why there’s so little info about it. Almost no one is using this API, I suspect.
So this will let our app lock itself into Guided Access mode, and then release the mode later. We can’t do it remotely, the app running on the device has to do it. That’s great, but our app (by default) has no way of knowing that there’s an update coming, so it doesn’t know when to release itself from Guided Access mode. And if there’s an update that gets pushed while the app is in Guided Access mode, it will fail.
So what we needed was a way to tell our app that an update is coming, and to therefore release itself from Guided Access mode and then shut down. That meant that we needed to modify our app and our server / admin dashboard to be able to push a notification to the app that an update was coming.
Once that was done, we were very close to having everything working.
Now upon initial launch, the app would put itself into Guided Access mode. In case of issues, a local staff member could still triple tap the home button and enter their passcode to break out of this Guided Access mode too, unlike the Single App mode pushed from the MDM server.
Then, here’s what the flow looked like for updating the app:
- First, we send a command from our server to the app that an update is coming
- The app receives that command, exits Guided Access mode, and shuts itself down
- We push the latest version of the app from Meraki to the devices, which all update the app to the latest version from Meraki
OK, great, but we still have a problem. Our update is installed, but the app still isn’t running at this point, and we need some way to remotely start it back up.
Sadly, the best solution we ended up finding was to enable Single App mode from the MDM server, which launches the app and locks it into Single App mode. Then we immediately disable Single App mode from the server, which crucially doesn’t shut the app down, just restores the ability for a user to hit the home button to exit the app.
Note: we still have the same risk here with Single App mode and loss of connectivity. While the device could still lose its internet connection so we could no longer disable Single App mode, it would have to happen in the minute or two between when the app is launched into Single App mode and when the MDM server send the command to disable Single App mode. We’ve found that risk to be acceptably small for our purposes.
Finally, we send a notification from our server to the app for it to request Guided Access mode again, which puts us back to the original state. The app has been updated, the device is locked to our app, and a local staff member with the passcode can triple tap the home button to escape Guided Access mode.
That’s a ridiculously complex procedure to accomplish something that should be pretty straightforward.
It’s also very brittle, unfortunately. It’s easy to see that Apple could change something in iOS that would break our flow above in some crucial way, either intentionally or as a new bug. And since this relies on some APIs and capabilities that are rarely used, it’s even more likely that Apple might unintentionally break them and be slow to respond to bug reports.
However, you do the best with what you have to work with when you leverage other platforms 🙂
By the way, if I could ask for anything from Apple with regard to this whole mess, it would be any of the following:
- Make it so I can remotely shut down, update, and restart an app on a supervised device, even if that app is running when I send the update.
- Make it so that someone with a passcode can exit Single App Mode, just like Guided Access mode.
- Make it so that Guided Access mode could be enabled or disabled remotely, just like Single App mode can.
Any of these changes would completely solve this whole problem. The only issue I see with the first one is that it’s a terrible user experience to shut down an app in use without any warning. Maybe you could get a countdown alert that would let a user cancel the update, but not require them to approve it?
Anecdotally, I’ve done some research on whether Android would be better for this whole scenario, and unfortunately, it seems that Android’s MDM and Single App mode capabilities are even more limited than iOS.
The only other option that we’re left with is a very thin wrapper for our app that we embed a web app in, removing the need to update the app at all (we’d just update the web app on our servers as often as we want). I’m normally pretty opposed to hybrid app development because of the negative impact on the overall user experience, but in this case, the hassles around updating this app remotely make me think that it might be worth it.
However, that would only partially solve the issue. Our app has several hardware devices that need to be integrated, as well as needing other APIs that can’t be accessed from a purely web-based app. So we’d need a native wrapper for the app that would have a decent amount of native code in it. That would eventually have to be updated, and even if it was only once or twice a year instead of every month, we’re still back to the same set of issues around coordinating those updates with going in and out of single app mode.
So for now, we’re stuck with the working-but-brittle solution we ended up with at the end of this.
If you made it this far in this little narrative, I hope you enjoyed this tale of woe. I’d like to say that this isn’t typical, but you’d be surprised to know how often I get mired in these kinds of messy situations where our business needs dictate a solution that’s not easily implemented, or even understood. The first issue with all of the above was just spending the time to understand fully what we were dealing with, and then be able to explain it to the client in a way that they could understand. From there we could make the necessary decisions together about the best way to go about solving the problem for our particular case.
By the way, if you have experience with the specific kind of use case above that we were trying to solve and can see anything that I should have done better, please drop me a line and let me know!