From 2efff01dcd85c14785eb707ffa683030ba85cfa4 Mon Sep 17 00:00:00 2001 From: peng <704047449@qq.com> Date: Wed, 17 Sep 2025 15:35:44 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=8A=AF=E5=9B=B0=E8=A1=A8?= =?UTF-8?q?=E6=83=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 1 + .../com/ismart/ib86/app/MainActivity.java | 14 +- .../NotificationDialogFragment.java | 22 +++ .../feature/audio/SystemVolumeController.java | 152 ++++++++++++++++++ .../feature/audio/VolumeControlExample.java | 66 ++++++++ .../feature/network/NetworkStateManager.java | 4 +- .../robotFace/RobotFaceAnimationManager.java | 85 +++++++++- 7 files changed, 335 insertions(+), 9 deletions(-) create mode 100644 app/src/main/java/com/ismart/ib86/feature/audio/SystemVolumeController.java create mode 100644 app/src/main/java/com/ismart/ib86/feature/audio/VolumeControlExample.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 01d83c8..705df03 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -22,6 +22,7 @@ + { @@ -1069,9 +1069,11 @@ public class MainActivity extends AppCompatActivity { handleConversationStarted(); break; case EVENT_HUMAN_SPEAKING_DETAIL: + Log.d(TAG_AI, "EVENT_HUMAN_SPEAKING_DETAIL"); handleSpeakingDetail(event.getResponse(), true); break; case EVENT_RESPONDING_DETAIL: + Log.d(TAG_AI, "EVENT_RESPONDING_DETAIL"); handleSpeakingDetail(event.getResponse(), false); handleResponseCommand(event.getResponse()); break; @@ -1116,6 +1118,7 @@ public class MainActivity extends AppCompatActivity { private void handleResponseCommand(String response) { String command = parseResponseCommand(response); if (command != null && authParams.getChainMode() == Constant.ChainMode.WEBSOCKET) { + Log.d(TAG_AI, "command:" + command); isExecutingCommand = true; ThreadPoolUtil.runOnSubThread(() -> executeCommand(command)); } @@ -1206,6 +1209,7 @@ public class MainActivity extends AppCompatActivity { JSONObject extraInfo = new JSONObject(output.getString("extra_info")); if (extraInfo.has("commands")) { String commands = extraInfo.getString("commands"); + Log.d(TAG_AI, "commands len:" + commands.length()); if (commands.length() > 6) { // 过滤空命令 return commands; } @@ -1240,11 +1244,11 @@ public class MainActivity extends AppCompatActivity { /////////////////////////////////////// 命令执行 /////////////////////////////////////// private void executeCommand(String command) { - Log.d(TAG, "执行命令: " + command); + Log.d(TAG_AI, "执行命令: " + command); try { String cmdName = new JSONArray(command).getJSONObject(0).getString("name"); - Log.d(TAG, "执行命令: " + cmdName); + Log.d(TAG_AI, "执行命令: " + cmdName); switch (cmdName) { case "check_battery": multiModalDialog.requestToRespond("transcript", "当前电量为87%", null); 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 index 0967b70..717aa3c 100644 --- a/app/src/main/java/com/ismart/ib86/common/notification/NotificationDialogFragment.java +++ b/app/src/main/java/com/ismart/ib86/common/notification/NotificationDialogFragment.java @@ -16,6 +16,7 @@ import android.view.WindowManager; import android.widget.ImageView; import android.widget.TextView; import com.ismart.ib86.app.R; +import com.ismart.ib86.robotFace.RobotFaceAnimationManager; /** * 通知弹窗DialogFragment @@ -286,6 +287,27 @@ public class NotificationDialogFragment extends DialogFragment { public void onDestroy() { super.onDestroy(); + // 记录当前销毁的NotificationType + if (notificationType != null) { + Log.d(TAG, "NotificationDialogFragment destroyed, NotificationType: " + notificationType); + RobotFaceAnimationManager animationManager = RobotFaceAnimationManager.getInstance(); + if (animationManager != null) { + if (notificationType.equals(NotificationType.NETWORK_LOST.name())) { + // 网络连接失败时,加载 sad 表情 + Log.d(TAG, "Loading sad face animation for network lost"); + animationManager.loadFaceAnimation(RobotFaceAnimationManager.FACE_SAD, false, 1); + } else if (notificationType.equals(NotificationType.NETWORK_CONNECTED.name())) { + // 网络连接成功时,加载 happy 表情 + Log.d(TAG, "Loading happy face animation for network connected"); + animationManager.loadFaceAnimation(RobotFaceAnimationManager.FACE_HAPPY, false, 1); + } + } else { + Log.w(TAG, "RobotFaceAnimationManager instance is null"); + } + } else { + Log.d(TAG, "NotificationDialogFragment destroyed, NotificationType is null"); + } + // 通知NotificationManager重置显示状态 if (getActivity() != null) { android.content.Intent intent = new android.content.Intent(ACTION_NOTIFICATION_CLOSED); diff --git a/app/src/main/java/com/ismart/ib86/feature/audio/SystemVolumeController.java b/app/src/main/java/com/ismart/ib86/feature/audio/SystemVolumeController.java new file mode 100644 index 0000000..6afd380 --- /dev/null +++ b/app/src/main/java/com/ismart/ib86/feature/audio/SystemVolumeController.java @@ -0,0 +1,152 @@ +package com.ismart.ib86.feature.audio; + +import android.content.Context; +import android.media.AudioManager; +import android.util.Log; + +/** + * 系统音量控制器 + * 提供读取当前系统媒体音量、静音、调节音量大小等功能 + */ +public class SystemVolumeController { + private static final String TAG = "SystemVolumeController"; + + private AudioManager audioManager; + private int maxVolume; + private int currentVolume; + private boolean isMuted = false; + private int streamType = AudioManager.STREAM_MUSIC; + + /** + * 构造函数 + * @param context 上下文 + */ + public SystemVolumeController(Context context) { + audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + if (audioManager != null) { + maxVolume = audioManager.getStreamMaxVolume(streamType); + currentVolume = audioManager.getStreamVolume(streamType); + } else { + Log.e(TAG, "无法获取AudioManager服务"); + } + } + + /** + * 获取当前媒体音量 + * @return 当前音量值 + */ + public int getCurrentVolume() { + if (audioManager != null) { + currentVolume = audioManager.getStreamVolume(streamType); + return currentVolume; + } + return 0; + } + + /** + * 获取最大音量 + * @return 最大音量值 + */ + public int getMaxVolume() { + return maxVolume; + } + + /** + * 设置音量 + * @param volume 音量值 + * @param showUI 是否显示系统音量UI + */ + public void setVolume(int volume, boolean showUI) { + if (audioManager != null) { + // 限制音量在有效范围内 + if (volume < 0) volume = 0; + if (volume > maxVolume) volume = maxVolume; + + int flags = 0; + if (showUI) { + flags |= AudioManager.FLAG_SHOW_UI; + } + + audioManager.setStreamVolume(streamType, volume, flags); + currentVolume = volume; + isMuted = (volume == 0); + } + } + + /** + * 调节音量(增加或减少) + * @param delta 音量变化量,正数为增加,负数为减少 + * @param showUI 是否显示系统音量UI + */ + public void adjustVolume(int delta, boolean showUI) { + if (audioManager != null) { + int flags = AudioManager.FLAG_PLAY_SOUND; + if (showUI) { + flags |= AudioManager.FLAG_SHOW_UI; + } + + audioManager.adjustStreamVolume(streamType, delta > 0 ? AudioManager.ADJUST_RAISE : AudioManager.ADJUST_LOWER, flags); + currentVolume = audioManager.getStreamVolume(streamType); + isMuted = (currentVolume == 0); + } + } + + /** + * 静音/取消静音 + * @param mute true为静音,false为取消静音 + */ + public void mute(boolean mute) { + if (audioManager != null) { + if (mute) { + // 保存当前音量后再设置为0 + if (currentVolume > 0) { + audioManager.setStreamVolume(streamType, 0, 0); + isMuted = true; + } + } else { + // 恢复音量 + int restoreVolume = (currentVolume > 0) ? currentVolume : maxVolume / 2; + audioManager.setStreamVolume(streamType, restoreVolume, 0); + currentVolume = restoreVolume; + isMuted = false; + } + } + } + + /** + * 切换静音状态 + */ + public void toggleMute() { + mute(!isMuted); + } + + /** + * 是否处于静音状态 + * @return true为静音,false为非静音 + */ + public boolean isMuted() { + return isMuted; + } + + /** + * 获取音量百分比 + * @return 音量百分比 (0-100) + */ + public int getVolumePercentage() { + if (maxVolume <= 0) return 0; + return (int) ((currentVolume * 100.0f) / maxVolume); + } + + /** + * 根据百分比设置音量 + * @param percentage 音量百分比 (0-100) + * @param showUI 是否显示系统音量UI + */ + public void setVolumeByPercentage(int percentage, boolean showUI) { + if (percentage < 0) percentage = 0; + if (percentage > 100) percentage = 100; + + int volume = (int) ((percentage * maxVolume) / 100.0f); + setVolume(volume, showUI); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ismart/ib86/feature/audio/VolumeControlExample.java b/app/src/main/java/com/ismart/ib86/feature/audio/VolumeControlExample.java new file mode 100644 index 0000000..bd36fb4 --- /dev/null +++ b/app/src/main/java/com/ismart/ib86/feature/audio/VolumeControlExample.java @@ -0,0 +1,66 @@ +package com.ismart.ib86.feature.audio; + +import android.content.Context; +import android.util.Log; + +/** + * 音量控制使用示例 + */ +public class VolumeControlExample { + private static final String TAG = "VolumeControlExample"; + + private SystemVolumeController volumeController; + + public VolumeControlExample(Context context) { + volumeController = new SystemVolumeController(context); + } + + /** + * 演示音量控制功能 + */ + public void demonstrateVolumeControl() { + // 获取当前音量 + int currentVolume = volumeController.getCurrentVolume(); + Log.d(TAG, "当前音量: " + currentVolume); + + // 获取最大音量 + int maxVolume = volumeController.getMaxVolume(); + Log.d(TAG, "最大音量: " + maxVolume); + + // 获取音量百分比 + int volumePercentage = volumeController.getVolumePercentage(); + Log.d(TAG, "音量百分比: " + volumePercentage + "%"); + + // 增加音量 + volumeController.adjustVolume(1, true); + Log.d(TAG, "增加音量后当前音量: " + volumeController.getCurrentVolume()); + + // 减少音量 + volumeController.adjustVolume(-1, true); + Log.d(TAG, "减少音量后当前音量: " + volumeController.getCurrentVolume()); + + // 设置特定音量值 + volumeController.setVolume(maxVolume / 2, true); + Log.d(TAG, "设置音量为一半: " + volumeController.getCurrentVolume()); + + // 根据百分比设置音量 + volumeController.setVolumeByPercentage(80, true); + Log.d(TAG, "设置音量为80%: " + volumeController.getCurrentVolume()); + + // 静音 + volumeController.mute(true); + Log.d(TAG, "静音后当前音量: " + volumeController.getCurrentVolume() + ", 是否静音: " + volumeController.isMuted()); + + // 取消静音 + volumeController.mute(false); + Log.d(TAG, "取消静音后当前音量: " + volumeController.getCurrentVolume() + ", 是否静音: " + volumeController.isMuted()); + + // 切换静音状态 + volumeController.toggleMute(); + Log.d(TAG, "切换静音状态后当前音量: " + volumeController.getCurrentVolume() + ", 是否静音: " + volumeController.isMuted()); + + // 再次切换静音状态 + volumeController.toggleMute(); + Log.d(TAG, "再次切换静音状态后当前音量: " + volumeController.getCurrentVolume() + ", 是否静音: " + volumeController.isMuted()); + } +} \ No newline at end of file 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 8af55cf..454dde4 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 @@ -10,6 +10,7 @@ import com.ismart.ib86.app.R; import com.ismart.ib86.common.notification.NotificationType; import com.ismart.ib86.common.notification.NotificationManager; import com.ismart.ib86.common.notification.NotificationPriority; +import com.ismart.ib86.robotFace.RobotFaceAnimationManager; /** * 网络状态管理器 @@ -41,7 +42,8 @@ public class NetworkStateManager { public void onNetworkLost() { Log.d(TAG, "onNetworkLost called"); // 网络断开使用高优先级 - showNotification(NotificationType.NETWORK_LOST, "设备已断开网络连接。", R.drawable.ic_network_lost, 10, NotificationPriority.HIGH); + showNotification(NotificationType.NETWORK_LOST, "设备已断开网络连接。", R.drawable.ic_network_lost, 5, NotificationPriority.HIGH); + } @Override diff --git a/app/src/main/java/com/ismart/ib86/robotFace/RobotFaceAnimationManager.java b/app/src/main/java/com/ismart/ib86/robotFace/RobotFaceAnimationManager.java index 321def4..27bec5a 100644 --- a/app/src/main/java/com/ismart/ib86/robotFace/RobotFaceAnimationManager.java +++ b/app/src/main/java/com/ismart/ib86/robotFace/RobotFaceAnimationManager.java @@ -12,10 +12,13 @@ import com.ismart.ib86.app.R; /** * 机器人表情动画管理器 * 负责加载、控制和切换不同的表情动画 + * 提供单例模式访问 */ public class RobotFaceAnimationManager { + // 单例实例 + private static RobotFaceAnimationManager instance; + private static final String TAG = "RobotFaceAnimManager"; // 动画类型常量 - public static final int FACE_LOOP_0 = 0; // 循环表情0 public static final int FACE_LOOP_1 = 1; // 循环表情1 public static final int FACE_LOOP_2 = 2; // 循环表情2 @@ -26,7 +29,6 @@ public class RobotFaceAnimationManager { // 动画资源ID数组 private static final int[] FACE_ANIMATIONS = { - R.drawable.anim_face_xunhuan, // 循环表情0 R.drawable.anim_face_xunhuan1, // 循环表情1 R.drawable.anim_face_xunhuan2, // 循环表情2 @@ -66,13 +68,48 @@ public class RobotFaceAnimationManager { this.handler = new Handler(Looper.getMainLooper()); } - private static final String TAG = "RobotFaceAnimManager"; + /** + * 获取单例实例 + * @param context 上下文 + * @param imageView 用于显示动画的ImageView + * @return RobotFaceAnimationManager 实例 + */ + public static synchronized RobotFaceAnimationManager getInstance(Context context, ImageView imageView) { + if (instance == null) { + instance = new RobotFaceAnimationManager(context, imageView); + } + return instance; + } + + /** + * 获取单例实例(需要先调用带参数的getInstance方法初始化) + * @return RobotFaceAnimationManager 实例 + */ + public static synchronized RobotFaceAnimationManager getInstance() { + return instance; + } + + + /** + * 加载指定类型的表情动画 + * @param faceType 表情类型 + */ /** * 加载指定类型的表情动画 * @param faceType 表情类型 */ public void loadFaceAnimation(int faceType) { + loadFaceAnimation(faceType, true, -1); // 默认循环播放,无限次数 + } + + /** + * 加载指定类型的表情动画 + * @param faceType 表情类型 + * @param loop 是否循环播放 + * @param repeatCount 重复次数,-1表示无限循环 + */ + public void loadFaceAnimation(int faceType, boolean loop, int repeatCount) { if (faceType < 0 || faceType >= FACE_ANIMATIONS.length) { faceType = FACE_LOOP_0; } @@ -96,6 +133,9 @@ public class RobotFaceAnimationManager { return; } + // 设置循环属性 + currentAnimation.setOneShot(!loop); + // 应用速度设置 // if (speedFactor != 1.0f) { // applySpeed(speedFactor); @@ -107,6 +147,11 @@ public class RobotFaceAnimationManager { // 如果不是暂停状态,则开始播放 if (!isPaused) { currentAnimation.start(); + + // 如果指定了重复次数且不为无限循环,则在指定次数后停止动画 + if (!loop && repeatCount > 0) { + scheduleStopAfterRepeats(repeatCount); + } } } catch (Exception e) { Log.e(TAG, "加载表情动画失败: " + FACE_NAMES[faceType], e); @@ -226,6 +271,37 @@ public class RobotFaceAnimationManager { imageView.setImageDrawable(currentAnimation); } + /** + * 在指定重复次数后停止动画,并随机选择一个循环表情继续播放 + * @param repeatCount 重复次数 + */ + private void scheduleStopAfterRepeats(int repeatCount) { + if (currentAnimation == null) return; + + // 计算总持续时间 + long totalDuration = 0; + int frameCount = currentAnimation.getNumberOfFrames(); + for (int i = 0; i < frameCount; i++) { + totalDuration += currentAnimation.getDuration(i); + } + + // 总时间 = 单次播放时间 * 重复次数 + long delay = totalDuration * repeatCount; + + // 使用Handler在指定时间后停止动画并随机选择循环表情 + handler.postDelayed(new Runnable() { + @Override + public void run() { + if (currentAnimation != null && currentAnimation.isRunning()) { + currentAnimation.stop(); + Log.d(TAG, "动画已播放完成指定次数: " + repeatCount); + + loadFaceAnimation(FACE_LOOP_0, true, -1); // 循环播放,无限次数 + } + } + }, delay); + } + /** * 切换到下一个表情 * @return 新表情的名称 @@ -310,5 +386,8 @@ public class RobotFaceAnimationManager { context = null; imageView = null; handler = null; + + // 清除单例实例 + instance = null; } } \ No newline at end of file