package com.YuyeTech.HeartRate; import android.app.Activity; import android.app.Application; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Build; import android.os.Bundle; import android.provider.Settings; import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import java.util.List; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.ble.mylockview.BuildConfig; import com.ble.mylockview.admin.KioskManager; import com.ble.mylockview.config.LaunchConfig; import io.dcloud.PandoraEntry; import io.dcloud.PandoraEntryActivity; import io.dcloud.application.DCloudApplication; import io.dcloud.uniplugin.NativeLogBridgeHelper; /** * 自定义 Application,实现 Kiosk 模式自动初始化 * Created on 2026/1/22. */ public class MyApplication extends DCloudApplication { private static final String TAG = "MyApplication"; // 屏幕状态广播接收器 private ScreenStateReceiver screenStateReceiver; @Override public void onCreate() { super.onCreate(); NativeLogBridgeHelper.init(this); if (BuildConfig.DEBUG) Log.d(TAG, "Application 初始化"); // ✅ 尽早禁用锁屏(Device Owner):避免开机自启时仍在锁屏阶段导致 IME 无法显示/被遮挡 KioskHelper.disableKeyguardIfOwner(this); // 启动时重置 WiFi 检测开关,避免上一次运行遗留为 true 导致一进来就弹框 try { getSharedPreferences("kiosk_prefs", Context.MODE_PRIVATE) .edit() .putBoolean("wifi_check_enabled", false) .apply(); if (BuildConfig.DEBUG) Log.d(TAG, "已在 Application 启动时将 wifi_check_enabled 重置为 false"); } catch (Exception e) { Log.e(TAG, "重置 wifi_check_enabled 失败", e); } // ✅ 修复:首次安装后自启动时输入法无法调起的问题 // 在应用启动时提前初始化输入法服务,确保输入法服务已就绪 initializeInputMethodService(); // ✅ 配置 Kiosk 启动 Activity - 直接启动主页面,跳过黑屏的 PandoraEntry LaunchConfig.setLaunchActivity(PandoraEntryActivity.class); // ✅ 启用 Debug 模式(发布时改为 false) // KioskManager.setDebug(true); KioskManager.setDebug(BuildConfig.DEBUG); // ✅ 注册屏幕状态广播接收器(监听电源键) registerScreenStateReceiver(); // ✅ 注册 Activity 生命周期监听,自动绑定 KioskManager registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() { @Override public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) { // 只对主 Activity 进行 Kiosk 绑定 if (activity instanceof io.dcloud.PandoraEntry || activity instanceof io.dcloud.PandoraEntryActivity) { if (BuildConfig.DEBUG) Log.d(TAG, "✅ 绑定 Kiosk 到: " + activity.getClass().getSimpleName()); // ✅ 先禁用锁屏(必须在 attach 之前) KioskHelper.disableLockScreen(activity); // 临时方案:不常亮,由系统休眠时间(如 50 分钟)控制;需在 attach 之前设置 KioskManager.setAllowKeepScreenOn(false); //KioskManager.setDebug(BuildConfig.DEBUG); KioskManager.attach(activity); //KioskHelper.clearKeepScreenOn(activity); PowerSavingManager.ensureWakeLockReleased(); // ✅ 拦截返回键,防止回到黑屏页面 interceptBackPress(activity); // ✅ 设置当前 Activity 到 PowerSavingManager(用于省电模式) PowerSavingManager.setCurrentActivity(activity); // ✅ WiFi 检测:仍在 Activity 创建时注册延迟检测任务 // 但实际是否执行检测由 DeviceStatusModule.startNetworkMonitor 设置的开关控制 if (BuildConfig.DEBUG) Log.d(TAG, "🔍 Activity 创建完成,注册 WiFi 启动检测任务: " + activity.getClass().getSimpleName()); WifiCheckHelper.checkWifiOnActivityStart(activity); } } @Override public void onActivityStarted(@NonNull Activity activity) {} @Override public void onActivityResumed(@NonNull Activity activity) { if (activity instanceof io.dcloud.PandoraEntry || activity instanceof io.dcloud.PandoraEntryActivity) { KioskManager.onResume(); // 临时方案:确保未加常亮(若 KioskManager.setAllowKeepScreenOn(false) 已设则此处为双保险) //KioskHelper.clearKeepScreenOn(activity); // ✅ 更新 PowerSavingManager 的 Activity 引用 PowerSavingManager.setCurrentActivity(activity); // ✅ 在 onResume 时检测 WiFi(每次恢复都检测,包括从设置返回、开机启动等所有情况) if (BuildConfig.DEBUG) Log.d(TAG, "📱 " + activity.getClass().getSimpleName() + " onResume → 开始 WiFi 检测"); WifiCheckHelper.checkWifiOnActivityResume(activity); } } @Override public void onActivityPaused(@NonNull Activity activity) { if (activity instanceof io.dcloud.PandoraEntry || activity instanceof io.dcloud.PandoraEntryActivity) { KioskManager.onPause(); } } @Override public void onActivityStopped(@NonNull Activity activity) {} @Override public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {} @Override public void onActivityDestroyed(@NonNull Activity activity) { if (activity instanceof io.dcloud.PandoraEntry || activity instanceof io.dcloud.PandoraEntryActivity) { KioskManager.onDestroy(); // 清理 WiFi 检测资源 WifiCheckHelper.cleanup(activity); // 清理 PowerSavingManager 的 Activity 引用 PowerSavingManager.setCurrentActivity(null); } } }); } /** * 初始化输入法服务 * 通过多种方式触发输入法服务的初始化,确保输入法服务已就绪 * 特别针对搜狗输入法等预装输入法进行优化 * 解决首次安装后自启动时输入法无法调起的问题 */ private void initializeInputMethodService() { try { InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); if (imm != null) { // 方法1: 获取输入法列表,触发系统初始化输入法服务 List allInputMethods = imm.getInputMethodList(); if (BuildConfig.DEBUG) Log.d(TAG, "所有输入法数量: " + (allInputMethods != null ? allInputMethods.size() : 0)); // 方法2: 获取启用的输入法列表(特别重要,确保搜狗输入法已启用) try { List enabledInputMethods = imm.getEnabledInputMethodList(); if (enabledInputMethods != null) { if (BuildConfig.DEBUG) Log.d(TAG, "已启用的输入法数量: " + enabledInputMethods.size()); // 检查搜狗输入法是否在已启用列表中 boolean hasSogou = false; for (android.view.inputmethod.InputMethodInfo info : enabledInputMethods) { String packageName = info.getPackageName(); String serviceName = info.getServiceName(); if (BuildConfig.DEBUG) Log.d(TAG, "已启用输入法: " + packageName + " / " + serviceName); // 检查是否是搜狗输入法(搜狗输入法的包名通常是 com.sohu.inputmethod.sogou) if (packageName != null && packageName.contains("sogou")) { hasSogou = true; if (BuildConfig.DEBUG) Log.d(TAG, "✅ 检测到搜狗输入法已启用: " + packageName); } } if (!hasSogou && allInputMethods != null) { // 检查搜狗输入法是否在全部列表中但未启用 for (android.view.inputmethod.InputMethodInfo info : allInputMethods) { String packageName = info.getPackageName(); if (packageName != null && packageName.contains("sogou")) { Log.w(TAG, "⚠️ 搜狗输入法存在但未启用: " + packageName); } } } } } catch (Exception e) { Log.w(TAG, "获取已启用输入法列表失败", e); } // 方法3: 获取当前输入法信息,进一步触发初始化 try { String currentInputMethodId = Settings.Secure.getString( getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); if (currentInputMethodId != null) { if (BuildConfig.DEBUG) Log.d(TAG, "当前默认输入法: " + currentInputMethodId); if (currentInputMethodId.contains("sogou")) { if (BuildConfig.DEBUG) Log.d(TAG, "✅ 当前默认输入法是搜狗输入法"); } } imm.getCurrentInputMethodSubtype(); } catch (Exception e) { // Android 某些版本可能不支持,忽略 Log.d(TAG, "获取当前输入法信息失败(可能不支持)", e); } // 方法4: 尝试获取输入法服务状态,进一步触发初始化 // 注意:该方法在部分 Android 版本/ROM 上可能不存在(会抛 NoSuchMethodError), // 因此这里用反射探测存在性并吞掉所有 Throwable,避免在 Application.onCreate 阶段致命崩溃。 try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { java.lang.reflect.Method m = InputMethodManager.class.getMethod("isInputMethodSuppressingSpellChecker"); Object r = m.invoke(imm); if (BuildConfig.DEBUG) Log.d(TAG, "IME spellChecker suppress: " + r); } } catch (Throwable t) { if (BuildConfig.DEBUG) Log.d(TAG, "IME 初始化探测方法不可用,已忽略", t); } if (BuildConfig.DEBUG) Log.d(TAG, "✅ 输入法服务已初始化(Application onCreate,已检查搜狗输入法)"); } else { Log.w(TAG, "⚠️ 无法获取InputMethodManager"); } } catch (Throwable t) { Log.w(TAG, "⚠️ 初始化输入法服务失败", t); } } /** * 注册屏幕状态广播接收器 * 监听 ACTION_SCREEN_OFF 和 ACTION_SCREEN_ON 来实现电源键省电功能 */ private void registerScreenStateReceiver() { try { screenStateReceiver = new ScreenStateReceiver(); IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(Intent.ACTION_SCREEN_ON); filter.setPriority(1000); // 高优先级,确保能及时收到广播 registerReceiver(screenStateReceiver, filter); if (BuildConfig.DEBUG) Log.d(TAG, "✅ 屏幕状态广播接收器已注册"); } catch (Exception e) { Log.e(TAG, "注册屏幕状态广播接收器失败", e); } } /** * 注销屏幕状态广播接收器 */ private void unregisterScreenStateReceiver() { try { if (screenStateReceiver != null) { unregisterReceiver(screenStateReceiver); screenStateReceiver = null; if (BuildConfig.DEBUG) Log.d(TAG, "✅ 屏幕状态广播接收器已注销"); } } catch (Exception e) { Log.e(TAG, "注销屏幕状态广播接收器失败", e); } } @Override public void onTerminate() { super.onTerminate(); // 注销广播接收器 unregisterScreenStateReceiver(); } /** * 拦截 Activity 的返回键事件 * 使用多种方式确保返回键被拦截 * @param activity Activity 实例 */ private void interceptBackPress(Activity activity) { try { // 方法 1:使用 View.OnKeyListener activity.getWindow().getDecorView().setOnKeyListener((v, keyCode, event) -> { if (keyCode == KeyEvent.KEYCODE_BACK) { if (BuildConfig.DEBUG) Log.d(TAG, "🔙 [OnKeyListener] 检测到返回键: action=" + event.getAction()); if (event.getAction() == KeyEvent.ACTION_UP) { return BackPressHandler.handleBackPress(activity); } return true; // 拦截 ACTION_DOWN } return false; }); // 方法 2:设置 View 为可聚焦,确保能接收按键事件 View decorView = activity.getWindow().getDecorView(); decorView.setFocusable(true); decorView.setFocusableInTouchMode(true); decorView.requestFocus(); // 方法 3:使用 Window.Callback 拦截(更底层) installWindowCallback(activity); if (BuildConfig.DEBUG) Log.d(TAG, "✅ 返回键拦截器已安装: " + activity.getClass().getSimpleName()); } catch (Exception e) { Log.e(TAG, "安装返回键拦截器失败", e); } } /** * 安装 Window.Callback 拦截器(底层方法) * @param activity Activity 实例 */ private void installWindowCallback(Activity activity) { try { Window.Callback original = activity.getWindow().getCallback(); activity.getWindow().setCallback(new Window.Callback() { @Override public boolean dispatchKeyEvent(KeyEvent event) { if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { if (BuildConfig.DEBUG) Log.d(TAG, "🔙 [WindowCallback] 检测到返回键: action=" + event.getAction()); if (event.getAction() == KeyEvent.ACTION_UP) { boolean handled = BackPressHandler.handleBackPress(activity); if (handled) { return true; // 已拦截 } } else if (event.getAction() == KeyEvent.ACTION_DOWN) { return true; // 拦截 DOWN 事件 } } return original != null && original.dispatchKeyEvent(event); } // 委托其他方法给原始 Callback @Override public boolean dispatchKeyShortcutEvent(KeyEvent event) { return original != null && original.dispatchKeyShortcutEvent(event); } @Override public boolean dispatchTouchEvent(MotionEvent event) { return original != null && original.dispatchTouchEvent(event); } @Override public boolean dispatchTrackballEvent(MotionEvent event) { return original != null && original.dispatchTrackballEvent(event); } @Override public boolean dispatchGenericMotionEvent(MotionEvent event) { return original != null && original.dispatchGenericMotionEvent(event); } @Override public boolean dispatchPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent event) { return original != null && original.dispatchPopulateAccessibilityEvent(event); } @Override public View onCreatePanelView(int featureId) { return original != null ? original.onCreatePanelView(featureId) : null; } @Override public boolean onCreatePanelMenu(int featureId, android.view.Menu menu) { return original != null && original.onCreatePanelMenu(featureId, menu); } @Override public boolean onPreparePanel(int featureId, View view, android.view.Menu menu) { return original != null && original.onPreparePanel(featureId, view, menu); } @Override public boolean onMenuOpened(int featureId, android.view.Menu menu) { return original != null && original.onMenuOpened(featureId, menu); } @Override public boolean onMenuItemSelected(int featureId, android.view.MenuItem item) { return original != null && original.onMenuItemSelected(featureId, item); } @Override public void onWindowAttributesChanged(WindowManager.LayoutParams attrs) { if (original != null) original.onWindowAttributesChanged(attrs); } @Override public void onContentChanged() { if (original != null) original.onContentChanged(); } @Override public void onWindowFocusChanged(boolean hasFocus) { if (original != null) original.onWindowFocusChanged(hasFocus); } @Override public void onAttachedToWindow() { if (original != null) original.onAttachedToWindow(); } @Override public void onDetachedFromWindow() { if (original != null) original.onDetachedFromWindow(); } @Override public void onPanelClosed(int featureId, android.view.Menu menu) { if (original != null) original.onPanelClosed(featureId, menu); } @Override public boolean onSearchRequested() { return original != null && original.onSearchRequested(); } @Override public boolean onSearchRequested(android.view.SearchEvent searchEvent) { return original != null && original.onSearchRequested(searchEvent); } @Override public android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback callback) { return original != null ? original.onWindowStartingActionMode(callback) : null; } @Override public android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback callback, int type) { return original != null ? original.onWindowStartingActionMode(callback, type) : null; } @Override public void onActionModeStarted(android.view.ActionMode mode) { if (original != null) original.onActionModeStarted(mode); } @Override public void onActionModeFinished(android.view.ActionMode mode) { if (original != null) original.onActionModeFinished(mode); } }); if (BuildConfig.DEBUG) Log.d(TAG, "✅ Window.Callback 拦截器已安装"); } catch (Exception e) { Log.e(TAG, "安装 Window.Callback 失败", e); } } }