package com.duolebo.blyrobot.data import android.content.Context import android.os.Environment import android.util.Log import com.duolebo.appbase.AppBaseHandler import com.duolebo.appbase.IAppBaseCallback import com.duolebo.appbase.IProtocol import com.duolebo.blyrobot.protocol.ApkReportProtocol import com.duolebo.blyrobot.util.AdbUtil import com.duolebo.blyrobot.util.AppUtil import com.duolebo.blyrobot.util.Config import net.gotev.uploadservice.ServerResponse import net.gotev.uploadservice.UploadInfo import net.gotev.uploadservice.UploadNotificationConfig import net.gotev.uploadservice.UploadStatusDelegate import net.gotev.uploadservice.ftp.FTPUploadRequest import net.gotev.uploadservice.ftp.UnixPermissions import org.json.JSONObject import java.io.File import java.text.SimpleDateFormat import java.util.* import kotlin.collections.ArrayList class Task : IAppBaseCallback { private val TAG = "Task" val STATUS_MAP = mapOf( "0" to "未处理", "1" to "进行中", "2" to "中断", "3" to "图片上传中", "4" to "上报失败", "5" to "处理完成") lateinit var apkInfo: ApkInfo // 截图存放路径 private lateinit var imagePath: String // 抓包存放路径 private var capturePath = Environment .getExternalStorageDirectory().absolutePath + "/capture.txt" private var uploadImages = ArrayList() private var channelIndex = 0 private var status = "0" private var reportProtocol: ApkReportProtocol private var dataHandler:AppBaseHandler private lateinit var reportJson: JSONObject private var context: Context private val imageDateFormat = SimpleDateFormat("yyyy_MM_dd_HH_mm_ss", Locale.CHINA) var proc: Process? = null constructor(context: Context) { this.context = context this.reportProtocol = ApkReportProtocol(context, Config.instance) this.dataHandler = AppBaseHandler(this) } fun from(apkInfo: ApkInfo) { this.apkInfo = apkInfo // this.imagePath = this.context.cacheDir.absolutePath + "/" + apkInfo.packageName this.imagePath = Environment .getExternalStorageDirectory().absolutePath + "/upload/" + apkInfo.packageName val dir = File(this.imagePath) if (!dir.exists()) dir.mkdirs() } fun start() { this.status = "1" this.reportJson = JSONObject() // 启动应用 this.launchApp() // 频道轮询处理 try { this.processChannels() // 上传图片 this.uploadImage() this.status = "3" } catch (e: java.lang.Exception) { this.status = "2" e.printStackTrace() } // 退出应用 AdbUtil.stopApp(this.apkInfo.packageName) } private fun finish(result: Boolean) { this.taskListener?.run { onComplete(result) } } private fun launchApp(reset: Boolean = false) { Log.i(TAG, "launchApp") if (reset) { try { AdbUtil.resetApp(this.apkInfo.packageName) } catch (e: java.lang.Exception) { e.printStackTrace() } } AdbUtil.launchApp("${this.apkInfo.packageName}/${this.apkInfo.launcher}") Thread.sleep(2 * 1000L) // 启动2s开始抓取,避免第一个频道没有抓到 capture(this.apkInfo.launchDelay + this.apkInfo.captureDelay) Thread.sleep(this.apkInfo.launchDelay * 1000L) } private fun processChannels() { Log.i(TAG, "processChannels") step() for (i in 1 until this.apkInfo.channelCount) { // 模拟按键事件. 切换频道进行抓取 this.channelIndex = i capture(apkInfo.captureDelay) AdbUtil.sendMultiKey(this.apkInfo.channelKeyEvent) step() } } private fun step() { // 截图保存 val screenShots = saveScreenShot() if (screenShots.size > 0) uploadImages.addAll(screenShots) val playUrlItems = analysisCapture() } // 抓包处理 private fun capture(delay: Int) { Log.i(TAG, "capture") val file = File(this.capturePath) if (file.exists()) file.delete() Thread(Runnable { proc = AdbUtil.tcpCapture(this.capturePath) }).start() Log.i(TAG, "capture sleep $delay seconds") Thread.sleep(delay * 1000L) proc?.destroy() AdbUtil.killTcpdump() Thread.sleep(2000L) } // 分析http抓包文件 private fun analysisCapture(): ArrayList { val playUrlItems = ArrayList() val file = File(this.capturePath) if (!file.exists()) return playUrlItems val lines = file.readLines() var partUrl = "" var timeStr = "" val tagGet = "GET /" lines.forEach { if (it.contains("IP (")) { val end = it.indexOf('.') timeStr = it.substring(0, end) } if (it.contains(tagGet)) { val start = it.indexOf(tagGet) + tagGet.length - 1 val end = it.lastIndexOf(' ') partUrl = it.substring(start, end) } if (it.contains("Host")) { val start = it.indexOf(' ') + 1 val host = it.substring(start) val url = "http://$host$partUrl" var add = true // 如果筛选url不为空,这里需要进行过滤 if (!this.apkInfo.filterUrl.isNullOrEmpty()) { if (url.contains(this.apkInfo.filterUrl)) add = false } if (add) { // 这里进行媒体视频播放地址识别 val extArr = this.apkInfo.mediaExt.split("|") for (ext in extArr) { if (url.contains(ext)) { add = true break } } } if (add) { val item = PlayInfo() item.url = url item.time = timeStr playUrlItems.add(item) } partUrl = "" } } return playUrlItems } // 截取一张频道图+一张显示频道节目的图 private fun saveScreenShot(): ArrayList { val screenImages = ArrayList() var time = imageDateFormat.format(Date()) val absName = this.imagePath + "/${this.apkInfo.packageName}_${channelIndex}_$time" screenShot(absName) screenImages.add("$absName.jpg") AdbUtil.sendMultiKey(this.apkInfo.menuKeyEvent) time = imageDateFormat.format(Date()) val absOkName = this.imagePath + "/${this.apkInfo.packageName}_${channelIndex}_${time}_ok" screenShot(absOkName) screenImages.add("$absOkName.jpg") return screenImages } // 截图处理,转成jpg private fun screenShot(absName : String) { val pngPath = "$absName.png" val jpgPath = pngPath.replace(".png", ".jpg") AdbUtil.screenShot(pngPath) // png转换成jpg AppUtil.pngToJpg(pngPath, jpgPath) } private fun uploadComplete(errorCount: Int) { report() } private fun uploadImageCallback(): UploadStatusDelegate { val totalUpload = this.uploadImages.size var uploadCount = 0 return object: UploadStatusDelegate { override fun onCancelled(context: Context?, uploadInfo: UploadInfo?) { } override fun onProgress(context: Context?, uploadInfo: UploadInfo?) { } override fun onError(context: Context?, uploadInfo: UploadInfo?, serverResponse: ServerResponse?, exception: java.lang.Exception?) { Log.i(TAG, "upload error " + uploadInfo?.toString()) uploadCount++ if (uploadCount == totalUpload) { uploadComplete(uploadCount - uploadInfo?.successfullyUploadedFiles!!.size) } } override fun onCompleted(context: Context?, uploadInfo: UploadInfo?, serverResponse: ServerResponse?) { uploadCount++ if (uploadCount == totalUpload) { uploadComplete(uploadCount - uploadInfo?.successfullyUploadedFiles!!.size) } } } } private fun uploadImage() { try { val uploadRequest = FTPUploadRequest(context, Config.instance.getFtpServer(), 21) .setUsernameAndPassword(Config.instance.getFtpUserName(), Config.instance.getFtpPassword()) .setNotificationConfig(UploadNotificationConfig()) .setCreatedDirectoriesPermissions(UnixPermissions("777")) .setSocketTimeout(5000) .setConnectTimeout(5000) .setDelegate(uploadImageCallback()) .setMaxRetries(4) this.uploadImages.forEach { val uploadFile = File(it) if (uploadFile.exists()) { uploadRequest.addFileToUpload(it, Config.instance.getFtpRemotePath()) } } val uploadId = uploadRequest.startUpload() Log.i(TAG, "upload id $uploadId") } catch (exc: Exception) { Log.e(TAG, exc.message, exc) } } private fun report() { reportProtocol.withBody(reportJson.toString()).execute(dataHandler) } override fun onProtocolFailed(p0: IProtocol?) { this.status = "4" finish(false) } override fun onHttpFailed(p0: IProtocol?) { this.status = "4" finish(false) } override fun onProtocolSucceed(p0: IProtocol?) { this.status = "5" finish(true) } var taskListener: OnTaskListener? = null interface OnTaskListener { fun onComplete(result: Boolean) } class PlayInfo { var time = "" var url = "" } }