[[PageOutline]]
[[Image(kioskblast.jpg,400px)]]
= 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 [http://www.gateworks.com/imx6-single-board-computer-gateworks-ventana-family 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: [#BootloaderConfiguration 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: [#BootloaderConfiguration Bootloader Configuration]
Please contact Gateworks support for a pre-compiled binary.
For Gateworks Single Board Computers, please see the following link: [http://www.gateworks.com/imx6-single-board-computer-gateworks-ventana-family]
[=#prebuilt]
=== 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 1GB/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:
* Android 5.1.1 (Lollipop) Kiosk:
* Built on 2019-03-23 from Gateworks Android Kiosk BSP:
* Source: https://github.com/Gateworks/imx_android/tree/gateworks_l5.1.1_2.1.0-ga_kioskmode
* Based on Freescale l5.1.1_2.1.0-ga source
* ubi images:
- [http://blog.gateworks.com/?wpdmpro=ventana-android-l5-1-1_2-1-0-ga_kioskmode-user-20190923-3_normal-ubi ventana-android-l5-1-1_2-1-0-ga_kioskmode-user-20190923-3_normal-ubi] - UBI image for 2K page size 'normal' geometry FLASH (see [wiki:/linux/ubi/#flashgeometry here] to determine your flash geometry)(sha256sum:447c9c09f0fa0e58dda38859ef857c6bb760ca9847658bfd1ca6708997dd8b1d)
- [http://blog.gateworks.com/?wpdmpro=ventana-android-l5-1-1_2-1-0-ga_kioskmode-user-20190923-3_large-ubi ventana-android-l5-1-1_2-1-0-ga_kioskmode-user-20190923-3_large-ubi] - UBI image for 4K page size 'large' geometry FLASH (see [wiki:/linux/ubi/#flashgeometry here] to determine your flash geometry)(sha256sum:447c9c09f0fa0e58dda38859ef857c6bb760ca9847658bfd1ca6708997dd8b1d)
- see [wiki:/linux/ubi#BasicMethod here] for info about flashing UBI images
* tarballs (for imaging onto removable block storage devices):
* [http://blog.gateworks.com/?wpdmpro=ventana-android-l5-1-1_2-1-0-ga_kioskmode-user-20190923-3-tar-xz ventana-android-l5-1-1_2-1-0-ga_kioskmode-user-20190923-3-ta.xz] (sha256sum:1d60401c23e5531d9f5738a6c7640358e185b222f5074e92bd8ee4fcd474f0f4)
To Install:
1. Download Pre-Built binary
2. Install Pre-Built Android Kiosk binary to the board, [wiki:Android/Building#GateworksPrecompiledAndroidBinary Instructions Here]
3. Boot board and enter into bootloader command prompt
4. Set bootloader variables as described here: [#BootloaderConfiguration 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 {{{com.android.browser}}} 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 [wiki:Android/Building#BuildingAndroidforVentanafromsource] if you have not already set up a working AOSP directory.
Once you reach the [wiki:Android/Building#FetchingSource 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:
* [#CreateaBootReceiver Create a Boot Receiver]
* [#SetImmersiveMode Set Immersive Mode]
* [#SetLockTaskMode Set Lock Task Mode]
* [#HidingBrowserNavigation Hiding Browser Navigation]
* [#AddBootloaderURLandExitLogic Add Bootloader URL and Exit Logic]
* [#SuppressBackNavigationButton Suppress Back Navigation Button]
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.
[=#boot-receiver]
=== 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}}}:
{{{#!xml
}}}
2. Create the {{{./src/com/android/browser/BootReceiver.java}}} class
{{{#!java
public class BootReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Intent myIntent = new Intent(context, MyKioskModeActivity.class);
myIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(myIntent);
}
}
}}}
[=#disable-navigation]
=== 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/Controller.java}}} class. In order to override closing your app when the back key is pressed (and to avoid potential bugs when using [#SetLockTaskMode Lock Task Mode]), make the following edits to the {{{goBackOnePageOrQuit()}}} method of the aforementioned {{{Controller.java}}} class.
{{{#!java
void goBackOnePageOrQuit() {
Tab current = mTabControl.getCurrentTab();
if (current == null) {
mActivity.moveTaskToBack(true);
return;
}
if (current.canGoBack()) {
current.goBack();
} else {
Tab parent = current.getParent();
if (parent != null) {
switchToTab(parent);
//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.
{{{#!java
@Override
public void onBackPressed() {
// nothing to do here
}
}}}
[=#disable-power]
=== 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:
{{{#!bash
Bus=0018 Vendor=0000 Product=0000 Version=0000
}}}
Otherwise, edit {{{/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-power-shortpress]
==== 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/OnScreenOffReceiver.java}}} class:
{{{#!java
public class OnScreenOffReceiver extends BroadcastReceiver {
private static final String PREF_KIOSK_MODE = "pref_kiosk_mode";
@Override
public void onReceive(Context context, Intent intent) {
if(Intent.ACTION_SCREEN_OFF.equals(intent.getAction())){
AppContext ctx = (AppContext) context.getApplicationContext();
// is Kiosk Mode active?
if(isKioskModeActive(ctx)) {
wakeUpDevice(ctx);
}
}
}
private void wakeUpDevice(AppContext context) {
PowerManager.WakeLock wakeLock = context.getWakeLock();
if (wakeLock.isHeld()) {
wakeLock.release();
}
wakeLock.acquire();
wakeLock.release();
}
private boolean isKioskModeActive(final Context context) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
return sp.getBoolean(PREF_KIOSK_MODE, false);
}
}
}}}
2. Edit the {{{./src/com/android/browser/Browser.java}}} class to add the following logic (other apps should create a new class that extends {{{Application}}}):
{{{#!java
public class Browser extends Application {
/* Added instance variables */
private PowerManager.WakeLock wakeLock;
private OnScreenOffReceiver onScreenOffReceiver;
@Override
public void onCreate() {
super.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;
}
}
}}}
3. Add the permission WAKE_LOCK to your manifest:
{{{#!xml
}}}
* Other apps will also need to register their subclass of {{{Application}}} in the top level {{{AndroidManifest.xml}}}:
{{{#!xml
}}}
4. To make the wake up also deactivate the keyguard/lockscreen, add the following indicated line to the main activity {{{./src/com/android/browser/BrowserActivity.java}}} '''following''' the {{{super.onCreate()}}} call:
{{{#!java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); // Add this
// …
}
}}}
[=#disable-power-longpress]
==== 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/BrowserActivity.java}}} class
{{{#!java
/* Add this method */
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if(!hasFocus) {
// Close every kind of system dialog
Intent closeDialog = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
sendBroadcast(closeDialog);
}
}
}}}
[=#disable-dimming]
=== 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).
[=#background-service]
=== 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/KioskService.java}}}:
{{{#!java
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;
@Override
public void onDestroy() {
Log.i(TAG, "Stopping service 'KioskService'");
running =false;
super.onDestroy();
}
@Override
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() {
@Override
public void run() {
do {
handleKioskMode();
try {
Thread.sleep(INTERVAL);
} catch (InterruptedException e) {
Log.i(TAG, "Thread interrupted: 'KioskService'");
}
}while(running);
stopSelf();
}
});
t.start();
return Service.START_NOT_STICKY;
}
private void handleKioskMode() {
// is Kiosk Mode active?
if(isKioskModeActive()) {
// is App in background?
if(isInBackground()) {
restoreApp();
}
}
}
private boolean isInBackground() {
ActivityManager am = (ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE);
List 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);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
|Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
|Intent.FLAG_ACTIVITY_SINGLE_TOP
|Intent.FLAG_ACTIVITY_NO_ANIMATION);
ctx.startActivity(i);
}
public boolean isKioskModeActive(final Context context) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
return sp.getBoolean(PREF_KIOSK_MODE, false);
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
}}}
2. Start the {{{KioskService}}} in the {{{onCreate()}}} method of {{{./src/com/android/browser/BrowserActivity.java}}}:
{{{#!java
@Override
public void onCreate() {
super.onCreate();
instance = this;
registerKioskModeScreenOffReceiver();
startService(new Intent(this, KioskService.class)); // add this
}
}}}
[=#immersive-mode]
=== 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 [#SetLockTaskMode 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:
{{{#!java
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_IMMERSIVE
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
}}}
[=#lock-task-mode]
=== 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 [#SuppressingBackNavigation 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.
{{{#!xml
}}}
2. Create the following {{{./src/com/android/browser/DeviceAdmin.java}}} class
{{{#!java
package com.android.browser;
import android.app.admin.DeviceAdminReceiver;
import android.content.Context;
import android.content.Intent;
public class DeviceAdmin extends DeviceAdminReceiver {
@Override
public void onEnabled(Context context, Intent intent) {
}
@Override
public void onDisabled(Context context, Intent intent) {
}
}
}}}
3. Add the following private method to the {{{./src/com/android/browser/BrowserActivity.java}}} class.
{{{#!java
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())) {
startLockTask();
}
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);
startLockTask();
}
return;
}
}}}
4. Make the following additions to the {{{onActivityResult()}}} method in the same class.
{{{#!java
@Override
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.");
}
startLockTask();
}
mController.onActivityResult(requestCode, resultCode, intent);
}
}}}
4. 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/ventana.mk}}}).
Additions to {{{PRODUCT_COPY_FILES}}} should be of the form:
{{{#!xml
:
}}}
* {{{device_owner.xml}}} contents:
{{{#!xml
}}}
* {{{device_policies.xml}}} contents:
{{{#!xml
}}}
For more information see the Android developer pages:
- [http://developer.android.com/about/versions/android-5.0.html#ScreenPinning Screen Pinning]
- [http://developer.android.com/guide/topics/admin/device-admin.html Device Admin]
- [http://developer.android.com/training/enterprise/cosu.html Single Use Devices].
[=#disable-navbar]
=== 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}}}:
{{{#!bash
qemu.hw.mainkeys=1
}}}
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}}}):
{{{#!xml
false
}}}
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 {{{imx6.mk}}} and {{{BoardConfig.mk}}} 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.
{{{#!java
android:visibility="gone"
}}}
2. Add the following line to the {{{onResume()}}} method in {{{./src/com/android/browser/BrowserActivity.java}}} to hide access to settings and tabs.
{{{#!java
getActionBar().hide();
}}}
3. Add the following line to {{{./res/values/styles.xml}}} in the {{{ActionBarStyle}}} tag:
{{{#!xml
}}}
4. Remove the {{{android:fitsSystemWindows="true"}}} attribute from the root {{{LinearLayout}}} in {{{./res/layout/tab.xml}}}
{{{#!xml
}}}
=== 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/BrowserActivity.java}}}. Be sure to insert the {{{ro.boot.kioskdisable}}} check after the {{{super.onCreate()}}} and the {{{ro.boot.url}}} check after the call to {{{createController()}}}.
{{{#!java
super.onCreate(icicle);
/* ADD THIS SECTION */
if (propReader("ro.boot.kioskdisable") != null) {
Log.d(LOGTAG, "Found a reset key");
finish();
return;
}
/* END OF ADDED SECTION 1 */
if (shouldIgnoreIntents()) {
finish();
return;
}
if (IntentHandler.handleWebSearchIntent(this, null, getIntent())) {
finish();
return;
}
mController = createController();
Intent intent = (icicle == null) ? getIntent() : null;
mController.start(intent);
/* ADD THIS SECTION */
String newUrl = propReader("ro.boot.url");
if (newUrl != null) {
Log.d(LOGTAG, "Found a url, setting to: " + newUrl);
BrowserSettings.getInstance().setHomePage(newUrl);
((UiController)mController).openTabToHomePage();
}
/* END OF ADDED SECTION 2 */
}}}
2. Add the following private method to {{{./src/com/android/browser/BrowserActivity.java}}}:
{{{#!java
/* 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) {
e.printStackTrace();
return null;
}
BufferedReader br = new BufferedReader(new InputStreamReader(proc.getInputStream()));
try {
if ((line = br.readLine()) != null && !line.isEmpty())
return line.trim();
} catch (IOException e) {
e.printStackTrace();
}
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.
2. Change the {{{homepage_base}}} string in {{{./res/values/strings.xml}}}
Original:
{{{#!xml
https://www.google.com/webhp?client={CID}&source=android-home
}}}
New:
{{{#!xml
http://www.gateworks-info.com/www/
}}}
=== 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}}}
{{{#!xml
}}}
2. Implement a boot receiver as described in the [#CreateaBootReceiver Create a Boot Receiver] section.
3. Remove the {{{Home}}}, {{{Launcher2}}}, and {{{Launcher3}}} packages as described in the [#TrimmingDowntheOS 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=}}} to the environment variable {{{extra}}}
{{{#!bash
setenv extra "${extra} androidboot.="
saveenv
}}}
* 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 {{{/packages/apps/Browser/Android.mk}}}.
For example:
{{{#!mk
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 {{{/device/gateworks/ventana/ventana.mk}}} 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.