Merge remote-tracking branch 'origin/timestamp' into appbase
This commit is contained in:
		
							
								
								
									
										14
									
								
								build.gradle
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								build.gradle
									
									
									
									
									
								
							| @@ -1,11 +1,8 @@ | |||||||
| // Top-level build file where you can add configuration options common to all sub-projects/modules. | // Top-level build file where you can add configuration options common to all sub-projects/modules. | ||||||
| buildscript { | buildscript { | ||||||
|     repositories { |     repositories { | ||||||
|         // 本地 Maven 仓库(默认路径为 ~/.m2/repository) |         //本地 maven 库 | ||||||
|         //mavenLocal() |         maven { url "file:///sdcard/.aide/maven/" } | ||||||
|         // 或自定义本地仓库路径 |  | ||||||
|         maven { url "file:///sdcard/.m2/repository" } |  | ||||||
|          |  | ||||||
|         // Nexus Maven 库地址 |         // Nexus Maven 库地址 | ||||||
|         // "WinBoLL Release" |         // "WinBoLL Release" | ||||||
|         maven { url "https://nexus.winboll.cc/repository/maven-public/" } |         maven { url "https://nexus.winboll.cc/repository/maven-public/" } | ||||||
| @@ -31,11 +28,8 @@ buildscript { | |||||||
|  |  | ||||||
| allprojects { | allprojects { | ||||||
|     repositories { |     repositories { | ||||||
|         // 本地 Maven 仓库(默认路径为 ~/.m2/repository) |         //本地 maven 库 | ||||||
|         //mavenLocal() |         maven { url "file:///sdcard/.aide/maven/" } | ||||||
|         // 或自定义本地仓库路径 |  | ||||||
|         maven { url "file:///sdcard/.m2/repository" } |  | ||||||
|          |  | ||||||
|         // Nexus Maven 库地址 |         // Nexus Maven 库地址 | ||||||
|         // "WinBoLL Release" |         // "WinBoLL Release" | ||||||
|         maven { url "https://nexus.winboll.cc/repository/maven-public/" } |         maven { url "https://nexus.winboll.cc/repository/maven-public/" } | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								timestamp/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								timestamp/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | ## TimpStamp | ||||||
|  | ## 时间戳工具集 | ||||||
|  |  | ||||||
|  | ## 使用要点: | ||||||
|  | 1。常驻通知栏按钮的正常使用, | ||||||
|  |    需要设置允许应用[写入剪贴板]的[始终允许]权限。 | ||||||
| @@ -24,7 +24,7 @@ android { | |||||||
|     defaultConfig { |     defaultConfig { | ||||||
|         applicationId "cc.winboll.studio.timestamp" |         applicationId "cc.winboll.studio.timestamp" | ||||||
|         minSdkVersion 24 |         minSdkVersion 24 | ||||||
|         targetSdkVersion 30 |         targetSdkVersion 29 | ||||||
|         versionCode 1 |         versionCode 1 | ||||||
|         // versionName 更新后需要手动设置  |         // versionName 更新后需要手动设置  | ||||||
|         // .winboll/winbollBuildProps.properties 文件的 stageCount=0 |         // .winboll/winbollBuildProps.properties 文件的 stageCount=0 | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| #Created by .winboll/winboll_app_build.gradle | #Created by .winboll/winboll_app_build.gradle | ||||||
| #Tue May 06 11:17:44 HKT 2025 | #Wed May 07 13:33:12 HKT 2025 | ||||||
| stageCount=1 | stageCount=12 | ||||||
| libraryProject= | libraryProject= | ||||||
| baseVersion=15.0 | baseVersion=15.0 | ||||||
| publishVersion=15.0.0 | publishVersion=15.0.11 | ||||||
| buildCount=0 | buildCount=0 | ||||||
| baseBetaVersion=15.0.1 | baseBetaVersion=15.0.12 | ||||||
|   | |||||||
| @@ -5,13 +5,17 @@ | |||||||
|  |  | ||||||
|     <!-- 运行前台服务 --> |     <!-- 运行前台服务 --> | ||||||
|     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> |     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> | ||||||
|     <uses-permission android:name="android.permission.READ_CLIPBOARD" /> |  | ||||||
|     <uses-permission android:name="android.permission.WRITE_CLIPBOARD" /> |     <!-- READ_CLIPBOARD --> | ||||||
|      |     <uses-permission android:name="android.permission.READ_CLIPBOARD"/> | ||||||
|  |  | ||||||
|  |     <!-- WRITE_CLIPBOARD --> | ||||||
|  |     <uses-permission android:name="android.permission.WRITE_CLIPBOARD"/> | ||||||
|  |  | ||||||
|     <application |     <application | ||||||
|         android:allowBackup="true" |         android:allowBackup="true" | ||||||
|         android:icon="@mipmap/ic_launcher" |         android:icon="@drawable/ic_launcher" | ||||||
|         android:roundIcon="@mipmap/ic_launcher_round" |         android:roundIcon="@drawable/ic_launcher" | ||||||
|         android:label="@string/app_name" |         android:label="@string/app_name" | ||||||
|         android:theme="@style/MyAppTheme" |         android:theme="@style/MyAppTheme" | ||||||
|         android:resizeableActivity="true" |         android:resizeableActivity="true" | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ package cc.winboll.studio.timestamp; | |||||||
|  * @Describe 主要服务 |  * @Describe 主要服务 | ||||||
|  */ |  */ | ||||||
| import android.app.Notification; | import android.app.Notification; | ||||||
|  | import android.app.NotificationManager; | ||||||
| import android.app.Service; | import android.app.Service; | ||||||
| import android.content.ComponentName; | import android.content.ComponentName; | ||||||
| import android.content.Context; | import android.content.Context; | ||||||
| @@ -25,13 +26,7 @@ import cc.winboll.studio.timestamp.receivers.ButtonClickReceiver; | |||||||
| import cc.winboll.studio.timestamp.utils.AppConfigsUtil; | import cc.winboll.studio.timestamp.utils.AppConfigsUtil; | ||||||
| import cc.winboll.studio.timestamp.utils.NotificationHelper; | import cc.winboll.studio.timestamp.utils.NotificationHelper; | ||||||
| import cc.winboll.studio.timestamp.utils.ServiceUtil; | import cc.winboll.studio.timestamp.utils.ServiceUtil; | ||||||
| import cc.winboll.studio.timestamp.utils.TimeStampRemoteViewsUtil; | import cc.winboll.studio.timestamp.utils.TimeStampUtil; | ||||||
| import java.time.Instant; |  | ||||||
| import java.time.LocalDateTime; |  | ||||||
| import java.time.ZoneId; |  | ||||||
| import java.time.format.DateTimeFormatter; |  | ||||||
| import java.util.Timer; |  | ||||||
| import java.util.TimerTask; |  | ||||||
|  |  | ||||||
| public class MainService extends Service { | public class MainService extends Service { | ||||||
|  |  | ||||||
| @@ -44,11 +39,11 @@ public class MainService extends Service { | |||||||
|     Notification mNotification; |     Notification mNotification; | ||||||
|     RemoteViews mRemoteViews; |     RemoteViews mRemoteViews; | ||||||
|     TextView mtvTimeStamp; |     TextView mtvTimeStamp; | ||||||
|     Timer mTimer; |     //Timer mTimer; | ||||||
|     private static boolean _mIsServiceAlive; |     private static boolean _mIsServiceAlive; | ||||||
|     public static final String EXTRA_APKFILEPATH = "EXTRA_APKFILEPATH"; |     public static final String EXTRA_APKFILEPATH = "EXTRA_APKFILEPATH"; | ||||||
|     final static int MSG_INSTALL_APK = 0; |     final static int MSG_INSTALL_APK = 0; | ||||||
|     MyHandler mMyHandler; |     static MyHandler _MyHandler; | ||||||
|     MyServiceConnection mMyServiceConnection; |     MyServiceConnection mMyServiceConnection; | ||||||
|     MainActivity mInstallCompletedFollowUpActivity; |     MainActivity mInstallCompletedFollowUpActivity; | ||||||
|  |  | ||||||
| @@ -60,10 +55,9 @@ public class MainService extends Service { | |||||||
|     @Override |     @Override | ||||||
|     public void onCreate() { |     public void onCreate() { | ||||||
|         super.onCreate(); |         super.onCreate(); | ||||||
|         // 创建 RemoteViews 对象,并使用包含自定义 View 的布局 |         mNotificationHelper = new NotificationHelper(); | ||||||
|         //mRemoteViews = new RemoteViews(getPackageName(), R.layout.remoteviews_timestamp); |         mNotificationHelper.createServiceNotificationChannel(this); | ||||||
|  |          | ||||||
|  |  | ||||||
|         // 创建广播接收器实例 |         // 创建广播接收器实例 | ||||||
|         mButtonClickReceiver = new ButtonClickReceiver(); |         mButtonClickReceiver = new ButtonClickReceiver(); | ||||||
|  |  | ||||||
| @@ -76,7 +70,7 @@ public class MainService extends Service { | |||||||
|         LogUtils.d(TAG, "onCreate()"); |         LogUtils.d(TAG, "onCreate()"); | ||||||
|         _mIsServiceAlive = false; |         _mIsServiceAlive = false; | ||||||
|  |  | ||||||
|         mMyHandler = new MyHandler(); |         _MyHandler = new MyHandler(); | ||||||
|         if (mMyServiceConnection == null) { |         if (mMyServiceConnection == null) { | ||||||
|             mMyServiceConnection = new MyServiceConnection(); |             mMyServiceConnection = new MyServiceConnection(); | ||||||
|         } |         } | ||||||
| @@ -101,17 +95,18 @@ public class MainService extends Service { | |||||||
|                 wakeupAndBindAssistant(); |                 wakeupAndBindAssistant(); | ||||||
|  |  | ||||||
|                 LogUtils.d(TAG, "running..."); |                 LogUtils.d(TAG, "running..."); | ||||||
|  |                 _MyHandler.sendEmptyMessage(MSG_UPDATE_TIMESTAMP); | ||||||
|  |  | ||||||
|                 mTimer = new Timer(); | //                mTimer = new Timer(); | ||||||
|                 TimerTask task = new TimerTask() { | //                TimerTask task = new TimerTask() { | ||||||
|                     @Override | //                    @Override | ||||||
|                     public void run() { | //                    public void run() { | ||||||
|                         //System.out.println("定时任务执行了"); | //                        //System.out.println("定时任务执行了"); | ||||||
|                         mMyHandler.sendEmptyMessage(MSG_UPDATE_TIMESTAMP); | //                        mMyHandler.sendEmptyMessage(MSG_UPDATE_TIMESTAMP); | ||||||
|                     } | //                    } | ||||||
|                 }; | //                }; | ||||||
|                 // 延迟1秒后开始执行,之后每隔100毫秒执行一次 | //                // 延迟1秒后开始执行,之后每隔100毫秒执行一次 | ||||||
|                 mTimer.schedule(task, 1000, 100); | //                mTimer.schedule(task, 1000, 100); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -125,11 +120,17 @@ public class MainService extends Service { | |||||||
|     @Override |     @Override | ||||||
|     public void onDestroy() { |     public void onDestroy() { | ||||||
|         super.onDestroy(); |         super.onDestroy(); | ||||||
|         if (mTimer != null) { |  | ||||||
|             mTimer.cancel(); |  | ||||||
|         } | //        if (mTimer != null) { | ||||||
|  | //            mTimer.cancel(); | ||||||
|  | //        } | ||||||
|  |  | ||||||
|  |         NotificationManager notificationManager = getSystemService(NotificationManager.class); | ||||||
|  |         notificationManager.cancelAll(); | ||||||
|  |  | ||||||
|         _mIsServiceAlive = false; |         _mIsServiceAlive = false; | ||||||
|  |         _MyHandler = null; | ||||||
|         LogUtils.d(TAG, "onDestroy()"); |         LogUtils.d(TAG, "onDestroy()"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -207,15 +208,10 @@ public class MainService extends Service { | |||||||
|             switch (message.what) { |             switch (message.what) { | ||||||
|                 case MSG_UPDATE_TIMESTAMP: |                 case MSG_UPDATE_TIMESTAMP: | ||||||
|                     { |                     { | ||||||
|                         long currentMillis = System.currentTimeMillis(); |                         String szTimeStampShowString = TimeStampUtil.getInstance(MainService.this).getTimeStampShowString(); | ||||||
|                         Instant instant = Instant.ofEpochMilli(currentMillis); |                         mNotificationHelper.sendForegroundNotification(MainService.this, "时间戳:\n" + szTimeStampShowString + "\n已拷贝到剪贴板。"); | ||||||
|                         LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); |  | ||||||
|                         String szTimeStampFormatString = AppConfigsUtil.getInstance(MainService.this).getAppConfigsModel().getTimeStampFormatString(); |  | ||||||
|                         DateTimeFormatter formatter = DateTimeFormatter.ofPattern(szTimeStampFormatString); |  | ||||||
|                         String formattedDateTime = ldt.format(formatter); |  | ||||||
|                         TimeStampRemoteViewsUtil.getInstance(MainService.this).showNotification(formattedDateTime); |  | ||||||
|  |  | ||||||
|                         //LogUtils.d(TAG, "Hello, World"); |                         LogUtils.d(TAG, "Hello, World! " + szTimeStampShowString); | ||||||
|                         break; |                         break; | ||||||
|                     } |                     } | ||||||
|                 default: |                 default: | ||||||
| @@ -224,4 +220,10 @@ public class MainService extends Service { | |||||||
|             super.handleMessage(message); |             super.handleMessage(message); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public static void updateCopiedTimeStamp() { | ||||||
|  |         if (_MyHandler != null) { | ||||||
|  |             _MyHandler.sendEmptyMessage(MSG_UPDATE_TIMESTAMP); | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -25,8 +25,8 @@ public class AppConfigsModel extends BaseBean { | |||||||
|  |  | ||||||
|     public AppConfigsModel() { |     public AppConfigsModel() { | ||||||
|         this.isEnableService = false; |         this.isEnableService = false; | ||||||
|         this.timeStampFormatString = "yyyy-MM-dd HH:mm:ss"; |         this.timeStampFormatString = "yyyy-MM-dd HH:mm:ss SSS"; | ||||||
|         this.timeStampCopyFormatString = "yyyy_MM_dd-HH_mm_ss"; |         this.timeStampCopyFormatString = "yyyy_MM_dd-HH_mm_ss-SSS"; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void setTimeStampCopyFormatString(String timeStampCopyFormatString) { |     public void setTimeStampCopyFormatString(String timeStampCopyFormatString) { | ||||||
| @@ -63,6 +63,7 @@ public class AppConfigsModel extends BaseBean { | |||||||
|         super.writeThisToJsonWriter(jsonWriter); |         super.writeThisToJsonWriter(jsonWriter); | ||||||
|         jsonWriter.name("isEnableService").value(isEnableService()); |         jsonWriter.name("isEnableService").value(isEnableService()); | ||||||
|         jsonWriter.name("timeStampFormatString").value(getTimeStampFormatString()); |         jsonWriter.name("timeStampFormatString").value(getTimeStampFormatString()); | ||||||
|  |         jsonWriter.name("timeStampCopyFormatString").value(getTimeStampCopyFormatString()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
| @@ -72,6 +73,8 @@ public class AppConfigsModel extends BaseBean { | |||||||
|                 setIsEnableService(jsonReader.nextBoolean()); |                 setIsEnableService(jsonReader.nextBoolean()); | ||||||
|             } else if (name.equals("timeStampFormatString")) { |             } else if (name.equals("timeStampFormatString")) { | ||||||
|                 setTimeStampFormatString(jsonReader.nextString()); |                 setTimeStampFormatString(jsonReader.nextString()); | ||||||
|  |             } else if (name.equals("timeStampCopyFormatString")) { | ||||||
|  |                 setTimeStampCopyFormatString(jsonReader.nextString()); | ||||||
|             } else { |             } else { | ||||||
|                 return false; |                 return false; | ||||||
|             } |             } | ||||||
|   | |||||||
| @@ -10,12 +10,9 @@ import android.content.Context; | |||||||
| import android.content.Intent; | import android.content.Intent; | ||||||
| import android.widget.Toast; | import android.widget.Toast; | ||||||
| import cc.winboll.studio.libappbase.LogUtils; | import cc.winboll.studio.libappbase.LogUtils; | ||||||
| import cc.winboll.studio.timestamp.utils.AppConfigsUtil; | import cc.winboll.studio.timestamp.MainService; | ||||||
| import cc.winboll.studio.timestamp.utils.ClipboardUtil; | import cc.winboll.studio.timestamp.utils.ClipboardUtil; | ||||||
| import java.time.Instant; | import cc.winboll.studio.timestamp.utils.TimeStampUtil; | ||||||
| import java.time.LocalDateTime; |  | ||||||
| import java.time.ZoneId; |  | ||||||
| import java.time.format.DateTimeFormatter; |  | ||||||
|  |  | ||||||
| public class ButtonClickReceiver extends BroadcastReceiver { | public class ButtonClickReceiver extends BroadcastReceiver { | ||||||
|  |  | ||||||
| @@ -28,17 +25,12 @@ public class ButtonClickReceiver extends BroadcastReceiver { | |||||||
|         LogUtils.d(TAG, "onReceive"); |         LogUtils.d(TAG, "onReceive"); | ||||||
|         if (intent.getAction().equals(BUTTON_COPYTIMESTAMP_ACTION)) { |         if (intent.getAction().equals(BUTTON_COPYTIMESTAMP_ACTION)) { | ||||||
|             // 在这里编写按钮点击后要执行的代码 |             // 在这里编写按钮点击后要执行的代码 | ||||||
|             long currentMillis = System.currentTimeMillis(); |             TimeStampUtil.getInstance(context).genTimeStamp(); | ||||||
|             Instant instant = Instant.ofEpochMilli(currentMillis); |             ClipboardUtil.copyTextToClipboard(context, TimeStampUtil.getInstance(context).getTimeStampCopyString()); | ||||||
|             LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); |  | ||||||
|             String szTimeStampFormatString = AppConfigsUtil.getInstance(context).getAppConfigsModel().getTimeStampCopyFormatString(); |  | ||||||
|             DateTimeFormatter formatter = DateTimeFormatter.ofPattern(szTimeStampFormatString); |  | ||||||
|             String formattedDateTime = ldt.format(formatter); |  | ||||||
|  |  | ||||||
|             ClipboardUtil.copyTextToClipboard(context, formattedDateTime); |  | ||||||
|  |  | ||||||
|             // 比如显示一个Toast |             // 比如显示一个Toast | ||||||
|             Toast.makeText(context, formattedDateTime + " 已复制", Toast.LENGTH_SHORT).show(); |             Toast.makeText(context, "时间戳:\n" + TimeStampUtil.getInstance(context).getTimeStampCopyString() + "\n已拷贝到剪贴板。", Toast.LENGTH_SHORT).show(); | ||||||
|  |             MainService.updateCopiedTimeStamp(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -22,12 +22,12 @@ public class AppConfigsUtil { | |||||||
|      |      | ||||||
|     AppConfigsUtil(Context context) { |     AppConfigsUtil(Context context) { | ||||||
|         this.mContext = context; |         this.mContext = context; | ||||||
|  |         loadAppConfigs(); | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     public synchronized static AppConfigsUtil getInstance(Context context){ |     public synchronized static AppConfigsUtil getInstance(Context context){ | ||||||
|         if(_AppConfigsUtil == null) { |         if(_AppConfigsUtil == null) { | ||||||
|             _AppConfigsUtil = new AppConfigsUtil(context); |             _AppConfigsUtil = new AppConfigsUtil(context); | ||||||
|             _AppConfigsUtil.loadAppConfigs(); |  | ||||||
|         } |         } | ||||||
|         return _AppConfigsUtil; |         return _AppConfigsUtil; | ||||||
|     } |     } | ||||||
| @@ -42,8 +42,8 @@ public class AppConfigsUtil { | |||||||
|         if (appConfigsModel != null) { |         if (appConfigsModel != null) { | ||||||
|             mAppConfigsModel = appConfigsModel; |             mAppConfigsModel = appConfigsModel; | ||||||
|         } else { |         } else { | ||||||
|             saveAppConfigs(new AppConfigsModel()); |             mAppConfigsModel = new AppConfigsModel(); | ||||||
|             _AppConfigsUtil = this; |             saveAppConfigs(mAppConfigsModel); | ||||||
|         } |         } | ||||||
|         return mAppConfigsModel; |         return mAppConfigsModel; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -0,0 +1,71 @@ | |||||||
|  | package cc.winboll.studio.timestamp.utils; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @Author ZhanGSKen | ||||||
|  |  * @Date 2025/05/07 02:38 | ||||||
|  |  * @Describe AudioPlayer | ||||||
|  |  */ | ||||||
|  | import android.content.Context; | ||||||
|  | import android.media.MediaPlayer; | ||||||
|  | import android.net.Uri; | ||||||
|  | import android.util.Log; | ||||||
|  |  | ||||||
|  | public class AudioPlayerMP3Util { | ||||||
|  |  | ||||||
|  |     public static final String TAG = "AudioPlayer"; | ||||||
|  |  | ||||||
|  |     private static MediaPlayer mediaPlayer; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 播放指定的 MP3 文件 | ||||||
|  |      * | ||||||
|  |      * @param context 上下文 | ||||||
|  |      * @param mp3FilePath MP3 文件的路径,例如:"/storage/emulated/0/Music/song.mp3" | ||||||
|  |      */ | ||||||
|  |     public static void playMp3(Context context, String mp3FilePath) { | ||||||
|  |         if (mediaPlayer != null && mediaPlayer.isPlaying()) { | ||||||
|  |             mediaPlayer.stop(); | ||||||
|  |             mediaPlayer.release(); | ||||||
|  |             mediaPlayer = null; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         try { | ||||||
|  |             mediaPlayer = new MediaPlayer(); | ||||||
|  |             Uri uri = Uri.parse(mp3FilePath); | ||||||
|  |             mediaPlayer.setDataSource(context, uri); | ||||||
|  |             mediaPlayer.prepare(); | ||||||
|  |             mediaPlayer.start(); | ||||||
|  |  | ||||||
|  |             mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { | ||||||
|  |                     @Override | ||||||
|  |                     public void onCompletion(MediaPlayer mp) { | ||||||
|  |                         releaseMediaPlayer(); | ||||||
|  |                     } | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() { | ||||||
|  |                     @Override | ||||||
|  |                     public boolean onError(MediaPlayer mp, int what, int extra) { | ||||||
|  |                         Log.e("AudioPlayer", "播放音频时出错: what=" + what + ", extra=" + extra); | ||||||
|  |                         releaseMediaPlayer(); | ||||||
|  |                         return true; | ||||||
|  |                     } | ||||||
|  |                 }); | ||||||
|  |         } catch (Exception e) { | ||||||
|  |             e.printStackTrace(); | ||||||
|  |             releaseMediaPlayer(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 释放 MediaPlayer 资源 | ||||||
|  |      */ | ||||||
|  |     private static void releaseMediaPlayer() { | ||||||
|  |         if (mediaPlayer != null) { | ||||||
|  |             mediaPlayer.stop(); | ||||||
|  |             mediaPlayer.release(); | ||||||
|  |             mediaPlayer = null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| @@ -0,0 +1,69 @@ | |||||||
|  | package cc.winboll.studio.timestamp.utils; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @Author ZhanGSKen | ||||||
|  |  * @Date 2025/05/07 02:31 | ||||||
|  |  * @Describe AudioPlayerUtil | ||||||
|  |  */ | ||||||
|  | import android.content.Context; | ||||||
|  | import android.media.MediaPlayer; | ||||||
|  | import android.net.Uri; | ||||||
|  | import android.util.Log; | ||||||
|  |  | ||||||
|  | public class AudioPlayerUriUtil { | ||||||
|  |  | ||||||
|  |     public static final String TAG = "AudioPlayerUtil"; | ||||||
|  |  | ||||||
|  |     private static MediaPlayer mediaPlayer; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 播放指定Uri的音频 | ||||||
|  |      * @param context 上下文 | ||||||
|  |      * @param audioUri 音频的Uri | ||||||
|  |      */ | ||||||
|  |     public static void playAudio(Context context, Uri audioUri) { | ||||||
|  |         if (mediaPlayer != null && mediaPlayer.isPlaying()) { | ||||||
|  |             mediaPlayer.stop(); | ||||||
|  |             mediaPlayer.release(); | ||||||
|  |             mediaPlayer = null; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         try { | ||||||
|  |             mediaPlayer = new MediaPlayer(); | ||||||
|  |             mediaPlayer.setDataSource(context, audioUri); | ||||||
|  |             mediaPlayer.prepare(); | ||||||
|  |             mediaPlayer.start(); | ||||||
|  |  | ||||||
|  |             mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { | ||||||
|  |                     @Override | ||||||
|  |                     public void onCompletion(MediaPlayer mp) { | ||||||
|  |                         releaseMediaPlayer(); | ||||||
|  |                     } | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() { | ||||||
|  |                     @Override | ||||||
|  |                     public boolean onError(MediaPlayer mp, int what, int extra) { | ||||||
|  |                         Log.e("AudioPlayer", "播放音频时出错: what=" + what + ", extra=" + extra); | ||||||
|  |                         releaseMediaPlayer(); | ||||||
|  |                         return true; | ||||||
|  |                     } | ||||||
|  |                 }); | ||||||
|  |         } catch (Exception e) { | ||||||
|  |             e.printStackTrace(); | ||||||
|  |             releaseMediaPlayer(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 释放MediaPlayer资源 | ||||||
|  |      */ | ||||||
|  |     private static void releaseMediaPlayer() { | ||||||
|  |         if (mediaPlayer != null) { | ||||||
|  |             mediaPlayer.stop(); | ||||||
|  |             mediaPlayer.release(); | ||||||
|  |             mediaPlayer = null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| @@ -12,212 +12,190 @@ import android.app.PendingIntent; | |||||||
| import android.content.Context; | import android.content.Context; | ||||||
| import android.content.Intent; | import android.content.Intent; | ||||||
| import android.graphics.BitmapFactory; | import android.graphics.BitmapFactory; | ||||||
| import android.os.Build; | import android.graphics.Color; | ||||||
|  | import android.media.RingtoneManager; | ||||||
|  | import android.net.Uri; | ||||||
| import android.widget.RemoteViews; | import android.widget.RemoteViews; | ||||||
| import androidx.annotation.RequiresApi; | import cc.winboll.studio.timestamp.MainActivity; | ||||||
| import androidx.core.app.NotificationCompat; | import cc.winboll.studio.timestamp.MainService; | ||||||
| import cc.winboll.studio.libappbase.LogUtils; |  | ||||||
| import cc.winboll.studio.timestamp.R; | import cc.winboll.studio.timestamp.R; | ||||||
| import java.util.HashMap; | import cc.winboll.studio.timestamp.receivers.ButtonClickReceiver; | ||||||
| import java.util.List; |  | ||||||
| import java.util.Map; |  | ||||||
|  |  | ||||||
| public class NotificationHelper { | public class NotificationHelper { | ||||||
|     public static final String TAG = "NotificationHelper"; |  | ||||||
|  |  | ||||||
|     // 渠道ID和名称 |     public static final String TAG = "NotificationUtil"; | ||||||
|     private static final String CHANNEL_ID_FOREGROUND = "foreground_channel"; |     public static final int ID_MSG_SERVICE = 10000; | ||||||
|     private static final String CHANNEL_NAME_FOREGROUND = "Foreground Service"; |  | ||||||
|     private static final String CHANNEL_ID_TEMPORARY = "temporary_channel"; |  | ||||||
|     private static final String CHANNEL_NAME_TEMPORARY = "Temporary Notifications"; |  | ||||||
|  |  | ||||||
|     // 通知ID |     static final String szSMSChannelID = "1"; | ||||||
|     public static final int FOREGROUND_NOTIFICATION_ID = 1001; |  | ||||||
|     public static final int TEMPORARY_NOTIFICATION_ID = 2001; |  | ||||||
|  |  | ||||||
|     private final Context mContext; |     static final String szServiceChannelID = "0"; | ||||||
|     private final NotificationManager mNotificationManager; |  | ||||||
|  |  | ||||||
|     // 示例:维护当前使用的渠道ID列表 |  | ||||||
|     // 键:渠道ID,值:渠道重要性级别 |  | ||||||
|     Map<String, Integer> activeChannelConfigs = new HashMap<>(); |  | ||||||
|  |  | ||||||
|     public NotificationHelper(Context context) { |     //static int mNumSendForegroundNotification = 10000; | ||||||
|  |     //static int mNumSendSMSNotification = 20000; | ||||||
|  |     Context mContext; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     public NotificationManager createServiceNotificationChannel(Context context) { | ||||||
|         mContext = context; |         mContext = context; | ||||||
|         mNotificationManager = context.getSystemService(NotificationManager.class); |         //创建通知渠道ID | ||||||
|  |         String channelId = szServiceChannelID; | ||||||
|         // 初始化配置 |         //创建通知渠道名称 | ||||||
|         activeChannelConfigs.put( |         String channelName = "Service Message"; | ||||||
|             CHANNEL_ID_FOREGROUND, |         //创建通知渠道重要性 | ||||||
|             NotificationManager.IMPORTANCE_HIGH |         int importance = NotificationManager.IMPORTANCE_MIN; | ||||||
|         ); |         NotificationChannel channel = new NotificationChannel(channelId, channelName, importance); | ||||||
|         activeChannelConfigs.put( |  | ||||||
|             CHANNEL_ID_TEMPORARY, |  | ||||||
|             NotificationManager.IMPORTANCE_DEFAULT |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         createNotificationChannels(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @RequiresApi(api = Build.VERSION_CODES.O) |  | ||||||
|     private void createNotificationChannels() { |  | ||||||
|         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |  | ||||||
|             createForegroundChannel(); |  | ||||||
|             createTemporaryChannel(); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @RequiresApi(api = Build.VERSION_CODES.O) |  | ||||||
|     private void createForegroundChannel() { |  | ||||||
|         NotificationChannel channel = new NotificationChannel( |  | ||||||
|             CHANNEL_ID_FOREGROUND, |  | ||||||
|             CHANNEL_NAME_FOREGROUND, |  | ||||||
|             NotificationManager.IMPORTANCE_LOW |  | ||||||
|         ); |  | ||||||
|         channel.setDescription("Persistent service notifications"); |  | ||||||
|         channel.setSound(null, null); |         channel.setSound(null, null); | ||||||
|         channel.enableVibration(false); |         NotificationManager notificationManager = (NotificationManager) context.getSystemService( | ||||||
|         mNotificationManager.createNotificationChannel(channel); |             Context.NOTIFICATION_SERVICE); | ||||||
|  |         notificationManager.createNotificationChannel(channel); | ||||||
|  |         return notificationManager; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @RequiresApi(api = Build.VERSION_CODES.O) |     public NotificationManager createSMSNotificationChannel(Context context) { | ||||||
|     private void createTemporaryChannel() { |         //创建通知渠道ID | ||||||
|         NotificationChannel channel = new NotificationChannel( |         String channelId = szSMSChannelID; | ||||||
|             CHANNEL_ID_TEMPORARY, |         //创建通知渠道名称 | ||||||
|             CHANNEL_NAME_TEMPORARY, |         String channelName = "SMS Message"; | ||||||
|             NotificationManager.IMPORTANCE_HIGH |         //创建通知渠道重要性 | ||||||
|         ); |         int importance = NotificationManager.IMPORTANCE_HIGH; | ||||||
|         channel.setDescription("Temporary alert notifications"); |         NotificationChannel channel = new NotificationChannel(channelId, channelName, importance); | ||||||
|         channel.setSound(null, null); |         channel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE), Notification.AUDIO_ATTRIBUTES_DEFAULT); | ||||||
|         channel.enableVibration(true); |         NotificationManager notificationManager = (NotificationManager) context.getSystemService( | ||||||
|         channel.setVibrationPattern(new long[]{100, 200, 300, 400}); |             Context.NOTIFICATION_SERVICE); | ||||||
|         channel.setBypassDnd(true); |         notificationManager.createNotificationChannel(channel); | ||||||
|         mNotificationManager.createNotificationChannel(channel); |         return notificationManager; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // 显示常驻通知(通常用于前台服务) |     // 创建通知 | ||||||
|     public Notification showForegroundNotification(Intent intent, String title, String content) { |     // | ||||||
|         PendingIntent pendingIntent = createPendingIntent(intent); |     public void sendForegroundNotification(MainService service, String message) { | ||||||
|  |         //创建Notification,传入Context和channelId | ||||||
|  |         Intent intent = new Intent();//这个intent会传给目标,可以使用getIntent来获取 | ||||||
|  |         intent.setClass(mContext, MainActivity.class); | ||||||
|  |  | ||||||
|         Notification notification = new NotificationCompat.Builder(mContext, CHANNEL_ID_FOREGROUND) |         //这里放一个count用来区分每一个通知 | ||||||
|             .setSmallIcon(R.drawable.ic_launcher) |         //intent.putExtra("intent", "intent--->" + count);//这里设置一个数据,带过去 | ||||||
|             .setLargeIcon(BitmapFactory.decodeResource(mContext.getResources(), R.drawable.ic_launcher)) |  | ||||||
|             //.setContentTitle(title) |  | ||||||
|             .setContentTitle(content) |  | ||||||
|             //.setContentText(content) |  | ||||||
|             .setContentIntent(pendingIntent) |  | ||||||
|             .setPriority(NotificationCompat.PRIORITY_LOW) |  | ||||||
|             .setOngoing(true) |  | ||||||
|             .build(); |  | ||||||
|  |  | ||||||
|         mNotificationManager.notify(FOREGROUND_NOTIFICATION_ID, notification); |         //参数1:context 上下文对象 | ||||||
|         return notification; |         //参数2:发送者私有的请求码(Private request code for the sender) | ||||||
|     } |         //参数3:intent 意图对象 | ||||||
|      |         //参数4:必须为FLAG_ONE_SHOT,FLAG_NO_CREATE,FLAG_CANCEL_CURRENT,FLAG_UPDATE_CURRENT,中的一个 | ||||||
|  |         PendingIntent mForegroundPendingIntent = PendingIntent.getActivity(service, ID_MSG_SERVICE, intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_CANCEL_CURRENT); | ||||||
|  |  | ||||||
|     // 显示常驻通知(通常用于前台服务) |         Notification mForegroundNotification = new Notification.Builder(service, szServiceChannelID) | ||||||
|     public Notification showCustomForegroundNotification(Intent intent, RemoteViews contentView, RemoteViews bigContentView) { |  | ||||||
|         PendingIntent pendingIntent = createPendingIntent(intent); |  | ||||||
|          |  | ||||||
|          |  | ||||||
|         Notification notification = new NotificationCompat.Builder(mContext, CHANNEL_ID_TEMPORARY) |  | ||||||
|             .setSmallIcon(R.drawable.ic_launcher) |  | ||||||
|             .setLargeIcon(BitmapFactory.decodeResource(mContext.getResources(), R.drawable.ic_launcher)) |  | ||||||
|             //.setContentTitle(title) |  | ||||||
|             .setContentIntent(pendingIntent) |  | ||||||
|             .setContent(contentView) |  | ||||||
|             .setCustomBigContentView(bigContentView) |  | ||||||
|             .setPriority(NotificationCompat.PRIORITY_HIGH) |  | ||||||
|             .setAutoCancel(true) |             .setAutoCancel(true) | ||||||
|             .setOngoing(true) |             //.setContentTitle(nessageNotificationBean.getTitle()) | ||||||
|             .build(); |             //.setContentText(nessageNotificationBean.getContent()) | ||||||
|  |             //.setContent(remoteviews) | ||||||
|         mNotificationManager.notify(FOREGROUND_NOTIFICATION_ID, notification); |             .setWhen(System.currentTimeMillis()) | ||||||
|         return notification; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // 显示临时通知(自动消失) |  | ||||||
|     public void showTemporaryNotification(Intent intent, String title, String content) { |  | ||||||
|         showTemporaryNotification(intent, TEMPORARY_NOTIFICATION_ID, title, content); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // 显示临时通知(自动消失) |  | ||||||
|     public void showTemporaryNotification(Intent intent, int notificationID, String title, String content) { |  | ||||||
|         PendingIntent pendingIntent = createPendingIntent(intent); |  | ||||||
|  |  | ||||||
|         Notification notification = new NotificationCompat.Builder(mContext, CHANNEL_ID_TEMPORARY) |  | ||||||
|             .setSmallIcon(R.drawable.ic_launcher) |             .setSmallIcon(R.drawable.ic_launcher) | ||||||
|             .setLargeIcon(BitmapFactory.decodeResource(mContext.getResources(), R.drawable.ic_launcher)) |             //设置红色 | ||||||
|             .setContentTitle(title) |             .setColor(Color.parseColor("#F00606")) | ||||||
|             .setContentText(content) |             .setLargeIcon(BitmapFactory.decodeResource(service.getResources(), R.drawable.ic_launcher)) | ||||||
|             .setContentIntent(pendingIntent) |             .setContentIntent(mForegroundPendingIntent) | ||||||
|             .setPriority(NotificationCompat.PRIORITY_HIGH) |  | ||||||
|             .setAutoCancel(true) |  | ||||||
|             .setVibrate(new long[]{100, 200, 300, 400}) |  | ||||||
|             .build(); |             .build(); | ||||||
|  |  | ||||||
|         mNotificationManager.notify(notificationID, notification); |         // 创建 RemoteViews 对象,加载布局 | ||||||
|     } |         RemoteViews remoteViews = new RemoteViews(mContext.getPackageName(), R.layout.custom_notification_layout); | ||||||
|  |  | ||||||
|     // 创建自定义布局通知(可扩展) |         // 自定义 TextView 的文本 | ||||||
|     public void showCustomNotification(Intent intent, RemoteViews contentView, RemoteViews bigContentView) { |         remoteViews.setTextViewText(R.id.tv_timestamp, message); | ||||||
|         PendingIntent pendingIntent = createPendingIntent(intent); |         // 自定义 TextView 的文本颜色 | ||||||
|  |         remoteViews.setTextColor(R.id.tv_timestamp, mContext.getResources().getColor(R.color.colorAccent, null)); | ||||||
|  |         // 这里虽然不能直接设置字体大小,但可以通过反射等方式尝试(不推荐,且有兼容性问题) | ||||||
|  |  | ||||||
|         Notification notification = new NotificationCompat.Builder(mContext, CHANNEL_ID_TEMPORARY) |         // 创建点击通知后的意图 | ||||||
|             .setSmallIcon(R.drawable.ic_launcher) |         Intent intentMain = new Intent(mContext, MainActivity.class); | ||||||
|             .setContentIntent(pendingIntent) |         PendingIntent pendingMainIntent = PendingIntent.getActivity(mContext, 0, intentMain, PendingIntent.FLAG_UPDATE_CURRENT); | ||||||
|             .setContent(contentView) |         // 设置通知的点击事件 | ||||||
|             .setCustomBigContentView(bigContentView) |         remoteViews.setOnClickPendingIntent(R.id.tv_timestamp, pendingMainIntent); | ||||||
|             .setPriority(NotificationCompat.PRIORITY_HIGH) |  | ||||||
|             .setAutoCancel(true) |  | ||||||
|             .build(); |  | ||||||
|  |  | ||||||
|         mNotificationManager.notify(TEMPORARY_NOTIFICATION_ID + 1, notification); |         // 创建点击按钮后要发送的广播 Intent | ||||||
|     } |         Intent broadcastIntent = new Intent(ButtonClickReceiver.BUTTON_COPYTIMESTAMP_ACTION); | ||||||
|  |         android.app.PendingIntent pendingIntent = android.app.PendingIntent.getBroadcast( | ||||||
|     // 取消所有通知 |  | ||||||
|     public void cancelAllNotifications() { |  | ||||||
|         mNotificationManager.cancelAll(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // 取消指定通知 |  | ||||||
|     public void cancelNotification(int notificationID) { |  | ||||||
|         mNotificationManager.cancel(notificationID); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // 创建PendingIntent(兼容不同API版本) |  | ||||||
|     private PendingIntent createPendingIntent(Intent intent) { |  | ||||||
|         int flags = PendingIntent.FLAG_UPDATE_CURRENT; |  | ||||||
| //        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { |  | ||||||
| //            flags |= PendingIntent.FLAG_IMMUTABLE; |  | ||||||
| //        } |  | ||||||
|         return PendingIntent.getActivity( |  | ||||||
|             mContext, |             mContext, | ||||||
|             0, |             0, | ||||||
|             intent, |             broadcastIntent, | ||||||
|             flags |             android.app.PendingIntent.FLAG_UPDATE_CURRENT | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
|  |         // 为按钮设置点击事件 | ||||||
|  |         remoteViews.setOnClickPendingIntent(R.id.btn_copytimestamp, pendingIntent); | ||||||
|  |  | ||||||
|  |         mForegroundNotification.contentView = remoteViews; | ||||||
|  |         mForegroundNotification.bigContentView = remoteViews; | ||||||
|  |  | ||||||
|  |         service.startForeground(ID_MSG_SERVICE, mForegroundNotification); | ||||||
|  |  | ||||||
|  |         // 播放默认短信铃声 | ||||||
|  |         Uri defaultSmsRingtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); | ||||||
|  |         AudioPlayerUriUtil.playAudio(service, defaultSmsRingtoneUri); | ||||||
|  |  | ||||||
|  |         // 播放应用铃声 | ||||||
|  |         // 获取MP3文件的Uri | ||||||
|  |         Uri soundUri = Uri.parse("android.resource://" + service.getPackageName() + "/" + R.raw.diweiyi); | ||||||
|  |         AudioPlayerUriUtil.playAudio(service, soundUri); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| //    public void sendSMSReceivedMessage(int notificationID, String szPhone, String szBody) { | //    public void sendSMSNotification(Context context, MessageNotificationBean messageNotificationBean) { | ||||||
| //        Intent intent = new Intent(mContext, SMSActivity.class); | //        NotificationManager notificationManager = (NotificationManager) context.getSystemService( | ||||||
| //        intent.putExtra(SMSActivity.EXTRA_PHONE, szPhone); | //            Context.NOTIFICATION_SERVICE); | ||||||
| //        String szTitle = mContext.getString(R.string.text_smsfrom)  + "<" + szPhone + ">"; | //        /*NotificationManager notificationManager = createSMSNotificationChannel(context); | ||||||
| //        String szContent = "[ " + szBody + " ]"; | //         if (notificationManager == null) { | ||||||
| //        showTemporaryNotification(intent, notificationID, szTitle, szContent); | //         LogUtils.d(TAG, "createSMSNotificationChannel failed."); | ||||||
|  | //         return; | ||||||
|  | //         }*/ | ||||||
|  | // | ||||||
|  | //        //创建Notification,传入Context和channelId | ||||||
|  | //        Intent intent = new Intent(context, SMSActivity.class); | ||||||
|  | //        intent.putExtra(SMSActivity.EXTRA_PHONE, messageNotificationBean.getPhone()); | ||||||
|  | //        LogUtils.d(TAG, "sendSMSNotification(...) message.getPhone() is : " + messageNotificationBean.getPhone()); | ||||||
|  | //        //Intent intent = new Intent();//这个intent会传给目标,可以使用getIntent来获取 | ||||||
|  | //        //intent.setClass(context, MainActivity.class); | ||||||
|  | //        //这里放一个count用来区分每一个通知 | ||||||
|  | //        //intent.putExtra("intent", "intent--->" + count);//这里设置一个数据,带过去 | ||||||
|  | // | ||||||
|  | //        //参数1:context 上下文对象 | ||||||
|  | //        //参数2:发送者私有的请求码(Private request code for the sender) | ||||||
|  | //        //参数3:intent 意图对象 | ||||||
|  | //        //参数4:必须为FLAG_ONE_SHOT,FLAG_NO_CREATE,FLAG_CANCEL_CURRENT,FLAG_UPDATE_CURRENT,中的一个 | ||||||
|  | //        PendingIntent mRemindPendingIntent = PendingIntent.getActivity(context, messageNotificationBean.getMessageId(), intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_CANCEL_CURRENT); | ||||||
|  | //        Notification mSMSNotification = new Notification.Builder(context, szSMSChannelID) | ||||||
|  | //            .setAutoCancel(true) | ||||||
|  | //            .setContentTitle(messageNotificationBean.getTitle()) | ||||||
|  | //            .setContentText(messageNotificationBean.getContent()) | ||||||
|  | //            .setWhen(System.currentTimeMillis()) | ||||||
|  | //            .setSmallIcon(R.drawable.ic_launcher) | ||||||
|  | //            //设置红色 | ||||||
|  | //            .setColor(Color.parseColor("#F00606")) | ||||||
|  | //            .setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_launcher)) | ||||||
|  | //            .setContentIntent(mRemindPendingIntent) | ||||||
|  | //            .build(); | ||||||
|  | // | ||||||
|  | //        RemoteViews mrvSMSNotificationView = new RemoteViews(context.getPackageName(), R.layout.remoteview); | ||||||
|  | //        mrvSMSNotificationView.setTextViewText(R.id.remoteviewTextView1, messageNotificationBean.getTitle()); | ||||||
|  | //        mrvSMSNotificationView.setTextViewText(R.id.remoteviewTextView2, messageNotificationBean.getContent()); | ||||||
|  | //        mrvSMSNotificationView.setImageViewResource(R.id.remoteviewImageView1, R.drawable.ic_launcher); | ||||||
|  | //        mSMSNotification.contentView = mrvSMSNotificationView; | ||||||
|  | //        mSMSNotification.bigContentView = mrvSMSNotificationView; | ||||||
|  | //        notificationManager.notify(messageNotificationBean.getMessageId(), mSMSNotification); | ||||||
|  | //        LogUtils.d(TAG, "getMessageId is : " + Integer.toString(messageNotificationBean.getMessageId())); | ||||||
|  | // | ||||||
| //    } | //    } | ||||||
|  |  | ||||||
|     public void cleanOldChannels() { | //    public void sendSMSReceivedMessage(Context context, int nMessageId, String szPhone, String szBody) { | ||||||
|         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { | //        String szTitle = context.getString(R.string.text_smsfrom)  + "<" + szPhone + ">"; | ||||||
|             List<NotificationChannel> allChannels = mNotificationManager.getNotificationChannels(); | //        String szContent = "[ " + szBody + " ]"; | ||||||
|             for (NotificationChannel channel : allChannels) { | //        sendSMSNotification(context, new MessageNotificationBean(nMessageId, szPhone, szTitle, szContent)); | ||||||
|                 LogUtils.d(TAG, "Clean channel : " + channel.getId()); | //    } | ||||||
|                 if (!activeChannelConfigs.containsKey(channel.getId())) { |  | ||||||
|                     // 安全删除渠道 | //    public static void cancelNotification(Context context, int notificationId) { | ||||||
|                     mNotificationManager.deleteNotificationChannel(channel.getId()); | //        // 获取 NotificationManager 实例 | ||||||
|                     LogUtils.d(TAG, String.format("Deleted Channel %s", channel.getId())); | //        NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); | ||||||
|                 } | //        // 撤回指定 ID 的通知栏消息 | ||||||
|             } | //        notificationManager.cancel(notificationId); | ||||||
|         } | //    } | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,96 +0,0 @@ | |||||||
| package cc.winboll.studio.timestamp.utils; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * @Author ZhanGSKen |  | ||||||
|  * @Date 2025/05/05 21:10 |  | ||||||
|  * @Describe TimeStampRemoteViewsUtil |  | ||||||
|  */ |  | ||||||
| import android.app.NotificationChannel; |  | ||||||
| import android.app.NotificationManager; |  | ||||||
| import android.content.Context; |  | ||||||
| import android.content.Intent; |  | ||||||
| import android.os.Build; |  | ||||||
| import android.widget.RemoteViews; |  | ||||||
| import android.widget.TextView; |  | ||||||
| import androidx.core.app.NotificationCompat; |  | ||||||
| import cc.winboll.studio.timestamp.R; |  | ||||||
| import cc.winboll.studio.timestamp.receivers.ButtonClickReceiver; |  | ||||||
|  |  | ||||||
| public class TimeStampRemoteViewsUtil { |  | ||||||
|  |  | ||||||
|     public static final String TAG = "TimeStampRemoteViewsUtil"; |  | ||||||
|  |  | ||||||
|     public static final String CHANNEL_ID = "TimeStampChannel"; |  | ||||||
|  |  | ||||||
|     static volatile TimeStampRemoteViewsUtil _TimeStampRemoteViewsUtil; |  | ||||||
|     Context mContext; |  | ||||||
|     RemoteViews mRemoteViews; |  | ||||||
|     TextView mtvMessage; |  | ||||||
|  |  | ||||||
|     TimeStampRemoteViewsUtil(Context context) { |  | ||||||
|         mContext = context; |  | ||||||
|         createNotificationChannel(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static synchronized TimeStampRemoteViewsUtil getInstance(Context context) { |  | ||||||
|         if (_TimeStampRemoteViewsUtil == null) { |  | ||||||
|             _TimeStampRemoteViewsUtil = new TimeStampRemoteViewsUtil(context); |  | ||||||
|         } |  | ||||||
|         return _TimeStampRemoteViewsUtil; |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     private void createNotificationChannel() { |  | ||||||
|         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |  | ||||||
|             CharSequence name = "自定义视图通知通道"; |  | ||||||
|             String description = "用于展示自定义视图的通知通道"; |  | ||||||
|             int importance = NotificationManager.IMPORTANCE_HIGH; |  | ||||||
|             NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance); |  | ||||||
|             channel.setDescription(description); |  | ||||||
|             NotificationManager notificationManager = mContext.getSystemService(NotificationManager.class); |  | ||||||
|             notificationManager.createNotificationChannel(channel); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void showNotification(String msg) { |  | ||||||
|         if (mRemoteViews == null) { |  | ||||||
|             // 创建 RemoteViews 对象,加载布局 |  | ||||||
|             mRemoteViews = new RemoteViews(mContext.getPackageName(), R.layout.custom_notification_layout); |  | ||||||
|              |  | ||||||
|         } |  | ||||||
|         // 自定义 TextView 的文本 |  | ||||||
|         mRemoteViews.setTextViewText(R.id.tv_timestamp, msg); |  | ||||||
|         // 自定义 TextView 的文本颜色 |  | ||||||
|         mRemoteViews.setTextColor(R.id.tv_timestamp, mContext.getResources().getColor(R.color.colorAccent, null)); |  | ||||||
|         // 这里虽然不能直接设置字体大小,但可以通过反射等方式尝试(不推荐,且有兼容性问题) |  | ||||||
|          |  | ||||||
|         // 创建点击通知后的意图 |  | ||||||
|         //Intent intent = new Intent(mContext, MainActivity.class); |  | ||||||
|         //PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); |  | ||||||
|         // 设置通知的点击事件 |  | ||||||
|         //mRemoteViews.setOnClickPendingIntent(R.id.btn_copytimestamp, pendingIntent); |  | ||||||
|          |  | ||||||
|         // 创建点击按钮后要发送的广播 Intent |  | ||||||
|         Intent broadcastIntent = new Intent(ButtonClickReceiver.BUTTON_COPYTIMESTAMP_ACTION); |  | ||||||
|         android.app.PendingIntent pendingIntent = android.app.PendingIntent.getBroadcast( |  | ||||||
|             mContext, |  | ||||||
|             0, |  | ||||||
|             broadcastIntent, |  | ||||||
|             android.app.PendingIntent.FLAG_UPDATE_CURRENT |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         // 为按钮设置点击事件 |  | ||||||
|         mRemoteViews.setOnClickPendingIntent(R.id.btn_copytimestamp, pendingIntent); |  | ||||||
|          |  | ||||||
|         // 构建通知 |  | ||||||
|         NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext, CHANNEL_ID) |  | ||||||
|             .setSmallIcon(android.R.drawable.ic_dialog_info) |  | ||||||
|             .setContent(mRemoteViews) |  | ||||||
|             .setPriority(NotificationCompat.PRIORITY_HIGH) |  | ||||||
|             .setOngoing(true) |  | ||||||
|             .setAutoCancel(true); |  | ||||||
|  |  | ||||||
|         // 显示通知 |  | ||||||
|         NotificationManager notificationManager = mContext.getSystemService(NotificationManager.class); |  | ||||||
|         notificationManager.notify(1, builder.build()); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -0,0 +1,57 @@ | |||||||
|  | package cc.winboll.studio.timestamp.utils; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @Author ZhanGSKen | ||||||
|  |  * @Date 2025/05/07 11:03 | ||||||
|  |  * @Describe TimeStampUtil | ||||||
|  |  */ | ||||||
|  | import android.content.Context; | ||||||
|  | import java.time.Instant; | ||||||
|  | import java.time.LocalDateTime; | ||||||
|  | import java.time.ZoneId; | ||||||
|  | import java.time.format.DateTimeFormatter; | ||||||
|  |  | ||||||
|  | public class TimeStampUtil { | ||||||
|  |  | ||||||
|  |     public static final String TAG = "TimeStampUtil"; | ||||||
|  |  | ||||||
|  |     volatile static TimeStampUtil _TimeStampUtil; | ||||||
|  |  | ||||||
|  |     Context mContext; | ||||||
|  |     long mTimeStamp; | ||||||
|  |  | ||||||
|  |     TimeStampUtil(Context context) { | ||||||
|  |         mContext = context; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public synchronized static TimeStampUtil getInstance(Context context) { | ||||||
|  |         if (_TimeStampUtil == null) { | ||||||
|  |             _TimeStampUtil = new TimeStampUtil(context); | ||||||
|  |         } | ||||||
|  |         return _TimeStampUtil; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void genTimeStamp() { | ||||||
|  |         mTimeStamp = System.currentTimeMillis(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public String getTimeStampShowString() { | ||||||
|  |         long currentMillis = mTimeStamp; | ||||||
|  |         Instant instant = Instant.ofEpochMilli(currentMillis); | ||||||
|  |         LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); | ||||||
|  |         String szTimeStampFormatString = AppConfigsUtil.getInstance(mContext).getAppConfigsModel().getTimeStampCopyFormatString(); | ||||||
|  |         DateTimeFormatter formatter = DateTimeFormatter.ofPattern(szTimeStampFormatString); | ||||||
|  |         String formattedDateTime = ldt.format(formatter); | ||||||
|  |         return formattedDateTime; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public String getTimeStampCopyString() { | ||||||
|  |         long currentMillis = mTimeStamp; | ||||||
|  |         Instant instant = Instant.ofEpochMilli(currentMillis); | ||||||
|  |         LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); | ||||||
|  |         String szTimeStampFormatString = AppConfigsUtil.getInstance(mContext).getAppConfigsModel().getTimeStampCopyFormatString(); | ||||||
|  |         DateTimeFormatter formatter = DateTimeFormatter.ofPattern(szTimeStampFormatString); | ||||||
|  |         String formattedDateTime = ldt.format(formatter); | ||||||
|  |         return formattedDateTime; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										
											BIN
										
									
								
								timestamp/src/main/res/drawable/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								timestamp/src/main/res/drawable/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 10 KiB | 
| @@ -28,21 +28,21 @@ | |||||||
| 		<Switch | 		<Switch | ||||||
| 			android:layout_width="match_parent" | 			android:layout_width="match_parent" | ||||||
| 			android:layout_height="wrap_content" | 			android:layout_height="wrap_content" | ||||||
| 			android:text="启用通知栏计时器" | 			android:text="启用时间戳常驻通知栏" | ||||||
| 			android:id="@+id/activitymainSwitch1" | 			android:id="@+id/activitymainSwitch1" | ||||||
| 			android:onClick="onSetMainServiceStatus"/> | 			android:onClick="onSetMainServiceStatus"/> | ||||||
|  |  | ||||||
| 	</LinearLayout> | 	</LinearLayout> | ||||||
|  |  | ||||||
| 	<LinearLayout | 	<LinearLayout | ||||||
| 		android:orientation="horizontal" | 		android:orientation="vertical" | ||||||
| 		android:layout_width="match_parent" | 		android:layout_width="match_parent" | ||||||
| 		android:layout_height="wrap_content"> | 		android:layout_height="wrap_content"> | ||||||
|  |  | ||||||
| 		<TextView | 		<TextView | ||||||
| 			android:layout_width="wrap_content" | 			android:layout_width="wrap_content" | ||||||
| 			android:layout_height="wrap_content" | 			android:layout_height="wrap_content" | ||||||
| 			android:text="Show Format :" | 			android:text="Show Format Preview:" | ||||||
| 			android:paddingRight="10dp"/> | 			android:paddingRight="10dp"/> | ||||||
|  |  | ||||||
| 		<TextView | 		<TextView | ||||||
| @@ -69,7 +69,7 @@ | |||||||
| 		<Button | 		<Button | ||||||
| 			android:layout_width="wrap_content" | 			android:layout_width="wrap_content" | ||||||
| 			android:layout_height="wrap_content" | 			android:layout_height="wrap_content" | ||||||
| 			android:text="ApplySave" | 			android:text="ApplyShow" | ||||||
| 			android:id="@+id/btn_saveformatstring" | 			android:id="@+id/btn_saveformatstring" | ||||||
| 			android:textAllCaps="false" | 			android:textAllCaps="false" | ||||||
| 			android:onClick="onSaveFormatString"/> | 			android:onClick="onSaveFormatString"/> | ||||||
| @@ -77,14 +77,14 @@ | |||||||
| 	</LinearLayout> | 	</LinearLayout> | ||||||
|  |  | ||||||
| 	<LinearLayout | 	<LinearLayout | ||||||
| 		android:orientation="horizontal" | 		android:orientation="vertical" | ||||||
| 		android:layout_width="match_parent" | 		android:layout_width="match_parent" | ||||||
| 		android:layout_height="wrap_content"> | 		android:layout_height="wrap_content"> | ||||||
|  |  | ||||||
| 		<TextView | 		<TextView | ||||||
| 			android:layout_width="wrap_content" | 			android:layout_width="wrap_content" | ||||||
| 			android:layout_height="wrap_content" | 			android:layout_height="wrap_content" | ||||||
| 			android:text="Copy Format :" | 			android:text="Copy Format Preview:" | ||||||
| 			android:paddingRight="10dp"/> | 			android:paddingRight="10dp"/> | ||||||
|  |  | ||||||
| 		<TextView | 		<TextView | ||||||
|   | |||||||
| @@ -13,14 +13,14 @@ | |||||||
| 		android:layout_height="wrap_content" | 		android:layout_height="wrap_content" | ||||||
| 		android:text="Text" | 		android:text="Text" | ||||||
| 		android:textColor="#000000" | 		android:textColor="#000000" | ||||||
| 		android:textSize="18sp" | 		android:textSize="14sp" | ||||||
| 		android:layout_weight="1.0"/> | 		android:layout_weight="1.0"/> | ||||||
|  |  | ||||||
| 	<Button | 	<Button | ||||||
| 		android:layout_width="wrap_content" | 		android:layout_width="60dp" | ||||||
| 		android:layout_height="wrap_content" | 		android:layout_height="60dp" | ||||||
| 		android:text="复制时间戳" | 		android:id="@+id/btn_copytimestamp" | ||||||
| 		android:id="@+id/btn_copytimestamp"/> | 		android:background="@drawable/ic_launcher"/> | ||||||
|  |  | ||||||
| </LinearLayout> | </LinearLayout> | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										
											BIN
										
									
								
								timestamp/src/main/res/raw/diweiyi.mp3
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								timestamp/src/main/res/raw/diweiyi.mp3
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -1,5 +1,5 @@ | |||||||
| <resources> | <resources> | ||||||
|     <string name="app_name">TimeStamp</string> |     <string name="app_name">TimeStamp</string> | ||||||
|     <string name="text_aboutservernotification">This is the prompt window when the SMS service runs, which you can set to hide this class notification in the notification message settings.</string> |     <string name="text_aboutservernotification">This is the prompt window when the SMS service runs, which you can set to hide this class notification in the notification message settings.</string> | ||||||
|      |     <string name="accessibility_service_description">Accessibility service description.</string> | ||||||
| </resources> | </resources> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 ZhanGSKen
					ZhanGSKen