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
+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()
}
}