Preventing Access to Navigation and Status Bars in Kiosk Mode for a React Native Android App

49 Views Asked by At

I've developed an app using React Native, which is intended to be used on a touch-enabled Android device in a public setting. To ensure a focused user experience and prevent users from navigating away from the app, I've implemented a kiosk mode. However, I'm encountering challenges in completely restricting access to system functions; users can still bring up the navigation bar and status bar.

Here are the specifics of my situation:

Environment: The app is built with React Native and is running on an Android device. Current Implementation: I've enabled kiosk mode to lock down the app's usage to a single activity.

AndroidManifest.xml:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.teafilterui">

    <!-- Gerekli izinler -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

    <application
      android:name=".MainApplication"
      android:label="@string/app_name"
      android:icon="@mipmap/ic_launcher"
      android:roundIcon="@mipmap/ic_launcher_round"
      android:allowBackup="false"
      android:theme="@style/AppTheme">
      
      <activity
        android:name=".MainActivity"
        android:label="@string/app_name"
       android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
        android:launchMode="singleTask"
        android:windowSoftInputMode="adjustResize"
        android:exported="true"
        android:stateNotNeeded="true" 
        android:screenOrientation="landscape"
        android:clearTaskOnLaunch="true"
        android:excludeFromRecents="true">

        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.HOME" />
            <category android:name="android.intent.category.DEFAULT" />
        </intent-filter>
      </activity>
      
      <receiver android:name=".BootReceiver"
          android:exported="true">
          <intent-filter>
              <action android:name="android.intent.action.BOOT_COMPLETED" />
          </intent-filter>
      </receiver>

    </application>
</manifest>

MainActivity.Java:

    package com.teafilterui;

import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
import com.facebook.react.defaults.DefaultReactActivityDelegate;
import android.os.Bundle;
import android.view.View;
import android.view.WindowInsetsController;
import android.view.WindowInsets;
import androidx.drawerlayout.widget.DrawerLayout;

public class MainActivity extends ReactActivity {

  @Override
  protected String getMainComponentName() {
    return "TeaFilterUI";
  }


  @Override
  protected ReactActivityDelegate createReactActivityDelegate() {
    return new DefaultReactActivityDelegate(
        this,
        getMainComponentName(),
        // If you opted-in for the New Architecture, we enable the Fabric Renderer.
        DefaultNewArchitectureEntryPoint.getFabricEnabled());
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

  
        final WindowInsetsController controller = getWindow().getInsetsController();
        if (controller != null) {
            controller.hide(WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars());
            controller.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_DEFAULT);
        }
  }


  @Override
  public void onWindowFocusChanged(boolean hasFocus) {
      super.onWindowFocusChanged(hasFocus);
      if (hasFocus) {
          final WindowInsetsController controller = getWindow().getInsetsController();
          if (controller != null) {
              controller.hide(WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars());
              controller.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
          }
      }
  }

  @Override
  public void onBackPressed() {
  }
}

MainAplication.Java:

    package com.teafilterui;

import android.app.Application;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
import com.facebook.react.defaults.DefaultReactNativeHost;
import com.facebook.soloader.SoLoader;
import java.util.List;

public class MainApplication extends Application implements ReactApplication {

  private final ReactNativeHost mReactNativeHost =
      new DefaultReactNativeHost(this) {
        @Override
        public boolean getUseDeveloperSupport() {
          return BuildConfig.DEBUG;
        }

        @Override
        protected List<ReactPackage> getPackages() {
          @SuppressWarnings("UnnecessaryLocalVariable")
          List<ReactPackage> packages = new PackageList(this).getPackages();
          // Packages that cannot be autolinked yet can be added manually here, for example:
          // packages.add(new MyReactNativePackage());
          return packages;
        }

        @Override
        protected String getJSMainModuleName() {
          return "index";
        }

        @Override
        protected boolean isNewArchEnabled() {
          return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
        }

        @Override
        protected Boolean isHermesEnabled() {
          return BuildConfig.IS_HERMES_ENABLED;
        }
      };

  @Override
  public ReactNativeHost getReactNativeHost() {
    return mReactNativeHost;
  }

  @Override
  public void onCreate() {
    super.onCreate();
    SoLoader.init(this, /* native exopackage */ false);
    if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
      // If you opted-in for the New Architecture, we load the native entry point for this app.
      DefaultNewArchitectureEntryPoint.load();
    }
    ReactNativeFlipper.initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
  }
}

BootReceiver.java:

    // BootReceiver.java

package com.teafilterui;

import com.teafilterui.MainActivity; 
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public class BootReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
            Intent i = new Intent(context, MainActivity.class);
            i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            context.startActivity(i);
        }
    }
}

Issue: Despite being in kiosk mode, users are still able to swipe and reveal the navigation bar and status bar, thereby accessing other device functions and settings. I'm looking for a way to completely prevent access to the navigation and status bars while my app is running. Ideally, users should not be able to swipe or otherwise interact with these system UI elements to ensure the app remains in the foreground and other device functions remain inaccessible.

Has anyone successfully implemented such a restriction in a React Native Android app? Any insights, code snippets, or pointers to relevant documentation would be greatly appreciated.

0

There are 0 best solutions below