diff --git a/.qoder/quests/project-interpretation-1758003461.md b/.qoder/quests/project-interpretation-1758003461.md new file mode 100644 index 0000000..e69de29 diff --git a/.qoder/quests/project-interpretation-1758003538.md b/.qoder/quests/project-interpretation-1758003538.md new file mode 100644 index 0000000..e69de29 diff --git a/.qoder/quests/project-interpretation.md b/.qoder/quests/project-interpretation.md new file mode 100644 index 0000000..e69de29 diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 629a22b..01d83c8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -61,11 +61,7 @@ android:name="com.ismart.ib86.feature.network.TestBSSIDActivity" android:exported="true" /> - - + = android.os.Build.VERSION_CODES.KITKAT) { - getWindow().getDecorView().setSystemUiVisibility( - View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_FULLSCREEN - | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); - } - - initializeViews(); - handleIntent(getIntent()); - setupCloseReceiver(); - playNotificationSound(); - } - - private void initializeViews() { - ivIcon = findViewById(R.id.iv_icon); - tvMessage = findViewById(R.id.tv_message); - } - - private void handleIntent(Intent intent) { - if (intent != null) { - String message = intent.getStringExtra(EXTRA_MESSAGE); - int iconResId = intent.getIntExtra(EXTRA_ICON, android.R.drawable.ic_dialog_alert); // 默认使用系统警告图标 - int durationSeconds = intent.getIntExtra(EXTRA_DURATION, 5); // 默认5秒 - notificationType = intent.getStringExtra(EXTRA_TYPE); - soundPath = intent.getStringExtra(EXTRA_SOUND_PATH); - - Log.d(TAG, "Received intent with duration: " + durationSeconds + " seconds"); - - if (message != null) { - tvMessage.setText(message); - } - - if (iconResId != 0) { - ivIcon.setImageResource(iconResId); - } - - // 设置自动关闭 - if (durationSeconds > 0) { - handler = new Handler(); - finishRunnable = () -> { - Log.d(TAG, "Auto closing notification activity after " + durationSeconds + " seconds"); - finish(); - }; - handler.postDelayed(finishRunnable, durationSeconds * 1000L); - Log.d(TAG, "Scheduled auto close in " + durationSeconds + " seconds"); - } else { - Log.d(TAG, "Duration is 0 or negative, no auto close scheduled"); - } - } else { - Log.w(TAG, "Received null intent"); - } - } - - private void setupCloseReceiver() { - closeReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (ACTION_CLOSE_ACTIVITY.equals(intent.getAction())) { - Log.d(TAG, "Received close activity broadcast"); - finish(); - } - } - }; - - IntentFilter filter = new IntentFilter(ACTION_CLOSE_ACTIVITY); - registerReceiver(closeReceiver, filter); - } - - private void playNotificationSound() { - try { - // 如果提供了自定义音效路径,则使用自定义音效 - if (soundPath != null && !soundPath.isEmpty()) { - if (soundPath.startsWith("android.resource://")) { - // 处理res/raw资源 - Uri uri = Uri.parse(soundPath); - mediaPlayer = MediaPlayer.create(this, uri); - } else { - // 处理文件路径 - mediaPlayer = new MediaPlayer(); - mediaPlayer.setDataSource(soundPath); - mediaPlayer.prepare(); - } - } else if (notificationType != null) { - // 根据通知类型使用对应的默认音效 - switch (notificationType) { - case "NETWORK_LOST": - mediaPlayer = MediaPlayer.create(this, R.raw.network_lost_notification); - break; - case "NETWORK_CONFIG": - mediaPlayer = MediaPlayer.create(this, R.raw.network_config_notification); - break; - case "BATTERY_LOW": - mediaPlayer = MediaPlayer.create(this, R.raw.battery_low_notification); - break; - default: - mediaPlayer = MediaPlayer.create(this, R.raw.default_notification); - break; - } - } else { - // 使用系统默认提示音 -// mediaPlayer = MediaPlayer.create(this, android.R.raw.notification_sound); - } - - if (mediaPlayer != null) { - mediaPlayer.setOnCompletionListener(mp -> { - try { - // 在播放完成后释放MediaPlayer资源 - mp.release(); - // 将mediaPlayer引用置为null,避免在onDestroy中重复释放 - mediaPlayer = null; - } catch (Exception e) { - Log.e(TAG, "Error releasing media player", e); - } - }); - mediaPlayer.start(); - } - } catch (Exception e) { - Log.e(TAG, "Failed to play notification sound", e); - } - } - - @Override - protected void onDestroy() { - Log.d(TAG, "onDestroy called"); - super.onDestroy(); - - // 通知NotificationManager重置显示状态 - Intent intent = new Intent(ACTION_NOTIFICATION_CLOSED); - sendBroadcast(intent); - - // 清理资源 - if (handler != null && finishRunnable != null) { - Log.d(TAG, "Removing callbacks from handler"); - handler.removeCallbacks(finishRunnable); - } - - if (mediaPlayer != null) { - try { - // 在调用isPlaying()之前检查MediaPlayer状态,避免IllegalStateException - if (mediaPlayer.isPlaying()) { - Log.d(TAG, "Stopping media player"); - mediaPlayer.stop(); - } - } catch (IllegalStateException e) { - // MediaPlayer可能已经处于无效状态,忽略这个异常 - Log.w(TAG, "MediaPlayer is in invalid state, skipping stop()", e); - } catch (Exception e) { - Log.e(TAG, "Error stopping media player", e); - } - - try { - Log.d(TAG, "Releasing media player"); - mediaPlayer.release(); - mediaPlayer = null; - } catch (Exception e) { - Log.e(TAG, "Error releasing media player", e); - } - } - - if (closeReceiver != null) { - try { - Log.d(TAG, "Unregistering receiver"); - unregisterReceiver(closeReceiver); - } catch (Exception e) { - Log.e(TAG, "Error unregistering receiver", e); - } - closeReceiver = null; - } - - Log.d(TAG, "onDestroy completed"); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/ismart/ib86/common/notification/NotificationDialogFragment.java b/app/src/main/java/com/ismart/ib86/common/notification/NotificationDialogFragment.java new file mode 100644 index 0000000..0967b70 --- /dev/null +++ b/app/src/main/java/com/ismart/ib86/common/notification/NotificationDialogFragment.java @@ -0,0 +1,321 @@ +package com.ismart.ib86.common.notification; + +import android.app.Dialog; +import android.app.DialogFragment; +import android.media.MediaPlayer; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.widget.ImageView; +import android.widget.TextView; +import com.ismart.ib86.app.R; + +/** + * 通知弹窗DialogFragment + */ +public class NotificationDialogFragment extends DialogFragment { + public static final String ACTION_NOTIFICATION_CLOSED = "com.ismart.ib86.notification.NOTIFICATION_CLOSED"; + public static final String ACTION_CLOSE_DIALOG = "com.ismart.ib86.notification.CLOSE_DIALOG"; + + private static final String ARG_MESSAGE = "message"; + private static final String ARG_ICON_RES_ID = "icon_res_id"; + private static final String ARG_DURATION = "duration"; + private static final String ARG_TYPE = "type"; + private static final String ARG_SOUND_PATH = "sound_path"; + private static final String TAG = "NotificationDialog"; + + private String message; + private int iconResId; + private int durationSeconds; + private String notificationType; + private String soundPath; + private MediaPlayer mediaPlayer; + private Handler handler; + private Runnable dismissRunnable; + + public static NotificationDialogFragment newInstance(String message, int iconResId, + int durationSeconds, String type, String soundPath) { + NotificationDialogFragment fragment = new NotificationDialogFragment(); + Bundle args = new Bundle(); + args.putString(ARG_MESSAGE, message); + args.putInt(ARG_ICON_RES_ID, iconResId); + args.putInt(ARG_DURATION, durationSeconds); + args.putString(ARG_TYPE, type); + args.putString(ARG_SOUND_PATH, soundPath); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setStyle(DialogFragment.STYLE_NORMAL, R.style.FullScreenDialog); + + // 设置DialogFragment的窗口标志以隐藏系统UI + setShowsDialog(true); + + if (getArguments() != null) { + message = getArguments().getString(ARG_MESSAGE); + iconResId = getArguments().getInt(ARG_ICON_RES_ID); + durationSeconds = getArguments().getInt(ARG_DURATION); + notificationType = getArguments().getString(ARG_TYPE); + soundPath = getArguments().getString(ARG_SOUND_PATH); + } + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + Dialog dialog = super.onCreateDialog(savedInstanceState); + if (dialog.getWindow() != null) { + Window window = dialog.getWindow(); + window.requestFeature(Window.FEATURE_NO_TITLE); + + // 添加窗口标志以确保全屏并隐藏系统UI + window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN); + window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); + window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); // 防止导航栏显示 + + // 设置窗口布局参数 + WindowManager.LayoutParams params = window.getAttributes(); + params.width = WindowManager.LayoutParams.MATCH_PARENT; + params.height = WindowManager.LayoutParams.MATCH_PARENT; + window.setAttributes(params); + } + return dialog; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.dialog_notification, container, false); + return view; + } + + @Override + public void onStart() { + super.onStart(); + // 设置Dialog为全屏并隐藏系统UI + Dialog dialog = getDialog(); + if (dialog != null && dialog.getWindow() != null) { + Window window = dialog.getWindow(); + window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + + // 设置窗口标志以确保全屏 + window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN); + window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); + window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); // 防止导航栏显示 + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + window.getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + | View.SYSTEM_UI_FLAG_IMMERSIVE); // 添加额外的沉浸模式标志 + } + + // 延迟设置焦点以确保系统UI正确隐藏 + window.getDecorView().post(() -> { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + window.getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + | View.SYSTEM_UI_FLAG_IMMERSIVE); + } + }); + } + } + + @Override + public void onResume() { + super.onResume(); + // 确保在恢复时系统UI状态正确 + Dialog dialog = getDialog(); + if (dialog != null && dialog.getWindow() != null) { + Window window = dialog.getWindow(); + + // 设置窗口标志以确保全屏 + window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN); + window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); + window.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); // 恢复焦点 + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + window.getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + | View.SYSTEM_UI_FLAG_IMMERSIVE); + } + + // 延迟设置焦点以确保系统UI正确隐藏 + window.getDecorView().post(() -> { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + window.getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + | View.SYSTEM_UI_FLAG_IMMERSIVE); + } + }); + } + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + initializeViews(view); + playNotificationSound(); + + // 设置自动关闭 + if (durationSeconds > 0) { + handler = new Handler(); + dismissRunnable = () -> { + if (getDialog() != null && getDialog().isShowing()) { + dismissAllowingStateLoss(); + } + }; + handler.postDelayed(dismissRunnable, durationSeconds * 1000L); + } + } + + private void initializeViews(View view) { + ImageView ivIcon = view.findViewById(R.id.iv_icon); + TextView tvMessage = view.findViewById(R.id.tv_message); + + if (message != null) { + tvMessage.setText(message); + } + + if (iconResId != 0) { + ivIcon.setImageResource(iconResId); + } + } + + private void playNotificationSound() { + try { + // 如果提供了自定义音效路径,则使用自定义音效 + if (soundPath != null && !soundPath.isEmpty() && getActivity() != null) { + if (soundPath.startsWith("android.resource://")) { + // 处理res/raw资源 + Uri uri = Uri.parse(soundPath); + mediaPlayer = MediaPlayer.create(getActivity(), uri); + } else { + // 处理文件路径 + mediaPlayer = new MediaPlayer(); + mediaPlayer.setDataSource(soundPath); + mediaPlayer.prepare(); + } + } else if (notificationType != null && getActivity() != null) { + // 根据通知类型使用对应的默认音效 + switch (notificationType) { + case "NETWORK_LOST": + mediaPlayer = MediaPlayer.create(getActivity(), R.raw.network_lost_notification); + break; + case "NETWORK_CONFIG": + mediaPlayer = MediaPlayer.create(getActivity(), R.raw.network_config_notification); + break; + case "BATTERY_LOW": + mediaPlayer = MediaPlayer.create(getActivity(), R.raw.battery_low_notification); + break; + default: + mediaPlayer = MediaPlayer.create(getActivity(), R.raw.default_notification); + break; + } + } + + if (mediaPlayer != null) { + mediaPlayer.setOnCompletionListener(mp -> { + try { + // 在播放完成后释放MediaPlayer资源 + mp.release(); + // 将mediaPlayer引用置为null,避免在onDestroy中重复释放 + mediaPlayer = null; + } catch (Exception e) { + Log.e(TAG, "Error releasing media player", e); + } + }); + mediaPlayer.start(); + } + } catch (Exception e) { + Log.e(TAG, "Failed to play notification sound", e); + } + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + + // 在销毁视图时确保系统UI状态正确 + if (getActivity() != null && getActivity().getWindow() != null) { + // 恢复Activity的系统UI状态 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + getActivity().getWindow().getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + } + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + + // 通知NotificationManager重置显示状态 + if (getActivity() != null) { + android.content.Intent intent = new android.content.Intent(ACTION_NOTIFICATION_CLOSED); + getActivity().sendBroadcast(intent); + } + + // 清理资源 + if (handler != null && dismissRunnable != null) { + handler.removeCallbacks(dismissRunnable); + } + + if (mediaPlayer != null) { + try { + // 在调用isPlaying()之前检查MediaPlayer状态,避免IllegalStateException + if (mediaPlayer.isPlaying()) { + mediaPlayer.stop(); + } + } catch (IllegalStateException e) { + // MediaPlayer可能已经处于无效状态,忽略这个异常 + Log.w(TAG, "MediaPlayer is in invalid state, skipping stop()", e); + } catch (Exception e) { + Log.e(TAG, "Error stopping media player", e); + } + + try { + mediaPlayer.release(); + mediaPlayer = null; + } catch (Exception e) { + Log.e(TAG, "Error releasing media player", e); + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ismart/ib86/common/notification/NotificationManager.java b/app/src/main/java/com/ismart/ib86/common/notification/NotificationManager.java index 4854de7..842e201 100644 --- a/app/src/main/java/com/ismart/ib86/common/notification/NotificationManager.java +++ b/app/src/main/java/com/ismart/ib86/common/notification/NotificationManager.java @@ -1,6 +1,7 @@ package com.ismart.ib86.common.notification; import android.app.Activity; +import android.app.FragmentManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -12,6 +13,8 @@ import android.util.Log; import java.util.LinkedList; import java.util.Queue; +import com.ismart.ib86.common.notification.NotificationDialogFragment; + /** * 通知管理器 * 专门负责通知的显示和隐藏 @@ -19,8 +22,8 @@ import java.util.Queue; public class NotificationManager { private static final String TAG = "NotificationManager"; private Context context; - private Activity activityContext; // 保存Activity上下文用于启动Activity - private boolean isActivityShowing = false; + private Activity activityContext; // 保存Activity上下文用于启动DialogFragment + private boolean isDialogShowing = false; private NotificationClosedReceiver notificationClosedReceiver; private NotificationRequest currentNotification; // 当前正在显示的通知 private Queue notificationQueue = new LinkedList<>(); // 通知队列 @@ -66,7 +69,7 @@ public class NotificationManager { NotificationRequest request = new NotificationRequest(type, message, iconResId, durationSeconds, soundPath, priority); // 如果当前没有通知在显示,则直接显示 - if (!isActivityShowing) { + if (!isDialogShowing) { displayNotification(request); } else { // 如果有通知在显示,检查优先级 @@ -94,74 +97,75 @@ public class NotificationManager { private void displayNotification(NotificationRequest request) { Log.d(TAG, "Displaying notification: " + request); - if (isActivityShowing) { - Log.d(TAG, "Notification activity is already showing"); + if (isDialogShowing) { + Log.d(TAG, "Notification dialog is already showing"); return; } // 检查是否有可用的Activity上下文 if (activityContext == null) { - Log.w(TAG, "Activity context is null, cannot show activity"); + Log.w(TAG, "Activity context is null, cannot show dialog"); return; } // 检查Activity是否有效 if (activityContext.isFinishing() || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && activityContext.isDestroyed())) { - Log.w(TAG, "Activity is not valid, cannot show activity"); + Log.w(TAG, "Activity is not valid, cannot show dialog"); return; } activityContext.runOnUiThread(() -> { try { - if (isActivityShowing) { - Log.d(TAG, "Notification activity is already showing"); + if (isDialogShowing) { + Log.d(TAG, "Notification dialog is already showing"); return; } - Intent intent = new Intent(activityContext, CommonNotificationActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); - intent.putExtra(CommonNotificationActivity.EXTRA_MESSAGE, request.getMessage()); - intent.putExtra(CommonNotificationActivity.EXTRA_ICON, request.getIconResId()); - intent.putExtra(CommonNotificationActivity.EXTRA_DURATION, request.getDurationSeconds()); - intent.putExtra(CommonNotificationActivity.EXTRA_TYPE, request.getType().name()); - if (request.getSoundPath() != null) { - intent.putExtra(CommonNotificationActivity.EXTRA_SOUND_PATH, request.getSoundPath()); - } - activityContext.startActivity(intent); - isActivityShowing = true; + // 创建并显示NotificationDialogFragment + NotificationDialogFragment dialogFragment = NotificationDialogFragment.newInstance( + request.getMessage(), + request.getIconResId(), + request.getDurationSeconds(), + request.getType().name(), + request.getSoundPath() + ); + + FragmentManager fragmentManager = activityContext.getFragmentManager(); + dialogFragment.show(fragmentManager, "NotificationDialogFragment"); + isDialogShowing = true; currentNotification = request; - Log.d(TAG, "Notification activity shown for " + request.getDurationSeconds() + " seconds, type: " + request.getType() + ", priority: " + request.getPriority()); + Log.d(TAG, "Notification dialog shown for " + request.getDurationSeconds() + " seconds, type: " + request.getType() + ", priority: " + request.getPriority()); } catch (Exception e) { - Log.e(TAG, "Failed to show notification activity", e); + Log.e(TAG, "Failed to show notification dialog", e); } }); } public void hideNotification() { - Log.d(TAG, "hideNotification called, isActivityShowing: " + isActivityShowing); - // 通过发送广播通知CommonNotificationActivity关闭自身 - if (isActivityShowing) { + Log.d(TAG, "hideNotification called, isDialogShowing: " + isDialogShowing); + // 通过发送广播通知NotificationDialogFragment关闭自身 + if (isDialogShowing) { try { - Intent intent = new Intent(CommonNotificationActivity.ACTION_CLOSE_ACTIVITY); + Intent intent = new Intent(NotificationDialogFragment.ACTION_CLOSE_DIALOG); context.sendBroadcast(intent); - isActivityShowing = false; + isDialogShowing = false; currentNotification = null; - Log.d(TAG, "Notification activity hidden"); + Log.d(TAG, "Notification dialog hidden"); } catch (Exception e) { - Log.e(TAG, "Failed to hide notification activity", e); + Log.e(TAG, "Failed to hide notification dialog", e); } } else { - Log.d(TAG, "Notification activity is not showing, nothing to hide"); + Log.d(TAG, "Notification dialog is not showing, nothing to hide"); } } - public boolean isActivityShowing() { - return isActivityShowing; + public boolean isDialogShowing() { + return isDialogShowing; } // 添加一个方法用于重置显示状态 public void resetShowingState() { - isActivityShowing = false; + isDialogShowing = false; currentNotification = null; processNextNotification(); } @@ -193,7 +197,7 @@ public class NotificationManager { private void registerNotificationClosedReceiver() { notificationClosedReceiver = new NotificationClosedReceiver(); - IntentFilter filter = new IntentFilter(CommonNotificationActivity.ACTION_NOTIFICATION_CLOSED); + IntentFilter filter = new IntentFilter(NotificationDialogFragment.ACTION_NOTIFICATION_CLOSED); context.registerReceiver(notificationClosedReceiver, filter); } @@ -211,7 +215,7 @@ public class NotificationManager { private class NotificationClosedReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - if (CommonNotificationActivity.ACTION_NOTIFICATION_CLOSED.equals(intent.getAction())) { + if (NotificationDialogFragment.ACTION_NOTIFICATION_CLOSED.equals(intent.getAction())) { Log.d(TAG, "Received notification closed broadcast, resetting showing state"); resetShowingState(); } diff --git a/app/src/main/java/com/ismart/ib86/feature/network/NetworkStateManager.java b/app/src/main/java/com/ismart/ib86/feature/network/NetworkStateManager.java index 82f6807..8af55cf 100644 --- a/app/src/main/java/com/ismart/ib86/feature/network/NetworkStateManager.java +++ b/app/src/main/java/com/ismart/ib86/feature/network/NetworkStateManager.java @@ -7,7 +7,6 @@ import android.os.Build; import android.util.Log; import com.ismart.ib86.app.R; -import com.ismart.ib86.common.notification.CommonNotificationActivity; import com.ismart.ib86.common.notification.NotificationType; import com.ismart.ib86.common.notification.NotificationManager; import com.ismart.ib86.common.notification.NotificationPriority; @@ -121,8 +120,8 @@ public class NetworkStateManager { } } - public boolean isActivityShowing() { - return notificationManager != null ? notificationManager.isActivityShowing() : false; + public boolean isDialogShowing() { + return notificationManager != null ? notificationManager.isDialogShowing() : false; } public NetworkState getCurrentNetworkState() { diff --git a/app/src/main/java/com/ismart/ib86/feature/network/NetworkStateManagerComparison.md b/app/src/main/java/com/ismart/ib86/feature/network/NetworkStateManagerComparison.md deleted file mode 100644 index 96844a9..0000000 --- a/app/src/main/java/com/ismart/ib86/feature/network/NetworkStateManagerComparison.md +++ /dev/null @@ -1,58 +0,0 @@ -# NetworkStateManager 设计方案比较 - -## 方案一:分离式设计(当前实现) - -### 组件结构 -1. **NetworkStateMonitor** - 负责网络状态监听 -2. **NetworkStateManager** - 负责状态管理和通知显示 - -### 优点 -1. **职责分离**:网络监听和状态管理完全分离 -2. **高内聚低耦合**:每个类只负责单一职责 -3. **可复用性**:NetworkStateMonitor可以被其他不涉及通知显示的组件复用 -4. **可测试性**:可以独立测试网络监听功能 -5. **可扩展性**:可以在NetworkStateManager中添加更多的状态管理逻辑 - -### 缺点 -1. **复杂性**:增加了类的数量和层级 -2. **间接性**:需要通过多个类来完成完整的功能 - -## 方案二:合并式设计(简化版本) - -### 组件结构 -1. **SimpleNetworkStateManager** - 同时负责网络状态监听和状态管理 - -### 优点 -1. **简洁性**:只有一个类负责所有功能 -2. **直接性**:减少了组件间的依赖关系 -3. **易理解**:代码结构更简单,易于理解 - -### 缺点 -1. **职责混合**:一个类承担了多个职责 -2. **可复用性差**:网络监听功能难以被其他组件复用 -3. **可测试性差**:难以独立测试网络监听功能 -4. **扩展性差**:添加新功能时可能需要修改现有代码 - -## 推荐方案 - -### 如果项目需求简单且稳定 -推荐使用**方案二(合并式设计)**,因为: -- 代码更简洁,易于维护 -- 减少了不必要的复杂性 -- 满足当前需求即可 - -### 如果项目需求复杂或多变 -推荐使用**方案一(分离式设计)**,因为: -- 更好的代码组织和架构 -- 更高的可扩展性和可维护性 -- 符合软件设计原则(单一职责、开闭原则等) - -## 当前实现的选择 - -当前实现采用了方案一(分离式设计),主要考虑: -1. 项目可能需要扩展更多的网络状态管理功能 -2. 网络监听功能可能在其他地方被复用 -3. 遵循了良好的软件设计原则 -4. 便于单元测试和维护 - -如果确定项目只需要基本的网络状态监听和通知功能,可以考虑切换到方案二以简化代码结构。 \ No newline at end of file diff --git a/app/src/main/java/com/ismart/ib86/feature/network/SimpleNetworkStateManager.java b/app/src/main/java/com/ismart/ib86/feature/network/SimpleNetworkStateManager.java deleted file mode 100644 index 5a7731e..0000000 --- a/app/src/main/java/com/ismart/ib86/feature/network/SimpleNetworkStateManager.java +++ /dev/null @@ -1,213 +0,0 @@ -package com.ismart.ib86.feature.network; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.net.ConnectivityManager; -import android.net.Network; -import android.net.NetworkCapabilities; -import android.net.NetworkRequest; -import android.os.Build; -import android.util.Log; -import com.ismart.ib86.common.notification.NotificationType; -import com.ismart.ib86.common.notification.CommonNotificationActivity; - -/** - * 简化版网络状态管理器 - * 将网络监听和状态管理合并为一个类 - */ -public class SimpleNetworkStateManager { - private static final String TAG = "SimpleNetworkStateManager"; - private Context context; - private ConnectivityManager connectivityManager; - private ConnectivityManager.NetworkCallback networkCallback; - private NetworkState currentState; - private boolean isActivityShowing = false; - - public SimpleNetworkStateManager(Context context) { - this.context = context.getApplicationContext(); - this.connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - this.currentState = NetworkState.NO_NETWORK; - initializeNetworkCallback(); - } - - private void initializeNetworkCallback() { - networkCallback = new ConnectivityManager.NetworkCallback() { - @Override - public void onAvailable(Network network) { - super.onAvailable(network); - Log.d(TAG, "Network available: " + network); - updateNetworkState(); - } - - @Override - public void onLost(Network network) { - super.onLost(network); - Log.d(TAG, "Network lost: " + network); - updateNetworkState(); - } - - @Override - public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) { - super.onCapabilitiesChanged(network, networkCapabilities); - Log.d(TAG, "Network capabilities changed: " + network); - updateNetworkState(); - } - }; - } - - public void registerNetworkCallback() { - if (connectivityManager == null) { - Log.e(TAG, "ConnectivityManager is null"); - return; - } - - NetworkRequest networkRequest = new NetworkRequest.Builder() - .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) - .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) - .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) - .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET) - .build(); - - try { - connectivityManager.registerNetworkCallback(networkRequest, networkCallback); - updateNetworkState(); // 初始化当前状态 - Log.d(TAG, "Network callback registered"); - } catch (SecurityException e) { - Log.e(TAG, "Failed to register network callback due to permission issue", e); - } catch (Exception e) { - Log.e(TAG, "Failed to register network callback", e); - } - } - - public void unregisterNetworkCallback() { - if (connectivityManager == null) { - Log.e(TAG, "ConnectivityManager is null"); - return; - } - - try { - connectivityManager.unregisterNetworkCallback(networkCallback); - Log.d(TAG, "Network callback unregistered"); - } catch (Exception e) { - Log.e(TAG, "Failed to unregister network callback", e); - } - } - - private void updateNetworkState() { - NetworkState newState = getNetworkState(); - if (newState != currentState) { - Log.d(TAG, "Network state changed from " + currentState + " to " + newState); - currentState = newState; - - // 处理状态变化 - if (currentState == NetworkState.NO_NETWORK) { - showNotification(NotificationType.NETWORK_LOST, "设备已断开网络连接,请检查WiFi或移动数据设置。", android.R.drawable.ic_dialog_alert, 5, null); - } else { - hideNotification(); - } - } - } - - public NetworkState getNetworkState() { - if (connectivityManager != null) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - Network activeNetwork = connectivityManager.getActiveNetwork(); - if (activeNetwork != null) { - NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(activeNetwork); - if (capabilities != null) { - if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { - return NetworkState.WIFI_CONNECTED; - } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { - return NetworkState.MOBILE_CONNECTED; - } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) { - return NetworkState.ETHERNET_CONNECTED; - } - } - } - } else { - // 兼容旧版本API - android.net.NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo(); - if (activeNetworkInfo != null && activeNetworkInfo.isConnected()) { - int type = activeNetworkInfo.getType(); - if (type == ConnectivityManager.TYPE_WIFI) { - return NetworkState.WIFI_CONNECTED; - } else if (type == ConnectivityManager.TYPE_MOBILE) { - return NetworkState.MOBILE_CONNECTED; - } else if (type == ConnectivityManager.TYPE_ETHERNET) { - return NetworkState.ETHERNET_CONNECTED; - } - } - } - } - return NetworkState.NO_NETWORK; - } - - public void showNotification(NotificationType type, String message, int iconResId, int durationSeconds) { - showNotification(type, message, iconResId, durationSeconds, null); - } - - public void showNotification(NotificationType type, String message, int iconResId, int durationSeconds, String soundPath) { - if (isActivityShowing) { - Log.d(TAG, "Notification activity is already showing"); - return; - } - - if (!(context instanceof Activity)) { - Log.w(TAG, "Context is not an Activity, cannot show activity"); - return; - } - - Activity activity = (Activity) context; - if (activity.isFinishing() || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && activity.isDestroyed())) { - Log.w(TAG, "Activity is not valid, cannot show activity"); - return; - } - - activity.runOnUiThread(() -> { - try { - if (isActivityShowing) { - Log.d(TAG, "Notification activity is already showing"); - return; - } - - Intent intent = new Intent(activity, CommonNotificationActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); - intent.putExtra(CommonNotificationActivity.EXTRA_MESSAGE, message); - intent.putExtra(CommonNotificationActivity.EXTRA_ICON, iconResId); - intent.putExtra(CommonNotificationActivity.EXTRA_DURATION, durationSeconds); - intent.putExtra(CommonNotificationActivity.EXTRA_TYPE, type.name()); - if (soundPath != null) { - intent.putExtra(CommonNotificationActivity.EXTRA_SOUND_PATH, soundPath); - } - activity.startActivity(intent); - isActivityShowing = true; - Log.d(TAG, "Notification activity shown for " + durationSeconds + " seconds, type: " + type); - } catch (Exception e) { - Log.e(TAG, "Failed to show notification activity", e); - } - }); - } - - public void hideNotification() { - // 通过发送广播通知CommonNotificationActivity关闭自身 - if (isActivityShowing) { - try { - Intent intent = new Intent(CommonNotificationActivity.ACTION_CLOSE_ACTIVITY); - context.sendBroadcast(intent); - isActivityShowing = false; - Log.d(TAG, "Notification activity hidden"); - } catch (Exception e) { - Log.e(TAG, "Failed to hide notification activity", e); - } - } - } - - public boolean isActivityShowing() { - return isActivityShowing; - } - - public NetworkState getCurrentState() { - return currentState; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/ismart/ib86/feature/network/TestWiFiScanResult.java b/app/src/main/java/com/ismart/ib86/feature/network/TestWiFiScanResult.java deleted file mode 100644 index 734923b..0000000 --- a/app/src/main/java/com/ismart/ib86/feature/network/TestWiFiScanResult.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.ismart.ib86.feature.network; - -import android.util.Log; -import java.util.Arrays; -import java.util.List; - -/** - * 测试WiFi扫描结果发送逻辑 - */ -public class TestWiFiScanResult { - private static final String TAG = "TestWiFiScanResult"; - - /** - * 测试格式化WiFi扫描结果 - */ - public static void testFormatScanResult() { - Log.i(TAG, "=== 开始测试WiFi扫描结果格式化 ==="); - - // 模拟WiFi扫描结果 - List mockSSIDs = Arrays.asList("1603", "1603-5G", "ismart_hw_2.4g"); - Log.i(TAG, "模拟WiFi扫描结果: " + mockSSIDs); - - // 创建ATCommandProcessor实例进行测试 - ATCommandProcessor processor = new ATCommandProcessor(null, null, null); - - // 调用格式化方法 - String result = null; - try { - // 使用反射调用私有方法 - java.lang.reflect.Method method = ATCommandProcessor.class.getDeclaredMethod("formatScanResult", List.class); - method.setAccessible(true); - result = (String) method.invoke(processor, mockSSIDs); - } catch (Exception e) { - Log.e(TAG, "调用formatScanResult方法时出错", e); - } - - Log.i(TAG, "格式化结果: " + result); - - // 验证结果 - if (result != null && result.startsWith("AT+SSID=3,")) { - Log.i(TAG, "✅ 格式化结果正确"); - } else { - Log.e(TAG, "❌ 格式化结果不正确"); - } - - Log.i(TAG, "=== WiFi扫描结果格式化测试完成 ==="); - } - - /** - * 测试完整的WiFi扫描结果发送流程 - */ - public static void testSendScanResult() { - Log.i(TAG, "=== 开始测试WiFi扫描结果发送 ==="); - - // 这个测试需要实际的BLE环境,所以只是模拟日志输出 - Log.i(TAG, "模拟WiFi扫描完成,找到3个网络: [1603, 1603-5G, ismart_hw_2.4g]"); - Log.i(TAG, "应该发送格式化结果: AT+SSID=3,1603,1603-5G,ismart_hw_2.4g"); - Log.i(TAG, "通过UUID 0xEA01 发送通知给小程序"); - - Log.i(TAG, "=== WiFi扫描结果发送测试完成 ==="); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/ismart/ib86/feature/network/TestWiFiScanResultWithBSSID.java b/app/src/main/java/com/ismart/ib86/feature/network/TestWiFiScanResultWithBSSID.java deleted file mode 100644 index c6aeb4f..0000000 --- a/app/src/main/java/com/ismart/ib86/feature/network/TestWiFiScanResultWithBSSID.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.ismart.ib86.feature.network; - -import android.util.Log; -import java.util.Arrays; -import java.util.List; - -/** - * 测试WiFi扫描结果发送逻辑(包含BSSID) - */ -public class TestWiFiScanResultWithBSSID { - private static final String TAG = "TestWiFiScanResultWithBSSID"; - - /** - * 测试格式化WiFi扫描结果(包含BSSID) - */ - public static void testFormatScanResultWithBSSID() { - Log.i(TAG, "=== 开始测试WiFi扫描结果格式化(包含BSSID) ==="); - - // 模拟WiFi扫描结果(包含BSSID信息) - List mockNetworks = Arrays.asList( - new WiFiNetworkInfo("1603", "e0:0c:e5:13:90:00", -87, 2437), - new WiFiNetworkInfo("1603-5G", "c4:2b:44:f1:25:80", -60, 5745), - new WiFiNetworkInfo("ismart_hw_2.4g", "c0:61:18:c9:53:2b", -95, 2412) - ); - Log.i(TAG, "模拟WiFi扫描结果: " + mockNetworks); - - // 创建ATCommandProcessor实例进行测试 - ATCommandProcessor processor = new ATCommandProcessor(null, null, null); - - // 调用新的格式化方法 - String result = null; - try { - // 使用反射调用私有方法 - java.lang.reflect.Method method = ATCommandProcessor.class.getDeclaredMethod("formatScanResultWithBSSID", List.class); - method.setAccessible(true); - result = (String) method.invoke(processor, mockNetworks); - } catch (Exception e) { - Log.e(TAG, "调用formatScanResultWithBSSID方法时出错", e); - } - - Log.i(TAG, "格式化结果(包含BSSID): " + result); - - // 验证结果 - if (result != null && result.startsWith("AT+SSID=3,") && result.contains(":")) { - Log.i(TAG, "✅ 格式化结果(包含BSSID)正确"); - } else { - Log.e(TAG, "❌ 格式化结果(包含BSSID)不正确"); - } - - Log.i(TAG, "=== WiFi扫描结果格式化测试(包含BSSID)完成 ==="); - } - - /** - * 测试完整的WiFi扫描结果发送流程(包含BSSID) - */ - public static void testSendScanResultWithBSSID() { - Log.i(TAG, "=== 开始测试WiFi扫描结果发送(包含BSSID) ==="); - - // 这个测试需要实际的BLE环境,所以只是模拟日志输出 - Log.i(TAG, "模拟WiFi扫描完成,找到3个网络:"); - Log.i(TAG, " [0] SSID: '1603-5G', BSSID: c4:2b:44:f1:25:80"); - Log.i(TAG, " [1] SSID: '1603', BSSID: e0:0c:e5:13:90:00"); - Log.i(TAG, " [2] SSID: 'ismart_hw_2.4g', BSSID: c0:61:18:c9:53:2b"); - Log.i(TAG, "应该发送格式化结果: AT+SSID=3,1603-5G:c4:2b:44:f1:25:80,1603:e0:0c:e5:13:90:00,ismart_hw_2.4g:c0:61:18:c9:53:2b"); - Log.i(TAG, "通过UUID 0xEA01 发送通知给小程序"); - - Log.i(TAG, "=== WiFi扫描结果发送测试(包含BSSID)完成 ==="); - } -} \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_notification.xml b/app/src/main/res/layout/dialog_notification.xml new file mode 100644 index 0000000..4d8e9bd --- /dev/null +++ b/app/src/main/res/layout/dialog_notification.xml @@ -0,0 +1,25 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..de661e9 --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index b9e96e2..49dfd55 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -1,9 +1,11 @@ -