Android App启动与权限管控

1.1 自动启动机制

功能说明

Android 系统开机完成后,自动启动具微遥控应用并进入主界面。

技术实现

方案一:BroadcastReceiver 监听开机广播(推荐)

kotlin 复制代码
// AutoStartReceiver.kt
class AutoStartReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
            // 启动应用主 Activity
            val starterIntent = Intent(context, MainActivity::class.java).apply {
                flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP
            }
            context.startActivity(starterIntent)
            
            // 启动锁定服务
            val serviceIntent = Intent(context, LockTaskService::class.java)
            context.startForegroundService(serviceIntent)
        }
    }
}

AndroidManifest.xml 配置

xml 复制代码
<receiver
    android:name=".receivers.AutoStartReceiver"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
        <action android:name="android.intent.action.QUICKBOOT_POWERON" />
        <!-- 华为设备 -->
        <action android:name="com.huawei.android.intent.action.BOOT" />
    </intent-filter>
</receiver>

<!-- 权限声明 -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.START_FOREGROUND_SERVICES" />

方案二:Launcher Activity 设置(辅助)

xml 复制代码
<!-- 将 MainActivity 设为 launcher,确保从桌面启动也能直接进入 -->
<activity
    android:name=".MainActivity"
    android:exported="true"
    android:launchMode="singleTask">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

启动时序要求

阶段 时间要求 实现要点
系统开机 → BroadcastReceiver 触发 < 5s 优先级高的 BroadcastReceiver
BroadcastReceiver → Activity 启动 < 2s 前台服务辅助启动
Activity 启动 → Lock Task 进入 < 1s Activity 创建后立即调用 startLockTask()
总启动时间 ≤ 10s 需要实测验证

2.2 Kiosk 模式(Lock Task Mode)

功能说明

进入无界应用模式,禁止用户退出到系统桌面、切换应用或使用系统导航。

技术实现

LockTaskManager.kt

kotlin 复制代码
class LockTaskManager(private val activity: MainActivity) {
    
    private var isLockTaskEnabled = false
    
    /**
     * 进入 Kiosk 模式
     * 使用 Activity.startLockTask() 进入无界应用模式
     */
    fun enterKioskMode() {
        if (!isLockTaskEnabled) {
            activity.startLockTask()
            isLockTaskEnabled = true
            Log.d(TAG, "进入 Kiosk 模式")
        }
    }
    
    /**
     * 退出 Kiosk 模式(仅工程模式可用)
     */
    fun exitKioskMode(authorization: Authorization): Boolean {
        if (!isLockTaskEnabled) return false
        
        if (!authorization.isValid) {
            Log.w(TAG, "退出 Kiosk 模式授权失败")
            return false
        }
        
        activity.stopLockTask()
        isLockTaskEnabled = false
        Log.d(TAG, "退出 Kiosk 模式")
        return true
    }
    
    /**
     * 检查是否处于锁定模式
     */
    fun isLocked(): Boolean = isLockTaskEnabled
    
    companion object {
        private const val TAG = "LockTaskManager"
    }
}

MainActivity.kt 集成

kotlin 复制代码
class MainActivity : AppCompatActivity() {
    
    private lateinit var lockTaskManager: LockTaskManager
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // 1. 设置全屏模式
        setupFullScreen()
        
        // 2. 初始化锁定管理器
        lockTaskManager = LockTaskManager(this)
        
        // 3. 进入 Kiosk 模式
        lockTaskManager.enterKioskMode()
        
