Version 1 (modified by trac, 7 years ago) ( diff )


Gateworks Android Kiosk Mode - Digital Signage Software

Digital Signage has exploded over the last few years and is now utilized in businesses ranging from fast food restaurants for menu boards to factories for showing real time productivity analytics. The uses for digital signage is unlimited and the cost has come down to a price point that even small businesses can afford.

Gateworks has created a simple digital signage software interface using Android to be used on the Gateworks Single Board Computers, specifically the Ventana Family. The signage solution can be connected to a large HDMI monitor or an LVDS LCD display.

The Gateworks Kiosk software uses the Android Operating System and Default Browser. Gateworks has modified the software so that the browser will launch automatically when the system is booted. The browser launches to a URL defined in the bootloader.

Kiosk mode has the following features:

  • Browser launches to URL defined in the bootloader
  • Failover URL to local storage is available when network connection is lost
  • A refresh rate can be defined through a variable in the bootloader
  • These features can be configured using the information: here

Gateworks has provided a pre-compiled binary for getting started quickly with the Kiosk Mode.

This pre-compiled binary features the following:

  • Fits on 256MB Flash devices (rare for Android, this is a trimmed down optimized Android build)
  • Boots to the URL defined in the bootloader as explained here: Bootloader Configuration

Please contact Gateworks support for a pre-compiled binary.

Getting Started with Pre-Built Binary

Gateworks provides pre-built Android images that you can evaluate without building the source if needed. You have the following options:

  • download UBI image if you have a board with 2GB of NAND flash and want to boot Android from flash
  • download tarball and image a removable block storage device (mSATA / USB / uSD)

Latest images:

To Install:

  1. Download Pre-Built binary
  2. Install Pre-Built Android Kiosk binary to the board, Instructions Here
  3. Boot board and enter into bootloader command prompt
  4. Set bootloader variables as described here: Bootloader Configuration
  5. Ensure board has proper internet connection
  6. Boot board and enjoy the signage!

Kiosk Mode Development

The goal of this page is to outline the process of modifying the stock browser app on Android systems to simulate a "Kiosk" mode. It also details modifications to the AOSP source that will supplement this function. This could potentially be used for demonstration purposes, digital signage, or for unsupervised public access applications. A self-service configuration such as this empowers multiple users to complete tasks or view information at their convenience and at their own pace while retaining the security of your device and any potentially sensitive data it may contain.

This particular implementation effects a system that immediately after booting opens the stock AOSP browser app and displays a web page that the user can navigate within. The opened browser app can not be closed or otherwise switched out of nor can they enter in their own URL. The kiosk mode browser will check for particular bootloader arguments at runtime that allow for a configurable target URL to be set. Keyboard, mouse, or touch-screen are supported but certain functions that would allow the user to break out of the browser or access the URL/address-bar are disabled. No attempt is made to secure the device by disabling serial console or adb, however one could easily add that if desired.

This process description assumes a basic understanding of both Android development and using the Android OS in general. It specifically pertains to modifying the application included in AOSP 5.1 (Lollipop) but can be used as a reference for modifying other applications and/or versions.

Building Kiosk Mode from Source

Before continuing, follow the steps at Android/Building if you have not already set up a working AOSP directory.

Once you reach the Fetching Source section of the build page, be sure to use gateworks_l5.1.1_2.1.0-ga_kioskmode as your target of the ./repo init command. This will apply all of the necessary target changes and modifications necessary for producing a 256M Kiosk image by following the rest of the build steps.

Design Theory

The following modifications create a kiosk mode environment for the stock Android Browser application. It is recommended to at least implement the following sections in order to implement a relatively secure Kiosk version of the browser app that still maintains proper functionality:

The decision ultimately falls on the developer to choose which Kiosk features they wish to implement. Pick whatever combination of modifications necessary to achieve the desired functionality of your kiosk/single use application.

Create a Boot Receiver

This section details the steps necessary for an app to receive the BOOT_COMPLETED intent action which will cause your application to begin immediately after system start up in place of the default launcher application.

