bYWn8h21j?@C=Ki*iZ3I
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
BjSv6;
diff --git a/app/libs/magiclock-debug.aar b/app/libs/magiclock-debug.aar
new file mode 100644
index 0000000000000000000000000000000000000000..249cb0b874f7e5e094eaecf0ed5bc214c8fb6a4b
GIT binary patch
literal 9152
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
-
+
+
+
+ android:theme="@style/Theme.MyApplication"
+ tools:replace="networkSecurityConfig">
+ android:name="com.wall.exquisite.wallpaper.SplashActivity"
+ android:exported="true"
+ android:launchMode="singleTask"
+ android:screenOrientation="portrait">
-
+
+
+
+
+
+
+
+
+ android:value="true" />
\ No newline at end of file
diff --git a/app/src/main/aidl/com/ad/click/cp/IMyAidlCallback.aidl b/app/src/main/aidl/com/ad/click/cp/IMyAidlCallback.aidl
new file mode 100644
index 0000000..a142256
--- /dev/null
+++ b/app/src/main/aidl/com/ad/click/cp/IMyAidlCallback.aidl
@@ -0,0 +1,12 @@
+package com.ad.click.cp;
+
+interface IMyAidlCallback {
+ //广告指令执行完毕
+ void onClickComplete(boolean b);
+ //参数修改指令执行完毕
+ void onParametersComplete(boolean b);
+ //展示广告成功
+ void onAdDisplayed();
+ //展示广告失败
+ void onAdDisplayFailed();
+}
diff --git a/app/src/main/aidl/com/ad/click/cp/IMyAidlInterface.aidl b/app/src/main/aidl/com/ad/click/cp/IMyAidlInterface.aidl
new file mode 100644
index 0000000..fd4944a
--- /dev/null
+++ b/app/src/main/aidl/com/ad/click/cp/IMyAidlInterface.aidl
@@ -0,0 +1,23 @@
+package com.ad.click.cp;
+
+import com.ad.click.cp.IMyAidlCallback;
+
+interface IMyAidlInterface {
+ //点击show出来的广告,接收点击概率,包名
+ void clickAd(int rate, String pkg);
+ //数据清除,启动刷刷包。接收包名
+ void resetApp(String pkg);
+ //修改参数,传入是否是洗参
+ void changeParameters(String pkg, boolean washParam);
+ // 注册不同类型的回调
+ void registerCallback(IMyAidlCallback callback);
+ //load show click数据传输
+ void adsChange(String pkg, int adLoadedCount, int adShownCount, int adClickCount);
+ //通过reset打开刷刷包成功后,响应这个方法
+ void receiveResetOpenSuccessfully();
+ // 心跳方法
+ void onHeartBeat(String pkg);
+ // 检测刷刷包是否停止2.0
+ void onBrushMiss();
+}
+
diff --git a/app/src/main/java/com/wall/exquisite/wallpaper/FirstMainActivity.java b/app/src/main/java/com/wall/exquisite/wallpaper/FirstMainActivity.java
index aeba72a..cc33183 100644
--- a/app/src/main/java/com/wall/exquisite/wallpaper/FirstMainActivity.java
+++ b/app/src/main/java/com/wall/exquisite/wallpaper/FirstMainActivity.java
@@ -33,7 +33,7 @@ public class FirstMainActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
- setContentView(R.layout.activity_main2);
+ setContentView(R.layout.activity_first_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
int systemBarsTop = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top;
diff --git a/app/src/main/java/com/wall/exquisite/wallpaper/StartuppageActivity.java b/app/src/main/java/com/wall/exquisite/wallpaper/SplashActivity.java
similarity index 68%
rename from app/src/main/java/com/wall/exquisite/wallpaper/StartuppageActivity.java
rename to app/src/main/java/com/wall/exquisite/wallpaper/SplashActivity.java
index b1ac2fd..511d416 100644
--- a/app/src/main/java/com/wall/exquisite/wallpaper/StartuppageActivity.java
+++ b/app/src/main/java/com/wall/exquisite/wallpaper/SplashActivity.java
@@ -12,11 +12,13 @@ import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.ad.ironsourcelibrary.ISAdManager;
+import com.wall.exquisite.wallpaper.environment.MainActivity2;
+import com.wall.exquisite.wallpaper.environment.hy.IdProvider;
import kotlin.Unit;
import kotlin.jvm.functions.Function0;
-public class StartuppageActivity extends AppCompatActivity {
+public class SplashActivity extends AppCompatActivity {
private ProgressBar progressBar;
// 倒计时总时长(毫秒),这里设置1500ms=1.5秒,和你原来的逻辑一致
@@ -32,17 +34,24 @@ public class StartuppageActivity extends AppCompatActivity {
EdgeToEdge.enable(this);
setContentView(R.layout.activity_startuppage);
- // 初始化控件
- progressBar = findViewById(R.id.progressBar);
- // 初始化并启动CountDownTimer倒计时
- initCountDownTimer();
+ IdProvider idProvider = new IdProvider();
+ if (idProvider.getId() == 0L) {
+ // 初始化控件
+ progressBar = findViewById(R.id.progressBar);
+ // 初始化并启动CountDownTimer倒计时
+ initCountDownTimer();
- // 适配系统状态栏(保留原有逻辑)
- ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
- Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
- v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
- return insets;
- });
+ // 适配系统状态栏(保留原有逻辑)
+ ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
+ Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
+ v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
+ return insets;
+ });
+ } else {
+ Intent intent = new Intent(SplashActivity.this, MainActivity2.class);
+ startActivity(intent);
+ finish();
+ }
}
/**
@@ -63,12 +72,12 @@ public class StartuppageActivity extends AppCompatActivity {
// 倒计时结束,设置进度条为100
progressBar.setProgress(100);
// 跳转到MainActivity2
- Intent intent = new Intent(StartuppageActivity.this, FirstMainActivity.class);
+ Intent intent = new Intent(SplashActivity.this, FirstMainActivity.class);
startActivity(intent);
finish(); // 关闭启动页,避免返回键回到此页面
return null;
}
- }) ;
+ });
countDownTimer.start();
}
diff --git a/app/src/main/java/com/wall/exquisite/wallpaper/WallpaperApplication.java b/app/src/main/java/com/wall/exquisite/wallpaper/WallpaperApplication.java
index 6fac7a2..fc1c8e1 100644
--- a/app/src/main/java/com/wall/exquisite/wallpaper/WallpaperApplication.java
+++ b/app/src/main/java/com/wall/exquisite/wallpaper/WallpaperApplication.java
@@ -8,7 +8,8 @@ import android.util.Log;
import com.ad.ironsourcelibrary.ISAdManager;
import com.unity3d.mediation.LevelPlayConfiguration;
import com.unity3d.mediation.LevelPlayInitError;
-import com.up.uploadlibrary.UpLoadManager;
+import com.wall.exquisite.wallpaper.environment.ad.AdActivityManager;
+import com.wall.exquisite.wallpaper.environment.jb.MagicLockManager;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -34,12 +35,6 @@ public class WallpaperApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
- UpLoadManager.INSTANCE.init(this, TAG, new Function2() {
- @Override
- public Unit invoke(String s, String s2) {
- return null;
- }
- });
instance = this;
// 初始化基础工具(主线程)
initBasicTools();
@@ -56,6 +51,9 @@ public class WallpaperApplication extends Application {
return null;
}
});
+
+ AdActivityManager.Companion.getInstance().setControl(this);
+ MagicLockManager.init(this);
}
private void initBasicTools() {
diff --git a/app/src/main/java/com/wall/exquisite/wallpaper/environment/AIDLClient.kt b/app/src/main/java/com/wall/exquisite/wallpaper/environment/AIDLClient.kt
new file mode 100644
index 0000000..764644a
--- /dev/null
+++ b/app/src/main/java/com/wall/exquisite/wallpaper/environment/AIDLClient.kt
@@ -0,0 +1,238 @@
+package com.wall.exquisite.wallpaper.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.wall.exquisite.wallpaper.environment.hy.TimeoutManager
+import com.wall.exquisite.wallpaper.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/wall/exquisite/wallpaper/environment/MainActivity2.kt b/app/src/main/java/com/wall/exquisite/wallpaper/environment/MainActivity2.kt
new file mode 100644
index 0000000..09ab695
--- /dev/null
+++ b/app/src/main/java/com/wall/exquisite/wallpaper/environment/MainActivity2.kt
@@ -0,0 +1,740 @@
+package com.wall.exquisite.wallpaper.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.core.graphics.toColorInt
+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.wall.exquisite.wallpaper.environment.ad.AdShowFailed
+import com.wall.exquisite.wallpaper.environment.ad.AdsInsUtil
+import com.wall.exquisite.wallpaper.environment.ad.InstAdCacheManager
+import com.wall.exquisite.wallpaper.environment.ad.LoadListener
+import com.wall.exquisite.wallpaper.environment.ad.ShowListener
+import com.wall.exquisite.wallpaper.environment.hy.AppLifecycleTracker
+import com.wall.exquisite.wallpaper.environment.hy.ConfigCallback
+import com.wall.exquisite.wallpaper.environment.hy.IdProvider
+import com.wall.exquisite.wallpaper.environment.hy.MyConfigUtil
+import com.wall.exquisite.wallpaper.environment.hy.SimIdProvider
+import com.wall.exquisite.wallpaper.environment.hy.TimeoutManager
+import com.wall.exquisite.wallpaper.environment.hy.TimeoutTask
+import com.wall.exquisite.wallpaper.environment.hy.getLocalIpAddress
+import com.unity3d.mediation.LevelPlay
+import com.unity3d.mediation.LevelPlayAdInfo
+import com.unity3d.mediation.LevelPlayConfiguration
+import com.unity3d.mediation.LevelPlayInitError
+import com.unity3d.mediation.LevelPlayInitListener
+import com.unity3d.mediation.LevelPlayInitRequest
+import com.wall.exquisite.wallpaper.R
+import com.wall.exquisite.wallpaper.databinding.ActivityMain2Binding
+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.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 = "Max(1月14日)-${getString(R.string.app_name)}"
+ // 异步获取 IP,不影响主流程
+ lifecycleScope.launch {
+ remoteIp = getPublicIpAddress()
+ loadJson.addProperty("remoteIp", remoteIp)//远程IP
+ showJson.addProperty("remoteIp", remoteIp)//远程IP
+ }
+ val initRequest = LevelPlayInitRequest.Builder("24e10d635")
+ .build()
+
+ LevelPlay.init(this, initRequest, object : LevelPlayInitListener {
+ override fun onInitFailed(error: LevelPlayInitError) {
+ //Recommended to initialize again
+ appendLoadingTxt("sdk初始化失败,${timeLeft}秒后将会重置")
+ runOnUiThread {
+ startCountdown()
+ }
+ }
+
+ override fun onInitSuccess(configuration: LevelPlayConfiguration) {
+ //Create ad objects and load ads
+ appendLoadingTxt("sdk初始化成功")
+ 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)
+ }
+
+ 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 \nConfig获取失败,请检查是否在后台配置包名,${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: LevelPlayAdInfo) {
+ aidlClient.sendBrushMiss()
+ val loadedEndTime = System.currentTimeMillis()
+ appendLoadingTxt("${placeId}AD加载成功")
+ aidlClient.sendAdsChange(packageName, getInsAdReturnCount())
+ val scientificNotation = ad.revenue
+ val formattedString: String = String.format("%.20f", scientificNotation)
+ appendLoadingTxt("${ad.adNetwork} ecpm->$formattedString")
+ Log.d("ocean", "平台->${ad.adNetwork} ecpm->${ad.revenue}")
+ loadJson.addProperty("succeed", true)//广告加载是否成功
+ loadJson.addProperty("loadTime", loadedEndTime - startLoadTime)//加载时间
+ loadJson.addProperty("adPlatform", "IronSource")//广告平台
+ loadJson.addProperty("countryCode", ad.country)//国家
+ loadJson.addProperty("adId", placeId)//广告Id
+ loadJson.addProperty("platformResponseTime", "")//平台广告响应时间
+ loadJson.addProperty("ecpm", formattedString)//广告单价
+ loadJson.addProperty("dsp", ad.adNetwork)
+ loadJson.addProperty("network", ad.adNetwork)
+ 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", "IronSource")//广告平台
+ 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: LevelPlayAdInfo) {
+ aidlClient.sendBrushMiss()
+ Handler(Looper.getMainLooper()).post {
+ onAdShownLiveData.value = true
+ }
+ val scientificNotation = ad.revenue
+ val formattedString: String = String.format("%.20f", scientificNotation)
+
+ showJson.addProperty("succeed", true)//广告加载是否成功
+ showJson.addProperty("adPlatform", "IronSource")//广告平台
+ showJson.addProperty("countryCode", ad.country)//国家代码
+ showJson.addProperty("adId", place)//广告Id
+ showJson.addProperty("platformResponseTime", "")//平台广告响应时间
+ showJson.addProperty("ecpm", formattedString)//广告单价
+ showJson.addProperty("dsp", ad.adNetwork)
+ showJson.addProperty("network", ad.adNetwork)
+ 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", "IronSource")//广告平台
+ 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("#CE3A54".toColorInt())
+ //gaID没有确的值就发送重置广播
+ appendLoadingTxt("没有获取到GAID,$timeLeft 秒后将会重置")
+ Log.d("ocean", "没有获取到GAID")
+ startCountdown()
+ } else {
+ Log.d("ocean", "获取到GAID")
+ binding.gaidLayout.setBackgroundColor("#60D889".toColorInt())
+ 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/wall/exquisite/wallpaper/environment/VmHttpUtil.kt b/app/src/main/java/com/wall/exquisite/wallpaper/environment/VmHttpUtil.kt
new file mode 100644
index 0000000..503c85b
--- /dev/null
+++ b/app/src/main/java/com/wall/exquisite/wallpaper/environment/VmHttpUtil.kt
@@ -0,0 +1,83 @@
+package com.wall.exquisite.wallpaper.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/wall/exquisite/wallpaper/environment/ad/AdActivityManager.kt b/app/src/main/java/com/wall/exquisite/wallpaper/environment/ad/AdActivityManager.kt
new file mode 100644
index 0000000..3b2ae15
--- /dev/null
+++ b/app/src/main/java/com/wall/exquisite/wallpaper/environment/ad/AdActivityManager.kt
@@ -0,0 +1,221 @@
+package com.wall.exquisite.wallpaper.environment.ad
+
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.app.ActivityManager
+import android.app.Application
+import android.content.Context
+import android.os.Bundle
+import android.provider.Settings
+import android.util.Log
+import android.view.WindowManager
+import com.vungle.ads.internal.ui.view.MRAIDAdWidget
+import com.wall.exquisite.wallpaper.environment.ad.async.Async
+
+
+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.ironsource.sdk.controller.ControllerActivity
+ || activity is com.ironsource.sdk.controller.OpenUrlActivity
+ || activity is com.unity3d.ironsourceads.internal.services.InlineStoreActivity
+ || activity is com.vungle.ads.internal.ui.AdActivity
+ || activity is com.mbridge.msdk.activity.MBCommonActivity
+ || activity is com.mbridge.msdk.config.activity.MBRewardVideoActivity
+ || activity is com.unity3d.services.ads.adunit.AdUnitActivity
+ || activity is sg.bigo.ads.ad.splash.AdSplashActivity
+ || activity is com.chartboost.sdk.internal.clickthrough.EmbeddedBrowserActivity
+ || activity is com.fyber.inneractive.sdk.activities.InneractiveInternalBrowserActivity
+ || activity is com.fyber.inneractive.sdk.activities.InneractiveFullscreenAdActivity
+ || activity is com.fyber.inneractive.sdk.activities.InneractiveRichMediaVideoPlayerActivityCore
+ || activity is com.fyber.inneractive.sdk.activities.InternalStoreWebpageActivity
+ || activity is com.fyber.inneractive.sdk.activities.FyberReportAdActivity
+ || activity is com.inmobi.ads.rendering.InMobiAdActivity
+ || activity is com.bytedance.sdk.openadsdk.activity.TTBaseActivity
+ )
+ }
+ }
+
+ 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")
+ /**
+ * 2025年10月15日,尝试使用对应广告的act,里面的关闭方法调用。
+ */
+ when (adActivity) {
+ is com.ironsource.sdk.controller.ControllerActivity -> {
+ adActivity.onCloseRequested()
+ }
+
+ is com.vungle.ads.internal.ui.AdActivity -> {
+ //反射方式关闭
+ try {
+ // 获取 mraidAdWidget 字段
+ val field =
+ com.vungle.ads.internal.ui.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")
+ }
+ }
+ is com.mbridge.msdk.activity.MBCommonActivity -> {
+ adActivity.finish()
+ }
+ is sg.bigo.ads.ad.splash.AdSplashActivity ->{
+ adActivity.finish()
+ }
+ is com.fyber.inneractive.sdk.activities.InneractiveInternalBrowserActivity->{
+ adActivity.finish()
+ }
+ is com.fyber.inneractive.sdk.activities.InneractiveFullscreenAdActivity->{
+ val closeButton = adActivity.closeButton
+ closeButton?.performClick() ?: adActivity.dismissAd(true)
+ }
+ else -> {
+ adActivity.finish()
+ 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) {
+ Log.d("ocean-brush", "广告 Activity 正在销毁,但可能残留窗口")
+ removeVungleWindow(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页面
+ }
+
+ private fun removeVungleWindow(activity: Activity) {
+ try {
+ val windowManager = activity.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+ val decorView = activity.window.decorView
+ windowManager.removeView(decorView)
+ Log.d("ocean-brush", "AdActivity 残留窗口已移除")
+ } catch (e: Exception) {
+ Log.e("ocean-brush", "移除 AdActivity 窗口失败", e)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/wall/exquisite/wallpaper/environment/ad/AdInstLoad.kt b/app/src/main/java/com/wall/exquisite/wallpaper/environment/ad/AdInstLoad.kt
new file mode 100644
index 0000000..82edd21
--- /dev/null
+++ b/app/src/main/java/com/wall/exquisite/wallpaper/environment/ad/AdInstLoad.kt
@@ -0,0 +1,64 @@
+package com.wall.exquisite.wallpaper.environment.ad
+
+import android.app.Activity
+import android.util.Log
+import com.unity3d.mediation.LevelPlayAdError
+import com.unity3d.mediation.LevelPlayAdInfo
+import com.unity3d.mediation.interstitial.LevelPlayInterstitialAd
+import com.unity3d.mediation.interstitial.LevelPlayInterstitialAdListener
+
+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() {
+ // Create the interstitial ad object
+ val mInterstitialAd = LevelPlayInterstitialAd(mPlace)
+ mInterstitialAd.setListener(object : LevelPlayInterstitialAdListener {
+ override fun onAdLoaded(levelPlayAdInfo: LevelPlayAdInfo) {
+ // Ad was loaded successfully
+ InstAdCacheManager.instance.setAdCache(mPlace, mInterstitialAd)
+ adLoadListener?.loaded(levelPlayAdInfo)
+ }
+ override fun onAdLoadFailed(levelPlayAdError: LevelPlayAdError) {
+ // Ad load failed
+ adLoadListener?.loadFailed("code->${levelPlayAdError.errorCode} message->${levelPlayAdError.errorMessage}")
+ Log.d("ocean", "load ad onError-> code->${levelPlayAdError.errorCode} message->${levelPlayAdError.errorMessage}")
+ }
+ override fun onAdDisplayed(levelPlayAdInfo: LevelPlayAdInfo) {
+ // Ad was displayed and visible on screen
+ }
+ override fun onAdDisplayFailed(levelPlayAdError: LevelPlayAdError, levelPlayAdInfo: LevelPlayAdInfo) {
+ // Ad fails to be displayed
+ // Optional
+ }
+ override fun onAdClicked(levelPlayAdInfo: LevelPlayAdInfo) {
+ // Ad was clicked
+ // Optional
+ }
+ override fun onAdClosed(levelPlayAdInfo: LevelPlayAdInfo) {
+ // Ad was closed
+ // Optional
+ }
+ override fun onAdInfoChanged(levelPlayAdInfo: LevelPlayAdInfo) {
+ // Called after the ad info is updated. Available when another interstitial ad has loaded, and includes a higher CPM/Rate
+ // Optional
+ }
+ })
+ mInterstitialAd.loadAd()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/wall/exquisite/wallpaper/environment/ad/AdInstShower.kt b/app/src/main/java/com/wall/exquisite/wallpaper/environment/ad/AdInstShower.kt
new file mode 100644
index 0000000..d0f843e
--- /dev/null
+++ b/app/src/main/java/com/wall/exquisite/wallpaper/environment/ad/AdInstShower.kt
@@ -0,0 +1,74 @@
+package com.wall.exquisite.wallpaper.environment.ad
+
+import android.app.Activity
+import android.util.Log
+import com.wall.exquisite.wallpaper.environment.ad.async.Async
+import com.unity3d.mediation.LevelPlayAdError
+import com.unity3d.mediation.LevelPlayAdInfo
+import com.unity3d.mediation.interstitial.LevelPlayInterstitialAdListener
+
+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?.setListener(object : LevelPlayInterstitialAdListener{
+ override fun onAdLoaded(levelPlayAdInfo: LevelPlayAdInfo) {
+ // Ad was loaded successfully
+ }
+ override fun onAdLoadFailed(levelPlayAdError: LevelPlayAdError) {
+ // Ad load failed
+ }
+ override fun onAdDisplayed(levelPlayAdInfo: LevelPlayAdInfo) {
+ // Ad was displayed and visible on screen
+ showListener?.onAdShown(levelPlayAdInfo)
+ Log.d("ocean", "onAdDisplayed 广告展示回调")
+ autoClose()
+ }
+ override fun onAdDisplayFailed(levelPlayAdError: LevelPlayAdError, levelPlayAdInfo: LevelPlayAdInfo) {
+ // Ad fails to be displayed
+ Log.d("ocean", "AdInstShower 视频广告播放失败回调->${levelPlayAdError.errorCode} ${levelPlayAdError.errorMessage}")
+ showListener?.onAdShowFailed(AdShowFailed("${levelPlayAdError.errorCode} ${levelPlayAdError.errorMessage}"))
+ }
+ override fun onAdClicked(levelPlayAdInfo: LevelPlayAdInfo) {
+ // Ad was clicked
+ showListener?.onAdClicked()
+ Log.d("ocean", "onAdClicked 广告点击回调")
+ }
+ override fun onAdClosed(levelPlayAdInfo: LevelPlayAdInfo) {
+ // Ad was closed
+ showListener?.onAdClosed()
+ Log.d("ocean", "onAdClosed 广告关闭回调")
+ }
+ override fun onAdInfoChanged(levelPlayAdInfo: LevelPlayAdInfo) {
+ // Called after the ad info is updated. Available when another interstitial ad has loaded, and includes a higher CPM/Rate
+ // Optional
+ }
+ })
+ interstitialAd?.showAd(activity!!)
+ }
+
+ 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/wall/exquisite/wallpaper/environment/ad/AdShowFailed.kt b/app/src/main/java/com/wall/exquisite/wallpaper/environment/ad/AdShowFailed.kt
new file mode 100644
index 0000000..0bb55e5
--- /dev/null
+++ b/app/src/main/java/com/wall/exquisite/wallpaper/environment/ad/AdShowFailed.kt
@@ -0,0 +1,5 @@
+package com.wall.exquisite.wallpaper.environment.ad
+
+data class AdShowFailed(
+ val msg: String = "",
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/wall/exquisite/wallpaper/environment/ad/AdsInsUtil.kt b/app/src/main/java/com/wall/exquisite/wallpaper/environment/ad/AdsInsUtil.kt
new file mode 100644
index 0000000..9d57608
--- /dev/null
+++ b/app/src/main/java/com/wall/exquisite/wallpaper/environment/ad/AdsInsUtil.kt
@@ -0,0 +1,28 @@
+package com.wall.exquisite.wallpaper.environment.ad
+
+import android.app.Activity
+
+object AdsInsUtil {
+
+ object Placement {
+ const val TOP_ON_AD_ONE = "z99sdrvrcmrnsr4b"
+ const val TOP_ON_AD_TOW = "q3apcnd67q25tkr7"
+ const val TOP_ON_AD_THREE = "hrnbibnvrc7akls1"
+ }
+
+ 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/wall/exquisite/wallpaper/environment/ad/InstAdCacheManager.kt b/app/src/main/java/com/wall/exquisite/wallpaper/environment/ad/InstAdCacheManager.kt
new file mode 100644
index 0000000..a8d49e1
--- /dev/null
+++ b/app/src/main/java/com/wall/exquisite/wallpaper/environment/ad/InstAdCacheManager.kt
@@ -0,0 +1,36 @@
+package com.wall.exquisite.wallpaper.environment.ad
+
+import com.unity3d.mediation.interstitial.LevelPlayInterstitialAd
+
+
+class InstAdCacheManager {
+ private val mAdCacheDict: MutableMap = mutableMapOf()
+
+ companion object {
+ val instance: InstAdCacheManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
+ InstAdCacheManager()
+ }
+ }
+
+ fun setAdCache(place: String, adCache: LevelPlayInterstitialAd) {
+ mAdCacheDict[place] = adCache
+ }
+
+ fun getAdCache(place: String): LevelPlayInterstitialAd? {
+ return mAdCacheDict[place]
+ }
+
+ fun getLoadedInstCount(): Int {
+ var count = 0
+ try {
+ mAdCacheDict.forEach { (key, value) ->
+ if (value.isAdReady) {
+ count += 1
+ }
+ }
+ } catch (_: Exception) {
+
+ }
+ return count
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/wall/exquisite/wallpaper/environment/ad/LoadListener.kt b/app/src/main/java/com/wall/exquisite/wallpaper/environment/ad/LoadListener.kt
new file mode 100644
index 0000000..345cf38
--- /dev/null
+++ b/app/src/main/java/com/wall/exquisite/wallpaper/environment/ad/LoadListener.kt
@@ -0,0 +1,8 @@
+package com.wall.exquisite.wallpaper.environment.ad
+
+import com.unity3d.mediation.LevelPlayAdInfo
+
+interface LoadListener {
+ fun loadFailed(error: String) {}
+ fun loaded(ad: LevelPlayAdInfo) {}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/wall/exquisite/wallpaper/environment/ad/ShowListener.kt b/app/src/main/java/com/wall/exquisite/wallpaper/environment/ad/ShowListener.kt
new file mode 100644
index 0000000..1738418
--- /dev/null
+++ b/app/src/main/java/com/wall/exquisite/wallpaper/environment/ad/ShowListener.kt
@@ -0,0 +1,10 @@
+package com.wall.exquisite.wallpaper.environment.ad
+
+import com.unity3d.mediation.LevelPlayAdInfo
+
+interface ShowListener {
+ fun onAdShown(ad: LevelPlayAdInfo) {}
+ fun onAdShowFailed(error: AdShowFailed?) {}
+ fun onAdClosed() {}
+ fun onAdClicked() {}
+}
diff --git a/app/src/main/java/com/wall/exquisite/wallpaper/environment/ad/async/Async.java b/app/src/main/java/com/wall/exquisite/wallpaper/environment/ad/async/Async.java
new file mode 100644
index 0000000..44370a4
--- /dev/null
+++ b/app/src/main/java/com/wall/exquisite/wallpaper/environment/ad/async/Async.java
@@ -0,0 +1,83 @@
+package com.wall.exquisite.wallpaper.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/wall/exquisite/wallpaper/environment/ad/async/HandlerPoster.java b/app/src/main/java/com/wall/exquisite/wallpaper/environment/ad/async/HandlerPoster.java
new file mode 100644
index 0000000..e167349
--- /dev/null
+++ b/app/src/main/java/com/wall/exquisite/wallpaper/environment/ad/async/HandlerPoster.java
@@ -0,0 +1,140 @@
+package com.wall.exquisite.wallpaper.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/wall/exquisite/wallpaper/environment/ad/async/SyncPost.java b/app/src/main/java/com/wall/exquisite/wallpaper/environment/ad/async/SyncPost.java
new file mode 100644
index 0000000..7377d61
--- /dev/null
+++ b/app/src/main/java/com/wall/exquisite/wallpaper/environment/ad/async/SyncPost.java
@@ -0,0 +1,44 @@
+package com.wall.exquisite.wallpaper.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/wall/exquisite/wallpaper/environment/ad/async/TaskQueue.java b/app/src/main/java/com/wall/exquisite/wallpaper/environment/ad/async/TaskQueue.java
new file mode 100644
index 0000000..a29a108
--- /dev/null
+++ b/app/src/main/java/com/wall/exquisite/wallpaper/environment/ad/async/TaskQueue.java
@@ -0,0 +1,46 @@
+package com.wall.exquisite.wallpaper.environment.ad.async;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class TaskQueue extends Thread{
+ private BlockingQueue