Changes between Initial Version and Version 1 of Android/Kiosk

10/22/2017 05:28:45 AM (5 years ago)



  • Android/Kiosk

    v1 v1  
     3= Gateworks Android Kiosk Mode - Digital Signage Software =
     5Digital 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.
     7Gateworks 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.
     9The 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.
     11Kiosk mode has the following features:
     12 * Browser launches to URL defined in the bootloader
     13 * Failover URL to local storage is available when network connection is lost
     14 * A refresh rate can be defined through a variable in the bootloader
     15 * These features can be configured using the information: [#BootloaderConfiguration here]
     17Gateworks has provided a pre-compiled binary for getting started quickly with the Kiosk Mode.
     19This pre-compiled binary features the following:
     20 * Fits on 256MB Flash devices (rare for Android, this is a trimmed down optimized Android build)
     21 * Boots to the URL defined in the bootloader as explained here: [#BootloaderConfiguration Bootloader Configuration]
     23Please contact Gateworks support for a pre-compiled binary.
     26=== Getting Started with Pre-Built Binary ===
     27Gateworks provides pre-built Android images that you can evaluate without building the source if needed. You have the following options:
     28 * download UBI image if you have a board with 2GB of NAND flash and want to boot Android from flash
     29 * download tarball and image a removable block storage device (mSATA / USB / uSD)
     31Latest images:
     32 * Android 5.1.1 (Lollipop) Kiosk:
     33  * Built on 2016-08-23 from Gateworks Android Kiosk BSP:
     34   * Source:
     35   * Based on Freescale l5.1.1_2.1.0-ga source
     36  * ubi images:
     37   * [ ventana-android-l5.1.1_2.1.0-ga_kioskmode-user-20160823-1853_large.ubi]  ('''2GB NAND devices''') (sha256sum:74ecfdf4413df47f2a5402ff448cd6321dc97893720580c93202b7966c0cc279)
     38   * [  ventana-android-l5.1.1_2.1.0-ga_kioskmode-user-20160823-1853_normal.ubi]  ('''256MB NAND devices''')(sha256sum:df3d920e867f0d06e3c2f695cc396fcf35ff7ffb5ae5bf092b1d16f5c06aa13d)
     39  * tarballs (for imaging onto removable block storage devices):
     40   * [ ventana-android-l5.1.1_2.1.0-ga_kioskmode-user-20160823-1853.tar.bz2] (c451c5846ab930079c793cc3027641048b0308a0f2bc067eff42e7e212554044)
     42To Install:
     431. Download Pre-Built binary
     442. Install Pre-Built Android Kiosk binary to the board, [wiki:Android/Building#GateworksPrecompiledAndroidBinary Instructions Here]
     453. Boot board and enter into bootloader command prompt
     464. Set bootloader variables as described here: [#BootloaderConfiguration Bootloader Configuration]
     475. Ensure board has proper internet connection
     486. Boot board and enjoy the signage!
     52= Kiosk Mode Development =
     53The 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.
     55This 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.
     57This 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.
     59== Building Kiosk Mode from Source ==
     60Before continuing, follow the steps at [wiki:Android/Building#BuildingAndroidforVentanafromsource] if you have not already set up a working AOSP directory.
     62Once 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.
     63== Design Theory ==
     64The 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:
     66* [#CreateaBootReceiver Create a Boot Receiver]
     67* [#SetImmersiveMode Set Immersive Mode]
     68* [#SetLockTaskMode Set Lock Task Mode]
     69* [#HidingBrowserNavigation Hiding Browser Navigation]
     70* [#AddBootloaderURLandExitLogic Add Bootloader URL and Exit Logic]
     71* [#SuppressBackNavigationButton Suppress Back Navigation Button]
     73The 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.
     76=== Create a Boot Receiver ===
     77This 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.
     79This 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.
     811. Add the following 2 sections to the top level {{{./AndroidManifest.xml}}}:
     83<!-- Should be placed near the top of the file with other permission declarations -->
     84<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
     86<!-- Should be placed near the bottom of the file with other receiver declarations -->
     87<receiver android:name=".BootReceiver">
     88    <intent-filter android:priority="999">
     89        <action android:name="android.intent.action.BOOT_COMPLETED"/>
     90    </intent-filter>
     942. Create the {{{./src/com/android/browser/}}} class
     96public class BootReceiver extends BroadcastReceiver {
     97  @Override
     98  public void onReceive(Context context, Intent intent) {
     99    Intent myIntent = new Intent(context, MyKioskModeActivity.class);
     100    myIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
     101    context.startActivity(myIntent);
     102  }
     108=== Suppress Back Navigation Button ===
     109This 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.
     111This step is specific to the Android app you are working with.
     113Due 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 [#SetLockTaskMode Lock Task Mode]), make the following edits to the {{{goBackOnePageOrQuit()}}} method of the aforementioned {{{}}} class.
     116void goBackOnePageOrQuit() {
     117   Tab current = mTabControl.getCurrentTab();
     118   if (current == null) {
     119      mActivity.moveTaskToBack(true);
     120      return;
     121   }
     122   if (current.canGoBack()) {
     123      current.goBack();
     124   } else {
     125      Tab parent = current.getParent();
     126      if (parent != null) {
     127         switchToTab(parent);
     128         //closeTab(current);               // Comment out this line (1/3)
     129      } else {
     130         if ((current.getAppId() != null) || current.closeOnBack()) {
     131            //closeCurrentTab(true);        // Comment out this line (2/3)
     132         }
     133         //mActivity.moveTaskToBack(true);  // Comment out this line (3/3)
     134      }
     135   }
     139If 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.
     142public void onBackPressed() {
     143    // nothing to do here
     149=== Disabling the Power Button ===
     150This 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.
     152Key layout files are searched for in the following order:
     153* {{{/system/usr/keylayout/Vendor_XXXX_Product_XXXX_Version_XXXX.kl}}}
     154* {{{/system/usr/keylayout/Vendor_XXXX_Product_XXXX.kl}}}
     155* {{{/system/usr/keylayout/DEVICE_NAME.kl}}}
     156* {{{/data/system/devices/keylayout/Vendor_XXXX_Product_XXXX_Version_XXXX.kl}}}
     157* {{{/data/system/devices/keylayout/Vendor_XXXX_Product_XXXX.kl}}}
     158* {{{/data/system/devices/keylayout/DEVICE_NAME.kl}}}
     159* {{{/system/usr/keylayout/Generic.kl}}}
     160* {{{/data/system/devices/keylayout/Generic.kl}}}
     162If 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:
     164Bus=0018 Vendor=0000 Product=0000 Version=0000
     167Otherwise, 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.
     169The 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.
     172==== Disable Short Button Press ====
     173Disabling 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:
     175 1. Create the {{{./src/com/android/browser/}}} class:
     177public class OnScreenOffReceiver extends BroadcastReceiver {
     178  private static final String PREF_KIOSK_MODE = "pref_kiosk_mode";
     180  @Override
     181  public void onReceive(Context context, Intent intent) {
     182    if(Intent.ACTION_SCREEN_OFF.equals(intent.getAction())){
     183      AppContext ctx = (AppContext) context.getApplicationContext();
     184      // is Kiosk Mode active?
     185      if(isKioskModeActive(ctx)) {
     186        wakeUpDevice(ctx);
     187      }
     188    }
     189  }
     191  private void wakeUpDevice(AppContext context) {
     192    PowerManager.WakeLock wakeLock = context.getWakeLock();
     193    if (wakeLock.isHeld()) {
     194      wakeLock.release();
     195    }
     196    wakeLock.acquire();
     197    wakeLock.release();
     198  }
     200  private boolean isKioskModeActive(final Context context) {
     201    SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
     202    return sp.getBoolean(PREF_KIOSK_MODE, false);
     203  }
     207 2. Edit the {{{./src/com/android/browser/}}} class to add the following logic (other apps should create a new class that extends {{{Application}}}):
     209public class Browser extends Application {
     211  /* Added instance variables */
     212  private PowerManager.WakeLock wakeLock;
     213  private OnScreenOffReceiver onScreenOffReceiver;
     215  @Override
     216  public void onCreate() {
     217    super.onCreate();
     219    registerKioskModeScreenOffReceiver(); // Add this
     220  }
     222  /* Add the following two methods */
     224  private void registerKioskModeScreenOffReceiver() {
     225    // register screen off receiver
     226    final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
     227    onScreenOffReceiver = new OnScreenOffReceiver();
     228    registerReceiver(onScreenOffReceiver, filter);
     229  }
     231  public PowerManager.WakeLock getWakeLock() {
     232    if(wakeLock == null) {
     233      // lazy loading: first call, create wakeLock via PowerManager.
     234      PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
     235      wakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, "wakeup");
     236    }
     237    return wakeLock;
     238  }
     242 3. Add the permission WAKE_LOCK to your manifest:
     244<uses-permission android:name="android.permission.WAKE_LOCK" />
     246  * Other apps will also need to register their subclass of {{{Application}}} in the top level {{{AndroidManifest.xml}}}:
     249        android:name=".AppContext"
     250        android:icon="@drawable/ic_launcher"
     251        android:label="@string/app_name">
     254 4. 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:
     257protected void onCreate(Bundle savedInstanceState) {
     258  super.onCreate(savedInstanceState);
     259  getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); // Add this
     260  // …
     265==== Disable Long Button Press ====
     266Disabling 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.
     268 1. Override the {{{onWindowFocusChanged()}}} method in the {{{./src/com/android/browser/}}} class
     270/* Add this method */
     272public void onWindowFocusChanged(boolean hasFocus) {
     273  super.onWindowFocusChanged(hasFocus);
     274  if(!hasFocus) {
     275      // Close every kind of system dialog
     276    Intent closeDialog = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
     277    sendBroadcast(closeDialog);
     278  }
     283=== Prevent Screen Dimming ===
     284Since 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.
     286Add {{{android:keepScreenOn="true"}}} attribute to the rootview of your activity ({{{./res/layout/browser_subwindow.xml}}} for stock browser app).
     289=== Create a Background Service ===
     290In 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.
     292 1. Add {{{src/com/android/browser/}}}:
     294public class KioskService extends Service {
     295  // periodic interval to check in seconds -> 2 seconds
     296  private static final long INTERVAL = TimeUnit.SECONDS.toMillis(2);
     297  private static final String TAG = KioskService.class.getSimpleName();
     298  private static final String PREF_KIOSK_MODE = "pref_kiosk_mode";
     300  private Thread t = null;
     301  private Context ctx = null;
     302  private boolean running = false;
     304  @Override
     305  public void onDestroy() {
     306    Log.i(TAG, "Stopping service 'KioskService'");
     307    running =false;
     308    super.onDestroy();
     309  }
     311  @Override
     312  public int onStartCommand(Intent intent, int flags, int startId) {
     313    Log.i(TAG, "Starting service 'KioskService'");
     314    running = true;
     315    ctx = this;
     317    // start a thread that periodically checks if your app is in the foreground
     318    t = new Thread(new Runnable() {
     319      @Override
     320      public void run() {
     321        do {
     322          handleKioskMode();
     323          try {
     324            Thread.sleep(INTERVAL);
     325          } catch (InterruptedException e) {
     326            Log.i(TAG, "Thread interrupted: 'KioskService'");
     327          }
     328        }while(running);
     329        stopSelf();
     330      }
     331    });
     333    t.start();
     334    return Service.START_NOT_STICKY;
     335  }
     337  private void handleKioskMode() {
     338    // is Kiosk Mode active?
     339      if(isKioskModeActive()) {
     340        // is App in background?
     341      if(isInBackground()) {
     342        restoreApp();
     343      }
     344    }
     345  }
     347  private boolean isInBackground() {
     348    ActivityManager am = (ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE);
     350    List<ActivityManager.RunningTaskInfo> taskInfo = am.getRunningTasks(1);
     351    ComponentName componentInfo = taskInfo.get(0).topActivity;
     352    return (!ctx.getApplicationContext().getPackageName().equals(componentInfo.getPackageName()));
     353  }
     355  private void restoreApp() {
     356    // Restart activity
     357    Intent i = new Intent(ctx, BrowserActivity.class);
     358    i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
     359        |Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
     360        |Intent.FLAG_ACTIVITY_SINGLE_TOP
     361        |Intent.FLAG_ACTIVITY_NO_ANIMATION);
     362    ctx.startActivity(i);
     363  }
     365  public boolean isKioskModeActive(final Context context) {
     366    SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
     367    return sp.getBoolean(PREF_KIOSK_MODE, false);
     368  }
     370  @Override
     371  public IBinder onBind(Intent intent) {
     372    return null;
     373  }
     377 2. Start the {{{KioskService}}} in the {{{onCreate()}}} method of {{{./src/com/android/browser/}}}:
     380public void onCreate() {
     381  super.onCreate();
     382  instance = this;
     383  registerKioskModeScreenOffReceiver();
     384  startService(new Intent(this, KioskService.class));  // add this
     390=== Set Immersive Mode ===
     391The 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.
     393To set immersive mode in your base activity's oncreate method:
     407=== Set Lock Task Mode ===
     408This section details the steps necessary to enter your device into what is referred as "Lock Task Mode" using a Device Policy Manager.
     410Adding 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.
     412To implement Lock Task Mode in the stock Android browser:
     4131. Add the following receiver block near the end the top level {{{AndroidManifest.xml}}} within the {{{application}}} tag.
     415<receiver android:name=""
     416    android:label="device_admin"
     417    android:permission="android.permission.BIND_DEVICE_ADMIN">
     418    <meta-data android:name=""
     419        android:resource="@xml/device_admin" />
     420    <intent-filter>
     421       <action android:name="" />
     422    </intent-filter>
     4262. Create the following {{{./src/com/android/browser/}}} class
     431import android.content.Context;
     432import android.content.Intent;
     434public class DeviceAdmin extends DeviceAdminReceiver {
     435  @Override
     436  public void onEnabled(Context context, Intent intent) {
     437  }
     439  @Override
     440  public void onDisabled(Context context, Intent intent) {
     441  }
     4453. Add the following private method to the {{{./src/com/android/browser/}}} class.
     447private void startKioskMode() {
     448    DevicePolicyManager DPM = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
     449    ComponentName mDeviceAdminSample = new ComponentName(this, DeviceAdmin.class);
     450    Intent intent;
     452    /* Check to see if application has already been authorized
     453     * for Lock Task Mode.
     454     */
     455    if (DPM.isLockTaskPermitted(this.getPackageName())) {
     456        startLockTask();
     457    }
     458    else if (!DPM.isDeviceOwner(getPackageName()) || !DPM.isAdminActive(mDeviceAdminSample)) {
     459        intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
     460        intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, mDeviceAdminSample);
     461        intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION, "Enabling force lock admin");
     462        startActivityForResult(intent, 99);
     463    }
     464    else {
     465        String[] packages = {getPackageName()};
     466        DPM.setLockTaskPackages(mDeviceAdminSample, packages);
     467        startLockTask();
     468    }
     470    return;
     4744. Make the following additions to the {{{onActivityResult()}}} method in the same class.
     477protected void onActivityResult(int requestCode, int resultCode,
     478        Intent intent) {
     479    if (requestCode == 99) {
     480        if(resultCode == Activity.RESULT_OK){
     481            DevicePolicyManager DPM = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
     482            ComponentName mDeviceAdminSample = new ComponentName(this, DeviceAdmin.class);
     483            String[] packages = {getPackageName()};
     484            DPM.setLockTaskPackages(mDeviceAdminSample, packages);
     485        }
     486        if (resultCode == Activity.RESULT_CANCELED) {
     487            Log.d(LOGTAG, getPackageName() + " was denied device admin.");
     488        }
     489        startLockTask();
     490    }
     491    mController.onActivityResult(requestCode, resultCode, intent);
     4954. Call the {{{startKioskMode()}}} method at the '''end''' of the {{{onCreate()}}} method in the same class.
     497Implementing 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/}}}).
     499Additions to {{{PRODUCT_COPY_FILES}}} should be of the form:
     504* {{{device_owner.xml}}} contents:
     506<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
     507<device-owner package="" />
     510* {{{device_policies.xml}}} contents:
     512<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
     513<policies setup-complete="true">
     514<admin name="">
     515<policies flags="8" />
     517<lock-task-component name="" />
     521For more information see the Android developer pages:
     522- [ Screen Pinning]
     523- [ Device Admin]
     524- [ Single Use Devices].
     527=== Remove System Navigation Bar ===
     528The Android System Navigation Bar is the bar at the bottom of the screen which presents the back, home, and app-switch soft-keys.
     530As the system navigation bar is independent from any installed Android application in particular, removing it is done in the Android OS.
     532To accomplish this on a rooted installed device you can set the {{{qemu.hw.mainkeys}}} property to 1 by editing {{{/system/build.prop}}}:
     537To 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}}}):
     539<bool name="config_showNavigationBar">false</bool>
     542Note 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/}}}).
     544Additionally, 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.
     546=== Hiding Browser Navigation ===
     547To disable the stock browser app's own navigation UI and fix resulting layout changes there are four necessary steps:
     5491. 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.
     5542. Add the following line to the {{{onResume()}}} method in {{{./src/com/android/browser/}}} to hide access to settings and tabs.
     5593. Add the following line to {{{./res/values/styles.xml}}} in the {{{ActionBarStyle}}} tag:
     561<style name="ActionBarStyle" parent="@android:style/Widget.Holo.ActionBar">
     562   <item name="android:background">@drawable/bg_urlbar</item>
     563   <item name="android:displayOptions"></item>
     564+  <item name="android:visibility">gone</item>
     5684. Remove the {{{android:fitsSystemWindows="true"}}} attribute from the root {{{LinearLayout}}} in {{{./res/layout/tab.xml}}}
     570<LinearLayout xmlns:android=""
     571    android:orientation="vertical"
     572-   android:fitsSystemWindows="true"
     573    android:layout_width="match_parent"
     574    android:layout_height="match_parent">
     577=== Add Bootloader URL and Exit Logic ===
     578The 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.
     5801. 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()}}}.
     585/* ADD THIS SECTION */
     586if (propReader("ro.boot.kioskdisable") != null) {
     587    Log.d(LOGTAG, "Found a reset key");
     588    finish();
     589    return;
     591/* END OF ADDED SECTION 1 */
     593if (shouldIgnoreIntents()) {
     594    finish();
     595    return;
     598if (IntentHandler.handleWebSearchIntent(this, null, getIntent())) {
     599    finish();
     600    return;
     602mController = createController();
     604Intent intent = (icicle == null) ? getIntent() : null;
     608/* ADD THIS SECTION */
     609String newUrl = propReader("ro.boot.url");
     610if (newUrl != null) {
     611    Log.d(LOGTAG, "Found a url, setting to: " + newUrl);
     612    BrowserSettings.getInstance().setHomePage(newUrl);
     613    ((UiController)mController).openTabToHomePage();
     615/* END OF ADDED SECTION 2 */
     6182. Add the following private method to {{{./src/com/android/browser/}}}:
     620/* Returns result of a getprop call taking a property
     621 * and returning a string upon success or null on failure.
     622 */
     623public static String propReader(String propName) {
     624    Process proc = null;
     625    String line;
     627    try {
     628        proc = Runtime.getRuntime().exec("/system/bin/getprop " + propName);
     629    } catch (IOException e) {
     630        e.printStackTrace();
     631        return null;
     632    }
     634    BufferedReader br = new BufferedReader(new InputStreamReader(proc.getInputStream()));
     636    try {
     637        if ((line = br.readLine()) != null && !line.isEmpty())
     638            return line.trim();
     639    } catch (IOException e) {
     640        e.printStackTrace();
     641    }
     643    return null;
     648=== Set Base Homepage ===
     649This 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.
     6512. Change the {{{homepage_base}}} string in {{{./res/values/strings.xml}}}
     653 Original:
     655<!-- The default homepage. -->
     656<string name="homepage_base" translatable="false">
     659 New:
     661<!-- The default homepage. -->
     662<string name="homepage_base" translatable="false">
     666=== Replace Default Home App ===
     667If 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.
     6691. Add the {{{HOME}}} category to the {{{MAIN}}} intent filter of your {{{BrowserActivity}}} activity tag in the {{{AndroidManifest.xml}}}
     672    <action android:name="android.intent.action.MAIN" />
     673    <!-- Add the following line: -->
     674    <category android:name="android.intent.category.HOME" />
     675    <category android:name="android.intent.category.DEFAULT" />
     676    <category android:name="android.intent.category.LAUNCHER" />
     677    <category android:name="android.intent.category.BROWSABLE" />
     678    <category android:name="android.intent.category.APP_BROWSER" />
     6822. Implement a boot receiver as described in the [#CreateaBootReceiver Create a Boot Receiver] section.
     6843. Remove the {{{Home}}}, {{{Launcher2}}}, and {{{Launcher3}}} packages as described in the [#TrimmingDowntheOS Trimming Down the OS] section.
     686== Bootloader Configuration ==
     687As 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.
     689The Kiosk Browser will check for the bootloader parameters:
     691||  Parameter                       ||  Value           ||  Description  ||
     692||  {{{androidboot.url}}}           || URL             || The primary URL target (should include the full {{{http://}}} prefix). ||
     693||  {{{androidboot.fallback}}}      || URL/File path   || The secondary URL target or file path (e.g. {{{file:///data/logo.png}}}). ||
     694||  {{{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. ||
     695||  {{{androidboot.kioskdisable}}}  || Boolean         || Any value, if present, will allow the Android settings to be accessed by swiping down from top of screen. ||
     696To edit one of the above parameters:
     697 1. Power on the device and break into the bootloader.
     698 2. Add {{{androidboot.url|fallback|refreshtime=<value>}}} to the environment variable {{{extra}}}
     699 {{{#!bash
     700setenv extra "${extra} androidboot.<PARAMETER>=<VALUE>"
     702 }}}
     703 * 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.
     705== Trimming Down the OS ==
     706Although 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.
     708Since 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/}}}.
     710For example:
     712LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 \
     713  Calculator Calender Settings Downloads Development \
     714  Camera Clock Contacts Email Gallery Messaging \
     715  Music Search SpeechRecorder
     718If 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.
     720The 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.