This step is not specific to the stock Android browser or kiosk mode and can be done to any app that you wish to launch on the completion of Android's boot-up.

  1. Add the following 2 sections to the top level ./AndroidManifest.xml:
    <!-- Should be placed near the top of the file with other permission declarations -->
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <!-- Should be placed near the bottom of the file with other receiver declarations -->
    <receiver android:name=".BootReceiver">
        <intent-filter android:priority="999">
            <action android:name="android.intent.action.BOOT_COMPLETED"/>
  1. Create the ./src/com/android/browser/ class
    public class BootReceiver extends BroadcastReceiver {
      public void onReceive(Context context, Intent intent) {
        Intent myIntent = new Intent(context, MyKioskModeActivity.class);

Suppress Back Navigation Button

This section details the process for consuming and ignoring a back navigation button press in your main activity. This disables the normal function of exiting your app once in the main activity.

This step is specific to the Android app you are working with.

Due to the nature of the stock Android browser app with its use of WebView elements in a single Activity, the back press is handled in the ./src/com/android/browser/ class. In order to override closing your app when the back key is pressed (and to avoid potential bugs when using Lock Task Mode), make the following edits to the goBackOnePageOrQuit() method of the aforementioned class.

void goBackOnePageOrQuit() {
   Tab current = mTabControl.getCurrentTab();
   if (current == null) {
   if (current.canGoBack()) {
   } else {
      Tab parent = current.getParent();
      if (parent != null) {
         //closeTab(current);               // Comment out this line (1/3)
      } else {
         if ((current.getAppId() != null) || current.closeOnBack()) {
            //closeCurrentTab(true);        // Comment out this line (2/3)
         //mActivity.moveTaskToBack(true);  // Comment out this line (3/3)

If not using the stock Android browser app, you need to edit your main activity class and override the onBackPressed() method. The main activity can be found by looking in your top level ./AndroidManifest.xml for android.intent.action.MAIN and seeing which activity contains it.

public void onBackPressed() {
    // nothing to do here

Disabling the Power Button

This section details how to disable the power button (a physical button tied to the Android POWER key event). This can be done one of two ways. The short and simple method is to disable the POWER key mapping in your AOSP's key layout file.

Key layout files are searched for in the following order:

  • /system/usr/keylayout/Vendor_XXXX_Product_XXXX_Version_XXXX.kl
  • /system/usr/keylayout/Vendor_XXXX_Product_XXXX.kl
  • /system/usr/keylayout/DEVICE_NAME.kl
  • /data/system/devices/keylayout/Vendor_XXXX_Product_XXXX_Version_XXXX.kl
  • /data/system/devices/keylayout/Vendor_XXXX_Product_XXXX.kl
  • /data/system/devices/keylayout/DEVICE_NAME.kl
  • /system/usr/keylayout/Generic.kl
  • /data/system/devices/keylayout/Generic.kl

If you are using an external input device such as a keyboard and are unsure about which key layout file applies to your input device, perform a adb shell cat /proc/bus/input/devices. This will list the currently connected devices. The vendor, product, and version information is found in the first line of each section in the following format:

Bus=0018 Vendor=0000 Product=0000 Version=0000

Otherwise, edit <AOSP_DIR>/out/target/product/ventana/system/usr/keylayout/Generic.kl and remove or comment out the lines containing the POWER event. Note that there may be multiple keys attached to the same event.

The second method for ignoring power events while in your application is substantially more complex, but can be preferred if you wish to keep any code changes contained to just the application source. Note that although this is done using a similar intent/receiver method used for creating a boot receiver, you can not declare the receivers in the top level AndroidManifest.xml when handling power button related intents. Therefore the receivers must be registered programmatically.

Disable Short Button Press

Disabling short power button presses is done by handling the ACTION_SCREEN_OFF intent and kicking the screen back to life by acquiring a wake lock:

  1. Create the ./src/com/android/browser/ class:
    public class OnScreenOffReceiver extends BroadcastReceiver {
      private static final String PREF_KIOSK_MODE = "pref_kiosk_mode";
      public void onReceive(Context context, Intent intent) {
          AppContext ctx = (AppContext) context.getApplicationContext();
          // is Kiosk Mode active?
          if(isKioskModeActive(ctx)) {
      private void wakeUpDevice(AppContext context) {
        PowerManager.WakeLock wakeLock = context.getWakeLock();
        if (wakeLock.isHeld()) {
      private boolean isKioskModeActive(final Context context) {
        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
        return sp.getBoolean(PREF_KIOSK_MODE, false);
  1. Edit the ./src/com/android/browser/ class to add the following logic (other apps should create a new class that extends Application):
    public class Browser extends Application {
      /* Added instance variables */
      private PowerManager.WakeLock wakeLock;
      private OnScreenOffReceiver onScreenOffReceiver;
      public void onCreate() {
        registerKioskModeScreenOffReceiver(); // Add this
      /* Add the following two methods */
      private void registerKioskModeScreenOffReceiver() {
        // register screen off receiver
        final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
        onScreenOffReceiver = new OnScreenOffReceiver();
        registerReceiver(onScreenOffReceiver, filter);
      public PowerManager.WakeLock getWakeLock() {
        if(wakeLock == null) {
          // lazy loading: first call, create wakeLock via PowerManager.
          PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
          wakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, "wakeup");
        return wakeLock;
  1. Add the permission WAKE_LOCK to your manifest:
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    • Other apps will also need to register their subclass of Application in the top level AndroidManifest.xml:
  1. To make the wake up also deactivate the keyguard/lockscreen, add the following indicated line to the main activity ./src/com/android/browser/ following the super.onCreate() call:
    protected void onCreate(Bundle savedInstanceState) {
      getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); // Add this
      // …

Disable Long Button Press

Disabling the the effect of a long-press on the power button disables the system dialogue window used for power off, reset, and emergency mode. Once again only relevant if the device has a physical button tied to the Android POWER key event.

  1. Override the onWindowFocusChanged() method in the ./src/com/android/browser/ class
    /* Add this method */
    public void onWindowFocusChanged(boolean hasFocus) {
      if(!hasFocus) {
          // Close every kind of system dialog
        Intent closeDialog = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);

Prevent Screen Dimming

Since the default behavior of the Android framework is to dim the screen after a set interval of inactivity in order to save battery life, a view attribute must be set to keep the screen lit while in your application.

Add android:keepScreenOn="true" attribute to the rootview of your activity (./res/layout/browser_subwindow.xml for stock browser app).

Create a Background Service

In order to guard against the circumstance that your app crashes and/or is somehow moved from the foreground, a background service will be created to catch these events and restore your application. This modification is only necessary if your app is not set as the default "home" application.

  1. Add src/com/android/browser/
    public class KioskService extends Service {
      // periodic interval to check in seconds -> 2 seconds
      private static final long INTERVAL = TimeUnit.SECONDS.toMillis(2);
      private static final String TAG = KioskService.class.getSimpleName();
      private static final String PREF_KIOSK_MODE = "pref_kiosk_mode";
      private Thread t = null;
      private Context ctx = null;
      private boolean running = false;
      public void onDestroy() {
        Log.i(TAG, "Stopping service 'KioskService'");
        running =false;
      public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "Starting service 'KioskService'");
        running = true;
        ctx = this;
        // start a thread that periodically checks if your app is in the foreground
        t = new Thread(new Runnable() {
          public void run() {
            do {
              try {
              } catch (InterruptedException e) {
                Log.i(TAG, "Thread interrupted: 'KioskService'");
        return Service.START_NOT_STICKY;
      private void handleKioskMode() {
        // is Kiosk Mode active?
          if(isKioskModeActive()) {
            // is App in background?
          if(isInBackground()) {
      private boolean isInBackground() {
        ActivityManager am = (ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningTaskInfo> taskInfo = am.getRunningTasks(1);
        ComponentName componentInfo = taskInfo.get(0).topActivity;
        return (!ctx.getApplicationContext().getPackageName().equals(componentInfo.getPackageName()));
      private void restoreApp() {
        // Restart activity
        Intent i = new Intent(ctx, BrowserActivity.class);
      public boolean isKioskModeActive(final Context context) {
        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
        return sp.getBoolean(PREF_KIOSK_MODE, false);
      public IBinder onBind(Intent intent) {
        return null;
  1. Start the KioskService in the onCreate() method of ./src/com/android/browser/
    public void onCreate() {
      instance = this;
      startService(new Intent(this, KioskService.class));  // add this

Set Immersive Mode

The aptly named "Immersive Mode" of the Android framework hides both the status and navigation bars while your app is in the foreground. However users can still swipe from the top/bottom of the screen to retrieve the system UI, so this method is not completely secure on its own. When this method is combined with lock task mode it makes for a clean and effective lockdown of the system while utilizing the maximum screen real estate.

To set immersive mode in your base activity's oncreate method:


Set Lock Task Mode

This section details the steps necessary to enter your device into what is referred as "Lock Task Mode" using a Device Policy Manager.

Adding a Device Policy Manager component to your application allows for further control of the device that is normally restricted by the Android OS. For the purposes of designing a single use device the Lock Task Mode will be set to allow for a bypass of system dialogue to enable screen pinning mode of the application on startup. Pinned mode can be considered the Android system's way of locking the pinned app to the foreground. Furthermore, when pinned mode is enabled by an app that has been authorized through the Device Policy Manager, pinned mode can not be exited manually. Due to the nature of the stock Android browser app, this modification also requires suppressing back navigation in order to function properly.

To implement Lock Task Mode in the stock Android browser:

  1. Add the following receiver block near the end the top level AndroidManifest.xml within the application tag.
    <receiver android:name=""
        <meta-data android:name=""
            android:resource="@xml/device_admin" />
           <action android:name="" />
  1. Create the following ./src/com/android/browser/ class
    import android.content.Context;
    import android.content.Intent;
    public class DeviceAdmin extends DeviceAdminReceiver {
      public void onEnabled(Context context, Intent intent) {
      public void onDisabled(Context context, Intent intent) {
  1. Add the following private method to the ./src/com/android/browser/ class.
    private void startKioskMode() {
        DevicePolicyManager DPM = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
        ComponentName mDeviceAdminSample = new ComponentName(this, DeviceAdmin.class);
        Intent intent;
        /* Check to see if application has already been authorized
         * for Lock Task Mode.
        if (DPM.isLockTaskPermitted(this.getPackageName())) {
        else if (!DPM.isDeviceOwner(getPackageName()) || !DPM.isAdminActive(mDeviceAdminSample)) {
            intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
            intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, mDeviceAdminSample);
            intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION, "Enabling force lock admin");
            startActivityForResult(intent, 99);
        else {
            String[] packages = {getPackageName()};
            DPM.setLockTaskPackages(mDeviceAdminSample, packages);
  1. Make the following additions to the onActivityResult() method in the same class.
    protected void onActivityResult(int requestCode, int resultCode,
            Intent intent) {
        if (requestCode == 99) {
            if(resultCode == Activity.RESULT_OK){
                DevicePolicyManager DPM = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
                ComponentName mDeviceAdminSample = new ComponentName(this, DeviceAdmin.class);
                String[] packages = {getPackageName()};
                DPM.setLockTaskPackages(mDeviceAdminSample, packages);
            if (resultCode == Activity.RESULT_CANCELED) {
                Log.d(LOGTAG, getPackageName() + " was denied device admin.");
        mController.onActivityResult(requestCode, resultCode, intent);
  1. Call the startKioskMode() method at the end of the onCreate() method in the same class.

Implementing these changes will cause the app to request authorization from the Device Policy Manager to become a device admin and enter the priviledged pinned mode referred to at the beginning of this section. However, if building from source you can bypass the request to the user and give your app device ownership/admin on first boot by injecting the following two files under out/target/product/ventana/data/system/. The injection is done by adding the files to the PRODUCT_COPY_FILES variable in your device configuration file (ie device/gateworks/ventana/

Additions to PRODUCT_COPY_FILES should be of the form:

  • device_owner.xml contents:
    <?xml version="1.0" encoding="utf-8" standalone="yes" ?>
    <device-owner package="" />
  • device_policies.xml contents:
    <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
    <policies setup-complete="true">
    <admin name="">
    <policies flags="8" />
    <lock-task-component name="" />

For more information see the Android developer pages:

Remove System Navigation Bar

The Android System Navigation Bar is the bar at the bottom of the screen which presents the back, home, and app-switch soft-keys.

As the system navigation bar is independent from any installed Android application in particular, removing it is done in the Android OS.

To accomplish this on a rooted installed device you can set the qemu.hw.mainkeys property to 1 by editing /system/build.prop:


To accomplish this at build time you would set the 'config_showNavigationBar' value to false in your device overlay (ie device/gateworks/ventana/overlay/frameworks/base/core/res/res/values/config.xml):

<bool name="config_showNavigationBar">false</bool>

Note you could also make build.prop modifications at build time by adding to the PRODUCT_DEFAULT_PROPERTY_OVERRIDES or ADDITIONAL_BUILD_PROPERTIES variables which can be found in your device target configuration (ie and in device/gateworks/ventana/).

Additionally, you can hide the navigation bar (and status bar) within an application by setting the SYSTEM_UI_FLAG_HIDE_NAVIGATION flag however it will pop back if the user touches anywhere on the screen.

Hiding Browser Navigation

To disable the stock browser app's own navigation UI and fix resulting layout changes there are four necessary steps:

  1. Add the following attribute to the top level RelativeLayout in the ./res/layout/nav_screen.xml. This will hide the toolbar containing the url and other navigation buttons, effectively limiting navigation away from the targeted website.
  1. Add the following line to the onResume() method in ./src/com/android/browser/ to hide access to settings and tabs.
  1. Add the following line to ./res/values/styles.xml in the ActionBarStyle tag:
    <style name="ActionBarStyle" parent="@android:style/Widget.Holo.ActionBar">
       <item name="android:background">@drawable/bg_urlbar</item>
       <item name="android:displayOptions"></item>
    +  <item name="android:visibility">gone</item>
  1. Remove the android:fitsSystemWindows="true" attribute from the root LinearLayout in ./res/layout/tab.xml
    <LinearLayout xmlns:android=""
    -   android:fitsSystemWindows="true"

Add Bootloader URL and Exit Logic

The final modification to the stock browser app is to provide dynamic URL pointing and an exit mechanism for the locked application. This allows for the changing of targeted website as well as normal access to the rest of the system without the need for recompiling.

  1. Add the following code to the onCreate() method in ./src/com/android/browser/ Be sure to insert the ro.boot.kioskdisable check after the super.onCreate() and the ro.boot.url check after the call to createController().
    if (propReader("ro.boot.kioskdisable") != null) {
        Log.d(LOGTAG, "Found a reset key");
    if (shouldIgnoreIntents()) {
    if (IntentHandler.handleWebSearchIntent(this, null, getIntent())) {
    mController = createController();
    Intent intent = (icicle == null) ? getIntent() : null;
    String newUrl = propReader("ro.boot.url");
    if (newUrl != null) {
        Log.d(LOGTAG, "Found a url, setting to: " + newUrl);
  1. Add the following private method to ./src/com/android/browser/
    /* Returns result of a getprop call taking a property
     * and returning a string upon success or null on failure.
    public static String propReader(String propName) {
        Process proc = null;
        String line;
        try {
            proc = Runtime.getRuntime().exec("/system/bin/getprop " + propName);
        } catch (IOException e) {
            return null;
        BufferedReader br = new BufferedReader(new InputStreamReader(proc.getInputStream()));
        try {
            if ((line = br.readLine()) != null && !line.isEmpty())
                return line.trim();
        } catch (IOException e) {
        return null;

Set Base Homepage

This section details the steps for the browser application to point to a new "Factory" default home page which will be the fallback in the case that a androidboot.url was not set in the extra environment variable of the bootloader.

  1. Change the homepage_base string in ./res/values/strings.xml


<!-- The default homepage. -->
<string name="homepage_base" translatable="false">{CID}&amp;source=android-home</string>


<!-- The default homepage. -->
<string name="homepage_base" translatable="false"></string>

Replace Default Home App

If designing your application for single use devices, or if you simply want to remove access to the android UI outside of your app, then your application can be made to replace the "Launcher" apps that act as Android's default home target. Replacing this will cause your app to behave just like the launcher from the perspective of the system, causing it to boot immediately, come to the foreground when the home button is pressed, and be restarted automatically in the event of termination.

  1. Add the HOME category to the MAIN intent filter of your BrowserActivity activity tag in the AndroidManifest.xml
        <action android:name="android.intent.action.MAIN" />
        <!-- Add the following line: -->
        <category android:name="android.intent.category.HOME" /> 
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.LAUNCHER" />
        <category android:name="android.intent.category.BROWSABLE" />
        <category android:name="android.intent.category.APP_BROWSER" />
  1. Implement a boot receiver as described in the Create a Boot Receiver section.
  1. Remove the Home, Launcher2, and Launcher3 packages as described in the Trimming Down the OS section.

Bootloader Configuration

As previously mentioned, the modified Browser application has the ability to check for particular bootloader arguments at run time that allow for configurable target URLs and refresh times to be set.

The Kiosk Browser will check for the bootloader parameters:

Parameter Value Description
androidboot.url URL The primary URL target (should include the full http:// prefix).
androidboot.fallback URL/File path The secondary URL target or file path (e.g. file:///data/logo.png).
androidboot.refreshtime Integer The time in seconds to refresh the primary URL page. If parameter is not present then the kiosk will not attempt to refresh.
androidboot.kioskdisable Boolean Any value, if present, will allow the Android settings to be accessed by swiping down from top of screen.

To edit one of the above parameters:

  1. Power on the device and break into the bootloader.
  2. Add androidboot.url|fallback|refreshtime=<value> to the environment variable extra
    setenv extra "${extra} androidboot.<PARAMETER>=<VALUE>"
  • Note that previous assignments of these bootloader parameters in the extra environment variable should be deleted before adding a new one and that parameters not assigned a value will be considered invalid.

Trimming Down the OS

Although the Android system will manage and allocate memory as needed, it may be beneficial to the function of your application to remove unnecessary packages and files during the build process. Not only does this allow for potentially faster operation and start up time, but could also reduce the total file size of the system image. A small enough system image could allow for smaller storage media such as 256M flash.

Since kiosk mode only runs the browser from power up, you can remove any other existing applications available to the user by setting the LOCAL_OVERRIDES_PACKAGES variable in <AOSP_DIR>/packages/apps/Browser/

For example:

LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 \
  Calculator Calender Settings Downloads Development \
  Camera Clock Contacts Email Gallery Messaging \
  Music Search SpeechRecorder

If you wanted to further cut down on file size you could add any number of packages to the LOCAL_OVERRIDES_PACKAGES variable to include unused libraries, system services, etc.

The full list of included packages are iteratively added to the PRODUCT_PACKAGES variable when the <AOSP_DIR>/device/gateworks/ventana/ makefile is executed. Note that at the top of this makefile, other makefiles are included which may add to PRODUCT_PACKAGES. To see the full list of installed packages you could either follow the tree of makefiles and track additions to PRODUCT_PACKAGES, or execute an adb shell pm list packages -f which will show all installed packages on your connected device. Another file which may be of interest is the out/target/product/ventana/installed-files.txt file which contains the final list of files added to the system image, sorted by file size.

Attachments (1)

Download all attachments as: .zip

Note: See TracWiki for help on using the wiki.