        // 4. 注册生命周期回调
        registerLifecycleCallbacks()
    }
    
    /**
     * 设置全屏模式
     */
    private fun setupFullScreen() {
        // 隐藏状态栏和导航栏
        WindowCompat.setDecorFitsSystemWindows(window, false)
        WindowInsetsControllerCompat(window, window.decorView).let { controller ->
            controller.hide(WindowInsetsCompat.Type.statusBars() or 
                           WindowInsetsCompat.Type.navigationBars())
            controller.systemBarsBehavior = 
                WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
        }
        
        // 禁止截屏(保护敏感信息)
        window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
    }
    
    /**
     * 注册生命周期回调,防止应用被切换到后台
     */
    private fun registerLifecycleCallbacks() {
        registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
            override fun onActivityPaused(activity: Activity) {
                // 应用进入后台时,立即重新进入前台
                if (activity is MainActivity && lockTaskManager.isLocked()) {
                    // 延迟一点重新进入,避免循环
                    Handler(Looper.getMainLooper()).postDelayed({
                        if (lockTaskManager.isLocked()) {
                            reenterForeground()
                        }
                    }, 500)
                }
            }
            
            // 其他回调...
        })
    }
    
    /**
     * 重新进入前台
     */
    private fun reenterForeground() {
        val intent = Intent(this, MainActivity::class.java).apply {
            flags = Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
        }
        startActivity(intent)
        lockTaskManager.enterKioskMode()
    }
    
    override fun onResume() {
        super.onResume()
        // 确保每次回到前台都重新进入锁定模式
        lockTaskManager.enterKioskMode()
    }
}

Lock Task 白名单配置

kotlin 复制代码
// LockTaskWhitelist.kt
class LockTaskWhitelist(private val context: Context) {
    
    // 允许在锁定模式下启动的系统组件
    private val whitelist: List<String> = listOf(
        // 系统设置(工程模式可用)
        "com.android.settings/.Settings",
        // 电话应用(紧急呼叫)
        "com.android.phone/.InCallScreen",
        // 电源管理
        "com.android.systemui/.GlobalActions"
    )
    
    fun isAllowed(componentName: String): Boolean {
        return whitelist.any { it in componentName }
    }
}

2.3 防退出机制

功能说明

禁止用户通过 Home 键、任务切换键、多任务界面等退出到系统桌面。

技术实现

onBackPressedDispatcher 拦截

kotlin 复制代码
class MainActivity : AppCompatActivity() {
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // 拦截返回键
        onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
            override fun handleOnBackPressed() {
                // 在 Kiosk 模式下,返回键无效
                if (lockTaskManager.isLocked()) {
                    // 可选:提供震动反馈
                    vibrate(50)
                } else {
                    isEnabled = false
                    onBackPressedDispatcher.onBackPressed()
                    isEnabled = true
                }
            }
        })
    }
    
    /**
     * 屏蔽 Home 键(Android 10+ 需要额外处理)
     */
    override fun onUserLeaveHint() {
        super.onUserLeaveHint()
        // 用户尝试按 Home 键时触发
        if (lockTaskManager.isLocked()) {
            // 重新进入前台
            reenterForeground()
        }
    }
    
    /**
     * 屏蔽最近任务键
     */
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        // 应用从外部返回时,重新进入锁定模式
        if (lockTaskManager.isLocked()) {
            lockTaskManager.enterKioskMode()
        }
    }
}

Home 键屏蔽(Android 5.0+)

kotlin 复制代码
/**
 * 方法1: 使用 setHomeButtonEnabled(false) - 仅适用于 ActionBar
 */
supportActionBar?.setHomeButtonEnabled(false)

/**
 * 方法2: 使用 Flags(适用于所有版本)
 */
window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
                WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)

Android 10+ 特殊处理

kotlin 复制代码
/**
 * Android 10+ 需要注册 Home 键监听
 */
class HomeKeyInterceptor(private val context: Context) {
    
    private var recentAppsListener: RecentAppsListener? = null
    
    fun startIntercept() {
        // 使用 AccessibilityService 监听任务切换
        recentAppsListener = RecentAppsListener(context).apply {
            startListening()
        }
    }
    
    fun stopIntercept() {
        recentAppsListener?.stopListening()
    }
}

/**
 * AccessibilityService 实现任务切换拦截
 */
class RecentAppsListener(context: Context) : AccessibilityService() {
    
    override fun onAccessibilityEvent(event: AccessibilityEvent?) {
        when (event?.eventType) {
            AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED -> {
                // 检测到应用切换,重新回到本应用
                val pkg = event.packageName?.toString()
                if (pkg != context.packageName) {
                    returnToApp()
                }
            }
        }
    }
    
