We've Launched "The Suddenly Remote Playbook,"
A Comprehensive Guide for Working Remotely
The Suddenly Remote Playbook
Read Now
Mobile
10 minute read

Building an Android POS App That Can’t Be Closed

Tudor is a full-stack software developer focused on JavaScript. He created numerous mobile and web apps, as well as server-side apps.

The world of mobile app development is vast and ever-evolving, with new frameworks and technologies popping up almost on a daily basis. When you think about a mobile device, you probably think of your phone or maybe your tablet, even though they are nowhere nearly as popular as smartphones.

Apple’s iOS and Google’s Android dominate the mobile market, each having had its ups and downs over the past decade. Today, I am going to talk more about Android and its use on devices that aren’t necessarily mobile.

Being open-source had a really interesting side effect on Google’s mobile operating system. Sure, we may think of all of the different Android forks from various smartphone companies, but what about all the devices running Android that aren’t mobile? Everything ranging from fridges, smart ovens, door locks, or even Point of Sale (POS) devices can run Android nowadays. The latter are the reason I ended up writing this article.

Point of Sale (POS) devices can run Android nowadays

Android POS Systems

About a year ago, I got to play with an Android device that was anything but ordinary, and it’s not something most people are likely to use. The device in question is an Android-based POS system from a Chinese vendor that also has an integrated thermal printer (like the ones used to print out receipts at stores or on ATMs).

The biggest surprise, though, was its software: It was running a bone stock version of Android. If I recall correctly, at the time, it was running Android 8, or Android Oreo if you prefer Google codenames. The device itself looks like an old school portable POS device, but instead of the physical keyboard where you would enter your PIN, it sports a capacitive touchscreen like the ones used in the Android phones of yesteryear.

My requirement was easy: I had to see if there was a way we could use this device’s features such as the thermal printer while also running the app we were developing. As soon as I realized that the requirement itself was possible, another problem came to my attention: security.

The thing is, if you have a device that handles card payments and other kinds of transactions, you might not want that same device to be able to run TikTok, Gmail, or Snapchat. This device was behaving exactly like a tablet, and it even came with Google’s Play Store pre-installed. Imagine going to a small convenience store and seeing your cashier taking selfies, opening emails from a Nigerian prince, and browsing weird, malware-ridden websites.

And afterward, the cashier hands you the same device to enter your PIN. Personally, I would not feel safe about providing my credit card information over such a device.

Locking Users Out of Android Menus

Security aside, I had to take on an even more important challenge: I had to lock the person using the Android POS device inside my app. Messing with the operating system was not an option since these devices were delivered to non-technical people.

Sure, cashiers are more than able to install an application, but most of them could not flash custom ROMs or handle other lower-level operations. The app itself was written in React Native, although that is irrelevant in this context. All the modifications I made are in native Java code, so no matter what you are using to develop your main application, these tweaks should work.

As a little disclaimer, this procedure only works for Android apps. Apple does not give us the control we need to easily accomplish something like this on an iPhone or iPad, which is understandable given the closed nature of iOS.

There are four ways a user could possibly exit an application:

  • Use the Home button.
  • Use the Back button.
  • Use the Recents button.
  • Leave your app through the notification bar.

Either clicking on a recent notification or going to settings from that bar would cause a user to exit our application. You also have gestures, but at the end of the day, these gestures trigger the exact same actions as regular button presses would.

Also, having a PIN system to unlock the application can be really useful for someone to manage the device. This way, only someone holding a PIN would be able to install a different version of the application, without offering deeper access to the end user.

The Home Button

In order to prevent a user from pressing the Home button, we don’t have to actually disable it.

One useful feature of Android is the availability of different launchers. Usually, these apps provide you with different home screens, app drawers, and access to various UI customizations. Every Android device has one pre-installed by the manufacturer. Ultimately, these are just normal, regular apps with one small but crucial exception.

