Changes between Initial Version and Version 1 of Android/Kiosk


Ignore:
Timestamp:
10/22/2017 05:28:45 AM (7 years ago)
Author:
trac
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Android/Kiosk

    v1 v1  
     1[[PageOutline]]
     2[[Image(kioskblast.jpg,400px)]]
     3= Gateworks Android Kiosk Mode - Digital Signage Software =
     4
     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.
     6
     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.
     8
     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.
     10
     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]
     16
     17Gateworks has provided a pre-compiled binary for getting started quickly with the Kiosk Mode.
     18
     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]
     22
     23Please contact Gateworks support for a pre-compiled binary.
     24
     25[=#prebuilt]
     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)
     30
     31Latest images:
     32 * Android 5.1.1 (Lollipop) Kiosk:
     33  * Built on 2016-08-23 from Gateworks Android Kiosk BSP:
     34   * Source: https://github.com/Gateworks/imx_android/tree/gateworks_l5.1.1_2.1.0-ga_kioskmode
     35   * Based on Freescale l5.1.1_2.1.0-ga source
     36  * ubi images:
     37   * [http://blog.gateworks.com/?wpdmpro=ventana-android-l5-1-1_2-1-0-ga_kioskmode-user_large-ubi ventana-android-l5.1.1_2.1.0-ga_kioskmode-user-20160823-1853_large.ubi]  ('''2GB NAND devices''') (sha256sum:74ecfdf4413df47f2a5402ff448cd6321dc97893720580c93202b7966c0cc279)
     38   * [http://blog.gateworks.com/?wpdmpro=ventana-android-l5-1-1_2-1-0-ga_kioskmode-user-normal-ubi  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   * [http://blog.gateworks.com/?wpdmpro=ventana-android-l5-1-1_2-1-0-ga_kioskmode-user-tar-bz2 ventana-android-l5.1.1_2.1.0-ga_kioskmode-user-20160823-1853.tar.bz2] (c451c5846ab930079c793cc3027641048b0308a0f2bc067eff42e7e212554044)
     41
     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!
     49
     50
     51
     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.
     54
     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.
     56
     57This 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.
     58
     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.
     61
     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:
     65
     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]
     72
     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.
     74
     75[=#boot-receiver]
     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.
     78
     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.
     80
     811. Add the following 2 sections to the top level {{{./AndroidManifest.xml}}}:
     82{{{#!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" />
     85
     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>
     91</receiver>
     92}}}
     93
     942. Create the {{{./src/com/android/browser/BootReceiver.java}}} class
     95{{{#!java
     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  }
     103}
     104}}}
     105
     106
     107[=#disable-navigation]
     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.
     110
     111This step is specific to the Android app you are working with.
     112
     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/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.
     114
     115{{{#!java
     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   }
     136}
     137}}}
     138
     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.
     140{{{#!java
     141@Override
     142public void onBackPressed() {
     143    // nothing to do here
     144}
     145}}}
     146
     147
     148[=#disable-power]
     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.
     151
     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}}}
     161
     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:
     163{{{#!bash
     164Bus=0018 Vendor=0000 Product=0000 Version=0000
     165}}}
     166
     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.
     168
     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.
     170
     171[=#disable-power-shortpress]
     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:
     174
     175 1. Create the {{{./src/com/android/browser/OnScreenOffReceiver.java}}} class:
     176{{{#!java
     177public class OnScreenOffReceiver extends BroadcastReceiver {
     178  private static final String PREF_KIOSK_MODE = "pref_kiosk_mode";
     179
     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  }
     190
     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  }
     199
     200  private boolean isKioskModeActive(final Context context) {
     201    SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
     202    return sp.getBoolean(PREF_KIOSK_MODE, false);
     203  }
     204}
     205}}}
     206
     207 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}}}):
     208{{{#!java
     209public class Browser extends Application {
     210
     211  /* Added instance variables */
     212  private PowerManager.WakeLock wakeLock;
     213  private OnScreenOffReceiver onScreenOffReceiver;
     214
     215  @Override
     216  public void onCreate() {
     217    super.onCreate();
     218
     219    registerKioskModeScreenOffReceiver(); // Add this
     220  }
     221
     222  /* Add the following two methods */
     223
     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  }
     230
     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  }
     239}
     240}}}
     241
     242 3. Add the permission WAKE_LOCK to your manifest:
     243{{{#!xml
     244<uses-permission android:name="android.permission.WAKE_LOCK" />
     245}}}
     246  * Other apps will also need to register their subclass of {{{Application}}} in the top level {{{AndroidManifest.xml}}}:
     247{{{#!xml
     248<application
     249        android:name=".AppContext"
     250        android:icon="@drawable/ic_launcher"
     251        android:label="@string/app_name">
     252}}}
     253
     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/BrowserActivity.java}}} '''following''' the {{{super.onCreate()}}} call:
     255{{{#!java
     256@Override
     257protected void onCreate(Bundle savedInstanceState) {
     258  super.onCreate(savedInstanceState);
     259  getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); // Add this
     260  // …
     261}
     262}}}
     263
     264[=#disable-power-longpress]
     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.
     267
     268 1. Override the {{{onWindowFocusChanged()}}} method in the {{{./src/com/android/browser/BrowserActivity.java}}} class
     269{{{#!java
     270/* Add this method */
     271@Override
     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  }
     279}
     280}}}
     281
     282[=#disable-dimming]
     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.
     285
     286Add {{{android:keepScreenOn="true"}}} attribute to the rootview of your activity ({{{./res/layout/browser_subwindow.xml}}} for stock browser app).
     287
     288[=#background-service]
     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.
     291
     292 1. Add {{{src/com/android/browser/KioskService.java}}}:
     293{{{#!java
     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";
     299
     300  private Thread t = null;
     301  private Context ctx = null;
     302  private boolean running = false;
     303
     304  @Override
     305  public void onDestroy() {
     306    Log.i(TAG, "Stopping service 'KioskService'");
     307    running =false;
     308    super.onDestroy();
     309  }
     310
     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;
     316
     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    });
     332
     333    t.start();
     334    return Service.START_NOT_STICKY;
     335  }
     336
     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  }
     346
     347  private boolean isInBackground() {
     348    ActivityManager am = (ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE);
     349
     350    List<ActivityManager.RunningTaskInfo> taskInfo = am.getRunningTasks(1);
     351    ComponentName componentInfo = taskInfo.get(0).topActivity;
     352    return (!ctx.getApplicationContext().getPackageName().equals(componentInfo.getPackageName()));
     353  }
     354
     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  }
     364
     365  public boolean isKioskModeActive(final Context context) {
     366    SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
     367    return sp.getBoolean(PREF_KIOSK_MODE, false);
     368  }
     369
     370  @Override
     371  public IBinder onBind(Intent intent) {
     372    return null;
     373  }
     374}
     375}}}
     376
     377 2. Start the {{{KioskService}}} in the {{{onCreate()}}} method of {{{./src/com/android/browser/BrowserActivity.java}}}:
     378{{{#!java
     379@Override
     380public void onCreate() {
     381  super.onCreate();
     382  instance = this;
     383  registerKioskModeScreenOffReceiver();
     384  startService(new Intent(this, KioskService.class));  // add this
     385}
     386}}}
     387
     388
     389[=#immersive-mode]
     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.
     392
     393To set immersive mode in your base activity's oncreate method:
     394{{{#!java
     395getWindow().getDecorView().setSystemUiVisibility(
     396  View.SYSTEM_UI_FLAG_LAYOUT_STABLE
     397  | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
     398  | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
     399  | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
     400  | View.SYSTEM_UI_FLAG_FULLSCREEN
     401  | View.SYSTEM_UI_FLAG_IMMERSIVE
     402  | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
     403}}}
     404
     405
     406[=#lock-task-mode]
     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.
     409
     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.
     411
     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.
     414{{{#!xml
     415<receiver android:name="com.android.browser.DeviceAdmin"
     416    android:label="device_admin"
     417    android:permission="android.permission.BIND_DEVICE_ADMIN">
     418    <meta-data android:name="android.app.device_admin"
     419        android:resource="@xml/device_admin" />
     420    <intent-filter>
     421       <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
     422    </intent-filter>
     423</receiver>
     424}}}
     425
     4262. Create the following {{{./src/com/android/browser/DeviceAdmin.java}}} class
     427{{{#!java
     428package com.android.browser;
     429
     430import android.app.admin.DeviceAdminReceiver;
     431import android.content.Context;
     432import android.content.Intent;
     433
     434public class DeviceAdmin extends DeviceAdminReceiver {
     435  @Override
     436  public void onEnabled(Context context, Intent intent) {
     437  }
     438
     439  @Override
     440  public void onDisabled(Context context, Intent intent) {
     441  }
     442}
     443}}}
     444
     4453. Add the following private method to the {{{./src/com/android/browser/BrowserActivity.java}}} class.
     446{{{#!java
     447private void startKioskMode() {
     448    DevicePolicyManager DPM = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
     449    ComponentName mDeviceAdminSample = new ComponentName(this, DeviceAdmin.class);
     450    Intent intent;
     451
     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    }
     469
     470    return;
     471}
     472}}}
     473
     4744. Make the following additions to the {{{onActivityResult()}}} method in the same class.
     475{{{#!java
     476@Override
     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);
     492}
     493}}}
     494
     4954. Call the {{{startKioskMode()}}} method at the '''end''' of the {{{onCreate()}}} method in the same class.
     496
     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/ventana.mk}}}).
     498
     499Additions to {{{PRODUCT_COPY_FILES}}} should be of the form:
     500{{{#!xml
     501<source_path>:<destination_path>
     502}}}
     503
     504* {{{device_owner.xml}}} contents:
     505{{{#!xml
     506<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
     507<device-owner package="com.android.browser" />
     508}}}
     509
     510* {{{device_policies.xml}}} contents:
     511{{{#!xml
     512<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
     513<policies setup-complete="true">
     514<admin name="com.android.browser/com.android.browser.DeviceAdmin">
     515<policies flags="8" />
     516</admin>
     517<lock-task-component name="com.android.browser" />
     518</policies>
     519}}}
     520
     521For more information see the Android developer pages:
     522- [http://developer.android.com/about/versions/android-5.0.html#ScreenPinning Screen Pinning]
     523- [http://developer.android.com/guide/topics/admin/device-admin.html Device Admin]
     524- [http://developer.android.com/training/enterprise/cosu.html Single Use Devices].
     525
     526[=#disable-navbar]
     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.
     529
     530As the system navigation bar is independent from any installed Android application in particular, removing it is done in the Android OS.
     531
     532To accomplish this on a rooted installed device you can set the {{{qemu.hw.mainkeys}}} property to 1 by editing {{{/system/build.prop}}}:
     533{{{#!bash
     534qemu.hw.mainkeys=1
     535}}}
     536
     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}}}):
     538{{{#!xml
     539<bool name="config_showNavigationBar">false</bool>
     540}}}
     541
     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 {{{imx6.mk}}} and {{{BoardConfig.mk}}} in {{{device/gateworks/ventana/}}}).
     543
     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.
     545
     546=== Hiding Browser Navigation ===
     547To disable the stock browser app's own navigation UI and fix resulting layout changes there are four necessary steps:
     548
     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.
     550{{{#!java
     551android:visibility="gone"
     552}}}
     553
     5542. Add the following line to the {{{onResume()}}} method in {{{./src/com/android/browser/BrowserActivity.java}}} to hide access to settings and tabs.
     555{{{#!java
     556getActionBar().hide();
     557}}}
     558
     5593. Add the following line to {{{./res/values/styles.xml}}} in the {{{ActionBarStyle}}} tag:
     560{{{#!xml
     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>
     565</style>
     566}}}
     567
     5684. Remove the {{{android:fitsSystemWindows="true"}}} attribute from the root {{{LinearLayout}}} in {{{./res/layout/tab.xml}}}
     569{{{#!xml
     570<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     571    android:orientation="vertical"
     572-   android:fitsSystemWindows="true"
     573    android:layout_width="match_parent"
     574    android:layout_height="match_parent">
     575}}}
     576
     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.
     579
     5801. 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()}}}.
     581{{{#!java
     582
     583super.onCreate(icicle);
     584
     585/* ADD THIS SECTION */
     586if (propReader("ro.boot.kioskdisable") != null) {
     587    Log.d(LOGTAG, "Found a reset key");
     588    finish();
     589    return;
     590}
     591/* END OF ADDED SECTION 1 */
     592
     593if (shouldIgnoreIntents()) {
     594    finish();
     595    return;
     596}
     597
     598if (IntentHandler.handleWebSearchIntent(this, null, getIntent())) {
     599    finish();
     600    return;
     601}
     602mController = createController();
     603
     604Intent intent = (icicle == null) ? getIntent() : null;
     605mController.start(intent);
     606
     607
     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();
     614}
     615/* END OF ADDED SECTION 2 */
     616}}}
     617
     6182. Add the following private method to {{{./src/com/android/browser/BrowserActivity.java}}}:
     619{{{#!java
     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;
     626
     627    try {
     628        proc = Runtime.getRuntime().exec("/system/bin/getprop " + propName);
     629    } catch (IOException e) {
     630        e.printStackTrace();
     631        return null;
     632    }
     633
     634    BufferedReader br = new BufferedReader(new InputStreamReader(proc.getInputStream()));
     635
     636    try {
     637        if ((line = br.readLine()) != null && !line.isEmpty())
     638            return line.trim();
     639    } catch (IOException e) {
     640        e.printStackTrace();
     641    }
     642
     643    return null;
     644}
     645}}}
     646
     647
     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.
     650
     6512. Change the {{{homepage_base}}} string in {{{./res/values/strings.xml}}}
     652
     653 Original:
     654{{{#!xml
     655<!-- The default homepage. -->
     656<string name="homepage_base" translatable="false">
     657    https://www.google.com/webhp?client={CID}&amp;source=android-home</string>
     658}}}
     659 New:
     660{{{#!xml
     661<!-- The default homepage. -->
     662<string name="homepage_base" translatable="false">
     663    http://www.gateworks-info.com/www/</string>
     664}}}
     665
     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.
     668
     6691. Add the {{{HOME}}} category to the {{{MAIN}}} intent filter of your {{{BrowserActivity}}} activity tag in the {{{AndroidManifest.xml}}}
     670{{{#!xml
     671<intent-filter>
     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" />
     679</intent-filter>
     680}}}
     681
     6822. Implement a boot receiver as described in the [#CreateaBootReceiver Create a Boot Receiver] section.
     683
     6843. Remove the {{{Home}}}, {{{Launcher2}}}, and {{{Launcher3}}} packages as described in the [#TrimmingDowntheOS Trimming Down the OS] section.
     685
     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.
     688
     689The Kiosk Browser will check for the bootloader parameters:
     690
     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>"
     701saveenv
     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.
     704
     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.
     707
     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/Android.mk}}}.
     709
     710For example:
     711{{{#!mk
     712LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 \
     713  Calculator Calender Settings Downloads Development \
     714  Camera Clock Contacts Email Gallery Messaging \
     715  Music Search SpeechRecorder
     716}}}
     717
     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.
     719
     720The full list of included packages are iteratively added to the {{{PRODUCT_PACKAGES}}} variable when the {{{<AOSP_DIR>/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.