    private fun returnToApp() {
        val intent = Intent(context, MainActivity::class.java).apply {
            flags = Intent.FLAG_ACTIVITY_REORDER_TO_FRONT or 
                    Intent.FLAG_ACTIVITY_NEW_TASK
        }
        context.startActivity(intent)
    }
    
    override fun onInterrupt() {}
    
    companion object {
        const val SERVICE_ID = "com.jvewit.p3s.remote.recentapps"
    }
}

AccessibilityService 配置

xml 复制代码
<!-- res/xml/accessibility_service_config.xml -->
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeWindowStateChanged"
    android:accessibilityFeedbackType="feedbackAllMask"
    android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows"
    android:canRetrieveWindowContent="true"
    android:description="@string/accessibility_service_description" />
xml 复制代码
<!-- AndroidManifest.xml -->
<service
    android:name=".services.RecentAppsListener"
    android:exported="true"
    android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
    <intent-filter>
        <action android:name="android.accessibilityservice.AccessibilityService" />
    </intent-filter>
    <meta-data
        android:name="android.accessibilityservice"
        android:resource="@xml/accessibility_service_config" />
</service>

2.4 工程模式保护

功能说明

工程模式需密码或数字证书才能进入,防止未经授权的系统配置修改。

技术实现

工程模式入口

kotlin 复制代码
/**
 * 工程模式激活方式(多种方式)
 */
class EngineeringModeManager(private val context: Context) {
    
    companion object {
        // 工程模式入口标识
        const val ENGINEERING_MODE_ACTION = "com.jvewit.p3s.ENGINEERING_MODE"
        
        // 激活码(SHA-256 存储)
        private const val DEFAULT_PASSWORD_HASH = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
    }
    
    /**
     * 方式1: 密码验证
     */
    fun enterByPassword(password: String): Boolean {
        val inputHash = sha256(password)
        val storedHash = getStoredPasswordHash()
        return inputHash == storedHash
    }
    
    /**
     * 方式2: 证书验证
     */
    fun enterByCertificate(cert: X509Certificate): Boolean {
        return cert.verify(context.applicationInfo.publicKey) &&
               isCertValid(cert)
    }
    
    /**
     * 方式3: 特定按键序列(备用)
     */
    fun enterByKeypadSequence(sequence: List<String>): Boolean {
        return sequence == getExpectedSequence()
    }
    
    /**
     * 方式4: 远程授权码(需联网)
     */
    suspend fun enterByRemoteCode(code: String): Boolean {
        return withContext(Dispatchers.IO) {
            val response = api.verifyEngineeringCode(code)
            response.isSuccess
        }
    }
}

密码管理

kotlin 复制代码
/**
 * 工程模式密码管理器
 */
class PasswordManager(private val context: Context) {
    
    private val prefs = EncryptedSharedPreferences.create(
        "engineering_prefs",
        MasterKey.Builder(context)
            .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
            .build(),
        context,
        EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
        EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
    )
    
    /**
     * 存储密码哈希
     */
    fun storePasswordHash(hash: String) {
        prefs.edit()
            .putString(PREF_PASSWORD_HASH, hash)
            .apply()
    }
    
    /**
     * 获取存储的密码哈希
     */
    fun getStoredPasswordHash(): String? {
        return prefs.getString(PREF_PASSWORD_HASH, null)
    }
    
    /**
     * 清除密码(恢复出厂设置时)
     */
    fun clearPassword() {
        prefs.edit()
            .remove(PREF_PASSWORD_HASH)
            .apply()
    }
    
    companion object {
        private const val PREF_PASSWORD_HASH = "engineering_password_hash"
    }
}

证书验证

kotlin 复制代码
/**
 * 证书验证器
 */
class CertificateValidator(private val context: Context) {
    
