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` - 平台判定失败的方式:`am start` 输出里有 `unable to resolve Intent` / `does not exist`
→ 视为"设备未安装 Agent" → 视为"设备未安装 Agent"
### 5.2 设备身份显示(待设备端实现) ### 5.2 设备身份显示(已实现)
```bash ```bash
adb -s <serial> shell am start -n <pkg>/.InfoActivity \ 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 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 # 关闭:--es close 1
``` ```
要求:全屏大字、再次调用可覆盖内容、`close` 可关闭、不驻留前台。 要求:全屏大字、再次调用可覆盖内容、`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 一次性配置下发(建议设备端实现) ### 5.3 一次性配置下发(建议设备端实现)
```bash ```bash
+2 -2
View File
@@ -11,8 +11,8 @@ android {
applicationId "com.example.deviceagent" applicationId "com.example.deviceagent"
minSdk 29 // Android 10;剪贴板限制就是从这一版开始的 minSdk 29 // Android 10;剪贴板限制就是从这一版开始的
targetSdk 34 targetSdk 34
versionCode 5 versionCode 6
versionName "1.4" versionName "1.5" // 1.5:身份页显示本机账号台账(accounts_b64)
} }
buildTypes { buildTypes {
@@ -4,11 +4,13 @@ import android.app.Activity
import android.content.Intent import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.util.Base64
import android.util.TypedValue import android.util.TypedValue
import android.view.Gravity import android.view.Gravity
import android.view.WindowManager import android.view.WindowManager
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import org.json.JSONObject
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
import java.util.Locale import java.util.Locale
@@ -18,16 +20,23 @@ import java.util.Locale
* *
* adb shell am start -n com.example.deviceagent/.InfoActivity \ * 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 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 * # 关闭:--es close 1
* *
* 用途:人站在机架前,一眼看出"手头这台是平台上的哪一台";排查断联时不用连电脑。 * 用途:人站在机架前,一眼看出"手头这台是平台上的哪一台"、**登着哪几个号**;
* 对号/排查断联时不用连电脑。
* 再次调用会覆盖内容(launchMode=singleTop + onNewIntent)。 * 再次调用会覆盖内容(launchMode=singleTop + onNewIntent)。
*
* `accounts_b64` 是可选的加字段(契约见平台 doc/DEVICE_AGENT.md §5.2.1 与本仓库 INTERFACE.md):
* **没有这个 extra = 这台设备在台账里没有账号**(不是"平台忘了给"),此时账号块整块不显示。
* 解码用**标准 base64**(不是 URL_SAFE)+ UTF-8。
*/ */
class InfoActivity : Activity() { class InfoActivity : Activity() {
private lateinit var tvName: TextView private lateinit var tvName: TextView
private lateinit var tvLines: TextView private lateinit var tvLines: TextView
private lateinit var tvAccounts: TextView
private lateinit var tvTime: TextView private lateinit var tvTime: TextView
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -61,9 +70,19 @@ class InfoActivity : Activity() {
} }
tvLines = TextView(this).apply { tvLines = TextView(this).apply {
setTextColor(Color.parseColor("#FFD54F")) setTextColor(Color.parseColor("#FFD54F"))
setTextSize(TypedValue.COMPLEX_UNIT_SP, 26f) setTextSize(TypedValue.COMPLEX_UNIT_SP, 24f)
gravity = Gravity.CENTER 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 { tvTime = TextView(this).apply {
setTextColor(Color.parseColor("#9E9E9E")) setTextColor(Color.parseColor("#9E9E9E"))
@@ -72,6 +91,7 @@ class InfoActivity : Activity() {
} }
root.addView(tvName) root.addView(tvName)
root.addView(tvLines) root.addView(tvLines)
root.addView(tvAccounts)
root.addView(tvTime) root.addView(tvTime)
setContentView(root) setContentView(root)
} }
@@ -90,5 +110,59 @@ class InfoActivity : Activity() {
"Agent v${Prefs.agentVersion(this)}" "Agent v${Prefs.agentVersion(this)}"
).filter { it.substringAfter(" ").trim().isNotEmpty() }.joinToString("\n") ).filter { it.substringAfter(" ").trim().isNotEmpty() }.joinToString("\n")
tvTime.text = "更新于 " + SimpleDateFormat("HH:mm:ss", Locale.CHINA).format(Date()) 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()
} }
} }