feat: 身份页显示本机账号台账(accounts_b64,v1.5)

平台在拉起身份页时顺带推这台设备登记的抖音账号(契约见
平台 doc/DEVICE_AGENT.md §5.2.1 与本仓库 INTERFACE.md §5.2.1):

- 解析 `--es accounts_b64`(标准 base64 + UTF-8 → JSONObject),在设备信息下面
  渲染账号列表:账号名称 / 抖音号 / [卡·发] 标记
- 一屏放不下:**最多显示 4 个** + "…还有 N 个,见平台「账号」页"
  (第一版 26sp 一行塞"名称+号+标记",720x1650 上第 3 个号就被切掉了,实测后收紧)
- 三种情况分清楚:无 extra → 整块不显示(= 这台没登记账号,行为与旧版逐字一致);
  有但解不开 → 显示一行"解析失败"(不静默,否则像"没账号");能解开 → 逐条列出
- 隐私:平台默认不推手机号(身份页是机器旁的公开屏幕)

版本 1.4 → 1.5(versionCode 6)。已在 A03 装机验证(带账号/不带账号两种截图)。
This commit is contained in:
2026-09-24 16:18:25 +08:00
parent 10a55849b6
commit b81aa1fd82
3 changed files with 112 additions and 8 deletions
+32 -2
View File
@@ -201,17 +201,47 @@ adb -s <serial> shell am start -n <pkg>/.ClipActivity --es text_b64 <base64(UTF-
- 平台判定失败的方式:`am start` 输出里有 `unable to resolve Intent` / `does not exist`
→ 视为"设备未安装 Agent"
### 5.2 设备身份显示(待设备端实现)
### 5.2 设备身份显示(已实现)
```bash
adb -s <serial> shell am start -n <pkg>/.InfoActivity \
--es name "A08" --es ip "192.168.20.100" --es serial "192.168.20.100:5555" \
--es server "http://192.168.20.220:18050"
--es server "http://192.168.20.220:18050" \
--es accounts_b64 "<base64(UTF-8 JSON)>" # 可选,见 §5.2.1
# 关闭:--es close 1
```
要求:全屏大字、再次调用可覆盖内容、`close` 可关闭、不驻留前台。
#### 5.2.1 本机账号台账(`accounts_b64`,可选 extras,已实现 v1.5)
平台在**打开**身份页时顺带推这台设备在「账号」页登记的抖音账号(`close` 时不带)。
**标准 base64(不是 URL_SAFE)+ UTF-8**:
```json
{"v":1,"total":4,"shown":4,"truncated":false,
"note":"设备『A03』登记 4 个号",
"accounts":[
{"name":"AA建材王总","douyin_id":"66057500463","device_name":"A03","device_no":"A03",
"sim_in_device":true,"can_post_video":true}]}
```
| 字段 | 说明 |
|------|------|
| `v` | 块格式版本(当前 **1**) |
| `total` / `shown` / `truncated` | 台账里的总数 / 本次几条 / 是否被截断 |
| `accounts[].name` / `douyin_id` | 账号名称 / **抖音号(纯号)** |
| `accounts[].sim_in_device` / `can_post_video` | 卡在机内 / 可发视频 |
| `accounts[].phone` | **默认不推**(大字页是公开屏幕);平台侧可传 `include_phone:true` 要 |
设备端三种情况要分清(InfoActivity 已按此实现):
| 情况 | 表现 |
|---|---|
| 没有该 extra | 账号块**整块不显示**(= 这台设备在台账里没登记账号,旧平台也走这条) |
| 有但解不开 | 显示一行**解析失败**提示(不要静默,否则看着像"这台没账号") |
| 有且能解开 | 逐条显示;`truncated` 时补"…还有 N 个,见平台「账号」页" |
### 5.3 一次性配置下发(建议设备端实现)
```bash
+2 -2
View File
@@ -11,8 +11,8 @@ android {
applicationId "com.example.deviceagent"
minSdk 29 // Android 10;剪贴板限制就是从这一版开始的
targetSdk 34
versionCode 5
versionName "1.4"
versionCode 6
versionName "1.5" // 1.5:身份页显示本机账号台账(accounts_b64)
}
buildTypes {
@@ -4,11 +4,13 @@ import android.app.Activity
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.util.Base64
import android.util.TypedValue
import android.view.Gravity
import android.view.WindowManager
import android.widget.LinearLayout
import android.widget.TextView
import org.json.JSONObject
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
@@ -18,16 +20,23 @@ import java.util.Locale
*
* adb shell am start -n com.example.deviceagent/.InfoActivity \
* --es name A08 --es ip 192.168.20.100 --es serial 192.168.20.100:5555 \
* --es server http://192.168.20.220:18050
* --es server http://192.168.20.220:18050 \
* --es accounts_b64 <base64(UTF-8 JSON)> # 可选:本机账号台账
* # 关闭:--es close 1
*
* 用途:人站在机架前,一眼看出"手头这台是平台上的哪一台";排查断联时不用连电脑。
* 用途:人站在机架前,一眼看出"手头这台是平台上的哪一台"、**登着哪几个号**;
* 对号/排查断联时不用连电脑。
* 再次调用会覆盖内容(launchMode=singleTop + onNewIntent)。
*
* `accounts_b64` 是可选的加字段(契约见平台 doc/DEVICE_AGENT.md §5.2.1 与本仓库 INTERFACE.md):
* **没有这个 extra = 这台设备在台账里没有账号**(不是"平台忘了给"),此时账号块整块不显示。
* 解码用**标准 base64**(不是 URL_SAFE)+ UTF-8。
*/
class InfoActivity : Activity() {
private lateinit var tvName: TextView
private lateinit var tvLines: TextView
private lateinit var tvAccounts: TextView
private lateinit var tvTime: TextView
override fun onCreate(savedInstanceState: Bundle?) {
@@ -61,9 +70,19 @@ class InfoActivity : Activity() {
}
tvLines = TextView(this).apply {
setTextColor(Color.parseColor("#FFD54F"))
setTextSize(TypedValue.COMPLEX_UNIT_SP, 26f)
setTextSize(TypedValue.COMPLEX_UNIT_SP, 24f)
gravity = Gravity.CENTER
setPadding(0, 40, 0, 40)
setPadding(0, 24, 0, 20)
}
// 本机账号(台账推过来的):站在机器旁就能看出这台登着哪几个号
// 字号/行距刻意比上面的设备信息小一档:一屏要塞下"设备信息 + 最多 4 个号",
// 26sp 时第 3 个号就被切掉了(实测 720x1650 上溢出)
tvAccounts = TextView(this).apply {
setTextColor(Color.parseColor("#80DEEA"))
setTextSize(TypedValue.COMPLEX_UNIT_SP, 22f)
gravity = Gravity.START
setPadding(0, 8, 0, 16)
visibility = android.view.View.GONE // 没有 accounts_b64 就不占位
}
tvTime = TextView(this).apply {
setTextColor(Color.parseColor("#9E9E9E"))
@@ -72,6 +91,7 @@ class InfoActivity : Activity() {
}
root.addView(tvName)
root.addView(tvLines)
root.addView(tvAccounts)
root.addView(tvTime)
setContentView(root)
}
@@ -90,5 +110,59 @@ class InfoActivity : Activity() {
"Agent v${Prefs.agentVersion(this)}"
).filter { it.substringAfter(" ").trim().isNotEmpty() }.joinToString("\n")
tvTime.text = "更新于 " + SimpleDateFormat("HH:mm:ss", Locale.CHINA).format(Date())
renderAccounts(intent?.getStringExtra("accounts_b64").orEmpty())
}
/**
* 本机账号块。契约(平台 doc/DEVICE_AGENT.md §5.2.1):
* `{"v":1,"total":4,"shown":4,"truncated":false,"accounts":[{"name","douyin_id",…}]}`
*
* 三种情况要分清,别混成一种:
* · 没有 extra → 这台设备在台账里没登记账号 → 整块不显示(旧平台/旧行为)
* · 有但解不开 → 格式变了或串被截断 → 显示一行提示(**不要静默**,否则像"没账号")
* · 有且能解开 → 逐条显示;`truncated` 时补一行"还有 N 个"
*/
private fun renderAccounts(b64: String) {
if (b64.isEmpty()) {
tvAccounts.visibility = android.view.View.GONE
return
}
tvAccounts.visibility = android.view.View.VISIBLE
val obj = try {
JSONObject(String(Base64.decode(b64, Base64.DEFAULT), Charsets.UTF_8))
} catch (e: Exception) {
// 解不开要说出来:不然跟"这台没账号"长得一样,平台侧还以为推成功了
tvAccounts.setTextColor(Color.parseColor("#FF8A65"))
tvAccounts.text = "账号信息解析失败(v?):${e.javaClass.simpleName}"
return
}
tvAccounts.setTextColor(Color.parseColor("#80DEEA"))
val total = obj.optInt("total", 0)
val arr = obj.optJSONArray("accounts") ?: org.json.JSONArray()
if (arr.length() == 0) {
// 有块但 0 条:明确写出来,免得人以为"这页没渲染完"
tvAccounts.text = "本机账号:暂无(平台「账号」页还没登记这台设备)"
return
}
// 一屏放不下太多:**只显示前 4 个**,其余用"还有 N 个"交代
// (人在机器旁要的是"这台是谁",不是把台账全量投到屏上)
val maxShow = 4
val n = minOf(arr.length(), maxShow)
val sb = StringBuilder("本机账号 $total 个")
for (i in 0 until n) {
val a = arr.optJSONObject(i) ?: continue
val name = a.optString("name").ifEmpty { "(未命名)" }
val id = a.optString("douyin_id")
val marks = buildList {
if (a.optBoolean("sim_in_device")) add("卡")
if (a.optBoolean("can_post_video")) add("发")
}
sb.append("\n").append(i + 1).append(". ").append(name)
if (id.isNotEmpty()) sb.append("\n ").append(id)
if (marks.isNotEmpty()) sb.append(" ").append(marks.joinToString("·"))
}
val rest = total - n
if (rest > 0) sb.append("\n…还有 ").append(rest).append(" 个,见平台「账号」页")
tvAccounts.text = sb.toString()
}
}