What that means is that if the operating system could recognize our application as being a launcher, we could set it as a default launcher. The side effect of this is that every time you press the Home button, the device will take you to the Home launcher. And if our application is the Home launcher, then basically, this Home button becomes useless. To do that, we have to edit the AndroidManifest XML file in our Android project and add these two lines of code:

<activity
   android:name=".MainActivity"
   android:label="@string/app_name"
   android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
   android:launchMode="singleTask"
   android:windowSoftInputMode="adjustResize">
   <intent-filter>
       <action android:name="android.intent.action.MAIN" />
       <category android:name="android.intent.category.LAUNCHER" />
       <category android:name="android.intent.category.HOME" />
       <category android:name="android.intent.category.DEFAULT" />
   </intent-filter>
</activity>

The first line will make our app eligible to be selected in case the user presses the Home button, and the second line allows our app to be set as the default whenever this action happens.

Now, the only thing left to do was for the field agent to install the application on the device when delivering it to the client. Whenever you install an app that has the potential to be a launcher, Android will ask you if you want to use another launcher and if you want to set that one as the default.

So now, if you pressed the Home button or if you cleared all your recent applications, the device would automatically direct you to my application.

Building an Android POS App That Can’t Be Closed

The Back Button

Next, we have to handle the Back button. Mobile apps usually provide an on-screen way of going back through screens, especially since many devices don’t have a dedicated “back” key.

A few years ago, this was the case with Apple’s iOS devices, which featured their already iconic design with a single physical button below the screen. However, in recent years, most Android devices have dropped physical Home buttons, too. First, they moved to on-screen buttons, and now we’re seeing them being phased out phones in favor of gestures, as phone makers move to all-screen devices with small bezels and chin.

What this means is that the Back button that Android provides by default isn’t really needed, and to render this button completely useless, we just need to add a simple block of code in our activity:

@Override
public void onBackPressed() {
}
Building an Android POS App That Can’t Be Closed

This is a pretty straightforward block of code: Our main activity allows us to intercept whenever a user presses the Back button. In our case, since we don’t want the user to be pressing that button too many times in order to exit the app, we can simply overwrite the default method with one that does nothing, telling our app to do nothing in case the Back button is pressed.

This is how some applications ask for confirmation before you accidentally exit them by going back too many times.

The Recents Button

We still need to handle the Recents button, and this is the trickiest one. Also, this is definitely not a best practice or something that you should push to the Play Store, but it does work for our niche case here.

The same way that the main activity allows us to know when the Back button is pressed, it also allows us to know when the app is paused. What does this mean? This code is triggered every time our app switches from being the foreground application to being in the background.

When intercepting this event, we will get the task ID of our current application and tell the activity manager to move this task to the front. To do this, we need one special permission in the same Android manifest file that we edited earlier.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 package="com.johnwick">
   <uses-permission android:name="android.permission.INTERNET" />
   <uses-permission android:name="android.permission.REORDER_TASKS" />
   <application
     android:name=".MainApplication"
     android:label="@string/app_name"
     android:icon="@mipmap/ic_launcher"
     android:roundIcon="@mipmap/ic_launcher_round"
     android:allowBackup="false"
     android:theme="@style/AppTheme">
     <activity
       android:name=".MainActivity"
       android:label="@string/app_name"
  android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
       android:launchMode="singleTask"
       android:windowSoftInputMode="adjustResize">
       <intent-filter>
           <action android:name="android.intent.action.MAIN" />
           <category android:name="android.intent.category.LAUNCHER" />

           <category android:name="android.intent.category.HOME" />
           <category android:name="android.intent.category.DEFAULT" />

       </intent-filter>
     </activity>
     <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
   </application>

</manifest>

This will allow us to read the ongoing tasks and make changes to these tasks. Also, we still need to intercept the moment when our application is being sent to the background. We can again override the onPause method in our activity.

Here, we get the task manager and force it to move a specific task to the foreground. In our case, that specific task is the one that has just been sent to the background (our application).