    /**
     * 验证客户端证书
     */
    fun validateClientCertificate(certBytes: ByteArray): Boolean {
        return try {
            val certFactory = CertificateFactory.getInstance("X.509")
            val cert = certFactory.generateCertificate(
                ByteArrayInputStream(certBytes)
            ) as X509Certificate
            
            // 验证证书有效期
            cert.checkValidity()
            
            // 验证证书指纹
            val fingerprint = calculateFingerprint(cert)
            fingerprint == getExpectedFingerprint()
            
        } catch (e: Exception) {
            Log.e(TAG, "证书验证失败: ${e.message}")
            false
        }
    }
    
    /**
     * 计算证书指纹(SHA-256)
     */
    fun calculateFingerprint(cert: X509Certificate): String {
        val md = MessageDigest.getInstance("SHA-256")
        val digest = md.digest(cert.encoded)
        return digest.joinToString(":") { "%02x".format(it) }
    }
    
    /**
     * 获取期望的证书指纹(硬编码或从服务器获取)
     */
    fun getExpectedFingerprint(): String {
        // 生产环境应从安全服务器获取
        return BuildConfig.ENGINEERING_CERT_FINGERPRINT
    }
    
    companion object {
        private const val TAG = "CertificateValidator"
    }
}

工程模式 Activity

kotlin 复制代码
/**
 * 工程模式入口 Activity
 */
class EngineeringModeActivity : AppCompatActivity() {
    
    private lateinit var passwordManager: PasswordManager
    private lateinit var certValidator: CertificateValidator
    private lateinit var lockTaskManager: LockTaskManager
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        passwordManager = PasswordManager(this)
        certValidator = CertificateValidator(this)
        lockTaskManager = LockTaskManager(this)
        
        // 显示验证界面
        setContentView(R.layout.activity_engineering_mode)
        
        setupVerificationMethods()
    }
    
    /**
     * 设置验证方式
     */
    private fun setupVerificationMethods() {
        // 密码验证
        findViewById<Button>(R.id.btn_password).setOnClickListener {
            val password = findViewById<EditText>(R.id.et_password).text.toString()
            if (passwordManager.enterByPassword(password)) {
                onVerificationSuccess()
            } else {
                onVerificationFailed()
            }
        }
        
        // 证书验证
        findViewById<Button>(R.id.btn_certificate).setOnClickListener {
            val certBytes = readCertificateFromIntent()
            if (certValidator.validateClientCertificate(certBytes)) {
                onVerificationSuccess()
            } else {
                onVerificationFailed()
            }
        }
        
        // 按键序列验证(备用)
        findViewById<Button>(R.id.btn_keypad).setOnClickListener {
            showKeypadDialog()
        }
    }
    
    /**
     * 验证成功
     */
    private fun onVerificationSuccess() {
        // 退出 Kiosk 模式
        lockTaskManager.exitKioskMode(Authorization.SUCCESS)
        
        // 启动工程模式主界面
        val intent = Intent(this, EngineeringDashboardActivity::class.java)
        startActivity(intent)
        finish()
    }
    
    /**
     * 验证失败
     */
    private fun onVerificationFailed() {
        // 记录失败日志
        logFailedAttempt()
        
        // 显示错误提示
        Snackbar.make(findViewById(R.id.root_layout), 
            "验证失败,请重试", Snackbar.LENGTH_SHORT).show()
        
        // 尝试次数过多时锁定
        if (getFailedAttempts() >= MAX_FAILED_ATTEMPTS) {
            lockoutForDuration()
        }
    }
}

安全策略

kotlin 复制代码
/**
 * 工程模式安全策略
 */
class EngineeringSecurityPolicy {
    
    // 最大失败尝试次数
    const val MAX_FAILED_ATTEMPTS = 5
    
    // 锁定持续时间(毫秒)
    const val LOCKOUT_DURATION_MS = 30_000L  // 30秒
    
    // 密码最小长度
    const val MIN_PASSWORD_LENGTH = 6
    
    // 密码最大长度
    const val MAX_PASSWORD_LENGTH = 32
    
