diff --git a/app/Http/Controllers/Frontdesk/DeviceController.php b/app/Http/Controllers/Frontdesk/DeviceController.php index 31eb963..5e978b6 100644 --- a/app/Http/Controllers/Frontdesk/DeviceController.php +++ b/app/Http/Controllers/Frontdesk/DeviceController.php @@ -8,6 +8,7 @@ use App\Models\Branch; use App\Models\Device; use App\Models\ReceptionDesk; use App\Services\Frontdesk\DeviceService; +use App\Services\Frontdesk\QrCodeService; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\View\View; @@ -97,7 +98,7 @@ class DeviceController extends Controller return redirect()->route('frontdesk.devices.index')->with('success', 'Device registered.'); } - public function edit(Request $request, Device $device): View + public function edit(Request $request, Device $device, QrCodeService $qrCodes): View { $this->authorizeAbility($request, 'devices.manage'); $this->authorizeOwner($request, $device); @@ -107,6 +108,9 @@ class DeviceController extends Controller 'deviceTypes' => config('frontdesk.device_types'), 'branches' => $this->branches($request, $device->organization_id), 'desks' => $this->desks($request, $device->organization_id), + 'kioskQrSvg' => $device->type === 'kiosk' && $device->device_token + ? $qrCodes->kioskSvg($device->device_token, 220) + : null, ]); } diff --git a/app/Services/Frontdesk/QrCodeService.php b/app/Services/Frontdesk/QrCodeService.php index 6203d8a..7ff9a0e 100644 --- a/app/Services/Frontdesk/QrCodeService.php +++ b/app/Services/Frontdesk/QrCodeService.php @@ -33,6 +33,16 @@ class QrCodeService return $this->renderSvg($this->employeeUrl($employee), $size); } + public function kioskUrl(string $deviceToken): string + { + return route('frontdesk.kiosk.device', $deviceToken); + } + + public function kioskSvg(string $deviceToken, int $size = 200): string + { + return $this->renderSvg($this->kioskUrl($deviceToken), $size); + } + protected function renderSvg(string $url, int $size = 200): string { $options = new QROptions([ diff --git a/docs/KIOSK-MANUFACTURER-FLASH.md b/docs/KIOSK-MANUFACTURER-FLASH.md new file mode 100644 index 0000000..ca52299 --- /dev/null +++ b/docs/KIOSK-MANUFACTURER-FLASH.md @@ -0,0 +1,319 @@ +# Ladill Frontdesk Kiosk — Manufacturer Flash Guide + +This guide is for OEM partners flashing Ladill visitor-kiosk tablets before shipment. It covers APK installation, kiosk lockdown, and customer provisioning. + +## Package summary + +| Field | Value | +|-------|-------| +| App name | Ladill Frontdesk Kiosk | +| Package ID | `com.ladill.frontdesk.kiosk` | +| Min Android | 8.0 (API 26) | +| Target Android | 15 (API 35) | +| Default server | `https://frontdesk.ladill.com` | +| Factory admin PIN | `462834` (change after first provision) | + +## Files you receive + +| File | Purpose | +|------|---------| +| `ladill-frontdesk-kiosk-1.0.0.apk` | Signed release APK to flash on each device | +| This guide | Flash, lockdown, and QA checklist | + +Forty8Tech builds the APK with: + +```bash +./scripts/build-kiosk-apk.sh +``` + +Output path: `apps/android/dist/ladill-frontdesk-kiosk-.apk` + +The Android source tree lives under `apps/android/` locally and is not committed to git. Distribute the built APK to manufacturers directly (USB, SFTP, or MDM bundle). + +--- + +## End-to-end flow + +```mermaid +flowchart LR + A[Flash APK] --> B[Set device owner / MDM] + B --> C[Pin as HOME launcher] + C --> D[Customer registers kiosk in Frontdesk] + D --> E[Scan provisioning QR on tablet] + E --> F[Kiosk runs fullscreen] +``` + +1. **Factory:** Install APK, apply kiosk policy, set app as default HOME. +2. **Customer:** Registers a Visitor Kiosk under Ladill Frontdesk → Devices. +3. **Field / factory QA:** Scan the provisioning QR shown on the device edit screen. +4. **Runtime:** Tablet loads the web kiosk, sends heartbeats, and stays locked. + +--- + +## 1. Generic pre-flash checklist + +Before flashing a batch: + +- [ ] Tablet is factory-reset (recommended for device-owner provisioning). +- [ ] Wi-Fi or Ethernet works; device can reach `https://frontdesk.ladill.com`. +- [ ] Date/time and timezone are correct (TLS and session cookies depend on this). +- [ ] Screen stays on when plugged in (disable aggressive battery saver). +- [ ] You have the signed APK for the target build version. + +### Install APK (ADB) + +```bash +adb install -r ladill-frontdesk-kiosk-1.0.0.apk +``` + +For system-privileged installs (some OEM images): + +```bash +adb root +adb remount +adb push ladill-frontdesk-kiosk-1.0.0.apk /system/priv-app/LadillFrontdeskKiosk/LadillFrontdeskKiosk.apk +adb shell chmod 644 /system/priv-app/LadillFrontdeskKiosk/LadillFrontdeskKiosk.apk +adb reboot +``` + +--- + +## 2. Android Device Owner (generic MDM) + +Device Owner mode is required for true kiosk lock (block Home, Recents, status bar). Use this for custom tablets or any MDM that supports Android Enterprise. + +### 2.1 Provision device owner via MDM (recommended) + +Device must have **no Google accounts** and ideally be fresh out of reset. + +Use your MDM’s **Android Enterprise device-owner** provisioning (QR, NFC, zero-touch, or StageNow). Assign `com.ladill.frontdesk.kiosk` as the **kiosk / lock-task** application. + +The current APK uses immersive fullscreen, HOME launcher registration, and `startLockTask()` when the OS allows it. For strict device-owner policies (block Settings, prevent uninstall, silent install), provision through your EMM—do not rely on manual ADB unless your OEM documents a supported path. + +> Need a build with a bundled `DeviceAdminReceiver` for `dpm set-device-owner`? Contact Forty8Tech for an OEM white-label package. + +### 2.2 MDM policy (any EMM) + +Configure these policies for `com.ladill.frontdesk.kiosk`: + +| Policy | Setting | +|--------|---------| +| Kiosk / lock-task app | `com.ladill.frontdesk.kiosk` | +| Single app mode | Enabled | +| Default launcher | `com.ladill.frontdesk.kiosk` | +| Stay on launcher | Enabled | +| Status bar | Hidden or restricted | +| System navigation | Disabled | +| Auto-launch on boot | Enabled | +| Allowlist URLs | `*.ladill.com` | +| Camera | Allowed | +| Microphone | Allowed | +| Install unknown apps | Disabled | +| Factory reset | PIN-protected | + +### 2.3 Set as default HOME launcher + +After first launch: + +1. Press Home (if visible during setup). +2. Choose **Ladill Frontdesk Kiosk**. +3. Select **Always**. + +Or via ADB: + +```bash +adb shell cmd package set-home-activity com.ladill.frontdesk.kiosk/.MainActivity +``` + +### 2.4 Boot behavior + +The APK registers for `BOOT_COMPLETED` and starts automatically. Confirm: + +```bash +adb shell am start -n com.ladill.frontdesk.kiosk/.MainActivity +adb reboot +# After boot, kiosk setup or web UI should appear without manual launch +``` + +--- + +## 3. Elo Touch Solutions + +Typical on Elo I-Series and Open Frame Android panels with **EloView** or **Elo Access**. + +### 3.1 EloView (cloud) + +1. Add device to your EloView account. +2. Upload `ladill-frontdesk-kiosk-1.0.0.apk` as a managed app. +3. Create a **Kiosk** profile: + - **Primary app:** Ladill Frontdesk Kiosk (`com.ladill.frontdesk.kiosk`) + - **Mode:** Single app / lock task + - **Auto launch:** On boot + - **Navigation bar:** Hidden + - **Power button:** Map to sleep only (not exit kiosk) +4. Allow **Camera** and **Microphone** in the profile (visitor photo + QR checkout). +5. Push profile to device group before shipment. + +### 3.2 Elo device settings (on-device) + +1. Settings → **Elo Device Settings** → **Home App** → Ladill Frontdesk Kiosk. +2. Enable **Start on boot**. +3. Disable **Android Settings access** for field users. +4. Set **Display timeout** to maximum; keep **USB power** connected in lobby installs. + +### 3.3 Elo QA + +- [ ] App survives power cycle. +- [ ] No browser chrome visible. +- [ ] Camera preview works on check-in photo step. +- [ ] QR scanner works on checkout step. + +--- + +## 4. Zebra Technologies + +Typical on ET40, ET45, TC series, and custom Zebra Android builds with **StageNow** and **MX**. + +### 4.1 StageNow barcode (recommended) + +1. Open **StageNow** on a PC. +2. Create a **Setup** profile: + - Install APK from `file://` path or download URL (host APK on your CDN). + - Set **Default Launcher** to `com.ladill.frontdesk.kiosk`. + - Enable **Mx/kiosk mode** → single app = Ladill Frontdesk Kiosk. +3. Export profile as a barcode. +4. Scan on factory line during device setup. + +### 4.2 MX KeyManager / AppMgr + +| MX setting | Value | +|------------|-------| +| `AppMgr` install | `ladill-frontdesk-kiosk-1.0.0.apk` | +| `KioskMode` enabled | `true` | +| `KioskMode` package | `com.ladill.frontdesk.kiosk` | +| `UiMgr` hide status bar | `true` | +| `PowerMgr` stay awake on AC | `true` | + +### 4.3 Zebra DataWedge (optional) + +The kiosk uses the **web** QR scanner (`html5-qrcode`) for badge checkout. DataWedge is **not required**. If you remap a hardware scan trigger, ensure it does not steal focus from the WebView during checkout. + +### 4.4 Zebra QA + +- [ ] StageNow profile applies on clean device. +- [ ] Lock task survives suspend/resume. +- [ ] Wi-Fi roaming does not drop kiosk session. + +--- + +## 5. Customer provisioning (after flash) + +The tablet ships **unlinked** until the customer pairs it. + +### 5.1 Customer steps (Ladill Frontdesk admin) + +1. Sign in to [Frontdesk](https://frontdesk.ladill.com). +2. Go to **Devices → Add device**. +3. Type: **Visitor Kiosk**. +4. Save. Open the device → note the **Provision Ladill Kiosk app** QR code. + +### 5.2 On-tablet steps + +1. Power on tablet; Ladill Frontdesk Kiosk opens to **Provision kiosk**. +2. Tap **Show QR scanner** and scan the QR from Frontdesk. + - Or paste the device token / full kiosk URL manually. +3. Tap **Start kiosk**. The visitor check-in UI loads. + +Provisioning URL format: + +``` +https://frontdesk.ladill.com/kiosk/d/{device_token} +``` + +Deep link (also supported): + +``` +ladillfrontdesk://kiosk/{device_token} +``` + +### 5.3 Verify online status + +In Frontdesk → Devices, the kiosk should show **Online** within a few minutes (heartbeat every 5 minutes, plus activity on page load). + +--- + +## 6. Admin access (factory and field service) + +Hidden admin panel for re-provision, reload, and PIN change: + +1. Tap the **top-left corner 7 times** within 2 seconds. +2. Enter admin PIN (factory default: `462834`). +3. Options: reload kiosk, re-provision, change PIN, exit admin. + +**Change the factory PIN** before shipping to end customers, or document that the customer must change it during provisioning QA. + +--- + +## 7. Security and signing + +| Item | Guidance | +|------|----------| +| Factory keystore | Replace before mass production; do not ship factory passwords to end customers. | +| APK signature | All devices in a SKU should use the same release key for OTA updates. | +| Network | HTTPS only; app blocks mixed content. | +| Token storage | Device token is stored in EncryptedSharedPreferences on-device. | +| Token rotation | If customer regenerates token in Frontdesk, re-scan QR or re-provision. | + +--- + +## 8. Factory QA checklist (per device) + +| # | Test | Pass | +|---|------|------| +| 1 | APK installs without error | ☐ | +| 2 | App is default HOME launcher | ☐ | +| 3 | Boots directly into Ladill app after cold start | ☐ | +| 4 | Home / Recents blocked in kiosk mode | ☐ | +| 5 | Provisioning QR scan links device | ☐ | +| 6 | Visitor welcome screen loads | ☐ | +| 7 | Camera permission prompt accepted; photo step works | ☐ | +| 8 | Device shows Online in Frontdesk | ☐ | +| 9 | Admin PIN changed from factory default | ☐ | +| 10 | Device serial / IMEI recorded on build sheet | ☐ | + +--- + +## 9. Troubleshooting + +| Symptom | Likely cause | Fix | +|---------|--------------|-----| +| “Device token required” / 404 | Invalid or revoked token | Re-scan QR from Frontdesk Devices | +| Stuck on setup screen | No network | Connect Wi-Fi; verify `frontdesk.ladill.com` reachable | +| Camera not working | Permission denied | Re-grant camera in admin → reload; check MDM camera policy | +| Device shows Offline | Network or heartbeat blocked | Allow `POST /api/devices/heartbeat`; disable battery optimization for app | +| User can exit to Settings | Lock task not active | Apply MDM kiosk policy; set device owner | +| White screen after provision | TLS or DNS issue | Check date/time; verify HTTPS certificate trust | +| Regenerated token | Old token invalid | Re-provision from admin panel or scan new QR | + +### Useful ADB commands + +```bash +# Logs +adb logcat -s LadillFrontdeskKiosk + +# Clear app data (forces re-provision) +adb shell pm clear com.ladill.frontdesk.kiosk + +# Check lock task state +adb shell dumpsys activity activities | grep mLockTaskMode +``` + +--- + +## 10. Contact + +For white-label APK signing, device-admin receiver builds, or OEM-specific MX/Elo profiles: + +**Forty8Tech / Ladill** — hardware@ladill.com + +Include: tablet model, Android version, MDM platform, batch size, and target market. diff --git a/resources/views/frontdesk/devices/edit.blade.php b/resources/views/frontdesk/devices/edit.blade.php index 338cceb..539f671 100644 --- a/resources/views/frontdesk/devices/edit.blade.php +++ b/resources/views/frontdesk/devices/edit.blade.php @@ -41,6 +41,13 @@ {{ $device->device_token }} @if ($device->type === 'kiosk') Open kiosk URL + @if (! empty($kioskQrSvg)) +
+

Provision Ladill Kiosk app

+

Scan with the tablet on first boot to link this device.

+
{!! $kioskQrSvg !!}
+
+ @endif @endif @endif diff --git a/scripts/build-kiosk-apk.sh b/scripts/build-kiosk-apk.sh new file mode 100755 index 0000000..18db7c0 --- /dev/null +++ b/scripts/build-kiosk-apk.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +ANDROID_DIR="$ROOT/apps/android" +OUTPUT_DIR="$ANDROID_DIR/dist" +KEYSTORE_FILE="$ANDROID_DIR/.factory-release.keystore" +KEYSTORE_PROPS="$ANDROID_DIR/keystore.properties" + +mkdir -p "$OUTPUT_DIR" + +if [[ ! -f "$KEYSTORE_PROPS" ]]; then + if [[ ! -f "$KEYSTORE_FILE" ]]; then + echo "Generating factory release keystore at $KEYSTORE_FILE" + keytool -genkeypair \ + -alias ladill-frontdesk-kiosk \ + -keyalg RSA \ + -keysize 4096 \ + -validity 10000 \ + -keystore "$KEYSTORE_FILE" \ + -storepass "change-me-before-production" \ + -keypass "change-me-before-production" \ + -dname "CN=Ladill Frontdesk Kiosk, OU=Hardware, O=Forty8Tech, L=Accra, ST=Greater Accra, C=GH" + fi + + cp "$ANDROID_DIR/keystore.properties.example" "$KEYSTORE_PROPS" +fi + +if [[ -z "${ANDROID_HOME:-}" && -d "$HOME/Library/Android/sdk" ]]; then + export ANDROID_HOME="$HOME/Library/Android/sdk" +fi + +if [[ -z "${JAVA_HOME:-}" ]]; then + if /usr/libexec/java_home -v 17 >/dev/null 2>&1; then + export JAVA_HOME="$(/usr/libexec/java_home -v 17)" + fi +fi + +cd "$ANDROID_DIR" +./gradlew --no-daemon clean assembleRelease + +APK_SRC="$ANDROID_DIR/app/build/outputs/apk/release/app-release.apk" +APK_DEST="$OUTPUT_DIR/ladill-frontdesk-kiosk-$(grep versionName app/build.gradle.kts | head -1 | sed -E 's/.*"([^"]+)".*/\1/').apk" + +cp "$APK_SRC" "$APK_DEST" + +echo "" +echo "Built release APK:" +echo " $APK_DEST" +echo "" +echo "Package: com.ladill.frontdesk.kiosk" +echo "Factory admin PIN: 462834 (change from kiosk admin after provisioning)" +echo "" +echo "See docs/KIOSK-MANUFACTURER-FLASH.md for full OEM flash instructions."