feat: 灵动岛参数模式(完整/极简)——极简模式验证白名单是否只拦完整岛结构
This commit is contained in:
@@ -13,8 +13,8 @@ android {
|
|||||||
applicationId = "com.smbfast.app"
|
applicationId = "com.smbfast.app"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = 24
|
versionCode = 25
|
||||||
versionName = "1.20.0"
|
versionName = "1.21.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ data class SpeedSettings(
|
|||||||
var streams: Int = 4, // 并行流数(每个流独立 SMB 句柄,分块并行下载)
|
var streams: Int = 4, // 并行流数(每个流独立 SMB 句柄,分块并行下载)
|
||||||
var chunkKB: Int = 1024, // 每个请求的分块大小 KB
|
var chunkKB: Int = 1024, // 每个请求的分块大小 KB
|
||||||
var islandOn: Boolean = true, // HyperOS 灵动岛/焦点通知
|
var islandOn: Boolean = true, // HyperOS 灵动岛/焦点通知
|
||||||
|
var islandMode: String? = null, // 岛参数模式:full=完整岛结构 minimal=极简(仅baseInfo/ticker,白名单实验);null 视为 full
|
||||||
var updateUrl: String = "http://192.168.31.3:8080/", // 应用内快捷更新的分发服务器
|
var updateUrl: String = "http://192.168.31.3:8080/", // 应用内快捷更新的分发服务器
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -51,7 +52,9 @@ class Prefs(context: Context) {
|
|||||||
fun loadSettings(): SpeedSettings {
|
fun loadSettings(): SpeedSettings {
|
||||||
val raw = loadJson("settings") ?: return SpeedSettings()
|
val raw = loadJson("settings") ?: return SpeedSettings()
|
||||||
return runCatching { gson.fromJson(raw, SpeedSettings::class.java) }
|
return runCatching { gson.fromJson(raw, SpeedSettings::class.java) }
|
||||||
.getOrNull() ?: SpeedSettings()
|
.getOrNull()
|
||||||
|
?.also { s -> runCatching { if (s.islandMode.isNullOrBlank()) s.islandMode = "full" } }
|
||||||
|
?: SpeedSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveSettings(s: SpeedSettings) {
|
fun saveSettings(s: SpeedSettings) {
|
||||||
|
|||||||
@@ -159,7 +159,8 @@ class TransferService : Service() {
|
|||||||
)
|
)
|
||||||
b.addAction(NotificationCompat.Action(android.R.drawable.ic_delete, "全部停止", stopIntent))
|
b.addAction(NotificationCompat.Action(android.R.drawable.ic_delete, "全部停止", stopIntent))
|
||||||
}
|
}
|
||||||
if (islandOn()) {
|
val isl = islandSettings()
|
||||||
|
if (isl != null && isl.second != MODE_MINIMAL) {
|
||||||
runCatching {
|
runCatching {
|
||||||
val extras = Bundle()
|
val extras = Bundle()
|
||||||
extras.putBundle("miui.focus.pics", islandPics())
|
extras.putBundle("miui.focus.pics", islandPics())
|
||||||
@@ -167,21 +168,23 @@ class TransferService : Service() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
val n = b.build()
|
val n = b.build()
|
||||||
if (islandOn()) {
|
if (isl != null) {
|
||||||
// 小米 HyperOS 焦点通知/超级岛:客户端本地上岛(官方 extras 协议)。
|
// 小米 HyperOS 焦点通知/超级岛:客户端本地上岛(官方 extras 协议)。
|
||||||
// 注意:HyperOS 对焦点通知有系统级白名单,侧载应用携带岛参数时可能被
|
// 注意:HyperOS 对焦点通知有系统级白名单,侧载应用携带岛参数时可能被
|
||||||
// 系统整体过滤(岛和通知都不显示)——在设置里关岛开关即可恢复普通通知。
|
// 系统整体过滤(岛和通知都不显示)——在设置里关岛开关即可恢复普通通知。
|
||||||
// islandFirstFloat 只在第一条通知为 true,否则每次更新都会重新弹出岛。
|
// islandFirstFloat 只在第一条通知为 true,否则每次更新都会重新弹出岛。
|
||||||
val first = firstIslandNotify
|
val first = firstIslandNotify
|
||||||
runCatching { n.extras.putString("miui.focus.param", islandParam(running, first)) }
|
runCatching { n.extras.putString("miui.focus.param", islandParam(running, first, isl.second)) }
|
||||||
firstIslandNotify = false
|
firstIslandNotify = false
|
||||||
}
|
}
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun islandOn(): Boolean =
|
/** 读取岛设置:(开关, 参数模式);读取失败返回 null 表示不带岛参数 */
|
||||||
runCatching { com.smbfast.app.data.Prefs(applicationContext).loadSettings().islandOn }
|
private fun islandSettings(): Pair<Boolean, String>? = runCatching {
|
||||||
.getOrDefault(true)
|
val s = com.smbfast.app.data.Prefs(applicationContext).loadSettings()
|
||||||
|
s.islandOn to (s.islandMode ?: MODE_FULL)
|
||||||
|
}.getOrNull()
|
||||||
|
|
||||||
/** 岛内图标:直接复用应用图标 */
|
/** 岛内图标:直接复用应用图标 */
|
||||||
private fun islandPics(): Bundle = Bundle().apply {
|
private fun islandPics(): Bundle = Bundle().apply {
|
||||||
@@ -192,12 +195,13 @@ class TransferService : Service() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 超级岛/焦点通知参数(miui.focus.param):
|
* 超级岛/焦点通知参数(miui.focus.param):
|
||||||
* - HyperOS 3 读 param_v2.param_island(屏幕顶部大/小岛)
|
* - MODE_FULL:param_v2 全量(param_island 大/小岛 + pics + baseInfo),HyperOS 3 超级岛;
|
||||||
* - HyperOS 2 读 param.baseInfo(状态栏焦点)
|
* 实测会被白名单整体过滤(通知都消失)
|
||||||
* 空闲(无任务)时展示"就绪"岛,让常驻通知不传文件也能上岛。
|
* - MODE_MINIMAL:param_v2 仅 baseInfo/ticker/aodTitle,无任何岛结构与图片引用,
|
||||||
* 每次更新同一通知 id + sequence 递增即可刷新岛内容。
|
* 用于验证白名单校验是否只在完整岛结构时触发(实验二)
|
||||||
|
* 空闲(无任务)时展示"就绪"内容。每次更新同一通知 id + sequence 递增即可刷新。
|
||||||
*/
|
*/
|
||||||
private fun islandParam(running: List<TransferItem>?, firstFloat: Boolean): String {
|
private fun islandParam(running: List<TransferItem>?, firstFloat: Boolean, mode: String): String {
|
||||||
islandSequence += 1
|
islandSequence += 1
|
||||||
val idle = running.isNullOrEmpty()
|
val idle = running.isNullOrEmpty()
|
||||||
val up = running?.count { it.direction == Direction.UPLOAD } ?: 0
|
val up = running?.count { it.direction == Direction.UPLOAD } ?: 0
|
||||||
@@ -218,6 +222,23 @@ class TransferService : Service() {
|
|||||||
val islandContent = if (idle) "后台就绪 · 等待任务" else "$speedText · $size 个任务"
|
val islandContent = if (idle) "后台就绪 · 等待任务" else "$speedText · $size 个任务"
|
||||||
val first = running?.firstOrNull()?.name ?: "SMBFast"
|
val first = running?.firstOrNull()?.name ?: "SMBFast"
|
||||||
|
|
||||||
|
val baseInfo = JSONObject()
|
||||||
|
.put("title", dirText)
|
||||||
|
.put("content", if (idle) "后台就绪" else "$first · $pct% · $speedText")
|
||||||
|
.put("colorTitle", "#22D3EE")
|
||||||
|
.put("type", 2)
|
||||||
|
|
||||||
|
val tickerText = if (idle) "SMBFast 就绪" else "$dirText $pct% · $speedText"
|
||||||
|
val paramV2 = JSONObject()
|
||||||
|
.put("protocol", 1)
|
||||||
|
.put("business", "smbfast_transfer")
|
||||||
|
.put("updatable", true)
|
||||||
|
.put("sequence", islandSequence)
|
||||||
|
.put("ticker", tickerText)
|
||||||
|
.put("aodTitle", tickerText)
|
||||||
|
.put("baseInfo", baseInfo)
|
||||||
|
|
||||||
|
if (mode != MODE_MINIMAL) {
|
||||||
val textInfo = JSONObject()
|
val textInfo = JSONObject()
|
||||||
.put("frontTitle", dirText)
|
.put("frontTitle", dirText)
|
||||||
.put("title", islandTitle)
|
.put("title", islandTitle)
|
||||||
@@ -235,25 +256,13 @@ class TransferService : Service() {
|
|||||||
.put("bigIslandArea", JSONObject().put("imageTextInfoLeft", bigLeft).put("picInfo", picInfo))
|
.put("bigIslandArea", JSONObject().put("imageTextInfoLeft", bigLeft).put("picInfo", picInfo))
|
||||||
.put("smallIslandArea", JSONObject().put("picInfo", picInfo))
|
.put("smallIslandArea", JSONObject().put("picInfo", picInfo))
|
||||||
|
|
||||||
val baseInfo = JSONObject()
|
paramV2
|
||||||
.put("title", dirText)
|
|
||||||
.put("content", if (idle) "后台就绪" else "$first · $pct% · $speedText")
|
|
||||||
.put("colorTitle", "#22D3EE")
|
|
||||||
.put("type", 2)
|
|
||||||
|
|
||||||
val paramV2 = JSONObject()
|
|
||||||
.put("protocol", 1)
|
|
||||||
.put("business", "smbfast_transfer")
|
|
||||||
.put("updatable", true)
|
|
||||||
.put("enableFloat", true) // 启用浮动(胶囊/浮窗形态);false 会压住岛不展示
|
.put("enableFloat", true) // 启用浮动(胶囊/浮窗形态);false 会压住岛不展示
|
||||||
.put("islandFirstFloat", firstFloat) // 仅首条通知展开一次
|
.put("islandFirstFloat", firstFloat) // 仅首条通知展开一次
|
||||||
.put("sequence", islandSequence)
|
|
||||||
.put("ticker", if (idle) "SMBFast 就绪" else "$dirText $pct% · $speedText")
|
|
||||||
.put("aodTitle", if (idle) "SMBFast 就绪" else "$dirText $pct% · $speedText")
|
|
||||||
.put("tickerPic", "miui.focus.pic_ticker")
|
.put("tickerPic", "miui.focus.pic_ticker")
|
||||||
.put("aodPic", "miui.focus.pic_ticker")
|
.put("aodPic", "miui.focus.pic_ticker")
|
||||||
.put("param_island", paramIsland)
|
.put("param_island", paramIsland)
|
||||||
.put("baseInfo", baseInfo)
|
}
|
||||||
|
|
||||||
// 单一根节点 param_v2:曾测试过 param+param_v2 双轨,HyperOS 解析异常会把
|
// 单一根节点 param_v2:曾测试过 param+param_v2 双轨,HyperOS 解析异常会把
|
||||||
// 整条通知吞掉(比不用岛更糟),故保持单一根节点
|
// 整条通知吞掉(比不用岛更糟),故保持单一根节点
|
||||||
@@ -308,6 +317,12 @@ class TransferService : Service() {
|
|||||||
const val ACTION_CANCEL_ALL = "com.smbfast.app.transfer.CANCEL_ALL"
|
const val ACTION_CANCEL_ALL = "com.smbfast.app.transfer.CANCEL_ALL"
|
||||||
const val ACTION_PERSISTENT = "com.smbfast.app.transfer.PERSISTENT"
|
const val ACTION_PERSISTENT = "com.smbfast.app.transfer.PERSISTENT"
|
||||||
|
|
||||||
|
/** 岛参数模式:完整岛结构(默认,会被白名单过滤) */
|
||||||
|
const val MODE_FULL = "full"
|
||||||
|
|
||||||
|
/** 岛参数模式:极简(仅 baseInfo/ticker,无岛结构,白名单实验二) */
|
||||||
|
const val MODE_MINIMAL = "minimal"
|
||||||
|
|
||||||
/** 有任务入队时调用;须在应用处于前台时启动(前台服务启动限制) */
|
/** 有任务入队时调用;须在应用处于前台时启动(前台服务启动限制) */
|
||||||
fun start(ctx: Context) {
|
fun start(ctx: Context) {
|
||||||
ContextCompat.startForegroundService(ctx, Intent(ctx, TransferService::class.java))
|
ContextCompat.startForegroundService(ctx, Intent(ctx, TransferService::class.java))
|
||||||
|
|||||||
@@ -191,6 +191,42 @@ fun SettingsScreen(vm: AppViewModel) {
|
|||||||
fontSize = 13.sp,
|
fontSize = 13.sp,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (s.islandOn) {
|
||||||
|
Spacer(Modifier.height(10.dp))
|
||||||
|
Text("参数模式(切换后立即重发通知,看状态栏图标是否消失即可判断是否被过滤)", color = Neon.TextDim, fontSize = 11.sp, lineHeight = 15.sp)
|
||||||
|
Spacer(Modifier.height(6.dp))
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||||
|
listOf(
|
||||||
|
"full" to "完整岛",
|
||||||
|
"minimal" to "极简(实验)",
|
||||||
|
).forEach { (mode, label) ->
|
||||||
|
FilterChip(
|
||||||
|
selected = (s.islandMode ?: "full") == mode,
|
||||||
|
onClick = {
|
||||||
|
vm.updateSettings { it.copy(islandMode = mode) }
|
||||||
|
runCatching {
|
||||||
|
com.smbfast.app.transfer.TransferService.startPersistent(ctx)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
label = { Text(label) },
|
||||||
|
colors = FilterChipDefaults.filterChipColors(
|
||||||
|
containerColor = Color.White.copy(alpha = 0.06f),
|
||||||
|
labelColor = Neon.TextDim,
|
||||||
|
selectedContainerColor = Neon.Cyan.copy(alpha = 0.22f),
|
||||||
|
selectedLabelColor = Neon.Cyan,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer(Modifier.height(6.dp))
|
||||||
|
Text(
|
||||||
|
"完整岛=含大/小岛结构(已知被白名单拦截);极简=仅 baseInfo/ticker 无岛结构," +
|
||||||
|
"验证白名单是否只拦完整参数",
|
||||||
|
color = Neon.TextDim.copy(alpha = 0.7f),
|
||||||
|
fontSize = 10.sp,
|
||||||
|
lineHeight = 14.sp,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NotificationDiagCard()
|
NotificationDiagCard()
|
||||||
|
|||||||
Reference in New Issue
Block a user