    /**
     * 验证密码强度
     */
    fun validatePasswordStrength(password: String): PasswordStrength {
        return when {
            password.length < MIN_PASSWORD_LENGTH -> PasswordStrength.WEAK
            password.length >= MAX_PASSWORD_LENGTH -> PasswordStrength.STRONG
            password.any { it.isDigit() } && password.any { it.isLetter() } -> PasswordStrength.MEDIUM
            else -> PasswordStrength.WEAK
        }
    }
    
    /**
     * 计算失败尝试次数
     */
    fun getFailedAttempts(context: Context): Int {
        val prefs = context.getSharedPreferences("security_prefs", Context.MODE_PRIVATE)
        return prefs.getInt("failed_attempts", 0)
    }
    
    /**
     * 增加失败尝试次数
     */
    fun incrementFailedAttempts(context: Context) {
        val prefs = context.getSharedPreferences("security_prefs", Context.MODE_PRIVATE)
        prefs.edit()
            .putInt("failed_attempts", getFailedAttempts(context) + 1)
            .apply()
    }
    
    /**
     * 重置失败尝试次数
     */
    fun resetFailedAttempts(context: Context) {
        val prefs = context.getSharedPreferences("security_prefs", Context.MODE_PRIVATE)
        prefs.edit()
            .remove("failed_attempts")
            .remove("lockout_until")
            .apply()
    }
    
    /**
     * 检查是否处于锁定状态
     */
    fun isLockedOut(context: Context): Boolean {
        val prefs = context.getSharedPreferences("security_prefs", Context.MODE_PRIVATE)
        val lockoutUntil = prefs.getLong("lockout_until", 0L)
        return lockoutUntil > System.currentTimeMillis()
    }
    
    /**
     * 设置锁定状态
     */
    fun setLockedOut(context: Context, durationMs: Long = LOCKOUT_DURATION_MS) {
        val prefs = context.getSharedPreferences("security_prefs", Context.MODE_PRIVATE)
        prefs.edit()
            .putLong("lockout_until", System.currentTimeMillis() + durationMs)
            .apply()
    }
}

3. 架构图

lua 复制代码
+---------------------------------------------------------------------+
│                        启动与权限管控架构                             │
+---------------------------------------------------------------------+
│                                                                     │
│  +------------------+    +------------------+    +----------------+  │
│  │  系统开机广播     │───>│  AutoStartReceiver│───>│  MainActivity│  │
│  │  BOOT_COMPLETED  │    │                  │    │              │  │
│  +------------------+    +------------------+    +-------+--------+  │
│                                                         │         │
│                                                         v         │
│  +----------------------------------------------------------------+ │
│  │                    LockTaskManager                             │ │
│  │  +-------------+  +-------------+  +----------------------+    │ │
│  │  │ enterKiosk  │  │  isLocked   │  │  exitKiosk           │    │ │
│  │  │  (进入锁定)  │  │  (检查状态)  │  │  (需授权)            │    │ │
│  │  +-------------+  +-------------+  +----------------------+    │ │
│  +----------------------------------------------------------------+ │
│                            │                                      │
│              +-------------+-------------+                       │
│              v             v             v                       │
│  +--------------+  +--------------+  +--------------+            │
│  │ 防退出机制    │  │ 工程模式保护  │  │ 安全策略      │            │
│  │              │  │              │  │              │            │
│  │ + Home键拦截  │  │ + 密码验证    │  │ + 失败计数    │            │
│  │ + 返回键拦截  │  │ + 证书验证    │  │ + 锁定策略    │            │
│  │ + 任务切换拦截│  │ + 按键序列    │  │ + 密码强度    │            │
│  │ + 后台恢复    │  │ + 远程授权    │  │ + 审计日志    │            │
│  +--------------+  +--------------+  +--------------+            │
│                                                                     │
+---------------------------------------------------------------------+

4. 安全设计要点

4.1 多层防护

层级 防护措施 实现方式
系统层 Lock Task Mode Android 原生 API
应用层 Activity 生命周期管理 registerActivityLifecycleCallbacks
服务层 Foreground Service 保持应用前台运行
辅助功能 Accessibility Service 拦截任务切换
安全层 密码/证书验证 EncryptedSharedPreferences + KeyStore

