一. 业务背景
隐私清除是手机质检的重要一环,我们回收的手机在经过自动化质检完成后,会对手机进行隐私清除。
在进行隐私清除之前,需要确保手机退出云服务的帐号。例如 iPhone 手机需要退出 iCloud ,华为、小米等手机都要退出对应的云服务。否则会造成隐私数据的泄漏的风险,也会让后续购买此手机的用户无法享受到云服务的功能。
因此,帐号检测是一项很重要的功能。本节以 Android 手机的帐号检测是否退出为例,主要是针对华为、小米等有比较明显的特征的手机,通过图像预处理、OCR 进行识别。
我们的隐私清除工具是一个桌面端程序,运行在 Ubuntu 系统上。

对于 Android 手机,桌面工具通过 adb 命令将隐私清除 App 安装到手机上,然后二者通过 WebSocket 进行通信,做手机的隐私清除。

二. 设计思路
在做帐号检测这个功能之前,我尝试过很多办法来判断帐号是否退出,例如找相关的 adb 命令,或者对应厂商的 API,都没有很好的效果。经过不断摸索后,采用如下的方式:
使用 adb 命令修改手机的休眠时间,确保手机一段时间内不会熄屏。
使用 adb 命令跳转到系统设置页面(不同的手机使用的命令略有不同)
使用 adb 命令对当前页面进行截图
使用 adb 命令将图片传输到桌面端的机器
通过程序对原图进行裁剪,保留原先的40%
对裁剪的图片进行图像二值化处理(不同的手机采用不同的二值化算法)
调用 OCR 进行特征字符串的识别。
比对字符串相似度,最终确定帐号是否退出。

这种方式在华为、小米手机上取得很好的效果。
三. 代码实现以及踩过的坑
核心代码
核心的代码使用 RxJava 将上述所有过程串联起来,每一个过程是一个 map 操作,下面展示检测华为手机的帐号是否退出:
object HuaweiDetect : IDetect {
val logger: Logger = LoggerFactory.getLogger(this.javaClass)
val list by lazy {
arrayOf("华为帐号、云空间、应用市场等"
,"华为帐号、付款与账单、云空间等"
,"华为帐号、云空间"
,"华为帐号、付款与账单")
}
override fun detectUserAccount(serialNumber:String,detail:String): Observable<Boolean> {
val file = File(detectAccountPath)
if (!file.exists()) {
file.mkdir()
}
val timeoutCmd = CommandBuilder.buildSudoCommand("aihuishou","$adbLocation -s $serialNumber shell settings put system screen_off_timeout 600000")
CommandExecutor.executeSync(timeoutCmd,appender = object : Appender {
override fun appendErrText(text: String) {
println(text)
}
override fun appendStdText(text: String) {
println(text)
}
}).getExecutionResult()
val cmd = CommandBuilder.buildSudoCommand("aihuishou","$adbLocation -s $serialNumber shell am start -S com.android.settings/.HWSettings")
val fileName = "${serialNumber}-${detail}.png"
return CommandExecutor.execute(cmd)
.asObservable()
.delay(2, TimeUnit.SECONDS)
.map {
&nb