特殊通道初步调试完成
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
#Created by .winboll/winboll_app_build.gradle
|
#Created by .winboll/winboll_app_build.gradle
|
||||||
#Thu Apr 09 02:36:26 HKT 2026
|
#Sat Apr 18 08:39:12 GMT 2026
|
||||||
stageCount=10
|
stageCount=10
|
||||||
libraryProject=
|
libraryProject=
|
||||||
baseVersion=15.14
|
baseVersion=15.14
|
||||||
publishVersion=15.14.9
|
publishVersion=15.14.9
|
||||||
buildCount=0
|
buildCount=25
|
||||||
baseBetaVersion=15.14.10
|
baseBetaVersion=15.14.10
|
||||||
|
|||||||
@@ -9,45 +9,64 @@
|
|||||||
<!-- 拨打电话 -->
|
<!-- 拨打电话 -->
|
||||||
<uses-permission android:name="android.permission.CALL_PHONE"/>
|
<uses-permission android:name="android.permission.CALL_PHONE"/>
|
||||||
|
|
||||||
<!-- 读取手机状态和身份(API 30+ 需细化权限) -->
|
<!-- 读取手机状态和身份 -->
|
||||||
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
|
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
|
||||||
|
|
||||||
|
<!-- 读取电话号码 -->
|
||||||
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS"/>
|
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS"/>
|
||||||
|
|
||||||
<!-- 修改系统设置(移除无效的 protectionLevel 声明,该属性由系统定义) -->
|
<!-- 修改系统设置 -->
|
||||||
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
|
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
|
||||||
|
|
||||||
<!-- 联系人权限(适配 Android 13+ 细分权限) -->
|
<!-- 读取联系人 -->
|
||||||
<uses-permission android:name="android.permission.READ_CONTACTS"/>
|
<uses-permission android:name="android.permission.READ_CONTACTS"/>
|
||||||
|
|
||||||
|
<!-- 修改您的通讯录 -->
|
||||||
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
|
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
|
||||||
|
|
||||||
|
<!-- GET_CONTACTS -->
|
||||||
<uses-permission android:name="android.permission.GET_CONTACTS"/>
|
<uses-permission android:name="android.permission.GET_CONTACTS"/>
|
||||||
|
|
||||||
<!-- 悬浮窗权限(需动态申请) -->
|
<!-- 此应用可显示在其他应用上方 -->
|
||||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||||
|
|
||||||
<!-- 更改音频设置 -->
|
<!-- 更改您的音频设置 -->
|
||||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
|
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
|
||||||
|
|
||||||
<!-- 通话记录权限(适配 Android 13+ 细分权限) -->
|
<!-- 读取通话记录 -->
|
||||||
<uses-permission android:name="android.permission.READ_CALL_LOG"/>
|
<uses-permission android:name="android.permission.READ_CALL_LOG"/>
|
||||||
|
|
||||||
|
<!-- 新建/修改/删除通话记录 -->
|
||||||
<uses-permission android:name="android.permission.WRITE_CALL_LOG"/>
|
<uses-permission android:name="android.permission.WRITE_CALL_LOG"/>
|
||||||
|
|
||||||
|
<!-- GET_CALL_LOG -->
|
||||||
<uses-permission android:name="android.permission.GET_CALL_LOG"/>
|
<uses-permission android:name="android.permission.GET_CALL_LOG"/>
|
||||||
|
|
||||||
<!-- 录音权限 -->
|
<!-- 录音 -->
|
||||||
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
|
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
|
||||||
|
|
||||||
<!-- 前台服务权限(按业务类型声明) -->
|
<!-- 运行前台服务 -->
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||||
|
|
||||||
|
<!-- 运行“dataSync”类型的前台服务 -->
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/>
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/>
|
||||||
|
|
||||||
|
<!-- 运行“phoneCall”类型的前台服务 -->
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL"/>
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL"/>
|
||||||
|
|
||||||
|
<!-- 运行“microphone”类型的前台服务 -->
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE"/>
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE"/>
|
||||||
|
|
||||||
<!-- API 30+ 通话筛选服务权限(替代 PROCESS_OUTGOING_CALLS) -->
|
<!-- BIND_CALL_SCREENING_SERVICE -->
|
||||||
<uses-permission android:name="android.permission.BIND_CALL_SCREENING_SERVICE"/>
|
<uses-permission android:name="android.permission.BIND_CALL_SCREENING_SERVICE"/>
|
||||||
|
|
||||||
|
<!-- 读取您共享存储空间中的内容 -->
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
|
|
||||||
|
<!-- 修改或删除您共享存储空间中的内容 -->
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||||
|
|
||||||
|
<!-- MANAGE_EXTERNAL_STORAGE -->
|
||||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
@@ -56,7 +75,7 @@
|
|||||||
android:icon="@drawable/ic_winboll"
|
android:icon="@drawable/ic_winboll"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:theme="@style/MyAppTheme"
|
android:theme="@style/MyAppTheme"
|
||||||
android:requestLegacyExternalStorage="true"
|
android:requestLegacyExternalStorage="true"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:networkSecurityConfig="@xml/network_security_config">
|
android:networkSecurityConfig="@xml/network_security_config">
|
||||||
|
|
||||||
@@ -66,8 +85,11 @@
|
|||||||
android:exported="true">
|
android:exported="true">
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
</activity>
|
</activity>
|
||||||
@@ -77,6 +99,7 @@
|
|||||||
android:label="CallActivity"
|
android:label="CallActivity"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
|
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
@@ -85,53 +108,60 @@
|
|||||||
android:exported="true">
|
android:exported="true">
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|
||||||
<action android:name="android.intent.action.DIAL"/>
|
<action android:name="android.intent.action.DIAL"/>
|
||||||
|
|
||||||
<action android:name="android.intent.action.VIEW"/>
|
<action android:name="android.intent.action.VIEW"/>
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT"/>
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
|
|
||||||
<category android:name="android.intent.category.BROWSABLE"/>
|
<category android:name="android.intent.category.BROWSABLE"/>
|
||||||
|
|
||||||
<data android:scheme="tel"/>
|
<data android:scheme="tel"/>
|
||||||
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|
||||||
<action android:name="android.intent.action.DIAL"/>
|
<action android:name="android.intent.action.DIAL"/>
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT"/>
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity android:name="cc.winboll.studio.contacts.activities.SettingsActivity"/>
|
<activity android:name="cc.winboll.studio.contacts.activities.SettingsActivity"/>
|
||||||
|
|
||||||
<!-- 主服务:仅 dataSync 类型(与代码中 0x00000008 匹配) -->
|
|
||||||
<service
|
<service
|
||||||
android:name=".services.MainService"
|
android:name=".services.MainService"
|
||||||
android:foregroundServiceType="dataSync"
|
android:foregroundServiceType="dataSync"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:stopWithTask="false"/>
|
android:stopWithTask="false"/>
|
||||||
|
|
||||||
<!-- 辅助服务:dataSync 类型 -->
|
|
||||||
<service
|
<service
|
||||||
android:name=".services.AssistantService"
|
android:name=".services.AssistantService"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:stopWithTask="false"/>
|
android:stopWithTask="false"/>
|
||||||
|
|
||||||
<!-- 通话UI服务(系统绑定) -->
|
|
||||||
<service
|
<service
|
||||||
android:name=".phonecallui.PhoneCallService"
|
android:name=".phonecallui.PhoneCallService"
|
||||||
android:permission="android.permission.BIND_INCALL_SERVICE"
|
android:permission="android.permission.BIND_INCALL_SERVICE"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:stopWithTask="false">
|
android:stopWithTask="false">
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.telecom.IN_CALL_SERVICE_UI"
|
android:name="android.telecom.IN_CALL_SERVICE_UI"
|
||||||
android:value="true"/>
|
android:value="true"/>
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|
||||||
<action android:name="android.telecom.InCallService"/>
|
<action android:name="android.telecom.InCallService"/>
|
||||||
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<!-- 通话监听服务:phoneCall 类型(与代码中 0x00000020 匹配) -->
|
|
||||||
<service
|
<service
|
||||||
android:name=".listenphonecall.CallListenerService"
|
android:name=".listenphonecall.CallListenerService"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
@@ -139,37 +169,52 @@
|
|||||||
android:stopWithTask="false">
|
android:stopWithTask="false">
|
||||||
|
|
||||||
<intent-filter android:priority="1000">
|
<intent-filter android:priority="1000">
|
||||||
|
|
||||||
<action android:name=".service.CallShowService"/>
|
<action android:name=".service.CallShowService"/>
|
||||||
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<!-- API 30+ 通话筛选服务(替代 PROCESS_OUTGOING_CALLS 权限) -->
|
<service
|
||||||
<service
|
android:name=".services.MyCallScreeningService"
|
||||||
android:name=".services.MyCallScreeningService"
|
android:permission="android.permission.BIND_CALL_SCREENING_SERVICE"
|
||||||
android:permission="android.permission.BIND_CALL_SCREENING_SERVICE"
|
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:stopWithTask="false">
|
android:stopWithTask="false">
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|
||||||
<action android:name="android.telecom.CallScreeningService"/>
|
<action android:name="android.telecom.CallScreeningService"/>
|
||||||
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<receiver android:name=".receivers.MainReceiver"
|
<receiver
|
||||||
android:stopWithTask="false">
|
android:name=".receivers.MainReceiver"
|
||||||
|
android:stopWithTask="false">
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|
||||||
<action android:name="cc.winboll.studio.contacts.receivers.MainReceiver"/>
|
<action android:name="cc.winboll.studio.contacts.receivers.MainReceiver"/>
|
||||||
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".widgets.APPStatusWidget"
|
android:name=".widgets.APPStatusWidget"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:stopWithTask="false">
|
android:stopWithTask="false">
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|
||||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
|
||||||
|
|
||||||
<action android:name="cc.winboll.studio.contacts.widgets.APPStatusWidget.ACTION_STATUS_ACTIVE"/>
|
<action android:name="cc.winboll.studio.contacts.widgets.APPStatusWidget.ACTION_STATUS_ACTIVE"/>
|
||||||
|
|
||||||
<action android:name="cc.winboll.studio.contacts.widgets.APPStatusWidget.ACTION_STATUS_NOACTIVE"/>
|
<action android:name="cc.winboll.studio.contacts.widgets.APPStatusWidget.ACTION_STATUS_NOACTIVE"/>
|
||||||
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
@@ -178,11 +223,16 @@
|
|||||||
|
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<receiver android:name=".widgets.APPStatusWidgetClickListener"
|
<receiver
|
||||||
android:stopWithTask="false">
|
android:name=".widgets.APPStatusWidgetClickListener"
|
||||||
|
android:stopWithTask="false">
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|
||||||
<action android:name="cc.winboll.studio.contacts.widgets.APPStatusWidgetClickListener.ACTION_APPICON_CLICK"/>
|
<action android:name="cc.winboll.studio.contacts.widgets.APPStatusWidgetClickListener.ACTION_APPICON_CLICK"/>
|
||||||
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
@@ -201,7 +251,8 @@
|
|||||||
|
|
||||||
<activity android:name="cc.winboll.studio.contacts.activities.AboutActivity"/>
|
<activity android:name="cc.winboll.studio.contacts.activities.AboutActivity"/>
|
||||||
|
|
||||||
|
<service android:name="cc.winboll.studio.contacts.services.LimitedTimeSpecialChannelService"/>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import android.view.View;
|
|||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import cc.winboll.studio.contacts.R;
|
import cc.winboll.studio.contacts.R;
|
||||||
|
import cc.winboll.studio.contacts.activities.UnitTestActivity;
|
||||||
import cc.winboll.studio.contacts.dun.Rules;
|
import cc.winboll.studio.contacts.dun.Rules;
|
||||||
|
import cc.winboll.studio.contacts.services.LimitedTimeSpecialChannelService;
|
||||||
import cc.winboll.studio.contacts.utils.IntUtils;
|
import cc.winboll.studio.contacts.utils.IntUtils;
|
||||||
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
|
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
|
||||||
import cc.winboll.studio.libappbase.LogUtils;
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
@@ -116,6 +118,13 @@ public class UnitTestActivity extends WinBollActivity implements IWinBoLLActivit
|
|||||||
LogUtils.d(TAG, String.format("onTestMain: 测试号码: %s | 匹配结果: %s", phone, isAllowed));
|
LogUtils.d(TAG, String.format("onTestMain: 测试号码: %s | 匹配结果: %s", phone, isAllowed));
|
||||||
}
|
}
|
||||||
LogUtils.d(TAG, "onTestMain: 批量号码规则测试完成");
|
LogUtils.d(TAG, "onTestMain: 批量号码规则测试完成");
|
||||||
|
|
||||||
|
new Thread(new Runnable(){
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
LimitedTimeSpecialChannelService.unitTest(UnitTestActivity.this);
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================== 私有工具函数区 ======================
|
// ====================== 私有工具函数区 ======================
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import cc.winboll.studio.contacts.activities.SettingsActivity;
|
|||||||
import cc.winboll.studio.contacts.bobulltoon.TomCat;
|
import cc.winboll.studio.contacts.bobulltoon.TomCat;
|
||||||
import cc.winboll.studio.contacts.model.PhoneConnectRuleBean;
|
import cc.winboll.studio.contacts.model.PhoneConnectRuleBean;
|
||||||
import cc.winboll.studio.contacts.model.SettingsBean;
|
import cc.winboll.studio.contacts.model.SettingsBean;
|
||||||
|
import cc.winboll.studio.contacts.services.LimitedTimeSpecialChannelService;
|
||||||
import cc.winboll.studio.contacts.services.MainService;
|
import cc.winboll.studio.contacts.services.MainService;
|
||||||
import cc.winboll.studio.contacts.utils.ContactUtils;
|
import cc.winboll.studio.contacts.utils.ContactUtils;
|
||||||
import cc.winboll.studio.contacts.utils.IntUtils;
|
import cc.winboll.studio.contacts.utils.IntUtils;
|
||||||
@@ -171,6 +172,14 @@ public class Rules {
|
|||||||
LogUtils.d(TAG, String.format("isDefend == %s\nisConnect == %s", isDefend, isConnect));
|
LogUtils.d(TAG, String.format("isDefend == %s\nisConnect == %s", isDefend, isConnect));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 限时特殊通道打开时返回连接
|
||||||
|
if (!isDefend && LimitedTimeSpecialChannelService.isServiceRunning()) {
|
||||||
|
LogUtils.d(TAG, String.format("PhoneNumber %s\n and Limited Time Special Channel Service Is Running.", phoneNumber));
|
||||||
|
isDefend = true;
|
||||||
|
isConnect = true;
|
||||||
|
LogUtils.d(TAG, String.format("isDefend == %s\nisConnect == %s", isDefend, isConnect));
|
||||||
|
}
|
||||||
|
|
||||||
// 检验拨不通号码群
|
// 检验拨不通号码群
|
||||||
if (!isDefend && MainService.isPhoneInBoBullToon(phoneNumber)) {
|
if (!isDefend && MainService.isPhoneInBoBullToon(phoneNumber)) {
|
||||||
LogUtils.d(TAG, String.format("PhoneNumber %s\n Is In BoBullToon", phoneNumber));
|
LogUtils.d(TAG, String.format("PhoneNumber %s\n Is In BoBullToon", phoneNumber));
|
||||||
|
|||||||
@@ -0,0 +1,285 @@
|
|||||||
|
package cc.winboll.studio.contacts.services;
|
||||||
|
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.Looper;
|
||||||
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2026/04/18 15:00:00 (香港时区)
|
||||||
|
* @LastEditTime 2026/04/18 22:15:00 (香港时区)
|
||||||
|
* @Describe 限时特殊通道服务
|
||||||
|
* 提供安全的服务启动、令牌校验及循环任务管理
|
||||||
|
* 新增功能:
|
||||||
|
* 1. 计时过程中实时输出剩余秒数
|
||||||
|
* 2. 服务销毁前,发送本地广播通知应用内其他组件
|
||||||
|
*/
|
||||||
|
public class LimitedTimeSpecialChannelService extends Service {
|
||||||
|
|
||||||
|
// ========================= 常量定义 =========================
|
||||||
|
public static final String TAG = "LimitedTimeSpecialChannelService";
|
||||||
|
public static final String EXTRA_DELAY_MILLIS = "EXTRA_DELAY_MILLIS";
|
||||||
|
private static final String EXTRA_SECURITY_TOKEN = "EXTRA_SECURITY_TOKEN";
|
||||||
|
|
||||||
|
// 新增:本地广播 Action 常量
|
||||||
|
public static final String ACTION_SERVICE_DESTROYED = "cc.winboll.studio.contacts.services.ACTION_SERVICE_DESTROYED";
|
||||||
|
|
||||||
|
// ========================= 静态变量 =========================
|
||||||
|
/**
|
||||||
|
* 公共静态私有字段:安全校验令牌
|
||||||
|
* 确保全局可访问且值不可变更
|
||||||
|
*/
|
||||||
|
public static final String mValidToken = "VALID_TOKEN_" + System.currentTimeMillis();
|
||||||
|
|
||||||
|
private static volatile LimitedTimeSpecialChannelService sInstance = null;
|
||||||
|
private static volatile boolean sIsServiceRunning = false;
|
||||||
|
|
||||||
|
// ========================= 成员变量 =========================
|
||||||
|
private final Handler mHandler = new Handler(Looper.getMainLooper());
|
||||||
|
private long mTotalMillis = 0; // 总定时时长
|
||||||
|
private long mRemainingMillis = 0; // 剩余时长
|
||||||
|
private LocalBroadcastManager mLocalBroadcastManager; // 本地广播管理器实例
|
||||||
|
|
||||||
|
// ========================= 公共静态方法 =========================
|
||||||
|
/**
|
||||||
|
* 公共静态方法:启动服务
|
||||||
|
* @param context 上下文
|
||||||
|
* @param delayMillis 定时时长(毫秒)
|
||||||
|
*/
|
||||||
|
public static void startService(Context context, long delayMillis) {
|
||||||
|
LogUtils.i(TAG, "调用 startService 入口");
|
||||||
|
LogUtils.i(TAG, "入参 - context: " + context + ", delayMillis: " + delayMillis);
|
||||||
|
|
||||||
|
if (context == null) {
|
||||||
|
LogUtils.w(TAG, "启动失败,上下文为null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isServiceRunning()) {
|
||||||
|
LogUtils.i(TAG, "服务已运行,忽略重复启动");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建Intent传递参数
|
||||||
|
Intent intent = new Intent(context, LimitedTimeSpecialChannelService.class);
|
||||||
|
intent.putExtra(EXTRA_SECURITY_TOKEN, mValidToken);
|
||||||
|
intent.putExtra(EXTRA_DELAY_MILLIS, delayMillis);
|
||||||
|
|
||||||
|
context.startService(intent);
|
||||||
|
LogUtils.i(TAG, "服务启动命令已发出");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 公共静态方法:停止服务
|
||||||
|
* @param context 上下文
|
||||||
|
*/
|
||||||
|
public static void stopService(Context context) {
|
||||||
|
LogUtils.i(TAG, "调用 stopService 入口");
|
||||||
|
LogUtils.i(TAG, "入参 - context: " + context);
|
||||||
|
|
||||||
|
if (context == null) {
|
||||||
|
LogUtils.w(TAG, "停止失败,上下文为null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Intent intent = new Intent(context, LimitedTimeSpecialChannelService.class);
|
||||||
|
context.stopService(intent);
|
||||||
|
LogUtils.i(TAG, "服务停止命令已发出");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 公共静态方法:查询服务运行状态
|
||||||
|
* @return true if running
|
||||||
|
*/
|
||||||
|
public static boolean isServiceRunning() {
|
||||||
|
return sIsServiceRunning;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【核心单元测试方法】
|
||||||
|
* 执行完整的单元测试流程
|
||||||
|
* @param context 上下文
|
||||||
|
*/
|
||||||
|
public static void unitTest(Context context) {
|
||||||
|
LogUtils.i(TAG, "=== 开始执行单元测试 ===");
|
||||||
|
|
||||||
|
// 测试1: 初始状态应为未运行
|
||||||
|
boolean initialState = isServiceRunning();
|
||||||
|
LogUtils.i(TAG, "测试1 - 初始状态检查: " + (initialState ? "失败(不应运行)" : "成功(未运行)"));
|
||||||
|
|
||||||
|
// 启动服务,设置5秒定时
|
||||||
|
startService(context, 5000L);
|
||||||
|
|
||||||
|
// 核心修复:同步等待服务启动完成
|
||||||
|
try {
|
||||||
|
Thread.sleep(1200);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LogUtils.e(TAG, "单元测试等待被中断", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试3: 验证服务已启动
|
||||||
|
boolean runningState = isServiceRunning();
|
||||||
|
LogUtils.i(TAG, "测试3 - 运行状态检查: " + (runningState ? "成功(运行中)" : "失败(未运行)"));
|
||||||
|
|
||||||
|
// 测试4: 尝试重复启动
|
||||||
|
LogUtils.i(TAG, "测试4 - 尝试重复启动(预期忽略)");
|
||||||
|
startService(context, 5000L);
|
||||||
|
|
||||||
|
// 测试5: 等待服务自动停止
|
||||||
|
LogUtils.i(TAG, "测试5 - 等待服务自动停止(5秒)...");
|
||||||
|
try {
|
||||||
|
// 等待超过服务的5秒运行时间,确保能观测到销毁状态
|
||||||
|
Thread.sleep(6000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LogUtils.e(TAG, "测试等待被中断", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证最终状态
|
||||||
|
boolean finalState = isServiceRunning();
|
||||||
|
LogUtils.i(TAG, "测试5 - 最终状态检查: " + (!finalState ? "成功(已销毁)" : "失败(仍运行)"));
|
||||||
|
LogUtils.i(TAG, "=== 单元测试执行完成 ===");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================= 生命周期 =========================
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
LogUtils.i(TAG, "服务 onCreate,创建实例");
|
||||||
|
sInstance = this;
|
||||||
|
sIsServiceRunning = true;
|
||||||
|
|
||||||
|
// 初始化本地广播管理器
|
||||||
|
mLocalBroadcastManager = LocalBroadcastManager.getInstance(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
|
LogUtils.i(TAG, "服务 onStartCommand,处理启动请求");
|
||||||
|
LogUtils.i(TAG, "入参 - intent: " + intent + ", flags: " + flags + ", startId: " + startId);
|
||||||
|
|
||||||
|
if (!isValidToken(intent)) {
|
||||||
|
LogUtils.w(TAG, "安全校验失败,拒绝启动");
|
||||||
|
stopSelf();
|
||||||
|
return START_NOT_STICKY;
|
||||||
|
}
|
||||||
|
|
||||||
|
long delayMillis = intent.getLongExtra(EXTRA_DELAY_MILLIS, 0);
|
||||||
|
if (delayMillis <= 0) {
|
||||||
|
LogUtils.w(TAG, "无效的定时时长: " + delayMillis + ",服务将退出");
|
||||||
|
stopSelf();
|
||||||
|
return START_NOT_STICKY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化总时长和剩余时长
|
||||||
|
mTotalMillis = delayMillis;
|
||||||
|
mRemainingMillis = delayMillis;
|
||||||
|
LogUtils.i(TAG, "初始化定时时长: " + (mTotalMillis / 1000) + " 秒");
|
||||||
|
|
||||||
|
startLoopTask();
|
||||||
|
// 设置自动停止定时器
|
||||||
|
mHandler.postDelayed(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
LogUtils.i(TAG, "定时结束,准备停止服务");
|
||||||
|
stopSelf();
|
||||||
|
}
|
||||||
|
}, delayMillis);
|
||||||
|
|
||||||
|
return START_STICKY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
LogUtils.i(TAG, "服务 onDestroy,准备销毁");
|
||||||
|
|
||||||
|
// 新增:发送本地广播,通知应用内其他组件服务即将销毁
|
||||||
|
sendServiceDestroyedBroadcast();
|
||||||
|
|
||||||
|
// 原有逻辑:重置状态
|
||||||
|
sIsServiceRunning = false;
|
||||||
|
sInstance = null;
|
||||||
|
|
||||||
|
// 原有逻辑:清理所有回调
|
||||||
|
mHandler.removeCallbacksAndMessages(null);
|
||||||
|
|
||||||
|
// 原有逻辑:执行父类销毁
|
||||||
|
super.onDestroy();
|
||||||
|
|
||||||
|
LogUtils.i(TAG, "服务销毁完成");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增:发送服务销毁的本地广播
|
||||||
|
* 确保应用内所有注册了该广播接收器的组件都能收到通知
|
||||||
|
*/
|
||||||
|
private void sendServiceDestroyedBroadcast() {
|
||||||
|
try {
|
||||||
|
Intent intent = new Intent(ACTION_SERVICE_DESTROYED);
|
||||||
|
// 可在此处添加额外数据,例如剩余时长等
|
||||||
|
intent.putExtra("REMAINING_SECONDS", mRemainingMillis / 1000);
|
||||||
|
// 使用本地广播管理器发送
|
||||||
|
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
|
||||||
|
LogUtils.i(TAG, "已发送本地广播 - 服务销毁通知: " + ACTION_SERVICE_DESTROYED);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtils.e(TAG, "发送服务销毁广播失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
LogUtils.i(TAG, "服务 onBind,不支持绑定");
|
||||||
|
// 不支持绑定
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================= 私有辅助方法 =========================
|
||||||
|
/**
|
||||||
|
* 校验令牌有效性
|
||||||
|
*/
|
||||||
|
private boolean isValidToken(Intent intent) {
|
||||||
|
LogUtils.i(TAG, "调用 isValidToken 校验");
|
||||||
|
LogUtils.i(TAG, "入参 - intent: " + intent);
|
||||||
|
|
||||||
|
if (intent == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String incomingToken = intent.getStringExtra(EXTRA_SECURITY_TOKEN);
|
||||||
|
LogUtils.i(TAG, "接收到外部传入令牌: " + incomingToken);
|
||||||
|
LogUtils.i(TAG, "比对本地存储令牌: " + mValidToken);
|
||||||
|
return mValidToken.equals(incomingToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动循环任务
|
||||||
|
*/
|
||||||
|
private void startLoopTask() {
|
||||||
|
mHandler.removeCallbacks(mLoopTaskRunnable);
|
||||||
|
mHandler.postDelayed(mLoopTaskRunnable, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 循环任务心跳
|
||||||
|
* 新增:实时计算并输出剩余秒数
|
||||||
|
*/
|
||||||
|
private final Runnable mLoopTaskRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (sIsServiceRunning) {
|
||||||
|
// 计算剩余秒数
|
||||||
|
long remainingSeconds = mRemainingMillis / 1000;
|
||||||
|
// 输出剩余秒数信息
|
||||||
|
LogUtils.i(TAG, "循环任务心跳:服务运行中,剩余 " + remainingSeconds + " 秒");
|
||||||
|
|
||||||
|
// 剩余时长递减
|
||||||
|
mRemainingMillis -= 1000;
|
||||||
|
|
||||||
|
// 递归调用,形成循环
|
||||||
|
startLoopTask();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,252 @@
|
|||||||
|
package cc.winboll.studio.contacts.views;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.widget.CompoundButton;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.Switch;
|
||||||
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||||
|
import cc.winboll.studio.contacts.R;
|
||||||
|
import cc.winboll.studio.contacts.services.LimitedTimeSpecialChannelService;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import cc.winboll.studio.libappbase.ToastUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2026/04/18 15:05
|
||||||
|
* @LastEditTime 2026/04/18 22:30:00 (香港时区)
|
||||||
|
* @Describe 限时特殊通道视图
|
||||||
|
* 功能更新:
|
||||||
|
* 1. Switch 打开时,EditText 不可编辑,布局背景变为绿色
|
||||||
|
* 2. Switch 关闭时,EditText 可编辑,布局背景恢复为白色
|
||||||
|
* 3. 监听服务销毁本地广播,到达后自动关闭 Switch 并恢复背景色
|
||||||
|
*/
|
||||||
|
public class LimitedTimeSpecialChannelView extends LinearLayout {
|
||||||
|
|
||||||
|
// ====================== 常量定义 =========================
|
||||||
|
public static final String TAG = "LimitedTimeSpecialChannelView";
|
||||||
|
|
||||||
|
// ====================== 成员变量 =========================
|
||||||
|
private Context mContext;
|
||||||
|
private EditText mEtSeconds;
|
||||||
|
private Switch mSwEnable;
|
||||||
|
private LinearLayout mLlMain;
|
||||||
|
|
||||||
|
// ====================== 构造函数 =========================
|
||||||
|
/**
|
||||||
|
* 代码创建View时调用
|
||||||
|
* @param context 上下文
|
||||||
|
*/
|
||||||
|
public LimitedTimeSpecialChannelView(Context context) {
|
||||||
|
super(context);
|
||||||
|
initView(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* XML布局中引用时调用
|
||||||
|
* @param context 上下文
|
||||||
|
* @param attrs 属性集合
|
||||||
|
*/
|
||||||
|
public LimitedTimeSpecialChannelView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
initView(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指定样式属性
|
||||||
|
* @param context 上下文
|
||||||
|
* @param attrs 属性集合
|
||||||
|
* @param defStyleAttr 默认样式属性
|
||||||
|
*/
|
||||||
|
public LimitedTimeSpecialChannelView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
initView(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指定样式资源
|
||||||
|
* @param context 上下文
|
||||||
|
* @param attrs 属性集合
|
||||||
|
* @param defStyleAttr 默认样式属性
|
||||||
|
* @param defStyleRes 默认样式资源
|
||||||
|
*/
|
||||||
|
public LimitedTimeSpecialChannelView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
|
initView(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================== 初始化方法 ========================
|
||||||
|
/**
|
||||||
|
* 初始化视图
|
||||||
|
* 加载布局文件,绑定控件,并注册广播接收器
|
||||||
|
* @param context 上下文
|
||||||
|
*/
|
||||||
|
private void initView(Context context) {
|
||||||
|
LogUtils.i(TAG, "初始化限时特殊通道视图");
|
||||||
|
this.mContext = context;
|
||||||
|
|
||||||
|
// 1. 加载布局文件
|
||||||
|
LayoutInflater.from(context)
|
||||||
|
.inflate(R.layout.view_limitedtimespecialchannel, this, true);
|
||||||
|
|
||||||
|
LogUtils.i(TAG, "布局加载完成:view_limitedtimespecialchannel.xml");
|
||||||
|
|
||||||
|
// 2. 绑定核心控件引用
|
||||||
|
bindViews();
|
||||||
|
|
||||||
|
// 3. 初始化开关状态与背景色关联
|
||||||
|
initSwitchState();
|
||||||
|
|
||||||
|
// 4. 注册服务销毁本地广播监听
|
||||||
|
registerServiceDestroyedListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绑定布局中的控件ID
|
||||||
|
*/
|
||||||
|
private void bindViews() {
|
||||||
|
mEtSeconds = (EditText) findViewById(R.id.et_seconds);
|
||||||
|
mSwEnable = (Switch) findViewById(R.id.sw_enable);
|
||||||
|
mLlMain = (LinearLayout) findViewById(R.id.ll_main);
|
||||||
|
|
||||||
|
// 健壮性检查
|
||||||
|
if (mEtSeconds == null) {
|
||||||
|
LogUtils.e(TAG, "未找到ID为 et_seconds 的EditText");
|
||||||
|
}
|
||||||
|
if (mSwEnable == null) {
|
||||||
|
LogUtils.e(TAG, "未找到ID为 sw_enable 的Switch");
|
||||||
|
}
|
||||||
|
if (mLlMain == null) {
|
||||||
|
LogUtils.w(TAG, "未找到ID为 ll_main 的LinearLayout,将无法设置背景色");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化开关状态
|
||||||
|
* 设置Switch的监听事件,以及初始背景色
|
||||||
|
*/
|
||||||
|
private void initSwitchState() {
|
||||||
|
// 设置初始状态
|
||||||
|
boolean isChecked = mSwEnable.isChecked();
|
||||||
|
mEtSeconds.setEnabled(!isChecked);
|
||||||
|
if (mLlMain != null) {
|
||||||
|
mLlMain.setBackgroundColor(isChecked ? android.graphics.Color.GREEN : android.graphics.Color.WHITE);
|
||||||
|
}
|
||||||
|
LogUtils.i(TAG, "初始状态 - 开关: " + (isChecked ? "启用" : "关闭") + ", 背景色: " + (isChecked ? "绿色" : "白色"));
|
||||||
|
|
||||||
|
// 为Switch设置切换监听
|
||||||
|
mSwEnable.setOnCheckedChangeListener(new Switch.OnCheckedChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||||
|
LogUtils.i(TAG, "开关状态变更: " + (isChecked ? "启用" : "关闭"));
|
||||||
|
|
||||||
|
// 核心逻辑:控制EditText的可编辑状态
|
||||||
|
mEtSeconds.setEnabled(!isChecked);
|
||||||
|
|
||||||
|
// 控制布局背景色
|
||||||
|
if (mLlMain != null) {
|
||||||
|
mLlMain.setBackgroundColor(isChecked ? android.graphics.Color.GREEN : android.graphics.Color.WHITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 控制服务启动与停止
|
||||||
|
if (isChecked) {
|
||||||
|
// 打开开关时,启动服务
|
||||||
|
startLimitedTimeSpecialChannelService();
|
||||||
|
} else {
|
||||||
|
// 关闭开关时,停止服务
|
||||||
|
LimitedTimeSpecialChannelService.stopService(mContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册服务销毁本地广播监听
|
||||||
|
* 当服务销毁消息到达时,自动关闭开关并恢复背景色
|
||||||
|
*/
|
||||||
|
private void registerServiceDestroyedListener() {
|
||||||
|
// 创建广播接收器
|
||||||
|
LocalBroadcastManager.getInstance(mContext)
|
||||||
|
.registerReceiver(new android.content.BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
// 校验广播Action
|
||||||
|
if (LimitedTimeSpecialChannelService.ACTION_SERVICE_DESTROYED.equals(intent.getAction())) {
|
||||||
|
LogUtils.i(TAG, "收到服务销毁本地广播,准备关闭开关并恢复背景");
|
||||||
|
|
||||||
|
// 核心逻辑:关闭Switch
|
||||||
|
if (mSwEnable != null && mSwEnable.isChecked()) {
|
||||||
|
mSwEnable.setChecked(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 恢复布局背景色为白色
|
||||||
|
if (mLlMain != null) {
|
||||||
|
mLlMain.setBackgroundColor(android.graphics.Color.WHITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 可选:通知用户服务已销毁
|
||||||
|
ToastUtils.show("服务已销毁,恢复初始状态");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, new IntentFilter(LimitedTimeSpecialChannelService.ACTION_SERVICE_DESTROYED));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动限时特殊通道服务
|
||||||
|
* 读取输入框中的秒数,转换为毫秒后启动服务
|
||||||
|
*/
|
||||||
|
private void startLimitedTimeSpecialChannelService() {
|
||||||
|
LogUtils.i(TAG, "检测到秒数输入框点击,准备启动服务");
|
||||||
|
|
||||||
|
// 1. 获取输入的秒数
|
||||||
|
String secondsStr = mEtSeconds.getText().toString().trim();
|
||||||
|
if (secondsStr.isEmpty()) {
|
||||||
|
LogUtils.w(TAG, "输入框为空,使用默认值 3600 秒");
|
||||||
|
secondsStr = "3600";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 2. 转换为毫秒数 (1秒 = 1000毫秒)
|
||||||
|
long seconds = Long.parseLong(secondsStr);
|
||||||
|
long delayMillis = seconds * 1000;
|
||||||
|
|
||||||
|
LogUtils.i(TAG, "输入秒数: " + seconds + " 秒,转换为毫秒: " + delayMillis);
|
||||||
|
|
||||||
|
// 3. 调用服务启动方法
|
||||||
|
ToastUtils.show("调用 LimitedTimeSpecialChannelService 服务");
|
||||||
|
LimitedTimeSpecialChannelService.startService(mContext, delayMillis);
|
||||||
|
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
LogUtils.e(TAG, "输入的秒数格式错误: " + secondsStr, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*【可选】视图销毁时反注册广播,防止内存泄漏
|
||||||
|
*/
|
||||||
|
// @Override
|
||||||
|
// protected void onDetachedFromWindow() {
|
||||||
|
// super.onDetachedFromWindow();
|
||||||
|
// // 反注册本地广播
|
||||||
|
// LocalBroadcastManager.getInstance(mContext).unregisterregisterReceiver(mServiceDestroyedReceiver);
|
||||||
|
// LogUtils.i(TAG, "反注册服务销毁本地广播完成");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// //【可选】定义广播接收器成员变量,方便反注册
|
||||||
|
// private android.content.BroadcastReceiver mServiceDestroyedReceiver;
|
||||||
|
//
|
||||||
|
// private void registerServiceDestroyedListener() {
|
||||||
|
// mServiceDestroyedReceiver = new android.content.BroadcastReceiver() {
|
||||||
|
// @Override
|
||||||
|
// public void onReceive(Context context, Intent intent) {
|
||||||
|
// // ... 原有onReceive逻辑 ...
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// LocalBroadcastManager.getInstance(mContext).registerReceiver(mServiceDestroyedReceiver, new IntentFilter(LimitedTimeSpecialChannelService.ACTION_SERVICE_DESTROYED));
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
@@ -58,6 +58,24 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="特殊通道设置:"/>
|
||||||
|
|
||||||
|
<cc.winboll.studio.contacts.views.LimitedTimeSpecialChannelView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/view_LimitedTimeSpecialChannel"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical|center_horizontal"
|
||||||
|
android:id="@+id/ll_main">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:layout_width="80dp"
|
||||||
|
android:inputType="number"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ems="10"
|
||||||
|
android:text="3600"
|
||||||
|
android:id="@+id/et_seconds"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="秒"/>
|
||||||
|
|
||||||
|
<Switch
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/sw_enable"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
Reference in New Issue
Block a user