4.2 防御策略

arduino 复制代码
攻击场景                    防御措施
─────────────────────────────────────────────────────
用户按 Home 键退出          Lock Task + 后台恢复
用户切换应用               Accessibility Service 拦截
用户尝试退出 Kiosk          需要工程模式授权
暴力破解密码               失败锁定 + 指数退避
篡改应用配置               EncryptedSharedPreferences
窃取敏感数据               FLAG_SECURE 防截屏

5. 测试用例

5.1 功能测试

用例ID 测试项 测试步骤 预期结果
T-APP-001 开机自动启动 重启设备,等待系统启动完成 10s 内进入具微遥控应用
T-APP-002 Home键拦截 应用运行中按 Home 键 应用立即回到前台,无法进入桌面
T-APP-003 返回键拦截 应用运行中按返回键 无响应,应用保持前台
T-APP-004 任务切换拦截 打开最近任务列表 无法切换到其他应用
T-APP-005 工程模式-密码 输入正确密码 成功进入工程模式
T-APP-006 工程模式-密码错误 输入错误密码 提示失败,记录尝试次数
T-APP-007 工程模式-证书 导入有效证书 成功进入工程模式
T-APP-008 工程模式-锁定 连续5次密码错误 锁定30秒
T-APP-009 后台恢复 强制杀死应用 5s 内自动重启并恢复锁定
T-APP-010 截屏保护 尝试截屏 截屏失败或显示黑屏

5.2 性能测试

用例ID 测试项 测试条件 通过标准
T-APP-P01 启动时间 冷启动 ≤10s 进入操控界面
T-APP-P02 内存占用 应用运行中 ≤200MB
T-APP-P03 CPU占用 待机状态 ≤5%
T-APP-P04 启动频率 连续重启10次 100% 成功启动

6. 附录:关键代码片段

6.1 完整 MainActivity

kotlin 复制代码
class MainActivity : AppCompatActivity() {
    
    private lateinit var lockTaskManager: LockTaskManager
    private lateinit var securityPolicy: EngineeringSecurityPolicy
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // 1. 设置窗口属性
        setupWindow()
        
        // 2. 初始化组件
        lockTaskManager = LockTaskManager(this)
        securityPolicy = EngineeringSecurityPolicy()
        
        // 3. 检查是否需要进入工程模式
        if (intent.action == EngineeringModeManager.ENGINEERING_MODE_ACTION) {
            startActivity(Intent(this, EngineeringModeActivity::class.java))
            finish()
            return
        }
        
        // 4. 设置布局
        setContentView(R.layout.activity_main)
        
        // 5. 进入 Kiosk 模式
        lockTaskManager.enterKioskMode()
        
        // 6. 注册生命周期回调
        registerLifecycleCallbacks()
        
        // 7. 启动必要服务
        startRequiredServices()
    }
    
    private fun setupWindow() {
        // 全屏模式
        WindowCompat.setDecorFitsSystemWindows(window, false)
        WindowInsetsControllerCompat(window, window.decorView).let { controller ->
            controller.hide(WindowInsetsCompat.Type.statusBars() or 
                           WindowInsetsCompat.Type.navigationBars())
        }
        
        // 禁止截屏
        window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
    }
    
    private fun registerLifecycleCallbacks() {
        registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
            override fun onActivityPaused(activity: Activity) {
                if (activity is MainActivity && lockTaskManager.isLocked()) {
                    Handler(Looper.getMainLooper()).postDelayed({
                        if (lockTaskManager.isLocked()) {
                            reenterForeground()
                        }
                    }, 500)
                }
            }
            
            override fun onActivityResumed(activity: Activity) {
                if (activity is MainActivity) {
                    lockTaskManager.enterKioskMode()
                }
            }
            
            // 其他回调...
        })
    }
    
    private fun reenterForeground() {
        val intent = Intent(this, MainActivity::class.java).apply {
            flags = Intent.FLAG_ACTIVITY_REORDER_TO_FRONT or 
                    Intent.FLAG_ACTIVITY_NEW_TASK
        }
        startActivity(intent)
    }
    
    override fun onBackPressed() {
        // 在 Kiosk 模式下禁用返回键
        if (!lockTaskManager.isLocked()) {
            super.onBackPressed()
        }
    }
    
    override fun onUserLeaveHint() {
        // Home 键被按下
        if (lockTaskManager.isLocked()) {
            reenterForeground()
        }
    }
    
    private fun startRequiredServices() {
        // 启动前台服务保持应用活跃
        val serviceIntent = Intent(this, KeepAliveService::class.java)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            startForegroundService(serviceIntent)
        } else {
            startService(serviceIntent)
        }
    }
}

