| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426 |
- 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<android.view.inputmethod.InputMethodInfo> allInputMethods = imm.getInputMethodList();
- if (BuildConfig.DEBUG) Log.d(TAG, "所有输入法数量: " + (allInputMethods != null ? allInputMethods.size() : 0));
-
- // 方法2: 获取启用的输入法列表(特别重要,确保搜狗输入法已启用)
- try {
- List<android.view.inputmethod.InputMethodInfo> 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);
- }
- }
- }
|