d
WE ARE EXPERTS IN TECHNOLOGY

Let’s Work Together

n

StatusNeo

Kiosk Mode : Android

What is Kiosk Mode and Why we use it?

Kiosk Mode basically allows a single Android application to have full control of the system.

 The client is kept from leaving the ongoing application or running different applications. Consider a theoretical ATM machine that is running Android. It would be terrible if the client would escape the “ATM application” and begin looking around with the basic framework.

Kiosk Mode works by disabling certain Android features that can be used to exit the current application or launch a new one. We must specifically disable or modify the following Android features:

  • The Home key
  • The Back key
  • The Power button
  • The “notification drawer” or “status bar”
  • The recent apps button
  • The volume button

Unfortunately, Android lacks a full-fledged Kiosk Mode that we can simply enable. Instead, we must disable or modify the behaviour of each of the four features separately.

Steps for Kiosk mode:

First of all we need to make sure your Android Kiosk app starts automatically after booting your device.
Add the following permission as a child of the manifest element to your Android manifest:

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

Now, your app has the permission to receive the RECEIVE_BOOT_COMPLETED broadcast. This message means that the phone was booted.

At next, add an intent filter to the manifest:

Disable the power button

Sadly, disabling the power button is not possible without custom modification to the core of the Android operating system. Nevertheless, it is possible to detect the result of the button press and react on it.

Short power button press:

You can detect a short button press by handling the ACTION_SCREEN_OFF intent and kick the screen back to life with acquiring a wake lock. What a hack!

Please note that you can’t declare ACTION_SCREEN_OFF in the AndroidManifest.xml! You are only allowed to catch them while your application is running. For that reason, create a class called OnScreenOffReceiver that extends BroadcastReceiver and add the following code:

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(); // get WakeLock reference via AppContext
 if (wakeLock.isHeld()) {
 wakeLock.release(); // release old wake lock
 }

 // create a new wake lock...
 wakeLock.acquire();

 // ... and release again
 wakeLock.release();
 }

 private boolean isKioskModeActive(final Context context) {
 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
 return sp.getBoolean(PREF_KIOSK_MODE, false);
 }
}

Create a subclass of Application and add the following code

public class AppContext extends Application {

 private AppContext instance;
 private PowerManager.WakeLock wakeLock;
 private OnScreenOffReceiver onScreenOffReceiver;


 @Override
 public void onCreate() {
 super.onCreate();
 instance = this;
 registerKioskModeScreenOffReceiver();
 }

 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;
 }
}

Add the permission WAKE_LOCK to your manifest:

<uses-permission android:name="android.permission.WAKE_LOCK" />

Register your subclass of Application in your manifest (android:name=”.AppContext”):

<application
 android:name=".AppContext"
 android:icon="@drawable/ic_launcher"
 android:label="@string/app_name">

To make the wake up a bit smarter, add the following line in your activity (before setContentView is called!). This line deactivates the lock screen:

getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);

For example:

@Override
protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
 setContentView(R.layout.my_activity);
 // … further layout init
}

Long power button press:

Now we come to my favorite hack to make a Kiosk work on Android OS: It is very simple but powerful.

Add the following snippet to your activity. It will surely prevent long press button.

@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);
 }
}

The idea is simple: in case any system dialog pops up, we kill it instantly by firing an ACTION_CLOSE_SYSTEM_DIALOG broadcast.

Disable the volume button

If necessary, you can easily deactivate the volume buttons by consuming both button calls. Just override dispatchKeyEvent in your Activity and handle the volume buttons:

private final List blockedKeys = new ArrayList(Arrays.asList(KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP));

@Override
public boolean dispatchKeyEvent(KeyEvent event) {
 if (blockedKeys.contains(event.getKeyCode())) {
 return true;
 } else {
 return super.dispatchKeyEvent(event);
 }
}

Disable the home button and detect when new applications are opened


Since Android 4 there is no effective method to deactivate the home button. That is the reason why we need another little hack to make your custom Android Kiosk work. In general the idea is to detect when a new application is in foreground and restart your activity immediately.

At first create a class called KioskService that extends Service and add the following snippet:

public class KioskService extends Service {

 private static final long INTERVAL = TimeUnit.SECONDS.toMillis(2); // periodic interval to check in seconds -> 2 seconds
 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(); // restore!
 }
 }
 }

 private boolean isInBackground() {
 ActivityManager am = (ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE);

 List<ActivityManager.RunningTaskInfo> taskInfo = am.getRunningTasks(1);
 ComponentName componentInfo = taskInfo.get(0).topActivity;
 return (!ctx.getApplicationContext().getPackageName().equals(componentInfo.getPackageName()));
 }

 private void restoreApp() {
 // Restart activity
 Intent i = new Intent(ctx, MyActivity.class);
 i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 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;
 }
}

Add the following method in your AppContext class to start the service via application context creation.

@Override
public void onCreate() {
super.onCreate();
instance = this;
registerKioskModeScreenOffReceiver();
startKioskService(); // add this
}
private void startKioskService() { // ... and this method
startService(new Intent(this, KioskService.class));
}

Lastly, add the service declaration and the permission for retrieving the foreground process to the manifest:

<service android:name=".KioskService" android:exported="false"/>
<uses-permission android:name="android.permission.GET_TASKS"/>

Basically, the thread checks every two seconds if your application is running in foreground. If not, the thread will immediately recreate your activity and your Android Kiosk is visible again.

Prevent screen dimming

It is also very easy to keep the screen bright as long as your app is visible (also forever). You only have to add the following flag to your root layout:

android:keepScreenOn="true"

Prevent status bar expansion / disable status bar pull down

public static void preventStatusBarExpansion(Context context) {
 WindowManager manager = ((WindowManager) context.getApplicationContext().getSystemService(Context.WINDOW_SERVICE));

 WindowManager.LayoutParams localLayoutParams = new WindowManager.LayoutParams();
 localLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
 localLayoutParams.gravity = Gravity.TOP;
 localLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL|WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;

 localLayoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;

 int resId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
 int result = 0;
 if (resId > 0) {
 result = context.getResources().getDimensionPixelSize(resId);
 } else {
 // Use Fallback size:
 result = 60; // 60px Fallback
 }

 localLayoutParams.height = result;
 localLayoutParams.format = PixelFormat.TRANSPARENT;

 customViewGroup view = new CustomViewGroup(context);
 manager.addView(view, localLayoutParams);
}

public static class CustomViewGroup extends ViewGroup {
 public CustomViewGroup(Context context) {
 super(context);
 }

 @Override
 protected void onLayout(boolean changed, int l, int t, int r, int b) {
 }

 @Override
 public boolean onInterceptTouchEvent(MotionEvent ev) {
 // Intercepted touch!
 return true;
 }
}
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

Add Comment