@Override
public void onPause() {
   super.onPause();
   ActivityManager activityManager = (ActivityManager) getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
   activityManager.moveTaskToFront(getTaskId(), 0);
}

Now, every time you want to go to the recents menu, the application will refocus automatically. Sure, you might get a little screen flickering at times, but you will not be able to exit this application. And here’s one more cool thing - remember how I said that you could also exit by clicking a notification or by going directly to the settings through the notification tray? Well, performing those actions will put the app in the background, which will trigger our code, and then the user is shoved right back in.

All of this happens so quickly that the user will not notice what happens in the background. Also, another nice part about this approach is that your quick toggles are still available. You can still select a wifi network, for example, or disable sounds, but anything that requires you to go into the actual Settings app is not allowed.

Building an Android POS App That Can’t Be Closed

The Solution

I am not sure this is the best way to do it, but it was nevertheless a really interesting process while researching a topic that I didn’t even know was possible. And it works! A word of warning: At this point, there are only two ways you can exit the application as a developer—you either reinstall the operating system or you kill/uninstall the app through ADB.

If you somehow lose the ADB connection to the device, I am not aware of an easy way that could get you out. To avoid that, I ended up building a PIN system.

Edge Cases

There are a few situations that we need to make sure we account for. First of all, what if a device reboots? It doesn’t have to be a manual reboot, it can also be a crash of the operating system.

Since we set our application as the default launcher earlier, as soon as the operating system boots back up, it should automatically start our application. But how does Android know to load your home screen on boot? It’s because it basically just loads your default launcher. Since we are the default launcher at this point, reboots should not be an issue. And could Android kill our application at some point? Theoretically, it could kill the app if the RAM memory fills up, but in real life, this is almost impossible. Since our app is unclosable, nobody can open up other apps, and so the RAM memory shouldn’t fill.

The only way I can think of it filling up is if our application has a huge memory leak, but in that case, you would have bigger issues than keeping your user inside the application. Still, even if Android somehow triggers a kill signal to our application, whenever you try to go back home, the OS will attempt to start our app again since it is the default launcher, thus keeping the user locked in.

Building a Back Door

As a quick explanation, there was a place in the application settings where you could enter a PIN to unlock the app. If the PIN is correct, it will disable the limitations set by our onPause and onBackPressed methods by doing a simple conditional statement. From there, a user would be allowed to enter the settings through the quick toggle menu. Afterward, you can always set the default launcher back to the stock one, and that would get you completely out of the app. There are a lot of ways one could handle this part, but it’s good to have a mechanism to disable the same limitations you put in place. Maybe you could do a fingerprint authentication to unlock. The possibilities are almost endless.

Building an Android POS App That Can’t Be Closed

Wrapping Up

Eventually, I was left with an application that nobody could close or kill. Even rebooting the device wouldn’t help since it would power back on directly to the default launcher, which currently is our application. It proved useful for our project, and the satisfaction of trying out something so wacky and out of line was indeed great and highly motivating.

There are a lot of devices and use cases where Android has made developers’ life easy. These days, writing an Android application is much easier than using many different platform-specific languages and tools. Think of IoT devices, kiosk applications, point of sale systems, navigation and payment gateways for taxis, and many more.

These are use cases where Android made app development easier, but also niche use cases where you would want to restrict access in a similar manner to what we demonstrated in this article.

Understanding the basics

How do you secure a POS system?

There are multiple ways in which an Android device can get compromised, but one of the easiest to defend against is user error. The fact that a cashier can’t access personal accounts or emails means it can’t download potentially malicious packages, making the application more secure from a user error point of view.

What is a POS breach?

In general terms, a POS breach is any attack that allows a malicious actor to steal a credit card number and (typically) send the stolen information to a remote server.

How are POS systems hacked?

It ultimately depends on the system in question, but obviously, proprietary systems are less at risk compared to those based on Android or other widely used operating systems.