6.2 KeepAliveService

kotlin 复制代码
class KeepAliveService : Service() {
    
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // 创建通知(前台服务必需)
        val notification = createNotification()
        startForeground(NOTIFICATION_ID, notification)
        
        // 定期检查应用状态
        scheduleHealthCheck()
        
        return START_STICKY
    }
    
    private fun scheduleHealthCheck() {
        val handler = Handler(Looper.getMainLooper())
        handler.postDelayed(object : Runnable {
            override fun run() {
                // 检查应用是否在前台
                if (!isAppInForeground()) {
                    bringAppToForeground()
                }
                // 定时检查
                handler.postDelayed(this, CHECK_INTERVAL_MS)
            }
        }, CHECK_INTERVAL_MS)
    }
    
    private fun isAppInForeground(): Boolean {
        val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
        val appProcesses = activityManager.runningAppProcesses
        for (proc in appProcesses) {
            if (proc.importance == ImportanceForeground && 
                proc.processName == packageName) {
                return true
            }
        }
        return false
    }
    
    private fun bringAppToForeground() {
        val intent = Intent(this, MainActivity::class.java).apply {
            flags = Intent.FLAG_ACTIVITY_REORDER_TO_FRONT or 
                    Intent.FLAG_ACTIVITY_NEW_TASK
        }
        startActivity(intent)
    }
    
    override fun onBind(intent: Intent?): IBinder? = null
    
    companion object {
        private const val NOTIFICATION_ID = 1001
        private const val CHECK_INTERVAL_MS = 5000L
    }
}

7. 总结

需求 实现方案 关键API/技术
自动启动 BroadcastReceiver + 前台服务 BOOT_COMPLETED
Kiosk模式 Lock Task Mode startLockTask()/stopLockTask()
防退出 Activity生命周期管理 registerActivityLifecycleCallbacks
工程模式 多因素认证 EncryptedSharedPreferences + KeyStore
安全防护 多层防御 FLAG_SECURE + AccessibilityService

本方案满足 PRD-APP-001 所有要求:

  • ✅ 开机自动进入应用
  • ✅ 无授权不得退出到系统桌面
  • ✅ 工程模式需密码或证书
相关推荐
嘟哩DuliDuli1 小时前
AI 短剧生成为什么要有角色库、场景库和镜头卡
android·人工智能·安全·ai·软件工程
NINO3 小时前
深度解析 Android ANR:从标准分析流程到 NativePollOnce 疑难破局
android
阿巴斯甜3 小时前
AppFunctions 完整介绍
android
蜡台3 小时前
Jetpack Compose 从入门到精通
android·前端·kotlin·compose·jepack
toooooop84 小时前
踩坑:PHP7.2导出Excel正常,升级7.3文件损坏需修复
android·excel
mmsx4 小时前
我明明调用了 zoomToBounds,地图却总是停在别处?延迟加到 5 秒也没用,真相只有一个
android·人工智能·bug·地图
蜡台6 小时前
Jetpack Compose 核心:状态管理 + Lazy 列表
android·kotlin·state·jetpack compose
Escalating_xu7 小时前
【Linux线程同步】从数据竞争到 mutex、条件变量与生产者消费者(上篇)
android·java·linux
00后程序员张7 小时前
Windows / Linux / Mac 上不用 Xcode 把 IPA 上传到 App Store,upload 命令详解
android·ios·小程序·https·uni-app·iphone·webview