Classroom digital signage

Author:

Zhenyu Yang <yangzhenyu@sust.edu.cn>

Last updated:

Apr 2, 2026

Overview

The signage/ directory contains the Android classroom signage app used on dedicated display devices. The current implementation is a native Kotlin kiosk application. It runs in landscape mode, hides the system UI, scans a QR code to bind itself to a classroom, and then pulls device-scoped signage data from the Django backend.

Important

The current app is display-only.

  • It does not perform classroom check-in.

  • It does not call face-service.

  • It does not use the old anonymous /signage/classrooms/{id} flow.

Repository layout

The signage app is a standalone Gradle project under signage/.

Key files in the current implementation:

  • signage/app/src/main/java/cn/edu/sust/myucspace/signage/MainActivity.kt full-screen activity, QR scanning, device binding, and schedule display

  • signage/app/src/main/java/cn/edu/sust/myucspace/signage/App.kt application entry that initializes device credential storage

  • signage/app/src/main/java/cn/edu/sust/myucspace/signage/storage/DeviceAuthStore.kt encrypted local storage for the API base URL and device token

  • signage/app/src/main/java/cn/edu/sust/myucspace/signage/network/SignageApiService.kt Retrofit interface for signage device APIs

  • signage/app/src/main/java/cn/edu/sust/myucspace/signage/repository/NetworkSignageRepository.kt device activation and signage data fetching

  • signage/app/src/main/res/layout/activity_main.xml main signage screen

  • signage/app/src/main/res/layout/dialog_camera.xml QR binding dialog

Warning

Older documentation referenced an embedded Android WebView and assets/index.html as the production runtime path. That is no longer the active implementation.

The signage/web/ package may still exist in the repository as historical code, but the current app does not render its UI through WebView.

How it works

Startup flow

On startup, MainActivity:

  1. Enables immersive full-screen mode.

  2. Initializes the signage UI and timeline components.

  3. Checks whether a device token already exists in encrypted local storage.

  4. If no token exists, opens the camera flow and waits for a setup QR code.

  5. If a token exists, requests: - GET /api/v1/signage/device - GET /api/v1/signage/device/schedule

  6. If the backend returns 401 or 403, clears the local device token and forces re-binding.

Device binding flow

The current signage app supports both activation-code binding and direct-token binding from a QR payload.

  1. A management-side caller requests QR binding data from the backend.

  2. The backend returns an activation-code payload containing classroom_id, activation_code and base_url.

  3. The Android signage scans the QR payload.

  4. The Android app loads or creates a local device_code and calls POST /api/v1/signage/activate when the payload contains activation_code.

  5. The app stores the device token in EncryptedSharedPreferences backed by Android Keystore.

Supported QR payload formats in the current app:

  • JSON payload containing activation_code and optional classroom_id and base_url

  • URL containing activation_code / code and explicit base_url

  • raw activation code text

Backend API

The current signage device flow uses these endpoints:

  • POST /api/v1/signage/activate public endpoint for one-time activation code exchange

  • GET /api/v1/signage/device authenticated device endpoint returning the current classroom signage config

  • GET /api/v1/signage/device/schedule?start_date=YYYY-MM-DD&end_date=YYYY-MM-DD authenticated device endpoint returning schedule items for the bound classroom

Management-side endpoints:

  • POST /api/v1/signage/classrooms/{id}/activation-code generate QR binding payload

  • PATCH /api/v1/signage/classrooms/{id}/config update signage configuration

  • POST /api/v1/signage/classrooms/{id}/revoke-token revoke the current device token

  • GET /api/v1/signage/classrooms/{id} admin-only signage device configuration lookup

  • GET /api/v1/signage/classrooms/{id}/schedule admin-only classroom schedule lookup

Signage device data model

The device is represented by apps.signage.models.SignageDevice and is still bound one-to-one with Classroom.

Important fields:

  • device_code

  • device_label

  • signage_title

  • signage_subtitle

  • signage_config

  • device_token_hash

  • token_issued_at

  • activated_at

  • last_seen_at

  • activation_code_hash

  • activation_code_expires_at

Important

The backend stores only token hashes and activation-code hashes. The Android device stores the plaintext device token locally after activation.

Important behavior changes

The following changes are especially important when reading older docs or upgrading existing signage deployments:

  • The signage runtime is native Kotlin, not WebView.

  • The Android signage no longer uses a hard-coded classroom ID.

  • The Android signage no longer uses anonymous classroom-scoped signage APIs.

  • /api/v1/signage/classrooms/{id}, /schedule and /config are management endpoints and should not be called by signage devices directly.

  • The signage app authenticates with a device token sent in the Authorization: Bearer ... header.

  • Management-side QR generation uses one-time activation codes.

  • Revoking a signage token immediately invalidates the bound device.

  • The current signage app does not perform sign-in, face recognition, or face-service upload.

Building and running

Prerequisites

  • Android Studio, or Android SDK + command-line tools

  • A compatible JDK for the Android Gradle Plugin in use

This project uses a Gradle Version Catalog; see signage/gradle/libs.versions.toml.

Build (Windows)

From the repo root:

cd signage
.\\gradlew.bat :app:assembleDebug

Build (Linux)

From the repo root:

cd signage
./gradlew :app:assembleDebug

Note

If you get a permission error, run chmod +x ./gradlew first.

The debug APK is typically generated under:

signage/app/build/outputs/apk/debug/

Install on a device

If adb is available:

adb install -r signage/app/build/outputs/apk/debug/app-debug.apk

Operational notes

  • BuildConfig.SIGNAGE_BASE_URL is the default API base URL used before the device is bound.

  • The app may receive a different base_url from the scanned QR payload and persist it locally for subsequent requests.

  • android.permission.CAMERA is required for initial device binding.

  • android.permission.INTERNET is required for backend communication.

  • android:allowBackup is disabled in the current manifest.

  • android:usesCleartextTraffic is still enabled in the current manifest for local development convenience. Production deployments should use HTTPS and review whether cleartext traffic can be disabled.