Commit 2bbff6d5 authored by wanglei's avatar wanglei

...init

parent 1a74edc3
Pipeline #1439 failed with stages
*.iml
.gradle
/local.properties
.idea
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
# reelshort white
/build
\ No newline at end of file
import org.gradle.internal.impldep.bsh.commands.dir
import org.gradle.internal.impldep.org.junit.experimental.categories.Categories.CategoryFilter.include
import java.text.SimpleDateFormat
import java.util.Locale
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
id("com.google.gms.google-services")
id("com.google.firebase.crashlytics")
}
android {
namespace = "com.base.appzxhy"
compileSdk = 34
defaultConfig {
applicationId = "com.tool.advanced.cleaner"
minSdk = 24
targetSdk = 34
versionCode = 1
versionName = "1.0"
ndk {
abiFilters.addAll(listOf("arm64-v8a", "armeabi-v7a"))
}
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
fun Long.toFormatTime3(): String {
return SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.ENGLISH).format(this)
}
setProperty("archivesBaseName", "appzxhy-v$versionName($versionCode)-${System.currentTimeMillis().toFormatTime3()}")
signingConfigs {
create("release") {
//E6:A2:DD:0A:E6:73:22:32:4E:98:08:78:73:29:01:C4:8D:A0:38:DB:D8:B2:DD:B9:AC:55:69:B2:6B:DF:B0:B4
storeFile = file("../smartcl.jks")
storePassword = "123456"
keyAlias = "key0"
keyPassword = "123456"
}
}
}
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
// 设置是否要自动上传
firebaseCrashlytics {
mappingFileUploadEnabled = true
}
signingConfig = signingConfigs.getByName("release")
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
viewBinding = true
buildConfig = true
}
}
gradle.taskGraph.whenReady {
tasks.forEach { task ->
if (task.name.contains("uploadCrashlyticsMappingFile")) {
task.enabled = false
}
}
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.material)
implementation(libs.androidx.activity)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.navigation.fragment.ktx)
implementation(libs.androidx.navigation.ui.ktx)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
//glide
implementation("com.github.bumptech.glide:glide:4.16.0")
//网络请求
implementation("com.google.code.gson:gson:2.11.0")
implementation("com.squareup.okhttp3:okhttp:4.2.1")
// 语种切换框架:https://github.com/getActivity/MultiLanguages
implementation("com.github.getActivity:MultiLanguages:9.3")
// 沉浸式状态栏,基础依赖包,必须要依赖
api(libs.immersionbar)
// 沉浸式状态栏kotlin扩展(可选)
api(libs.immersionbar.ktx)
//solar 归因
implementation("com.reyun.solar.engine.oversea:solar-engine-core:1.2.8.3")
//第三方UI控件
implementation("io.github.cymchad:BaseRecyclerViewAdapterHelper4:4.1.4")
implementation("io.github.youth5201314:banner:2.2.3")
//firebase
implementation(platform("com.google.firebase:firebase-bom:32.3.1"))
implementation("com.google.firebase:firebase-messaging")
implementation("com.google.firebase:firebase-messaging-directboot")
implementation("com.google.firebase:firebase-analytics-ktx")
implementation("com.google.firebase:firebase-crashlytics")
implementation("com.google.firebase:firebase-config")
//facebook
implementation("com.facebook.android:facebook-android-sdk:[8,9)")
//广告
//admob渠道
implementation(libs.vungle)
implementation(libs.facebook)
implementation(libs.mintegral)
implementation(libs.pangle)
//applovin sdk
implementation(libs.applovin)
//applovin渠道
implementation(libs.applovin.google)
implementation(libs.applovin.admob)
implementation(libs.applovin.facebook) //meta
implementation(libs.applovin.mintegral)//mintegral
implementation(libs.applovin.pangle) //pangle
implementation(libs.applovin.vungle) //vungle
//work
implementation("androidx.work:work-runtime-ktx:2.7.1") // 请使用最新版本
//短剧依赖的sdk:
//aar
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar"))))
// APM
implementation("com.pangle.global:crash_monitor:1.3.8.22")
// Pangle广告,便于测试信息流广告
implementation("com.bytedanceapi:ttsdk-player_standard:1.42.300.1")
//Pangle广告,便于测试信息流广告
implementation("com.pangle.global:ads-sdk:6.2.0.4")
}
\ No newline at end of file
{
"project_info": {
"project_number": "755421476297",
"project_id": "comtooladvancedcleaner",
"storage_bucket": "comtooladvancedcleaner.firebasestorage.app"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:755421476297:android:c66d8129d83b17e8af6418",
"android_client_info": {
"package_name": "com.tool.advanced.cleaner"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyAhCB4trMZPymkWCNl_m-bFGPpASG6bsQI"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}
\ No newline at end of file
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-dontwarn javax.annotation.Nullable
#-keep class com.base.locationsharewhite.bean.** { *; }
-keep class com.google.gson.reflect.** { *; }
-keep class * extends com.google.gson.reflect.TypeToken
-keep class com.google.gson.stream.** { *; }
-keep class com.squareup.okhttp.** { *; }
-keepattributes *Annotation*
-keep class * extends com.google.gson.TypeAdapter
-keep class * extends com.google.gson.JsonSerializer
-keep class * extends com.google.gson.Deserializer
-keep class com.google.gson.TypeAdapters{*;}
-keep class com.bytedance.sdk.** { *; }
#pangSdk
-keep class com.ss.ttm.** {*;}
-keep class com.ss.ttvideoengine.** {*;}
-keep class com.ss.mediakit.** {*;}
-keep class com.ss.texturerender.** {*;}
-keep class com.bytedance.**{*;}
-keep class com.pandora.ttlicense2.**{*;}
-keep class com.pandora.common.applog.**{*;}
-keep class com.pandora.vod.VodSDK {*;}
-dontwarn com.bytedance.vcloud.**
-dontwarn com.ss.**
-dontwarn com.google.**
-dontwarn okhttp3.**
-dontwarn kotlinx.**
package com.base.appzxhy
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.base.localweatherwhite", appContext.packageName)
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application
android:name=".MyApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.LocalWeatherWhite"
tools:targetApi="31">
<activity
android:name=".ui.start.StartActivity"
android:exported="true"
android:launchMode="singleTask"
android:theme="@style/splash.theme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ui.main.MainActivity"
android:exported="true">
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<receiver
android:name=".fcm.alarm.AlarmReceiver"
android:enabled="true"
android:exported="true"
android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
<intent-filter>
<action android:name="android.intent.action.USER_PRESENT" />
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.ACTION_POWER_CONNECTED" />
<action android:name="android.intent.action.ACTION_POWER_DISCONNECTED" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PACKAGE_ADDED" />
<action android:name="android.intent.action.PACKAGE_REMOVED" />
<action android:name="android.intent.action.PACKAGE_REPLACED" />
<data android:scheme="package" />
</intent-filter>
<intent-filter>
<action android:name="android.net.wifi.WIFI_STATE_CHANGED" />
<action android:name="android.net.wifi.STATE_CHANGE" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MEDIA_EJECT" />
<action android:name="android.intent.action.MEDIA_MOUNTED" />
<data android:scheme="file" />
</intent-filter>
</receiver>
<receiver
android:name=".fcm.receiver.FileJobReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.USER_PRESENT" />
<action android:name="android.intent.action.TIMEZONE_CHANGED" />
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
<action android:name="android.intent.action.ACTION_POWER_CONNECTED" />
<action android:name="android.intent.action.ACTION_POWER_DISCONNECTED" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PACKAGE_ADDED" />
<action android:name="android.intent.action.PACKAGE_REMOVED" />
<action android:name="android.intent.action.PACKAGE_REPLACED" />
<data android:scheme="package" />
</intent-filter>
<intent-filter>
<action android:name="android.net.wifi.WIFI_STATE_CHANGED" />
<action android:name="android.net.wifi.STATE_CHANGE" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MEDIA_EJECT" />
<action android:name="android.intent.action.MEDIA_MOUNTED" />
<data android:scheme="file" />
</intent-filter>
</receiver>
<service
android:name=".service.StayJobService"
android:exported="false"
android:foregroundServiceType="dataSync"
android:permission="android.permission.BIND_JOB_SERVICE" />
<service
android:name=".fcm.MessagingService"
android:directBootAware="true"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<receiver
android:name=".fcm.FcmReceiver"
android:directBootAware="true"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<category android:name="com.tool.advanced.cleaner" />
</intent-filter>
</receiver>
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-3940256099942544~3347511713" />
<meta-data
android:name="com.facebook.sdk.ApplicationId"
android:value="@string/facebook_app_id" />
</application>
</manifest>
\ No newline at end of file
package com.base.appzxhy
/**
*Create by SleepDog on 2024-12-24
*/
object GlobalConfig {
//包名
const val PACKAGE_NAME = "com.loactation.alibabab.ccccaa"
// 域名
/**
* Url Event 上报接口
*/
const val URL_EVENT = "https://rp.gamexzonerk.xyz"
/**
* Url Api 业务接口
*/
const val URL_API = "https://api.gamexzonerk.xyz"
/**
* Url Privacy 隐私链接
*/
const val URL_PRIVACY = "https://sites.google.com/view/locationuses/location"
/**
* Url Use 使用条款
*/
const val URL_USE = ""
/**
* Key Aes 加密key
*/
const val KEY_AES = "pex50vwzuhpim3yh"
/**
* Key solar 归因key
*/
const val KEY_SOLAR = ""
// admob广告位id
inline val ID_ADMOB_INTER get() = if (BuildConfig.DEBUG) "ca-app-pub-3940256099942544/1033173712" else "ca-app-pub-3940256099942544/1033173712"
inline val ID_ADMOB_NATIVE get() = if (BuildConfig.DEBUG) "ca-app-pub-3940256099942544/2247696110" else "ca-app-pub-3940256099942544/2247696110"
inline val ID_ADMOB_OPEN get() = if (BuildConfig.DEBUG) "ca-app-pub-3940256099942544/9257395921" else "ca-app-pub-3940256099942544/9257395921"
inline val ID_ADMOB_BANNER get() = if (BuildConfig.DEBUG) "ca-app-pub-3940256099942544/9214589741" else "ca-app-pub-3940256099942544/9214589741"
inline val ID_ADMOB_REWARD get() = if (BuildConfig.DEBUG) "ca-app-pub-3940256099942544/5224354917" else "ca-app-pub-3940256099942544/5224354917"
// max广告位id
inline val KEY_MAX get() = if (BuildConfig.DEBUG) "GGPreND6SRmCt1zJgn5faiLGD8c2PVGPLgPpSg7cHanVTud1DhtuI9MmteTqlEviaJ57WnxW68kQDaATJ5z3cW" else "GGPreND6SRmCt1zJgn5faiLGD8c2PVGPLgPpSg7cHanVTud1DhtuI9MmteTqlEviaJ57WnxW68kQDaATJ5z3cW"
inline val ID_MAX_INTER get() = if (BuildConfig.DEBUG) "3a9efc39d536ffe6" else "3a9efc39d536ffe6"
inline val ID_MAX_NATIVE get() = if (BuildConfig.DEBUG) "cc0cc5b36a2608ca" else "cc0cc5b36a2608ca"
inline val ID_MAX_OPEN get() = if (BuildConfig.DEBUG) "ad4efa4a0bc2a585" else "ad4efa4a0bc2a585"
inline val ID_MAX_BANNER get() = if (BuildConfig.DEBUG) "" else ""
inline val ID_MAX_REWARD get() = if (BuildConfig.DEBUG) "" else ""
}
\ No newline at end of file
package com.base.appzxhy
import AlarmUtils.startAlarm
import android.app.Activity
import android.app.Application
import android.content.Context
import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import com.base.appzxhy.ads.AdsMgr
import com.base.appzxhy.bean.ConstObject.appLanguageCountrySp
import com.base.appzxhy.bean.ConstObject.appLanguageSp
import com.base.appzxhy.bean.config.AdConfigBean
import com.base.appzxhy.bean.config.ConfigBean
import com.base.appzxhy.bean.config.PopupConfigBean
import com.base.appzxhy.fcm.FCMManager
import com.base.appzxhy.fcm.notification.MyNotificationManager
import com.base.appzxhy.fcm.timer.TimerManager.Companion.changeTimer
import com.base.appzxhy.fcm.receiver.BatteryStatusReceiver
import com.base.appzxhy.fcm.receiver.PackageStatusReceiver
import com.base.appzxhy.fcm.receiver.ScreenStatusReceiver
import com.base.appzxhy.fcm.work.RepeatingWorker.Companion.schedulePeriodicWork
import com.base.appzxhy.helper.EventUtils
import com.base.appzxhy.helper.InstallHelps
import com.base.appzxhy.helper.NewComUtils
import com.base.appzxhy.service.StayJobService.Companion.startJob
import com.base.appzxhy.ui.start.StartActivity
import com.base.appzxhy.utils.ActivityManagerUtils
import com.base.appzxhy.utils.AppPreferences
import com.base.appzxhy.utils.LogEx
import com.base.appzxhy.utils.SolarEngineUtils.initSolarEngine
import com.base.appzxhy.utils.SolarEngineUtils.solarkey
import com.facebook.FacebookSdk
import com.google.android.gms.ads.identifier.AdvertisingIdClient
import com.google.gson.Gson
import com.hjq.language.MultiLanguages
import com.hjq.language.OnLanguageListener
import com.reyun.solar.engine.SolarEngineManager
import org.json.JSONObject
import java.util.Locale
import java.util.UUID
class MyApplication : Application() {
private val TAG = "MyApplication"
var uuid = ""
companion object {
lateinit var appContext: MyApplication
@JvmField
var PAUSED_VALUE = 0
val noLoadingActivities = listOf(
"full", // 过滤全屏广告
"adActivity",
"AdActivity",
"AppLovinFullscreenActivity",
StartActivity::class.java.simpleName,
// 返回前台时不跳转启动页的 activity
)
}
override fun onCreate() {
super.onCreate()
appContext = this
initUUid()
initGid()
initApp()
// 初始化语种切换框架
MultiLanguages.init(this)
// 设置语种变化监听器
MultiLanguages.setOnLanguageListener(object : OnLanguageListener {
override fun onAppLocaleChange(oldLocale: Locale, newLocale: Locale) {
appLanguageSp = newLocale.language
appLanguageCountrySp = newLocale.country
Log.i("MultiLanguages", "监听到应用切换了语种,旧语种:$oldLocale,新语种:$newLocale")
}
override fun onSystemLocaleChange(oldLocale: Locale, newLocale: Locale) {
Log.i(
"MultiLanguages", "监听到系统切换了语种,旧语种:" + oldLocale + ",新语种:" + newLocale +
",是否跟随系统:" + MultiLanguages.isSystemLanguage(this@MyApplication)
)
}
})
}
private fun initGid() {
Thread {
val info: AdvertisingIdClient.Info = AdvertisingIdClient.getAdvertisingIdInfo(applicationContext)
val advertisingId = info.id
AppPreferences.getInstance().put("gid", advertisingId)
}.start()
}
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(MultiLanguages.attach(base))
}
private fun initUUid() {
uuid = AppPreferences.getInstance().getString("uuid", "")
if (TextUtils.isEmpty(uuid)) {
uuid = UUID.randomUUID().toString() + System.currentTimeMillis()
AppPreferences.getInstance().put("uuid", uuid)
LogEx.logDebug(TAG, "uuid=$uuid")
}
LogEx.logDebug(TAG, "uuid=${AppPreferences.getInstance().getString("uuid", "")}")
}
private fun initApp() {
kotlin.runCatching {
startJob()
}
//初始化广告相关业务
AdsMgr.init(appContext)
FacebookSdk.sdkInitialize(applicationContext)
// val token = AppPreferences.getInstance().getString("token", "")
val topic = GlobalConfig.PACKAGE_NAME + "_push"
// LogEx.logDebug(TAG, "topic=${topic} token=$token")
FCMManager.initFirebase(this)
FCMManager.subscribeToTopic(topic)
initAppConfig()
SolarEngineManager.getInstance().preInit(this, solarkey)
initLifeListener()
ScreenStatusReceiver.registerScreenStatusReceiver(this)
PackageStatusReceiver.registerPackageStatusReceiver(this)
BatteryStatusReceiver.registerBatteryStatusReceiver(this)
//workManager
schedulePeriodicWork(appContext)
startAlarm(appContext, 24)
startAlarm(appContext, 48)
startAlarm(appContext, 72)
//开启通知队列
MyNotificationManager.startNotificationQueue()
appContext.initSolarEngine(true)
}
private fun initAppConfig() {
Thread {
val config = AppPreferences.getInstance().getString("config", "")
if (config.isNotEmpty()) {
initConfig(config)
}
InstallHelps.init {
initRemoteConfig()
}
}.start()
}
private var lastTimePause = 0L
private var lastTimeResume = 0L
private fun isHotLaunch(): Boolean {
if ((lastTimeResume - lastTimePause) > 5000) {
return true
}
return false
}
private fun initLifeListener() {
registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
private var count = 0
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
ActivityManagerUtils.getInstance().addActivity(activity)
}
override fun onActivityStarted(activity: Activity) {
count++
lastTimeResume = System.currentTimeMillis()
if (count == 1 && isHotLaunch()) {
val topActivity: Activity? = ActivityManagerUtils.getInstance().topActivity
val flag = if (topActivity == null) {
true
} else {
noLoadingActivities.all { !topActivity.localClassName.contains(it, true) }
}
LogEx.logDebug(TAG, "flag=$flag" + " activity:" + activity.localClassName)
if (flag) {
// topActivity?.startActivity(
// Intent(
// topActivity, SplashActivity::class.java
// ).apply {
// putExtra("isHotLaunch", true)
// putExtra("type", -1)
// })
}
}
lastTimeResume = 0
}
override fun onActivityResumed(activity: Activity) {
PAUSED_VALUE = 1
}
override fun onActivityPaused(activity: Activity) {
PAUSED_VALUE = 2
lastTimePause = System.currentTimeMillis()
}
override fun onActivityStopped(activity: Activity) {
count--
}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
override fun onActivityDestroyed(activity: Activity) {
ActivityManagerUtils.getInstance().removeActivity(activity)
}
})
}
private fun initRemoteConfig() {
NewComUtils.requestCfg { config ->
LogEx.logDebug("requestCfg", "config=$config")
if (config != null) {
AppPreferences.getInstance().put("config", config)
initConfig(config)
} else {
EventUtils.event("configNull")
}
}
}
private fun initConfig(config: String) {
// kotlin.runCatching {
val configBean = Gson().fromJson(config, ConfigBean::class.java)
val jsonObject = JSONObject()
jsonObject.put("ut", configBean.ut)
EventUtils.event("user_type", ext = jsonObject)
//配置
ConfigBean.configBean = configBean
//广告
AdConfigBean.adsConfigBean = configBean.adConfigBean
LogEx.logDebug("initConfig", "adsConfigBean=${configBean.adConfigBean.timeInterval}")
//通知配置
PopupConfigBean.popupConfigBean = configBean.popupConfigBean
LogEx.logDebug("initConfig", "popupConfigBean=${configBean.popupConfigBean.timerInterval}")
//启动定时器
changeTimer()
// }
}
}
\ No newline at end of file
package com.base.appzxhy.ads
import android.animation.ObjectAnimator
import android.animation.ValueAnimator.INFINITE
import android.app.AlertDialog
import android.content.Context
import android.view.LayoutInflater
import android.view.animation.LinearInterpolator
import com.base.appzxhy.databinding.DialogAdPreparingBinding
import com.base.appzxhy.utils.DensityUtils
import com.base.appzxhy.utils.LogEx
object AdDialog {
private val TAG = "com.base.appzxhy.ads.AdDialog"
fun Context.showAdPreparingDialog(where: Int = 0): AlertDialog {
LogEx.logDebug(TAG, "where=$where")
val binding = DialogAdPreparingBinding.inflate(LayoutInflater.from(this))
val dialog = AlertDialog.Builder(this).create()
dialog.setView(binding.root)
dialog.setCancelable(false)
dialog.setCanceledOnTouchOutside(false)
dialog.show()
val params = dialog.window?.attributes
params?.width = DensityUtils.dip2px(200f)
params?.height = DensityUtils.dip2px(146f)
dialog.window?.attributes = params
dialog.window?.setBackgroundDrawableResource(android.R.color.transparent)
// 创建一个旋转动画
val rotateAnimator = ObjectAnimator.ofFloat(binding.iv, "rotation", 0f, -360f)
rotateAnimator.setDuration(1000) // 设置动画持续时间为1000毫秒
rotateAnimator.repeatCount = INFINITE
rotateAnimator.interpolator = LinearInterpolator() // 设置插值器为线性插值
rotateAnimator.start()
return dialog
}
}
\ No newline at end of file
package com.base.appzxhy.ads
import com.base.appzxhy.MyApplication
import com.base.appzxhy.helper.EventUtils
import com.base.appzxhy.utils.LogEx
import org.json.JSONObject
import java.util.UUID
val taichiPref by lazy {
MyApplication.appContext.getSharedPreferences("TaichiTroasCache", 0)
}
val taichiSharedPreferencesEditor by lazy {
taichiPref.edit()
}
abstract class AdEvent {
abstract val TAG: String
var adUnit: String = ""
var from: String = ""
val reqId = UUID.randomUUID().toString()
fun adPrepareShow() {
val obj1 = JSONObject()
obj1.put("ad_unit", adUnit)
obj1.put("req_id", reqId)
obj1.put("from", from)
EventUtils.event("ad_prepare_show", ext = obj1)
LogEx.logDebug(TAG, "ad_prepare_show $obj1")
}
fun adPulStart() {
val obj = JSONObject()
obj.put("req_id", reqId)
obj.put("ad_unit", adUnit)
obj.put("ad_type", adUnit)
obj.put("from", from)
EventUtils.event("ad_pull_start", ext = obj)
LogEx.logDebug(TAG, "ad_pull_start $obj")
}
fun adShowError(reason: Any) {
val obj = JSONObject()
obj.put("ad_unit", adUnit)
obj.put("req_id", reqId)
obj.put("from", from)
obj.put("reason", reason.toString())
EventUtils.event("ad_show_error", ext = obj)
LogEx.logDebug(TAG, "ad_show_error $obj")
}
fun adLimited(value: String) {
val obj = JSONObject()
obj.put("ad_unit", adUnit)
obj.put("req_id", reqId)
obj.put("from", from)
EventUtils.event("ad_limit", value, obj)
LogEx.logDebug(TAG, "ad_limit $obj")
}
}
\ No newline at end of file
package com.base.appzxhy.ads
import android.app.Activity
import android.content.Context
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import com.applovin.sdk.AppLovinMediationProvider
import com.applovin.sdk.AppLovinSdk
import com.applovin.sdk.AppLovinSdkInitializationConfiguration
import com.base.appzxhy.BuildConfig
import com.base.appzxhy.GlobalConfig
import com.base.appzxhy.ads.admob.AdBannerMgr
import com.base.appzxhy.ads.admob.AdInsertMgr
import com.base.appzxhy.ads.admob.AdNativeMgr
import com.base.appzxhy.ads.admob.AdOpenMgr
import com.base.appzxhy.ads.admob.AdmobEvent
import com.base.appzxhy.ads.applovin.AdMaxEvent
import com.base.appzxhy.ads.applovin.MaxInsertMgr
import com.base.appzxhy.ads.applovin.MaxNativeMgr
import com.base.appzxhy.ads.applovin.MaxOpenMgr
import com.base.appzxhy.bean.config.AdConfigBean.Companion.adsConfigBean
import com.base.appzxhy.bean.config.ConfigBean.Companion.configBean
import com.base.appzxhy.helper.EventUtils
import com.base.appzxhy.utils.AppPreferences
import com.base.appzxhy.utils.LogEx
import com.base.appzxhy.utils.ToastUtils.toast
import com.google.android.gms.ads.MobileAds
import com.google.android.gms.ads.identifier.AdvertisingIdClient
import com.google.android.gms.ads.initialization.AdapterStatus
import java.util.Collections
import java.util.concurrent.Executors
/**
* 广告管理类
*/
object AdsMgr {
private val adOpenMgr by lazy {
AdOpenMgr()
}
private val adInsertMgr by lazy {
AdInsertMgr()
}
private val adNativeMgr by lazy {
AdNativeMgr()
}
private val adBannerMgr by lazy {
AdBannerMgr()
}
private val maxOpenMgr by lazy {
MaxOpenMgr()
}
private val maxInsertMgr by lazy {
MaxInsertMgr()
}
private val maxNativeMgr by lazy {
MaxNativeMgr()
}
/**
* 是否初始化
*/
var isAdmobInit = false
private set
/**
* 是否初始化
*/
var isMaxInit = false
private set
/**
* Init 初始化
*
* @param context 这里最好是appContext,因为是耗时操作,等它初始化完毕马上加载开屏和插屏广告
*/
fun init(context: Context) {
if (configBean.isInBlackList) {
EventUtils.event("isInBlackList", configBean.isInBlackList.toString())
return
}
initAdmob(context)
initMax(context)
}
private fun initAdmob(context: Context) {
MobileAds.initialize(context) {
val readyAdapter = it.adapterStatusMap.entries.find { entry ->
entry.value.initializationState == AdapterStatus.State.READY
}
isAdmobInit = readyAdapter != null
EventUtils.event("AdmobInit", "AdmobInit")
// context.toast("admob init")
if (adsConfigBean.adSwitch) {
admobInitCallBack?.invoke()
admobInitCallBack = null
adOpenMgr.loadAd(context, false, AdmobEvent("openAd", context::class.java.simpleName))
adInsertMgr.loadAd(context, false, AdmobEvent("interAd", context::class.java.simpleName))
}
}
}
private fun initMax(context: Context) = kotlin.runCatching {
val executor = Executors.newSingleThreadExecutor()
executor.execute {
val currentGaid = AdvertisingIdClient.getAdvertisingIdInfo(context).id
AppPreferences.getInstance().getString("gid", currentGaid)
val build = AppLovinSdkInitializationConfiguration
.builder(GlobalConfig.KEY_MAX, context)
build.setMediationProvider(AppLovinMediationProvider.MAX)
if (BuildConfig.DEBUG) {
build.testDeviceAdvertisingIds = Collections.singletonList(currentGaid)
}
val initConfig = build.build()
runCatching {
AppLovinSdk.getInstance(context).initialize(initConfig) {
isMaxInit = true
// maxOpenMgr.loadAd(context)
if (!adsConfigBean.adSwitch) {
maxInsertMgr.loadAd(context, false, AdMaxEvent("interAd", context::class.java.simpleName))
context.toast("max init")
maxInitCallBack?.invoke()
maxInitCallBack = null
}
}
}
}
}
private var admobInitCallBack: (() -> Unit)? = null
private var maxInitCallBack: (() -> Unit)? = null
/**
* 展示开屏广告
*
* @param activity 当前页面
* @param showCallBack 展示回调
*/
fun showOpen(
activity: Activity,
isUnLimit: Boolean = false,
showCallBack: AdsShowCallBack? = null,
) {
if (configBean.isInBlackList) {
EventUtils.event("isInBlackList", "isInBlackList=${configBean.isInBlackList}")
return
}
val from = activity::class.java.simpleName
if (adsConfigBean.adSwitch) {
if (isAdmobInit) {
adOpenMgr.show(activity, isUnLimit, AdmobEvent("openAd", from), showCallBack)
} else {
admobInitCallBack = {
}
adOpenMgr.show(activity, isUnLimit, AdmobEvent("openAd", from), showCallBack)
}
} else {
if (isMaxInit) {
maxOpenMgr.show(activity, isUnLimit, AdMaxEvent("openAd", from), showCallBack)
} else {
maxInitCallBack = {
maxOpenMgr.show(activity, isUnLimit, AdMaxEvent("openAd", from), showCallBack)
}
}
}
}
/**
* 展示插屏广告
*
* @param activity 当前页面
* @param showCallBack 展示回调
* @param isUnLimit 是否不受限制 默认false 代表受到限制
*/
fun showInsert(
activity: Activity,
isUnLimit: Boolean = false,
showCallBack: AdsShowCallBack? = null,
) {
if (configBean.isInBlackList) {
EventUtils.event("isInBlackList", configBean.isInBlackList.toString())
return
}
LogEx.logDebug("showAd", "adSwitch=${adsConfigBean.adSwitch}")
val from = activity::class.java.simpleName
if (adsConfigBean.adSwitch) {
adInsertMgr.show(activity, isUnLimit, AdmobEvent("interAd", from), showCallBack)
} else {
maxInsertMgr.show(activity, isUnLimit, AdMaxEvent("interAd", from), showCallBack)
}
}
/**
* 展示原生广告
*
* @param nativeView 需要展示广告的布局容器
* @param layout 原生广告布局 ,这里传入的layout要和com.example.mydemo.strategy.ads.admob.NativeView里的id一致
*/
fun showNative(
nativeView: NativeParentView,
@LayoutRes layout: Int,
nativeCallBack: ((Any?) -> Unit)? = null
) {
if (configBean.isInBlackList) {
EventUtils.event("isInBlackList", configBean.isInBlackList.toString())
return
}
if (!isAdmobInit) return
if (adsConfigBean.adSwitch) {
adNativeMgr.show(AdmobEvent("nativeAd", "nativeAd"), nativeView, layout, nativeCallBack)
} else {
maxNativeMgr.show(AdMaxEvent("nativeAd", "nativeAd"), nativeView, layout, nativeCallBack)
}
}
fun isNativeShow() = (isAdmobInit && !configBean.isInBlackList) && LimitUtils.isAdShow(AdsType.NATIVE, null)
/**
* 展示banner广告
*
* @param parent 展示广告的父布局容器
*/
fun showBanner(parent: ViewGroup, collapsible: Boolean = true, adClose: (() -> Unit)? = null) {
if (configBean.isInBlackList) {
EventUtils.event("isInBlackList", configBean.isInBlackList.toString())
return
}
if (adsConfigBean.adSwitch) {
adBannerMgr.show(parent, collapsible, adClose)
}
}
}
\ No newline at end of file
package com.base.appzxhy.ads
abstract class AdsShowCallBack {
open fun show() {}
abstract fun close(where: Int = 0)
abstract fun failed(where: Int = 0)
abstract fun googleFailed(where: Int = 0)
}
\ No newline at end of file
package com.base.appzxhy.ads
/**
* 广告类型
* 0=开屏广告、1=插屏广告、2=原生广告、3=Banner横幅广告
*/
@JvmInline
value class AdsType private constructor(val value: Int) {
companion object {
val OPEN = AdsType(0)
val INSERT = AdsType(1)
val NATIVE = AdsType(2)
val BANNER = AdsType(3)
fun from(adsType: Int): AdsType {
return when (adsType) {
OPEN.value -> OPEN
INSERT.value -> INSERT
NATIVE.value -> NATIVE
else -> BANNER
}
}
}
}
\ No newline at end of file
package com.base.appzxhy.ads
import android.app.Activity
import android.app.Dialog
import android.content.Context
import java.lang.ref.WeakReference
class adState<T>() {
var adDialog: Dialog? = null
/**
* 当前缓存的广告
*/
var currentAd: T? = null
/**
* 是否正在缓存加载广告
*/
var loadingAd: Boolean = false
/**
* 是否正在显示广告
*/
var showingAd: Boolean = false
/**
* 用于保存引用现有页面,在此页面显示广告(因为要等待广告加载完毕)
*/
var activityRef: WeakReference<Activity>? = null
/**
* 上一次的缓存成功时间
*/
var lastLoadTime: Long = 0
/**
* 上次展示时间
*/
var lastShowTime: Long = 0
fun onAdDisplayed() {
showingAd = true
adDialog?.dismiss()
adDialog = null
lastShowTime = System.currentTimeMillis()
currentAd = null
activityRef = null
}
fun onAdHidden() {
// 广告关闭,清空缓存数据,重新加载
showingAd = false
}
fun onAdDisplayFailed() {
adDialog?.dismiss()
adDialog = null
showingAd = false
currentAd = null
activityRef = null
}
fun onAdLoaded(ad: T?) {
//这里可能提前设置,所有可以不设置,max回调的类型可能不同
if (ad != null) {
currentAd = ad
}
loadingAd = false
lastLoadTime = System.currentTimeMillis()
}
fun onAdLoadFailed() {
adDialog?.dismiss()
adDialog = null
loadingAd = false
}
}
/**
* 特定广告管理基类
* @param T 缓存广告的类
*/
abstract class BaseAdMgr<T> {
/**
* 广告展示回调
*/
protected var showCallBack: AdsShowCallBack? = null
/**
* 当前缓存的广告
*/
protected var currentAd: T? = null
/**
* 是否正在缓存加载广告
*/
protected var loadingAd: Boolean = false
/**
* 是否正在显示广告
*/
protected var showingAd: Boolean = false
/**
* 用于保存引用现有页面,在此页面显示广告(因为要等待广告加载完毕)
*/
protected var activityRef: WeakReference<Activity>? = null
/**
* 上一次的缓存成功时间
*/
protected var lastLoadTime: Long = 0
/**
* 预加载广告
*
* @param context 加载所用的上下文,一般使用appContext
*/
abstract fun loadAd(context: Context, isUnLimit: Boolean = true, adEvent: AdEvent)
/**
* 广告显示
*
* @param activity 当前页面
*/
abstract fun show(activity: Activity, isUnLimit: Boolean = true, adEvent: AdEvent, showCallBack: AdsShowCallBack? = null)
/**
* 预加载的缓存超时判断
*
* @return true:没有超时 false:超时需要重新加载
*/
abstract fun adAvailable(): Boolean
}
\ No newline at end of file
package com.base.appzxhy.ads
import com.base.appzxhy.bean.config.AdConfigBean
import com.base.appzxhy.utils.AppPreferences
import com.base.appzxhy.utils.KotlinExt.toFormatTime4
/**
* 控制广告计数与判断显示条件
*
*/
object LimitUtils {
const val NUM_DISPLAY = "local_numDisplayLimit"
const val NUM_REQUEST = "local_numRequestLimit"
const val NUM_CLICK = "local_numClickLimit"
const val SAVE_DATE = "local_SAVE_DATE"
/**
* 保存的时间,用来判断是否是当天,不是当天要重置计数次数
*/
private var saveDate
get() = AppPreferences.getInstance()
.getString(SAVE_DATE, System.currentTimeMillis().toFormatTime4())
set(value) = AppPreferences.getInstance().put(SAVE_DATE, value)
/**
* 广告请求是否到达限制
*/
private inline val isRequestLimited: Boolean
get() {
val maxCount = AdConfigBean.adsConfigBean.numRequestLimit
return maxCount > -1 && AppPreferences.getInstance()
.getInt(NUM_REQUEST, 0) >= maxCount
}
/**
* 广告展示是否到达限制
*/
private inline val isDisplayLimited: Boolean
get() {
val maxCount = AdConfigBean.adsConfigBean.numDisplayLimit
return maxCount > -1 && AppPreferences.getInstance()
.getInt(NUM_DISPLAY, 0) >= maxCount
}
/**
* 广告点击是否到达限制
*/
private inline val isClickLimited: Boolean
get() {
val maxCount = AdConfigBean.adsConfigBean.numClickLimit
return maxCount > -1 && AppPreferences.getInstance()
.getInt(NUM_CLICK, 0) >= maxCount
}
private fun getAdEventMsg(adsType: AdsType): String {
return when (adsType) {
AdsType.OPEN -> "Open"
AdsType.INSERT -> "Inter"
AdsType.NATIVE -> "Native"
else -> "Banner"
}
}
/**
* 是否显示广告
*
* @return true or false
*/
fun isAdShow(adsType: AdsType, adEvent: AdEvent?): Boolean {
val currentDate = System.currentTimeMillis().toFormatTime4()
if (saveDate != currentDate) {
//如果已经不是今天了,就重置个数
saveDate = currentDate
AppPreferences.getInstance().put(NUM_DISPLAY, 0)
AppPreferences.getInstance().put(NUM_REQUEST, 0)
AppPreferences.getInstance().put(NUM_CLICK, 0)
}
if (isDisplayLimited) {
val value = "current${getAdEventMsg(adsType)} " +
"show=${AppPreferences.getInstance().getInt(NUM_DISPLAY, 0)} " +
"${getAdEventMsg(adsType).lowercase()}_" + "max_show=${AdConfigBean.adsConfigBean.numDisplayLimit}"
adEvent?.adLimited(value)
}
if (isClickLimited) {
val value =
"current${getAdEventMsg(adsType)}Click=${AppPreferences.getInstance().getInt(NUM_CLICK, 0)} "
"${getAdEventMsg(adsType).lowercase()}_max_click=${AdConfigBean.adsConfigBean.numClickLimit}"
adEvent?.adLimited(value)
}
if (isRequestLimited) {
val value = "current${getAdEventMsg(adsType)}Request=${AppPreferences.getInstance().getInt(NUM_REQUEST, 0)} " +
"${getAdEventMsg(adsType).lowercase()}_max_request=${AdConfigBean.adsConfigBean.numRequestLimit}"
adEvent?.adLimited(value)
}
return !(isDisplayLimited || isClickLimited || isRequestLimited)
}
private fun addNum(key: String) {
val currentDate = System.currentTimeMillis().toFormatTime4()
if (saveDate != currentDate) {
//如果已经不是今天了,就重置个数
saveDate = currentDate
AppPreferences.getInstance().put(key, 1)
return
}
AppPreferences.getInstance()
.put(key, (AppPreferences.getInstance().getInt(key, 0) + 1))
}
fun addDisplayNum() {
addNum(NUM_DISPLAY)
}
fun addRequestNum() {
addNum(NUM_REQUEST)
}
fun addClickNum() {
addNum(NUM_CLICK)
}
/**
* 开屏和插页广告的显示间隔限制
*
* @param lastTime 上一次显示的时间
*/
fun isIntervalLimited(lastTime: Long, adEvent: AdEvent): Boolean {
val flag = ((System.currentTimeMillis() - lastTime) / 1000).toInt() < (AdConfigBean.adsConfigBean.timeInterval)
if (flag) {
adEvent.adShowError("ad in timeInterval")
}
return flag
}
}
\ No newline at end of file
package com.base.appzxhy.ads
import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.Button
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.LayoutRes
import com.applovin.mediation.MaxAd
import com.applovin.mediation.nativeAds.MaxNativeAdLoader
import com.applovin.mediation.nativeAds.MaxNativeAdView
import com.applovin.mediation.nativeAds.MaxNativeAdViewBinder
import com.base.appzxhy.R
import com.google.android.gms.ads.nativead.NativeAd
import com.google.android.gms.ads.nativead.NativeAdView
@SuppressLint("ViewConstructor")
class NativeParentView(context: Context, attrs: AttributeSet? = null) :
FrameLayout(context, attrs) {
private val TAG = "NativeParentView"
private var removeAll = true
init {
// 获取XML属性
val a = context.obtainStyledAttributes(
attrs,
R.styleable.NativeParentView,
0,
0
)
// 从XML中读取属性值
// 例如,如果你的XML中有自定义属性 `app:customAttribute="value"`
removeAll = a.getBoolean(R.styleable.NativeParentView_removeAll, true)
// 回收TypedArray
a.recycle()
}
fun setNativeAd(
nativeAd: NativeAd,
@LayoutRes resource: Int? = null
) {
val layout = resource ?: R.layout.layout_admob_native_custom
val adView =
layout.let { LayoutInflater.from(context).inflate(it, null) } as NativeAdView
adView.mediaView = adView.findViewById(R.id.ad_media)
adView.headlineView = adView.findViewById(R.id.ad_headline)
adView.bodyView = adView.findViewById(R.id.ad_body)
adView.callToActionView = adView.findViewById(R.id.ad_call_to_action)
adView.iconView = adView.findViewById(R.id.ad_app_icon)
(adView.headlineView as TextView?)?.text = nativeAd.headline
adView.mediaView?.mediaContent = nativeAd.mediaContent
if (nativeAd.body == null) {
adView.bodyView?.visibility = View.INVISIBLE
} else {
adView.bodyView?.visibility = View.VISIBLE
(adView.bodyView as TextView?)?.text = nativeAd.body
}
if (nativeAd.callToAction == null) {
adView.callToActionView?.visibility = View.INVISIBLE
} else {
adView.callToActionView?.visibility = View.VISIBLE
(adView.callToActionView as Button?)?.text = nativeAd.callToAction
}
if (nativeAd.icon == null) {
adView.iconView?.visibility = View.GONE
} else {
(adView.iconView as ImageView?)?.setImageDrawable(
nativeAd.icon?.drawable
)
adView.iconView?.visibility = View.VISIBLE
}
adView.setNativeAd(nativeAd)
setBackgroundResource(0)
if (removeAll) {
removeAllViews()
}
addView(adView)
}
fun setNativeAd(
nativeAdLoader: MaxNativeAdLoader,
nativeAd: MaxAd,
@LayoutRes resource: Int? = null
) {
val layout = resource ?: R.layout.layout_max_native_custom
val binder: MaxNativeAdViewBinder =
MaxNativeAdViewBinder.Builder(layout)
.setTitleTextViewId(R.id.title_text_view)
.setBodyTextViewId(R.id.body_text_view)
.setAdvertiserTextViewId(R.id.advertiser_text_view)
.setIconImageViewId(R.id.icon_image_view)
.setMediaContentViewGroupId(R.id.media_view_container)
.setOptionsContentViewGroupId(R.id.options_view)
.setStarRatingContentViewGroupId(R.id.star_rating_view)
.setCallToActionButtonId(R.id.cta_button)
.build()
val adView = MaxNativeAdView(binder, context)
nativeAdLoader.render(adView, nativeAd)
setBackgroundResource(0)
removeAllViews()
addView(adView)
}
}
package com.base.appzxhy.ads.admob
import android.os.Bundle
import android.view.ViewGroup
import android.view.ViewTreeObserver
import com.base.appzxhy.GlobalConfig
import com.base.appzxhy.ads.AdsMgr
import com.base.appzxhy.ads.AdsType
import com.base.appzxhy.ads.LimitUtils
import com.base.appzxhy.bean.config.AdConfigBean
import com.google.ads.mediation.admob.AdMobAdapter
import com.google.android.gms.ads.AdListener
import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.AdSize
import com.google.android.gms.ads.AdView
import com.google.android.gms.ads.LoadAdError
import java.util.UUID
/**
*banner广告加载显示管理类
*/
class AdBannerMgr {
private var adView: AdView? = null
private var listener: ViewTreeObserver.OnGlobalLayoutListener? = null
fun show(parent: ViewGroup, collapsible: Boolean, adClose: (() -> Unit)? = null) {
if (!AdConfigBean.adsConfigBean.adSwitch) {
return
}
val admobEvent = AdmobEvent("banner", "banner")
if (!LimitUtils.isAdShow(AdsType.BANNER, admobEvent)) {
adView = null
return
}
parent.removeAllViews()
adView?.destroy()
adView = null
adView = AdView(parent.context)
parent.addView(adView)
adView?.onPaidEventListener = AdmobEvent.EventOnPaidEventListener(adView)
listener = ViewTreeObserver.OnGlobalLayoutListener {
val screenPixelDensity = parent.context.resources.displayMetrics.density
val adWidth = (parent.width / screenPixelDensity).toInt()
val adSize =
AdSize.getCurrentOrientationAnchoredAdaptiveBannerAdSize(parent.context, adWidth)
adView?.adUnitId = GlobalConfig.ID_ADMOB_BANNER
adView?.setAdSize(adSize)
loadAd(admobEvent, collapsible, adClose)
parent.viewTreeObserver.removeOnGlobalLayoutListener(listener)
}
parent.viewTreeObserver.addOnGlobalLayoutListener(listener)
}
fun loadAd(admobEvent: AdmobEvent, collapsible: Boolean, adClose: (() -> Unit)?) {
val build = AdRequest.Builder()
if (collapsible) {
val extras = Bundle()
extras.putString("collapsible", "bottom")
extras.putString("collapsible_request_id", UUID.randomUUID().toString())
build.addNetworkExtrasBundle(AdMobAdapter::class.java, extras)
}
val adRequest = build.build()
adView?.adListener =
object : AdListener() {
override fun onAdFailedToLoad(loadAdError: LoadAdError) {
super.onAdFailedToLoad(loadAdError)
admobEvent.pullAd(loadAdError.responseInfo, loadAdError)
}
override fun onAdLoaded() {
admobEvent.pullAd(adView?.responseInfo)
}
override fun onAdOpened() {
}
override fun onAdClosed() {
super.onAdClosed()
adClose?.invoke()
}
}
adView?.loadAd(adRequest)
}
}
\ No newline at end of file
package com.base.appzxhy.ads.admob
import android.app.Activity
import android.content.Context
import com.base.appzxhy.GlobalConfig
import com.base.appzxhy.ads.AdDialog.showAdPreparingDialog
import com.base.appzxhy.ads.AdEvent
import com.base.appzxhy.ads.AdsShowCallBack
import com.base.appzxhy.ads.AdsType
import com.base.appzxhy.ads.LimitUtils
import com.base.appzxhy.ads.adState
import com.google.android.gms.ads.AdError
import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.FullScreenContentCallback
import com.google.android.gms.ads.LoadAdError
import com.google.android.gms.ads.interstitial.InterstitialAd
import com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback
import java.lang.ref.WeakReference
/**
*插屏广告加载显示管理类
*/
class AdInsertMgr {
private var adState = adState<InterstitialAd>()
private var showCallBack: AdsShowCallBack? = null
fun show(activity: Activity, isUnLimit: Boolean, adEvent: AdEvent, showCallBack: AdsShowCallBack?) {
if (activity.isFinishing || activity.isDestroyed) {
showCallBack?.failed(0)
return
}
if (!isUnLimit) {
if (!LimitUtils.isAdShow(AdsType.INSERT, adEvent)) {
showCallBack?.failed(2)
return
}
if (LimitUtils.isIntervalLimited(adState.lastShowTime, adEvent)) {
showCallBack?.failed(3)
return
}
}
if (adState.showingAd) {
showCallBack?.failed(1)
adEvent.adShowError("showingAd")
adState.showingAd = false
return
}
if (showCallBack != null) {
this.showCallBack = showCallBack
adState.activityRef = WeakReference(activity)
if (adState.adDialog == null) {
adState.adDialog = activity.showAdPreparingDialog(1)
}
adEvent.adPrepareShow()
}
if (!adState.loadingAd) {
if (adState.currentAd == null) {
loadAd(activity, isUnLimit, adEvent)
return
}
if (!adAvailable()) {
loadAd(activity, isUnLimit, adEvent)
return
}
showReadyAd(adEvent, activity)
}
}
private fun showReadyAd(adEvent: AdEvent, activity: Activity) {
adState.currentAd?.run {
fullScreenContentCallback = object : FullScreenContentCallback() {
override fun onAdShowedFullScreenContent() {
super.onAdShowedFullScreenContent()
adState.onAdDisplayed()
showCallBack?.show()
(adEvent as AdmobEvent).showAd(responseInfo, activity)
LimitUtils.addDisplayNum()
}
override fun onAdFailedToShowFullScreenContent(adError: AdError) {
super.onAdFailedToShowFullScreenContent(adError)
adState.onAdDisplayFailed()
showCallBack?.googleFailed()
showCallBack = null
adEvent.adShowError(adError)
}
override fun onAdDismissedFullScreenContent() {
super.onAdDismissedFullScreenContent()
adState.onAdHidden()
showCallBack?.close()
showCallBack = null
loadAd(activity.applicationContext, false, AdmobEvent("interAd", "preload"))
}
override fun onAdClicked() {
super.onAdClicked()
(adEvent as AdmobEvent).clickAd(responseInfo)
//计数
LimitUtils.addClickNum()
}
}
show(activity)
}
}
fun loadAd(context: Context, isUnLimit: Boolean, adEvent: AdEvent) {
if (!isUnLimit) {
if (!LimitUtils.isAdShow(AdsType.INSERT, adEvent)) {
this.showCallBack?.close(4)
this.showCallBack = null
adState.onAdLoadFailed()
return
}
}
if (!adState.loadingAd) {
adState.loadingAd = true
adEvent.adPulStart()
InterstitialAd.load(context, GlobalConfig.ID_ADMOB_INTER, AdRequest.Builder().build(),
object : InterstitialAdLoadCallback() {
override fun onAdLoaded(ad: InterstitialAd) {
adState.onAdLoaded(ad)
val ac = adState.activityRef?.get()
if (ac != null) {
show(ac, isUnLimit, adEvent, null)
}
(adEvent as AdmobEvent).pullAd(ad.responseInfo)
LimitUtils.addRequestNum()
ad.onPaidEventListener = AdmobEvent.EventOnPaidEventListener(ad)
}
override fun onAdFailedToLoad(loadAdError: LoadAdError) {
adState.onAdLoadFailed()
(adEvent as AdmobEvent).pullAd(loadAdError.responseInfo, loadAdError)
showCallBack?.googleFailed()
showCallBack = null
}
}
)
}
}
private fun adAvailable() = ((System.currentTimeMillis() - adState.lastLoadTime) / 1000 / 60).toInt() < 30
}
\ No newline at end of file
package com.base.appzxhy.ads.admob
import com.base.appzxhy.GlobalConfig
import com.base.appzxhy.ads.AdsType
import com.base.appzxhy.ads.LimitUtils
import com.base.appzxhy.ads.NativeParentView
import com.google.android.gms.ads.AdListener
import com.google.android.gms.ads.AdLoader
import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.LoadAdError
import com.google.android.gms.ads.nativead.NativeAd
import com.google.android.gms.ads.nativead.NativeAdOptions
import java.util.concurrent.ConcurrentLinkedDeque
/**
*原生广告加载显示管理类
*/
class AdNativeMgr {
/**
* 上一次的缓存成功时间
*/
protected var lastTime: Long = 0
/**
* 原生广告缓存队列
*/
private val cacheItems = ConcurrentLinkedDeque<NativeAd>()
private fun loadAd(
admobEvent: AdmobEvent,
parent: NativeParentView,
layout: Int
) {
admobEvent.adPulStart()
if (!LimitUtils.isAdShow(AdsType.NATIVE, admobEvent)) return
var currentNativeAd: NativeAd? = null
val adLoader = AdLoader.Builder(
parent.context,
GlobalConfig.ID_ADMOB_NATIVE
).forNativeAd { nativeAd ->
currentNativeAd = nativeAd
cacheItems.offer(nativeAd)
lastTime = System.currentTimeMillis()
nativeAd.setOnPaidEventListener(AdmobEvent.EventOnPaidEventListener(nativeAd))
admobEvent.pullAd(nativeAd.responseInfo)
show(admobEvent, parent, layout)
}.withAdListener(object : AdListener() {
override fun onAdFailedToLoad(error: LoadAdError) {
admobEvent.pullAd(error.responseInfo, error)
}
override fun onAdClicked() {
super.onAdClicked()
admobEvent.clickAd(currentNativeAd?.responseInfo)
}
override fun onAdClosed() {
super.onAdClosed()
}
}).withNativeAdOptions(
NativeAdOptions.Builder()
.build()
).build()
adLoader.loadAds(AdRequest.Builder().build(), 1)
}
fun show(
admobEvent: AdmobEvent,
parent: NativeParentView,
layout: Int,
nativeCallBack: ((Any?) -> Unit)? = null
) {
admobEvent.adPrepareShow()
if (!LimitUtils.isAdShow(AdsType.NATIVE, admobEvent)) {
cacheItems.clear()
return
}
val nativeAd = cacheItems.peek()
if (nativeAd == null || !adAvailable()) {
//缓存过期了就清空
cacheItems.clear()
loadAd(admobEvent, parent, layout)
return
}
nativeCallBack?.invoke(nativeAd)
parent.setNativeAd(nativeAd, layout)
admobEvent.showAd(nativeAd.responseInfo)
}
private fun adAvailable(): Boolean {
return ((System.currentTimeMillis() - lastTime) / 1000 / 60).toInt() < 30
}
}
\ No newline at end of file
package com.base.appzxhy.ads.admob
import android.app.Activity
import android.content.Context
import com.base.appzxhy.GlobalConfig
import com.base.appzxhy.ads.AdsType
import com.base.appzxhy.ads.AdEvent
import com.base.appzxhy.ads.AdsShowCallBack
import com.base.appzxhy.ads.LimitUtils
import com.base.appzxhy.ads.adState
import com.google.android.gms.ads.AdError
import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.FullScreenContentCallback
import com.google.android.gms.ads.LoadAdError
import com.google.android.gms.ads.appopen.AppOpenAd
import java.lang.ref.WeakReference
/**
* 开屏广告加载显示管理类
*/
class AdOpenMgr {
private val adState = adState<AppOpenAd>()
private var showCallBack: AdsShowCallBack? = null
fun show(activity: Activity, isUnLimit: Boolean, adEvent: AdEvent, showCallBack: AdsShowCallBack?) {
if (activity.isFinishing || activity.isDestroyed) {
return
}
if (!isUnLimit) {
if (!LimitUtils.isAdShow(AdsType.OPEN, adEvent)) {
showCallBack?.failed()
return
}
if (LimitUtils.isIntervalLimited(adState.lastShowTime, adEvent)) {
showCallBack?.failed()
return
}
}
if (adState.showingAd) {
showCallBack?.failed()
adEvent.adShowError("showingAd")
adState.showingAd = false
return
}
if (showCallBack != null) {
this.showCallBack = showCallBack
adState.activityRef = WeakReference(activity)
adEvent.adPrepareShow()
}
if (!adState.loadingAd) {
if (!adAvailable() || adState.currentAd == null) {
loadAd(activity, isUnLimit, adEvent)
return
}
showReadyAd(adEvent, activity)
}
}
private fun showReadyAd(adEvent: AdEvent, activity: Activity) {
adState.currentAd?.run {
fullScreenContentCallback = object : FullScreenContentCallback() {
override fun onAdShowedFullScreenContent() {
showCallBack?.show()
(adEvent as AdmobEvent).showAd(this@run.responseInfo, activity)
adState.onAdDisplayed()
//计数
LimitUtils.addDisplayNum()
}
override fun onAdFailedToShowFullScreenContent(adError: AdError) {
super.onAdFailedToShowFullScreenContent(adError)
showCallBack?.googleFailed()
showCallBack = null
adState.onAdDisplayFailed()
(adEvent as AdmobEvent).adShowError(adError)
}
override fun onAdDismissedFullScreenContent() {
super.onAdDismissedFullScreenContent()
showCallBack?.close()
showCallBack = null
adState.onAdHidden()
loadAd(activity.applicationContext, false, AdmobEvent("openAd", "preload"))
}
override fun onAdClicked() {
(adEvent as AdmobEvent).clickAd(this@run.responseInfo)
//计数
LimitUtils.addClickNum()
}
}
show(activity)
}
}
fun loadAd(context: Context, isUnLimit: Boolean, adEvent: AdEvent) {
if (!isUnLimit) {
if (!LimitUtils.isAdShow(AdsType.OPEN, adEvent)) {
this.showCallBack?.close()
this.showCallBack = null
adState.onAdLoadFailed()
return
}
}
if (!adState.loadingAd) {
adState.loadingAd = true
adEvent.adPulStart()
AppOpenAd.load(
context,
GlobalConfig.ID_ADMOB_OPEN,
AdRequest.Builder().build(),
object : AppOpenAd.AppOpenAdLoadCallback() {
override fun onAdLoaded(appOpenAd: AppOpenAd) {
adState.onAdLoaded(appOpenAd)
val ac = adState.activityRef?.get()
if (ac != null) {
show(ac, isUnLimit, adEvent, null)
}
(adEvent as AdmobEvent).pullAd(appOpenAd.responseInfo)
appOpenAd.onPaidEventListener = AdmobEvent.EventOnPaidEventListener(appOpenAd)
LimitUtils.addRequestNum()
}
override fun onAdFailedToLoad(loadAdError: LoadAdError) {
showCallBack?.googleFailed()
showCallBack = null
adState.onAdLoadFailed()
(adEvent as AdmobEvent).pullAd(loadAdError.responseInfo, loadAdError)
}
}
)
}
}
private fun adAvailable() = ((System.currentTimeMillis() - adState.lastLoadTime) / 1000 / 60).toInt() < 30
}
\ No newline at end of file
This diff is collapsed.
package com.base.appzxhy.ads.applovin
import android.os.Bundle
import com.applovin.mediation.MaxAd
import com.applovin.mediation.MaxAdRevenueListener
import com.applovin.mediation.MaxError
import com.applovin.sdk.AppLovinSdk
import com.base.appzxhy.MyApplication
import com.base.appzxhy.ads.AdEvent
import com.base.appzxhy.ads.taichiPref
import com.base.appzxhy.ads.taichiSharedPreferencesEditor
import com.base.appzxhy.helper.EventUtils
import com.base.appzxhy.utils.LogEx
import com.base.appzxhy.utils.LogEx.logDebug
import com.facebook.appevents.AppEventsConstants
import com.facebook.appevents.AppEventsLogger
import com.google.firebase.analytics.FirebaseAnalytics
import org.json.JSONObject
class AdMaxEvent : AdEvent {
override val TAG: String = "AdMaxEvent"
constructor(adUnit: String, from: String) : super() {
this.adUnit = adUnit
this.from = from
}
fun pullAd(
ad: MaxAd?,
error: MaxError? = null
) {
val obj = JSONObject()
obj.put("UnitId", ad?.adUnitId)
obj.put("ad_unit", adUnit)
obj.put(
"creativeId",
ad?.creativeId
)
obj.put("req_id", reqId)
obj.put("from", from)
obj.put("status", if (ad == null) "0" else "1")
obj.put("networkname", ad?.networkName)
obj.put("placement", ad?.placement)
obj.put("networkplacement", ad?.networkPlacement)
obj.put("latency", ad?.requestLatencyMillis)
obj.put("valueMicros", ad?.revenue)
if (error == null) {
obj.put("status", "1")
} else {
obj.put("errMsg", error)
obj.put("status", "2")
}
EventUtils.event("ad_pull", ext = obj)
logDebug(TAG, "ad_pull $obj")
}
fun clickAd(ad: MaxAd?) {
val obj = JSONObject()
obj.put("UnitId", ad?.adUnitId)
obj.put("ad_unit", adUnit)
obj.put(
"creativeId",
ad?.creativeId
)
obj.put("networkname", ad?.networkName)
obj.put("placement", ad?.placement)
obj.put("networkplacement", ad?.networkPlacement)
obj.put("latency", ad?.requestLatencyMillis)
obj.put("valueMicros", ad?.revenue)
if (!adUnit.equals("nativeAd")) {
EventUtils.event("ad_click", ext = obj)
} else {
EventUtils.event("ad_click", ext = obj)
}
}
fun showAd(ad: MaxAd?, activity: String?) {
val obj = JSONObject()
obj.put("UnitId", ad?.adUnitId)
obj.put("ad_unit", adUnit)
obj.put(
"creativeId",
ad?.creativeId
)
obj.put("networkname", ad?.networkName)
obj.put("placement", ad?.placement)
obj.put("networkplacement", ad?.networkPlacement)
obj.put("latency", ad?.requestLatencyMillis)
obj.put("valueMicros", ad?.revenue)
obj.put("from", activity)
obj.put("mediation", "applovin")
if (adUnit != "nativeAd") {
EventUtils.event("ad_show", ext = obj)
} else {
EventUtils.event("ad_show", ext = obj)
}
}
class EventOnPaidEventListener : MaxAdRevenueListener {
override fun onAdRevenuePaid(ad: MaxAd) {
val params = Bundle()
val currentImpressionRevenue: Double = ad.revenue // In USD
val mFirebaseAnalytics = FirebaseAnalytics.getInstance(MyApplication.appContext)
params.putString(FirebaseAnalytics.Param.AD_PLATFORM, "appLovin")
params.putString(FirebaseAnalytics.Param.AD_SOURCE, ad.networkName)
params.putString(FirebaseAnalytics.Param.AD_FORMAT, ad.format.getDisplayName())
params.putString(FirebaseAnalytics.Param.AD_UNIT_NAME, ad.adUnitId)
params.putDouble(FirebaseAnalytics.Param.VALUE, currentImpressionRevenue)
params.putString(FirebaseAnalytics.Param.CURRENCY, "USD")
mFirebaseAnalytics.logEvent(FirebaseAnalytics.Event.AD_IMPRESSION, params)
mFirebaseAnalytics.logEvent("Ad_Impression_Revenue", params)
val previousTaichiTroasCache = taichiPref.getFloat("TaichiTroasCache", 0f)
val currentTaichiTroasCache = previousTaichiTroasCache + currentImpressionRevenue
if (currentTaichiTroasCache >= 0.01) {
val roasbundle = Bundle()
roasbundle.putDouble(FirebaseAnalytics.Param.VALUE, currentTaichiTroasCache)
roasbundle.putString(FirebaseAnalytics.Param.CURRENCY, "USD")///(Required)tROAS事件必须
mFirebaseAnalytics.logEvent("Total_Ads_Revenue_001", roasbundle) // 给Taichi用
taichiSharedPreferencesEditor.putFloat("TaichiTroasCache", 0f)//重新清零,开始计算
val logger = AppEventsLogger.newLogger(MyApplication.appContext)
val parameters = Bundle()
parameters.putString(AppEventsConstants.EVENT_PARAM_CURRENCY, "USD")
logger.logEvent("ad_value", currentTaichiTroasCache, parameters)
} else {
taichiSharedPreferencesEditor.putFloat(
"TaichiTroasCache",
currentTaichiTroasCache.toFloat()
)
taichiSharedPreferencesEditor.commit()
}
val obj = JSONObject()
val revenue = ad.revenue
val countryCode =
AppLovinSdk.getInstance(MyApplication.appContext).configuration.countryCode
val networkName = ad.networkName
val adUnitId = ad.adUnitId
val adFormat = ad.format
val placement = ad.placement
val networkPlacement = ad.networkPlacement
obj.put("valueMicros", revenue)
obj.put("currencyCode", countryCode)
obj.put("adUnitId", adUnitId)
obj.put("networkName", networkName)
obj.put("adFormat", adFormat)
obj.put("placement", placement)
obj.put("networkPlacement", networkPlacement)
EventUtils.event("ad_price", ext = obj)
}
}
}
\ No newline at end of file
package com.base.appzxhy.ads.applovin
import android.app.Activity
import android.content.Context
import com.applovin.mediation.MaxAd
import com.applovin.mediation.MaxAdListener
import com.applovin.mediation.MaxError
import com.applovin.mediation.ads.MaxInterstitialAd
import com.base.appzxhy.GlobalConfig
import com.base.appzxhy.ads.AdsType
import com.base.appzxhy.ads.AdDialog.showAdPreparingDialog
import com.base.appzxhy.ads.AdEvent
import com.base.appzxhy.ads.AdsShowCallBack
import com.base.appzxhy.ads.LimitUtils
import com.base.appzxhy.ads.adState
import java.lang.ref.WeakReference
/**
*插屏广告加载显示管理类
*/
class MaxInsertMgr {
private var adState = adState<MaxInterstitialAd>()
private var showCallBack: AdsShowCallBack? = null
fun show(
activity: Activity,
isUnLimit: Boolean,
adEvent: AdEvent,
showCallBack: AdsShowCallBack?
) {
if (activity.isFinishing || activity.isDestroyed) {
showCallBack?.failed(1)
return
}
if (adState.showingAd) {
showCallBack?.failed(2)
return
}
if (showCallBack != null) {
adState.activityRef = WeakReference(activity)
this.showCallBack = showCallBack
if (adState.adDialog == null) {
adState.adDialog = activity.showAdPreparingDialog(2)
}
adEvent.adPrepareShow()
}
if (!adState.loadingAd) {
if (!isUnLimit) {
if (!LimitUtils.isAdShow(AdsType.INSERT, adEvent)) {
showCallBack?.failed(3)
return
}
if (LimitUtils.isIntervalLimited(adState.lastShowTime, adEvent)) {
showCallBack?.failed(4)
return
}
}
if (!adAvailable() || adState.currentAd == null) {
loadAd(activity, isUnLimit, adEvent)
return
}
if (adState.currentAd?.isReady == false) {
loadAd(activity, isUnLimit, adEvent)
return
}
showReadyAd(adEvent)
}
}
private fun showReadyAd(adEvent: AdEvent) {
adState.currentAd?.run {
setListener(object : MaxAdListener {
override fun onAdLoaded(p0: MaxAd) = Unit
override fun onAdLoadFailed(p0: String, p1: MaxError) = Unit
override fun onAdDisplayed(ad: MaxAd) {
adState.onAdDisplayed()
showCallBack?.show()
(adEvent as AdMaxEvent).showAd(ad, activity::class.simpleName)
//计数
LimitUtils.addDisplayNum()
}
override fun onAdDisplayFailed(ad: MaxAd, error: MaxError) {
adState.onAdDisplayFailed()
showCallBack?.googleFailed()
showCallBack = null
(adEvent as AdMaxEvent).adShowError(error)
}
override fun onAdHidden(p0: MaxAd) {
adState.onAdHidden()
showCallBack?.close()
loadAd(activity.applicationContext, false, AdMaxEvent("interAd", "preload"))
}
override fun onAdClicked(ad: MaxAd) {
(adEvent as AdMaxEvent).clickAd(ad)
//计数
LimitUtils.addClickNum()
}
})
setRevenueListener(AdMaxEvent.EventOnPaidEventListener())
showAd(activity)
}
}
fun loadAd(
context: Context,
isUnLimit: Boolean,
adEvent: AdEvent,
) {
if (!isUnLimit) {
if (!LimitUtils.isAdShow(AdsType.INSERT, adEvent)) {
this.showCallBack?.close(4)
this.showCallBack = null
return
}
}
if (!adState.loadingAd) {
adState.loadingAd = true
adEvent.adPulStart()
adState.currentAd = MaxInterstitialAd(GlobalConfig.ID_MAX_INTER, context)
adState.currentAd?.setListener(object : MaxAdListener {
override fun onAdDisplayed(p0: MaxAd) = Unit
override fun onAdHidden(p0: MaxAd) = Unit
override fun onAdClicked(p0: MaxAd) = Unit
override fun onAdDisplayFailed(p0: MaxAd, p1: MaxError) = Unit
override fun onAdLoaded(ad: MaxAd) {
adState.onAdLoaded(null)
val ac = adState.activityRef?.get()
if (ac != null) {
show(ac, isUnLimit, adEvent, null)
}
(adEvent as AdMaxEvent).pullAd(ad)
LimitUtils.addRequestNum()
}
override fun onAdLoadFailed(ad: String, error: MaxError) {
adState.onAdLoadFailed()
(adEvent as AdMaxEvent).pullAd(null, error)
showCallBack?.googleFailed(5)
showCallBack = null
}
})
adState.currentAd?.loadAd()
}
}
private fun adAvailable() = ((System.currentTimeMillis() - adState.lastLoadTime) / 1000 / 60).toInt() < 30
}
\ No newline at end of file
package com.base.appzxhy.ads.applovin
import androidx.annotation.LayoutRes
import com.applovin.mediation.MaxAd
import com.applovin.mediation.MaxError
import com.applovin.mediation.nativeAds.MaxNativeAdListener
import com.applovin.mediation.nativeAds.MaxNativeAdLoader
import com.applovin.mediation.nativeAds.MaxNativeAdView
import com.base.appzxhy.GlobalConfig
import com.base.appzxhy.ads.AdsType
import com.base.appzxhy.ads.LimitUtils
import com.base.appzxhy.ads.NativeParentView
import com.base.appzxhy.helper.EventUtils
import org.json.JSONObject
import java.util.UUID
/**
*原生广告加载显示管理类
*/
class MaxNativeMgr {
/**
* 上一次的缓存成功时间
*/
protected var lastTime: Long = 0
/**
* 原生广告
*/
private var currentAd: MaxAd? = null
private var currentLoader: MaxNativeAdLoader? = null
private fun loadAd(
adMaxEvent: AdMaxEvent,
parent: NativeParentView,
@LayoutRes layout: Int
) {
if (!LimitUtils.isAdShow(AdsType.NATIVE, adMaxEvent)) return
val reqId = UUID.randomUUID().toString()
val obj = JSONObject()
obj.put("req_id", reqId)
obj.put("ad_type", "nativeAd")
val nativeAdLoader = MaxNativeAdLoader(GlobalConfig.ID_MAX_NATIVE, parent.context)
nativeAdLoader.setNativeAdListener(object : MaxNativeAdListener() {
override fun onNativeAdLoaded(nativeAdView: MaxNativeAdView?, ad: MaxAd) {
currentLoader = nativeAdLoader
currentAd = ad
lastTime = System.currentTimeMillis()
adMaxEvent.pullAd(ad)
nativeAdLoader.setRevenueListener(AdMaxEvent.EventOnPaidEventListener())
show(adMaxEvent, parent, layout)
}
override fun onNativeAdLoadFailed(adUnitId: String, error: MaxError) {
adMaxEvent.pullAd(null, error)
}
override fun onNativeAdClicked(ad: MaxAd) {
}
})
nativeAdLoader.loadAd()
}
fun show(
adMaxEvent: AdMaxEvent,
parent: NativeParentView,
@LayoutRes layout: Int,
nativeCallBack: ((Any?) -> Unit)? = null
) {
if (!LimitUtils.isAdShow(AdsType.NATIVE, adMaxEvent)) {
currentLoader = null
currentAd = null
return
}
val nativeAd = currentAd
val nativeLoader = currentLoader
if ((nativeAd == null || nativeLoader == null).also {
if (it) {
val obj2 = JSONObject()
obj2.put("reason", "no_ad")
obj2.put("ad_unit", "nativeAd")
EventUtils.event("ad_show_error", ext = obj2)
}
} || (!adAvailable()).also {
if (it) {
val obj2 = JSONObject()
obj2.put("ad_unit", "nativeAd")
EventUtils.event("ad_expire", ext = obj2)
}
}) {
//缓存过期了就清空
currentLoader = null
currentAd = null
loadAd(AdMaxEvent("nativeAd", "preload"), parent, layout)
return
}
val obj = JSONObject()
obj.put("ad_unit", "nativeAd")
EventUtils.event("ad_prepare_show", ext = obj)
parent.setNativeAd(nativeLoader!!, nativeAd!!, layout)
nativeCallBack?.invoke(nativeAd)
}
private fun adAvailable(): Boolean {
return ((System.currentTimeMillis() - lastTime) / 1000 / 60).toInt() < 30
}
}
\ No newline at end of file
package com.base.appzxhy.ads.applovin
import android.app.Activity
import android.content.Context
import com.applovin.mediation.MaxAd
import com.applovin.mediation.MaxAdListener
import com.applovin.mediation.MaxError
import com.applovin.mediation.ads.MaxAppOpenAd
import com.base.appzxhy.GlobalConfig
import com.base.appzxhy.ads.AdsType
import com.base.appzxhy.ads.AdEvent
import com.base.appzxhy.ads.AdsShowCallBack
import com.base.appzxhy.ads.LimitUtils
import com.base.appzxhy.ads.adState
import java.lang.ref.WeakReference
/**
* 开屏广告加载显示管理类
*/
class MaxOpenMgr {
private val adState = adState<MaxAppOpenAd>()
private var showCallBack: AdsShowCallBack? = null
fun show(activity: Activity, isUnLimit: Boolean, adEvent: AdEvent, showCallBack: AdsShowCallBack?) {
if (activity.isFinishing || activity.isDestroyed) {
return
}
if (adState.showingAd) {
showCallBack?.failed()
adEvent.adShowError("showingAd")
return
}
if (showCallBack != null) {
this.showCallBack = showCallBack
adState.activityRef = WeakReference(activity)
adEvent.adPrepareShow()
}
if (!adState.loadingAd) {
if (!isUnLimit) {
if (!LimitUtils.isAdShow(AdsType.OPEN, adEvent)) {
showCallBack?.failed()
return
}
if (LimitUtils.isIntervalLimited(adState.lastShowTime, adEvent)) {
showCallBack?.failed()
return
}
}
if (!adAvailable() || adState.currentAd == null) {
loadAd(activity, isUnLimit, adEvent)
return
}
if (adState.currentAd?.isReady != true) {
loadAd(activity, isUnLimit, adEvent)
return
}
showReadyAd(activity, adEvent)
}
}
private fun showReadyAd(activity: Activity, adEvent: AdEvent) {
adState.currentAd?.run {
setListener(object : MaxAdListener {
override fun onAdLoaded(p0: MaxAd) {
}
override fun onAdDisplayed(ad: MaxAd) {
adState.onAdDisplayed()
showCallBack?.show()
LimitUtils.addDisplayNum()
(adEvent as AdMaxEvent).showAd(ad, activity::class.simpleName)
}
override fun onAdHidden(p0: MaxAd) {
adState.onAdHidden()
showCallBack?.close()
showCallBack = null
loadAd(activity.applicationContext, false, AdMaxEvent("openAd", "preload"))
}
override fun onAdClicked(ad: MaxAd) {
(adEvent as AdMaxEvent).clickAd(ad)
//计数
LimitUtils.addClickNum()
}
override fun onAdLoadFailed(p0: String, p1: MaxError) {
}
override fun onAdDisplayFailed(p0: MaxAd, error: MaxError) {
adState.onAdDisplayFailed()
showCallBack?.googleFailed()
showCallBack = null
adEvent.adShowError(error)
}
})
showAd()
}
}
fun loadAd(context: Context, isUnLimit: Boolean, adEvent: AdEvent) {
if (!isUnLimit) {
if (!LimitUtils.isAdShow(AdsType.OPEN, adEvent)) {
this.showCallBack?.close()
this.showCallBack = null
return
}
}
if (!adState.loadingAd) {
adState.loadingAd = true
adEvent.adPulStart()
adState.currentAd = MaxAppOpenAd(GlobalConfig.ID_MAX_OPEN, context)
adState.currentAd?.setListener(object : MaxAdListener {
override fun onAdLoaded(ad: MaxAd) {
adState.onAdLoaded(null)
val ac = adState.activityRef?.get()
if (ac != null) {
show(ac, isUnLimit, adEvent, null)
}
(adEvent as AdMaxEvent).pullAd(ad)
LimitUtils.addRequestNum()
}
override fun onAdDisplayed(p0: MaxAd) {
}
override fun onAdHidden(p0: MaxAd) {
}
override fun onAdClicked(p0: MaxAd) {
}
override fun onAdLoadFailed(p0: String, error: MaxError) {
adState.onAdLoadFailed()
showCallBack?.googleFailed()
showCallBack = null
(adEvent as AdMaxEvent).pullAd(null, error)
}
override fun onAdDisplayFailed(p0: MaxAd, p1: MaxError) {
}
})
adState.currentAd?.loadAd()
}
}
fun adAvailable() = ((System.currentTimeMillis() - adState.lastLoadTime) / 1000 / 60).toInt() < 30
}
\ No newline at end of file
package com.base.appzxhy.base
import android.app.Activity
import android.app.Dialog
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import androidx.viewbinding.ViewBinding
import com.base.appzxhy.ui.main.MainActivity
import com.base.appzxhy.R
import com.base.appzxhy.bean.ConstObject.appLanguageCountrySp
import com.base.appzxhy.bean.ConstObject.appLanguageSp
import com.base.appzxhy.helper.EventUtils
import com.base.appzxhy.utils.ActivityLauncher
import com.base.appzxhy.utils.ActivityManagerUtils
import com.base.appzxhy.utils.LogEx
import com.gyf.immersionbar.ktx.immersionBar
import com.hjq.language.MultiLanguages
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.Locale
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.random.Random
abstract class BaseActivity<VB : ViewBinding>(
bindingInflater: (LayoutInflater) -> VB
) : AppCompatActivity() {
open val TAG = javaClass.simpleName
private val tagNo = Random.nextInt(500)
val binding by lazy(LazyThreadSafetyMode.NONE) {
bindingInflater(layoutInflater)
}
lateinit var launcher: ActivityLauncher
private var currentLocale: Locale? = null
var onCreateI = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
configSystemBar()
onCreateI++
LogEx.logDebug(TAG, "lifecycle $tagNo onCreate ${javaClass.simpleName} $onCreateI")
launcher = ActivityLauncher(this)
setContentView(binding.root)
EventUtils.event("page_${javaClass.simpleName}")
currentLocale = Locale(appLanguageSp, appLanguageCountrySp)
initView()
initListener()
}
protected open fun initView() {}
protected open fun initListener() {}
fun finishToMain(delay: Long = 0) {
val flag = this !is MainActivity && !ActivityManagerUtils.getInstance().isActivityInStack(MainActivity::class.java)
if (flag) {
startActivity(Intent(this, MainActivity::class.java).apply {
putExtra("where", "finishToMain")
})
}
lifecycleScope.launch(Dispatchers.Main) {
delay(delay)
finish()
}
}
fun finishToMainTop() {
val intent = Intent(this, MainActivity::class.java)
intent.putExtra("where", "finishToMainTop")
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
startActivity(intent)
}
var dialog: Dialog? = null
override fun onDestroy() {
super.onDestroy()
ActivityManagerUtils.getInstance().removeActivity(this)
if (dialog != null) {
dialog?.dismiss()
dialog = null
}
}
override fun attachBaseContext(newBase: Context?) {
super.attachBaseContext(MultiLanguages.attach(newBase))
}
fun changeLanguage(currentActivity: Activity, bundle: Bundle = Bundle()): Boolean {
val spLanguage = Locale(appLanguageSp, appLanguageCountrySp)
val flag = currentLocale != spLanguage
val restart = MultiLanguages.setAppLanguage(this, spLanguage)
LogEx.logDebug(
TAG, "changeLanguage " +
"flag=$flag restart=$restart currentLocale=$currentLocale spLanguage=$spLanguage"
)
if (restart) {
currentLocale = Locale(appLanguageSp, appLanguageCountrySp)
}
if (flag || restart) {
// 1.使用 recreate 来重启 Activity,效果差,有闪屏的缺陷
// recreate();
// 2.使用常规 startActivity 来重启 Activity,有从左向右的切换动画
// 稍微比 recreate 的效果好一点,但是这种并不是最佳的效果
// startActivity(new Intent(this, LanguageActivity.class));
// finish();
// 3.我们可以充分运用 Activity 跳转动画,在跳转的时候设置一个渐变的效果,相比前面的两种带来的体验是最佳的
//需要设置启动模式,避免AndroidManifest.xml设置singleTop启动模式导致重复启动不生效
//这种方式如果设置了singleTop或者singleTask的,再次回到该activity无法改变于语言,因为还在堆栈里没有重新创建
needRecreate()
if (needRecreate()) {
LogEx.logDebug(TAG, "changeLanguage recreate")
recreate()
} else {
startActivity(Intent(this, currentActivity::class.java).apply {
putExtras(bundle)
})
overridePendingTransition(R.anim.activity_alpha_in, R.anim.activity_alpha_out)
finish()
}
return true
}
return false
}
/**
* 判断是否需要重新创建对SingleTop启动模式的有效
*/
fun needRecreate(): Boolean {
return TAG.contains("MainActivity")
}
/**
* 避免在onResume--onPause周期内,多次调用
* 在onStop中重置标志
*/
private val onResumeCalled = AtomicBoolean(false)
private var onResumeOneShootI = 0
open fun onResumeOneShoot() {
onResumeOneShootI++
LogEx.logDebug(TAG, "lifecycle $tagNo onResumeOneShoot ${javaClass.simpleName} $onResumeOneShootI")
}
private var onResumeI = 0
override fun onResume() {
super.onResume()
onResumeI++
LogEx.logDebug(TAG, "lifecycle $tagNo onResume ${javaClass.simpleName} $onResumeI")
if (!onResumeCalled.get()) {
onResumeCalled.set(true)
onResumeOneShoot()
}
}
private var onStartI = 0
override fun onStart() {
super.onStart()
onStartI++
LogEx.logDebug(TAG, "lifecycle $tagNo onStart ${javaClass.simpleName} $onStartI")
}
private var onStopI = 0
override fun onStop() {
super.onStop()
onStopI++
LogEx.logDebug(TAG, "lifecycle $tagNo onStop ${javaClass.simpleName} $onStopI")
onResumeCalled.set(false)
}
private var onPauseI = 0
override fun onPause() {
super.onPause()
onPauseI++
LogEx.logDebug(TAG, "lifecycle $tagNo onPause ${javaClass.simpleName} $onPauseI")
}
open fun configSystemBar() {
immersionBar {
statusBarColor("#FFFFFFFF")
navigationBarColor("#FFFFFFFF")
statusBarDarkFont(true)
navigationBarDarkIcon(true)
fitsSystemWindows(true)
}
}
}
\ No newline at end of file
@file:Suppress("unused")
package com.base.appzxhy.base
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.viewbinding.ViewBinding
/**
*
* @param VB : ViewBinding
* 基本的fragment绑定数据
*/
abstract class BaseFragment<VB : ViewBinding>(
private val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> VB
) : Fragment() {
private var _binding: VB? = null
private var fragmentInit = false
open val TAG = javaClass.simpleName
//重写binding get()方法不直接暴露真实的可空_binding数据
@Suppress("MemberVisibilityCanBePrivate")
val binding: VB
get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = bindingInflater.invoke(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initView()
initListener()
}
protected open fun initView() {}
protected open fun initListener() {}
open fun onResumeOneShoot() = Unit
override fun onResume() {
if (!fragmentInit) {
onResumeOneShoot()
fragmentInit = true
}
super.onResume()
}
override fun onDestroyView() {
super.onDestroyView()
//要手动置null防止内存泄漏
_binding = null
}
}
inline fun <VB : ViewBinding> BaseFragment<VB>.viewBind(block: VB.() -> Unit) {
binding.apply(block)
}
inline fun Fragment.goToAc(clazz: Class<out Activity>, block: (Intent.() -> Unit) = {}) {
context?.startActivity(Intent(context, clazz).apply(block))
}
package com.base.appzxhy.bean
import com.base.appzxhy.utils.AppPreferences
import java.util.Locale
object ConstObject {
var isFirstLauncher = true
get() {
return AppPreferences.getInstance().getBoolean("isFirstLauncher", field)
}
set(value) {
field = value
AppPreferences.getInstance().put("isFirstLauncher", value, true)
}
var appLanguageSp = Locale.getDefault().language
get() {
return AppPreferences.getInstance().getString("languageSp", field)
}
set(value) {
field = value
AppPreferences.getInstance().put("languageSp", value, true)
}
var appLanguageCountrySp = Locale.getDefault().country
get() {
return AppPreferences.getInstance().getString("languageCountrySp", field)
}
set(value) {
field = value
AppPreferences.getInstance().put("languageCountrySp", value, true)
}
var noShowFriendsShareGuide = false
get() {
return AppPreferences.getInstance().getBoolean("noShowFriendsShareGuide", field)
}
set(value) {
field = value
AppPreferences.getInstance().put("noShowFriendsShareGuide", value, true)
}
}
\ No newline at end of file
package com.base.appzxhy.bean
class FunctionUIBean(
val key: String = "",
val icon: Int = 0,
val desc: String = ""
) {
}
\ No newline at end of file
package com.base.appzxhy.bean
data class HomeTabUIBean(
val icon:Int,
val tab:String
)
\ No newline at end of file
package com.base.appzxhy.bean
data class LanguageBean(
val key: String,
val language: String,
val country:String,
) {
var isSelect: Boolean = false
companion object {
const val English = "English"
const val 简体中文 = "简体中文"
const val português = "português"
const val Español = "Español "
const val Français = "Français"
const val Deutsch = "Deutsch"
const val ไทย = "ไทย"
const val Bahasa_Indonesia = "Bahasa Indonesia"
const val Melayu = "Melayu"
const val Tiếng_Việt = "Tiếng Việt"
const val 日本語 = "日本語"
const val Italiano = "Italiano"
const val 한국인 = "한국인"
const val Nederlands = "Nederlands"
const val 繁體中文 = "繁體中文"
const val Türk = "Türk"
}
}
\ No newline at end of file
package com.base.appzxhy.bean
import android.content.Context
import android.content.Intent
import android.widget.RemoteViews
class NotificationSendBean(
val context: Context,
val where: String = "",
val canSend: () -> Boolean = { true },//是否可以发送
var sendSuccess: (() -> Unit)? = null,//发送成功回调
var valueMap: HashMap<String, Any> = hashMapOf()//发送通知可能携带的参数
) {
var actionId: String = ""
var bigRemoteViews: RemoteViews? = null
var smallRemoteViews: RemoteViews? = null
var intent: Intent? = null
var notificationId: Int = 1999
companion object {
const val POPUP_WHERE_LOCK = "Lock"
const val POPUP_WHERE_FCM = "fcm"
const val POPUP_WHERE_ALARM = "Alarm"
const val POPUP_WHERE_BATTERY = "battery"
const val POPUP_WHERE_PACKAGE = "package"
const val POPUP_WHERE_WORK_MANAGER = "workManager"
const val POPUP_WHERE_FILE_JOB = "FileJob"
const val POPUP_WHERE_TIMER = "Timer"
const val ACTION_ID_STAY_SCAN = "action_id_scan_1"
const val ACTION_ID_SCAN = "action_id_scan_2"
}
}
\ No newline at end of file
package com.base.appzxhy.bean.config
class AdConfigBean(
var adSwitch: Boolean = true,//true 走admob,false走max
var numDisplayLimit: Int = -1,
var numRequestLimit: Int = -1,
var numClickLimit: Int = -1,
var timeInterval: Int = 1,
var openAdLoading: Int = 15,
var functionBackShowAd: Boolean = true,
var functionInShowAd: Boolean = true,
){
companion object{
/**
* 广告配置项目
*/
var adsConfigBean: AdConfigBean = AdConfigBean()
}
}
\ No newline at end of file
package com.base.appzxhy.bean.config
/**
* 后台配置
*/
data class ConfigBean(
var isInBlackList: Boolean = false,
val ut: Int = 0,
val adConfigBean: AdConfigBean = AdConfigBean(),
val popupConfigBean: PopupConfigBean = PopupConfigBean(),
) {
companion object {
var configBean: ConfigBean = ConfigBean()
}
}
\ No newline at end of file
package com.base.appzxhy.bean.config
class PopupConfigBean(
var popupForegroundCanPush: Boolean = false,
var popupStatus: Boolean = true,
var popupCount: Int = 24,
var popupStart: Int = 0,
var popupEnd: Int = 24,
var popupInterval: Int = 1,
var popupHoverStatus: Boolean = true,
var popupHoverCount: Int = 5,
var popupHoverDelay: Int = 5000,
//定时器
var timerS: Boolean = true,
var timerDelay: Int = 1,
var timerInterval: Int = 1,
//解锁
var screenS: Boolean = true,
var popupScreenCount: Int = 20,
var popupScreenInterval: Int = 1,
//电量
var batteryS: Boolean = true,
var popupBatteryValue: Int = 20,
var popupBatteryInterval: Int = 1,
//安装卸载
var packageS: Boolean = true,
var popupPackageCount: Int = 20,
var popupPackageInterval: Int = 1
) {
companion object {
var popupConfigBean: PopupConfigBean = PopupConfigBean()
}
}
\ No newline at end of file
package com.base.appzxhy.fcm
import android.content.Context
import android.util.Log
import com.base.appzxhy.helper.EventUtils.event
import com.base.appzxhy.utils.AppPreferences
import com.base.appzxhy.utils.LogEx
import com.google.android.gms.tasks.OnCompleteListener
import com.google.android.gms.tasks.Task
import com.google.firebase.FirebaseApp
import com.google.firebase.messaging.FirebaseMessaging
import org.json.JSONObject
object FCMManager {
fun initFirebase(context: Context?) {
FirebaseApp.initializeApp(context!!)
LogEx.logDebug("initFirebase", "initFirebase")
getToken()
}
fun subscribeToTopic(topic: String) {
Log.e("FCMUtil", "subscribeToTopic")
FirebaseMessaging.getInstance().subscribeToTopic(topic)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
getToken()
Log.e("FCMUtil", "isSuccessful")
event("FCM_Topic_$topic", null, null)
} else {
event("FCM_Error", "subscribeToTopic task.isSuccessful=${task.isSuccessful} ", null)
}
}
}
fun unsubscribeFromTopic(topic: String?) {
FirebaseMessaging.getInstance().unsubscribeFromTopic(topic!!)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
} else {
}
}
}
/**
* 有vpn有google商店才能获取
*/
private fun getToken() {
FirebaseMessaging.getInstance().token
.addOnCompleteListener(object : OnCompleteListener<String> {
override fun onComplete(task: Task<String>) {
LogEx.logDebug("FCM", "onComplete ${task.isSuccessful}")
if (!task.isSuccessful) {
Log.e("FCM", "Fetching FCM registration token failed", task.exception)
return
}
// Get new FCM registration token
val token: String = task.result
LogEx.logDebug("FCM", "token=$token")
val json = JSONObject()
json.put("token", token)
event("fcm_message_received", ext = json)
AppPreferences.getInstance().put("token", token)
// Handle new token
LogEx.logDebug("FCM", "FCM Registration Token: $token")
}
})
}
}
package com.base.appzxhy.fcm
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.base.appzxhy.bean.NotificationSendBean
import com.base.appzxhy.bean.NotificationSendBean.Companion.POPUP_WHERE_FCM
import com.base.appzxhy.fcm.notification.MyNotificationManager
import com.base.appzxhy.helper.EventUtils.event
class FcmReceiver : BroadcastReceiver() {
private val TAG = "FcmReceiver"
override fun onReceive(context: Context, intent: Intent) {
event("FCM_Received", "FcmReceiver", null)
val sendBean = NotificationSendBean(context, POPUP_WHERE_FCM, canSend = { true }, sendSuccess = {})
MyNotificationManager.submitSendBean(sendBean)
}
}
package com.base.appzxhy.fcm
import android.annotation.SuppressLint
import com.base.appzxhy.bean.NotificationSendBean
import com.base.appzxhy.bean.NotificationSendBean.Companion.POPUP_WHERE_FCM
import com.base.appzxhy.fcm.notification.MyNotificationManager
import com.base.appzxhy.helper.EventUtils.event
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import org.json.JSONObject
@SuppressLint("MissingFirebaseInstanceTokenRefresh")
class MessagingService : FirebaseMessagingService() {
override fun onMessageReceived(remoteMessage: RemoteMessage) {
super.onMessageReceived(remoteMessage)
val data = JSONObject(remoteMessage.data.toString())
event("FCM_Received", "MessagingService", data)
val sendBean = NotificationSendBean(this, POPUP_WHERE_FCM, canSend = { true }, sendSuccess = {})
MyNotificationManager.submitSendBean(sendBean)
}
}
\ No newline at end of file
package com.base.appzxhy.fcm.alarm
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.base.appzxhy.bean.NotificationSendBean
import com.base.appzxhy.bean.NotificationSendBean.Companion.POPUP_WHERE_ALARM
import com.base.appzxhy.fcm.notification.MyNotificationManager
import com.base.appzxhy.fcm.work.RepeatingWorker.Companion.schedulePeriodicWork
import com.base.appzxhy.helper.EventUtils
import com.base.appzxhy.utils.LogEx
/**
*
*/
class AlarmReceiver : BroadcastReceiver() {
private val TAG = "AlarmJobReceiver"
@SuppressLint("UnsafeProtectedBroadcastReceiver")
override fun onReceive(context: Context, intent: Intent?) {
LogEx.logDebug(TAG, "AlarmJobReceiver onReceive")
EventUtils.event("alarm_push")
val sendBean = NotificationSendBean(context, POPUP_WHERE_ALARM, canSend = { true }, sendSuccess = {})
MyNotificationManager.submitSendBean(sendBean)
schedulePeriodicWork(context)
return
}
}
\ No newline at end of file
import android.app.AlarmManager
import android.app.PendingIntent
import android.app.job.JobService
import android.content.Context
import android.content.Intent
import com.base.appzxhy.BuildConfig
import com.base.appzxhy.fcm.alarm.AlarmReceiver
import com.base.appzxhy.fcm.work.RepeatingWorker.Companion.schedulePeriodicWork
import java.util.Calendar
object AlarmUtils {
fun startAlarm(context: Context, hour: Int) {
// context.startJob()
//闹钟定时任务
val alarmManager = context.getSystemService(JobService.ALARM_SERVICE) as AlarmManager
val pendingIntent = PendingIntent.getBroadcast(
context, hour, Intent(context, AlarmReceiver::class.java),
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
val calendar = Calendar.getInstance()
calendar.set(Calendar.HOUR_OF_DAY, 6)
calendar.set(Calendar.MINUTE, 0)
calendar.set(Calendar.SECOND, 0)
val oneHour = 60 * 60 * 1000L
var delay = oneHour * hour
if (BuildConfig.DEBUG) {
delay = 1 * 60 * 1000L
}
alarmManager.setRepeating(
AlarmManager.RTC_WAKEUP, calendar.timeInMillis, delay, pendingIntent
)
schedulePeriodicWork(context)
}
}
\ No newline at end of file
package com.base.appzxhy.fcm.notification
import android.content.Context
import com.base.appzxhy.BuildConfig
import com.base.appzxhy.MyApplication
import com.base.appzxhy.bean.NotificationSendBean
import com.base.appzxhy.bean.NotificationSendBean.Companion.ACTION_ID_SCAN
import com.base.appzxhy.bean.NotificationSendBean.Companion.POPUP_WHERE_TIMER
import com.base.appzxhy.bean.config.PopupConfigBean.Companion.popupConfigBean
import com.base.appzxhy.helper.EventUtils
import com.base.appzxhy.utils.AppPreferences
import com.base.appzxhy.utils.KotlinExt.currentDate
import com.base.appzxhy.utils.LogEx
import com.base.appzxhy.utils.ToastUtils.toast
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.async
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import java.util.Calendar
import java.util.concurrent.ArrayBlockingQueue
import java.util.concurrent.atomic.AtomicBoolean
/**
* 发送通知的管理类,方便测试限制条件
*/
object MyNotificationManager {
private val TAG = "NotificationManager"
private var sendBeanBlockingQueue = ArrayBlockingQueue<NotificationSendBean>(10)
fun submitSendBean(bean: NotificationSendBean) {
//测试哪些位置触发
if (BuildConfig.DEBUG) {
if (!testWhere.contains(bean.where)) return
}
if (bean.actionId.isEmpty()) {
val actionId = getNextActionId()
bean.actionId = actionId
}
try {
sendBeanBlockingQueue.put(bean)
} catch (e: Exception) {
EventUtils.event("Notification_Error", "submitSendBean Exception")
LogEx.logDebug("canSendNotification", "submitSendBean Exception")
}
}
private var isStartSendQueue = AtomicBoolean(false)
private var isUnLimit: Boolean = if (BuildConfig.DEBUG) false else false
fun startNotificationQueue() {
if (isStartSendQueue.get()) return
isStartSendQueue.set(true)
MainScope().launch(Dispatchers.IO) {
while (isActive) {
val bean = sendBeanBlockingQueue.take()
//测试哪些位置触发
if (BuildConfig.DEBUG) {
if (!testWhere.contains(bean.where)) continue
}
LogEx.logDebug(TAG, "sendNotificationIfCan where=${bean.where}")
EventUtils.event("Notification_Popup_Start", "where=${bean.where}")
if (!isUnLimit) {
//应用在前台不推
if (MyApplication.PAUSED_VALUE == 1 && !popupConfigBean.popupForegroundCanPush) {
LogEx.logDebug(TAG, "app Foreground")
continue
}
//总的限制条件
if (!canSendNotification(bean.context)) continue
//当条推送是否可以推送
if (!bean.canSend.invoke()) continue
}
async(Dispatchers.Main) {
NotificationUiUtil.setNotification(bean)
}.await()
//上报通知
EventUtils.event("Notification_Popup", "where=${bean.where} actionId=${bean.actionId}")
//当天次数加一
dayPopupCount += 1
//推送时间
lastPopupTime = System.currentTimeMillis()
//这条推送回调
bean.sendSuccess?.invoke()
async(Dispatchers.Main) {
//发送悬停
NotificationHoverUtils.sendHoverNotification(bean)
}.await()
}
isStartSendQueue.set(false)
}
}
//当天推送次数
private var dayPopupCount = 0
get() {
return AppPreferences.getInstance().getInt("dayPopupCount_${currentDate()}", field)
}
set(value) {
field = value
AppPreferences.getInstance().put("dayPopupCount_${currentDate()}", value, true)
}
//上次推送时间
private var lastPopupTime = 0L
get() {
return AppPreferences.getInstance().getLong("lastPopupTime", field)
}
set(value) {
field = value
AppPreferences.getInstance().put("lastPopupTime", value, true)
}
/**
* 总的限制条件
*/
private fun canSendNotification(context: Context): Boolean {
//是否开启推送
if (!popupConfigBean.popupStatus) {
EventUtils.event("Notification_Error", "status=${popupConfigBean.popupStatus}")
LogEx.logDebug("canSendNotification", "status")
if (BuildConfig.DEBUG) {
MainScope().launch(Dispatchers.Main) {
context.toast("配置关闭推送")
}
}
return false
}
//当天推送次数
val count = popupConfigBean.popupCount
if (dayPopupCount > count) {
LogEx.logDebug("canSendNotification", "count")
EventUtils.event("Notification_Error", "dayPopupCount=$dayPopupCount count=$count ")
return false
}
//判断是否在时间区域
val start = popupConfigBean.popupStart
val end = popupConfigBean.popupEnd
val calendar = Calendar.getInstance()
val currentHour = calendar.get(Calendar.HOUR_OF_DAY)
if (currentHour !in start until end) {
LogEx.logDebug("canSendNotification", "start-end currentHour=$currentHour start=$start end=$end")
EventUtils.event("Notification_Error", "start=$start end=$end currentHour=$currentHour")
return false
}
//总时间间隔,单位分钟
val interval = popupConfigBean.popupInterval
val passedTime = System.currentTimeMillis() - lastPopupTime
if (passedTime < interval * 60 * 1000L) {
EventUtils.event("Notification_Error", "interval=$interval passedTime=$passedTime")
LogEx.logDebug("canSendNotification", "interval=$interval passedTime=$passedTime")
return false
}
return true
}
/**
* 值测某些类型
*/
private var testWhere = listOf(
POPUP_WHERE_TIMER
)
private val looper_actionId = listOf(
ACTION_ID_SCAN,
)
private var actionIdList = arrayListOf<String>()
private fun getNextActionId(): String {
if (actionIdList.isEmpty()) {
actionIdList.addAll(looper_actionId)
}
val next = actionIdList[0]
actionIdList.removeAt(0)
if (BuildConfig.DEBUG) {
return ACTION_ID_SCAN
}
return next
}
}
\ No newline at end of file
package com.base.appzxhy.fcm.notification
import android.os.Handler
import android.os.HandlerThread
import com.base.appzxhy.MyApplication
import com.base.appzxhy.bean.NotificationSendBean
import com.base.appzxhy.bean.config.PopupConfigBean.Companion.popupConfigBean
/**
* 悬停
*/
object NotificationHoverUtils {
private val TAG = "NotificationHoverUtils"
private var handlerThread: HandlerThread? = null
private var handler: Handler? = null
/**
* 发送悬停通知
*/
fun sendHoverNotification(sendBean: NotificationSendBean) {
val hoverStatus = popupConfigBean.popupHoverStatus
val hoverCount = popupConfigBean.popupHoverCount
val hoverDelay = popupConfigBean.popupHoverDelay.toLong()
if (!hoverStatus) return
stopNotificationHandler()
if (handlerThread == null) {
handlerThread = HandlerThread("NotificationHandlerThread")
handlerThread?.start()
}
// 创建 Handler
if (handler == null) {
handlerThread?.let {
val looper = it.getLooper() ?: return
handler = Handler(looper)
}
}
for (i in 1..hoverCount) {
val time = i * hoverDelay
handler?.postDelayed(Runnable {
// LogEx.logDebug(TAG, "handler ${MyApplication.PAUSED_VALUE}")
if (MyApplication.PAUSED_VALUE == 1) {
handler?.removeCallbacksAndMessages(null)
} else {
NotificationUiUtil.setNotification(sendBean)
}
}, time)
}
}
private fun stopNotificationHandler() {
// 停止 HandlerThread
if (handler != null) {
handler?.removeCallbacksAndMessages(null)
}
if (handlerThread != null) {
handlerThread?.quit()
handlerThread = null
}
handler = null
}
}
\ No newline at end of file
package com.base.appzxhy.fcm.notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.graphics.drawable.Icon
import android.os.Build
import android.widget.RemoteViews
import androidx.core.app.NotificationCompat
import androidx.core.graphics.drawable.IconCompat
import com.base.appzxhy.R
import com.base.appzxhy.bean.NotificationSendBean
import com.base.appzxhy.ui.start.StartActivity
import kotlin.random.Random
/**
* 发送通知UI部分
*/
object NotificationUiUtil {
private val CHANNEL_ID = "heelShort Channel"
private val CHANNEL_NAME = "heelShort Channel NAME"
fun setNotification(sendBean: NotificationSendBean) {
when (sendBean.actionId) {
}
val intent = Intent(sendBean.context, StartActivity::class.java)
intent.putExtra("actionId", sendBean.actionId)
sendBean.intent = intent
sendCustomNotification(sendBean)
}
private fun sendCustomNotification(
sendBean: NotificationSendBean
) {
val notificationManager = sendBean.context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val context = sendBean.context
//创建channel
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH
)
channel.lockscreenVisibility = NotificationCompat.VISIBILITY_PUBLIC
channel.enableVibration(false)
channel.vibrationPattern = longArrayOf(0)
notificationManager.createNotificationChannel(channel)
}
// Create the notification
val builder: NotificationCompat.Builder = NotificationCompat.Builder(context, CHANNEL_ID)
//设置状态栏内的小图标
val smallIcon = IconCompat.createFromIcon(
context, Icon.createWithResource(
context, R.drawable.log_svg
)
)
smallIcon?.let {
builder.setSmallIcon(smallIcon)
}
builder.setContentTitle(sendBean.context.resources.getString(R.string.app_name))
// .setContentText("notification")
val requestCode = Random.nextInt(1000)
val pendingIntent = PendingIntent.getActivity(context, requestCode, sendBean.intent, PendingIntent.FLAG_IMMUTABLE)
builder.setContentIntent(pendingIntent)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setAutoCancel(true)
var small: RemoteViews? = sendBean.bigRemoteViews
//Android 12以下需要适配小RemoteViews
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
small = sendBean.smallRemoteViews
}
// 设置小视图
sendBean.smallRemoteViews?.let { builder.setCustomContentView(it) }
// 设置悬浮通知视图(Android 12 及以上)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
sendBean.smallRemoteViews.let { builder.setCustomHeadsUpContentView(it) }
}
// 设置大视图
sendBean.bigRemoteViews?.let { builder.setCustomBigContentView(it) }
notificationManager.notify(sendBean.notificationId, builder.build())
}
}
\ No newline at end of file
package com.base.appzxhy.fcm.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import android.os.Build
import com.base.appzxhy.bean.NotificationSendBean
import com.base.appzxhy.bean.NotificationSendBean.Companion.POPUP_WHERE_BATTERY
import com.base.appzxhy.bean.config.PopupConfigBean.Companion.popupConfigBean
import com.base.appzxhy.fcm.notification.MyNotificationManager
import com.base.appzxhy.helper.EventUtils
import com.base.appzxhy.utils.AppPreferences
import com.base.appzxhy.utils.KotlinExt.currentDate
import com.base.appzxhy.utils.LogEx
import kotlin.math.absoluteValue
/**
*电量监听
*/
class BatteryStatusReceiver() : BroadcastReceiver() {
companion object {
private val TAG = "BatteryStatusReceiver"
fun registerBatteryStatusReceiver(context: Context) {
val intentFilter = IntentFilter().apply {
addAction(Intent.ACTION_BATTERY_CHANGED)
addAction(Intent.ACTION_POWER_CONNECTED)
addAction(Intent.ACTION_POWER_DISCONNECTED)
}
val applicationContext = context.applicationContext
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
applicationContext.registerReceiver(
BatteryStatusReceiver(),
intentFilter,
Context.RECEIVER_EXPORTED
)
} else {
applicationContext.registerReceiver(BatteryStatusReceiver(), intentFilter)
}
}
/**
* 当前天推送数量
*/
private var todayBatteryPush = 0
get() {
return AppPreferences.getInstance().getInt("todayBatteryPush_${currentDate()}", field)
}
set(value) {
field = value
AppPreferences.getInstance().put("todayBatteryPush_${currentDate()}", value, true)
}
/**
* 上次成功推送
*/
private var batteryLastPushTime = 0L
get() {
return AppPreferences.getInstance().getLong("batteryLastPushTime", field)
}
set(value) {
field = value
AppPreferences.getInstance().put("batteryLastPushTime", value, true)
}
/**
* 电池电量是否可推送
* 总的限制条件判断后才判断
*/
fun canBatteryStatusReceiverPush(): Boolean {
if (!popupConfigBean.batteryS) return false
val popupBatteryCount = popupConfigBean.popupBatteryValue
val flag1 = todayBatteryPush <= popupBatteryCount
val minute = 60 * 1000L
val interval = popupConfigBean.popupBatteryInterval
val passTime = System.currentTimeMillis() - batteryLastPushTime
val flag2 = batteryLastPushTime == 0L || passTime > interval * minute
val flag = flag1 && flag2
if (!flag) {
EventUtils.event(
"Notification_Error", "todayBatteryPush=$todayBatteryPush " +
"popupBatteryCount=$popupBatteryCount where=$POPUP_WHERE_BATTERY"
)
LogEx.logDebug(
"canSendNotification", "Notification_Error todayBatteryPush=$todayBatteryPush " +
"popupBatteryCount=$popupBatteryCount where=$POPUP_WHERE_BATTERY"
)
}
return flag
}
/**
* 推送成功后保存值
*/
fun saveBatteryPushedData() {
todayBatteryPush += 1
batteryLastPushTime = System.currentTimeMillis()
}
}
private var currentBatteryPercentage = 0f
override fun onReceive(context: Context, intent: Intent?) {
val action = intent?.action
when (action) {
Intent.ACTION_BATTERY_CHANGED -> {
val batteryLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
val batteryScale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
val batteryPercentage = (batteryLevel / batteryScale.toFloat()) * 100
//避免频繁触发
val changeValue = currentBatteryPercentage - batteryPercentage
if (batteryPercentage < popupConfigBean.popupBatteryValue && changeValue.absoluteValue >= 1f) {
//推送次数没有达到限制并且展示的最小时间间隔大于配置时间(分钟)
LogEx.logDebug(TAG, "onReceive changed")
val sendBean = NotificationSendBean(context, POPUP_WHERE_BATTERY,
canSend = {
canBatteryStatusReceiverPush()
},
sendSuccess = {
saveBatteryPushedData()
})
MyNotificationManager.submitSendBean(sendBean)
}
currentBatteryPercentage = batteryPercentage
}
Intent.ACTION_POWER_CONNECTED -> {
val sendBean = NotificationSendBean(context, POPUP_WHERE_BATTERY,
canSend = {
canBatteryStatusReceiverPush()
},
sendSuccess = {
saveBatteryPushedData()
})
MyNotificationManager.submitSendBean(sendBean)
}
}
}
}
\ No newline at end of file
package com.base.appzxhy.fcm.receiver
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.base.appzxhy.bean.NotificationSendBean
import com.base.appzxhy.bean.NotificationSendBean.Companion.POPUP_WHERE_FILE_JOB
import com.base.appzxhy.fcm.notification.MyNotificationManager
class FileJobReceiver : BroadcastReceiver() {
@SuppressLint("UnsafeProtectedBroadcastReceiver")
override fun onReceive(context: Context?, intent: Intent?) {
// context?.startJob()
context?.let {
val sendBean = NotificationSendBean(context, POPUP_WHERE_FILE_JOB, canSend = { true }, sendSuccess = {})
MyNotificationManager.submitSendBean(sendBean)
}
}
}
\ No newline at end of file
package com.base.appzxhy.fcm.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import com.base.appzxhy.bean.NotificationSendBean
import com.base.appzxhy.bean.NotificationSendBean.Companion.POPUP_WHERE_PACKAGE
import com.base.appzxhy.bean.config.PopupConfigBean.Companion.popupConfigBean
import com.base.appzxhy.fcm.notification.MyNotificationManager
import com.base.appzxhy.helper.EventUtils
import com.base.appzxhy.utils.AppPreferences
import com.base.appzxhy.utils.KotlinExt.currentDate
import com.base.appzxhy.utils.LogEx
class PackageStatusReceiver() : BroadcastReceiver() {
companion object {
private val TAG = "PackageStatusReceiver"
fun registerPackageStatusReceiver(context: Context) {
LogEx.logDebug(TAG, "registerPackageStatusReceiver")
val intentFilter = IntentFilter().apply {
addAction(Intent.ACTION_PACKAGE_ADDED)
addAction(Intent.ACTION_PACKAGE_REMOVED)
addDataScheme("package")
}
val applicationContext = context.applicationContext
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
applicationContext.registerReceiver(
PackageStatusReceiver(),
intentFilter,
Context.RECEIVER_EXPORTED
)
} else {
applicationContext.registerReceiver(PackageStatusReceiver(), intentFilter)
}
}
/**
* 当前天推送数量
*/
private var todayPackagePush = 0
get() {
return AppPreferences.getInstance().getInt("todayPackagePush_${currentDate()}", field)
}
set(value) {
field = value
AppPreferences.getInstance().put("todayPackagePush_${currentDate()}", value, true)
}
/**
* 上次成功推送
*/
private var packageLastPushTime = 0L
get() {
return AppPreferences.getInstance().getLong("packageLastPushTime", field)
}
set(value) {
field = value
AppPreferences.getInstance().put("packageLastPushTime", value, true)
}
/**
* 安装卸载是否可推送
* 总的限制条件判断后才判断
*/
fun canPackageStatusReceiverPush(): Boolean {
if (!popupConfigBean.packageS) return false
val popupPackageCount = popupConfigBean.popupPackageCount
val flag1 = todayPackagePush <= popupPackageCount
val minute = 60 * 1000L
val interval = popupConfigBean.popupPackageInterval
val passTime = System.currentTimeMillis() - packageLastPushTime
val flag2 = packageLastPushTime == 0L || passTime > interval * minute
val flag = flag1 && flag2
if (!flag) {
EventUtils.event(
"Notification_Error", "todayPackagePush=$todayPackagePush " +
"popupPackageCount=$popupPackageCount where=$POPUP_WHERE_PACKAGE"
)
LogEx.logDebug(
"canSendNotification", "Notification_Error todayPackagePush=$todayPackagePush " +
"popupPackageCount=$popupPackageCount where=$POPUP_WHERE_PACKAGE"
)
}
return flag
}
/**
* 推送成功后保存值
*/
fun savePackagePushedData() {
todayPackagePush += 1
packageLastPushTime = System.currentTimeMillis()
}
}
override fun onReceive(context: Context, intent: Intent?) {
val action = intent?.action
LogEx.logDebug(TAG, "onReceive action=$action")
if (action == Intent.ACTION_PACKAGE_ADDED || action == Intent.ACTION_PACKAGE_REMOVED) {
val sendBean = NotificationSendBean(context, POPUP_WHERE_PACKAGE, canSend = {
canPackageStatusReceiverPush()
}, sendSuccess = {
savePackagePushedData()
})
MyNotificationManager.submitSendBean(sendBean)
}
}
}
\ No newline at end of file
package com.base.appzxhy.fcm.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import com.base.appzxhy.bean.NotificationSendBean
import com.base.appzxhy.bean.NotificationSendBean.Companion.POPUP_WHERE_LOCK
import com.base.appzxhy.bean.config.PopupConfigBean.Companion.popupConfigBean
import com.base.appzxhy.fcm.notification.MyNotificationManager
import com.base.appzxhy.helper.EventUtils
import com.base.appzxhy.utils.AppPreferences
import com.base.appzxhy.utils.KotlinExt.currentDate
import com.base.appzxhy.utils.LogEx
import java.util.Objects
class ScreenStatusReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val action = intent.action
when (Objects.requireNonNull<String?>(action)) {
Intent.ACTION_SCREEN_ON -> isDeviceInteractive = true
Intent.ACTION_SCREEN_OFF -> {
isDeviceInteractive = false
isSecureLockActive = true
}
Intent.ACTION_USER_PRESENT -> {
isSecureLockActive = false
if (isDeviceInteractive && !isSecureLockActive) {
if (popupConfigBean.screenS) {
val sendBean = NotificationSendBean(
context,
POPUP_WHERE_LOCK,
canSend = { canScreenStatusReceiverPush() },
sendSuccess = { saveScreenPushedData() })
MyNotificationManager.submitSendBean(sendBean)
}
}
}
}
}
companion object {
private val TAG = "ScreenStatusReceiver"
var isDeviceInteractive: Boolean = true
var isSecureLockActive: Boolean = false
fun registerScreenStatusReceiver(context: Context) {
val intentFilter = IntentFilter()
intentFilter.addAction(Intent.ACTION_SCREEN_OFF)
intentFilter.addAction(Intent.ACTION_SCREEN_ON)
intentFilter.addAction(Intent.ACTION_USER_PRESENT)
val applicationContext = context.applicationContext
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
applicationContext.registerReceiver(ScreenStatusReceiver(), intentFilter, Context.RECEIVER_EXPORTED)
} else {
applicationContext.registerReceiver(ScreenStatusReceiver(), intentFilter)
}
}
/**
* 当前天推送数量
*/
private var todayScreenPush = 0
get() {
return AppPreferences.getInstance().getInt("todayScreenPush_${currentDate()}", field)
}
set(value) {
field = value
AppPreferences.getInstance().put("todayScreenPush_${currentDate()}", value, true)
}
/**
* 上次成功推送
*/
private var screenLastPushTime = 0L
get() {
return AppPreferences.getInstance().getLong("screenLastPushTime", field)
}
set(value) {
field = value
AppPreferences.getInstance().put("screenLastPushTime", value, true)
}
/**
* 解锁是否可推送
* 总的限制条件判断后才判断
*/
fun canScreenStatusReceiverPush(): Boolean {
if (!popupConfigBean.popupStatus) return false
val popupScreenCount = popupConfigBean.popupScreenCount
val flag1 = todayScreenPush <= popupScreenCount
val minute = 60 * 1000L
val interval = popupConfigBean.popupScreenInterval
val passTime = System.currentTimeMillis() - screenLastPushTime
val flag2 = screenLastPushTime == 0L || passTime > interval * minute
val flag = flag1 && flag2
if (!flag) {
EventUtils.event(
"Notification_Error", "todayScreenPush=$todayScreenPush " +
"popupScreenCount=$popupScreenCount where=$POPUP_WHERE_LOCK"
)
LogEx.logDebug(
"canSendNotification",
"Notification_Error todayScreenPush=$todayScreenPush " +
"popupScreenCount=$popupScreenCount where=$POPUP_WHERE_LOCK"
)
}
return flag
}
/**
* 推送成功后保存值
*/
fun saveScreenPushedData() {
todayScreenPush += 1
screenLastPushTime = System.currentTimeMillis()
}
}
}
\ No newline at end of file
package com.base.appzxhy.fcm.timer
import com.base.appzxhy.MyApplication
import com.base.appzxhy.bean.NotificationSendBean
import com.base.appzxhy.bean.NotificationSendBean.Companion.POPUP_WHERE_TIMER
import com.base.appzxhy.bean.config.PopupConfigBean.Companion.popupConfigBean
import com.base.appzxhy.fcm.notification.MyNotificationManager
import com.base.appzxhy.fcm.receiver.ScreenStatusReceiver
import com.base.appzxhy.utils.LogEx.logDebug
import java.util.Timer
import java.util.TimerTask
class TimerManager private constructor() {
private val TAG = "TimerManager"
private var taskTimer: Timer? = null
private var isTaskTimerActive: Boolean = false
fun scheduleTask(delay: Long, period: Long) {
logDebug(TAG, "scheduleTask")
synchronized(TimerManager::class.java) {
ensureTimerIsStopped() // 确保定时器未运行
taskTimer = Timer() // 创建新的 Timer 实例
val task: TimerTask = object : TimerTask() {
override fun run() {
logDebug(TAG, "scheduleTask run")
// 确保设备处于交互状态,未锁定,且应用未暂停
if (ScreenStatusReceiver.isDeviceInteractive
&& !ScreenStatusReceiver.isSecureLockActive
&& MyApplication.PAUSED_VALUE != 1
) {
logDebug(TAG, "scheduleTask send")
val sendBean =
NotificationSendBean(MyApplication.appContext, POPUP_WHERE_TIMER, canSend = { true }, sendSuccess = {})
MyNotificationManager.submitSendBean(sendBean)
}
}
}
taskTimer?.schedule(task, delay, period) // 调度任务
isTaskTimerActive = true // 设置定时器状态为活跃
}
}
private fun ensureTimerIsStopped() {
if (isTaskTimerActive) {
if (taskTimer != null) {
taskTimer?.cancel()
taskTimer?.purge() // 清除所有取消的任务
}
isTaskTimerActive = false // 重置定时器状态
}
}
fun stopTaskTimer() {
synchronized(TimerManager::class.java) {
ensureTimerIsStopped() // 停止定时器
}
}
companion object {
private val instance: TimerManager by lazy((LazyThreadSafetyMode.SYNCHRONIZED)) { TimerManager() }
fun changeTimer() {
if (!popupConfigBean.timerS) {
instance.stopTaskTimer()
} else {
val timerDelay: Int = popupConfigBean.timerDelay
val timerInterval: Int = popupConfigBean.timerInterval
val isTaskTimerActive = instance.isTaskTimerActive
if (!isTaskTimerActive) {
instance.scheduleTask(
(timerDelay * 60 * 1000).toLong(),
(timerInterval * 60 * 1000).toLong()
)
}
}
}
}
}
\ No newline at end of file
package com.base.appzxhy.fcm.work
import android.content.Context
import androidx.work.Constraints
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.NetworkType
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.base.appzxhy.bean.NotificationSendBean
import com.base.appzxhy.bean.NotificationSendBean.Companion.POPUP_WHERE_WORK_MANAGER
import com.base.appzxhy.fcm.notification.MyNotificationManager
import com.base.appzxhy.helper.EventUtils
import java.util.concurrent.TimeUnit
class RepeatingWorker(val appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) {
override fun doWork(): Result {
// 这里执行你的任务
// 例如,更新UI,发送网络请求等
EventUtils.event("workManager_live")
try {
val sendBean = NotificationSendBean(appContext, POPUP_WHERE_WORK_MANAGER, canSend = { true }, sendSuccess = {})
MyNotificationManager.submitSendBean(sendBean)
} catch (e: Exception) {
EventUtils.event("WorkManager Error")
}
return Result.success()
}
companion object {
const val TAG = "uniqueWorkName"
fun schedulePeriodicWork(context: Context) {
WorkManager.getInstance(context).cancelAllWorkByTag(TAG)
val request = PeriodicWorkRequestBuilder<RepeatingWorker>(15, TimeUnit.MINUTES)
.setConstraints(
Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()
)
.build()
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
TAG, ExistingPeriodicWorkPolicy.KEEP, request
)
}
}
}
\ No newline at end of file
package com.base.appzxhy.helper
import android.util.Base64
import com.base.appzxhy.GlobalConfig
import java.security.SecureRandom
import javax.crypto.Cipher
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.SecretKeySpec
object AESHelper {
private const val aesKey = GlobalConfig.KEY_AES
private val cipher by lazy {
Cipher.getInstance("AES/GCM/NoPadding")
}
fun encrypt(content: String): String {
try {
val iv = ByteArray(12).apply {
SecureRandom().nextBytes(this)
}
val contentBytes = content.toByteArray(Charsets.UTF_8)
val params = GCMParameterSpec(128, iv)
cipher.init(
Cipher.ENCRYPT_MODE,
secretKey, params
)
val encryptData = cipher.doFinal(contentBytes)
if (encryptData.size != contentBytes.size + 16) {
throw IllegalStateException("Encryption failed")
}
val message = ByteArray(12 + contentBytes.size + 16)
System.arraycopy(iv, 0, message, 0, 12)
System.arraycopy(encryptData, 0, message, 12, encryptData.size)
return String(Base64.encode(message, Base64.NO_WRAP), Charsets.UTF_8)
} catch (_: Exception) {
}
return content
}
@Synchronized
fun decrypt(content: String): String {
try {
val con = content.replace(" ".toRegex(), "+")
val contentByte = Base64.decode(con, Base64.NO_WRAP)
require(contentByte.size >= 12 + 16)
val params = GCMParameterSpec(128, contentByte, 0, 12)
cipher.init(
Cipher.DECRYPT_MODE,
secretKey, params
)
val decryptData = cipher.doFinal(contentByte, 12, contentByte.size - 12)
return String(decryptData, Charsets.UTF_8)
} catch (_: Exception) {
}
return content
}
private val secretKey by lazy {
SecretKeySpec(aesKey.toByteArray(), "AES")
}
}
\ No newline at end of file
package com.base.appzxhy.helper
import android.os.Build
import com.base.appzxhy.BuildConfig
import com.base.appzxhy.GlobalConfig
import com.base.appzxhy.helper.ReportUtils.doPost
import com.base.appzxhy.utils.AppPreferences
import com.base.appzxhy.utils.LogEx
import org.json.JSONException
import org.json.JSONObject
import java.util.TimeZone
object EventUtils {
private val TAG = "EventUtils"
var ifAgreePrivacy = true
get() {
return AppPreferences.getInstance().getBoolean("ifAgreePrivacy", field)
}
set(value) {
field = value
AppPreferences.getInstance().put("ifAgreePrivacy", value, true)
}
fun event(
key: String,
value: String? = null,
ext: JSONObject? = null,
) {
if (!ifAgreePrivacy) {
return
}
Thread {
var paramJson: String? = ""
try {
val pkg = GlobalConfig.PACKAGE_NAME
val s = JSONObject()
.put("action", key)
.put("value", value)
.put("ext", ext)
val s2 = JSONObject()
.put("${pkg}_3", AppPreferences.getInstance().getString("Equipment", ""))
.put("${pkg}_4", AppPreferences.getInstance().getString("Manufacturer", ""))
.put("${pkg}_5", Build.VERSION.SDK_INT)
.put("${pkg}_9", AppPreferences.getInstance().getString("uuid", ""))
.put("${pkg}_10", AppPreferences.getInstance().getString("gid", ""))
.put("${pkg}_13", "android")
.put("${pkg}_15", "google")
.put("${pkg}_14", BuildConfig.VERSION_CODE)
.put("${pkg}_8", BuildConfig.VERSION_NAME)
.put("${pkg}_24", BuildConfig.BUILD_TYPE)
.put("${pkg}_34", TimeZone.getDefault().getID())
val data = JSONObject()
.put("data", s)
.put("bp", s2)
.toString()
LogEx.logDebug(TAG, "uuid=${AppPreferences.getInstance().getString("uuid", "")}")
LogEx.logDebug(TAG, "gid=${AppPreferences.getInstance().getString("gid", "")}")
paramJson = AESHelper.encrypt(data)
} catch (e: JSONException) {
paramJson = ""
}
LogEx.logDebug(TAG, "url=$url")
doPost(
url,
HashMap(),
paramJson
)
}.start()
}
private val url by lazy {
val pkg = GlobalConfig.PACKAGE_NAME
val url = StringBuilder(
"${GlobalConfig.URL_EVENT}/${
pkg.filter { it.isLowerCase() }.substring(4, 9)
}sp"
)
url.append("?pkg=$pkg")
url.toString()
}
}
\ No newline at end of file
package com.base.appzxhy.helper
import com.android.installreferrer.api.InstallReferrerClient
import com.android.installreferrer.api.InstallReferrerStateListener
import com.base.appzxhy.BuildConfig
import com.base.appzxhy.MyApplication
import com.base.appzxhy.utils.AppPreferences
import com.base.appzxhy.utils.LogEx
import org.json.JSONObject
/**
* call before agree
*/
object InstallHelps {
private val TAG = "InstallHelps"
fun init(requestCfg: () -> Unit) {
val referrerClient = InstallReferrerClient.newBuilder(MyApplication.appContext).build()
referrerClient.startConnection(object : InstallReferrerStateListener {
override fun onInstallReferrerSetupFinished(responseCode: Int) {
try {
when (responseCode) {
InstallReferrerClient.InstallReferrerResponse.OK -> {
val response = referrerClient.installReferrer
val installInfo = response.installReferrer
val obj = JSONObject()
obj.put("referrerUrl", response.installReferrer)
obj.put("referrerClickTime", response.referrerClickTimestampSeconds)
obj.put("appInstallTime", response.installBeginTimestampSeconds)
obj.put("instantExperienceLaunched", installInfo.toString())
EventUtils.event("install_referrer", ext = obj)
LogEx.logDebug(TAG, "referrerUrl=${response.installReferrer}")
AppPreferences.getInstance().put("install_referrer", response.installReferrer)
if (listOf(
"gclid",
"facebook",
"instagram"
).all { !installInfo.contains(it, true) }
) {
//自然用户
if (BuildConfig.DEBUG) {
AppPreferences.getInstance().put("install_source", "channel")
} else {
AppPreferences.getInstance().put("install_source", "origin")
}
} else {
//渠道用户
AppPreferences.getInstance().put("install_source", "channel")
}
requestCfg()
}
else -> {
EventUtils.event("install_referrer_error")
requestCfg()
}
}
} catch (_: Exception) {
EventUtils.event("install_referrer_error")
requestCfg()
}
}
override fun onInstallReferrerServiceDisconnected() {
}
})
}
}
\ No newline at end of file
package com.base.appzxhy.helper
import android.util.Log
import com.base.appzxhy.BuildConfig
import com.base.appzxhy.GlobalConfig
import com.base.appzxhy.utils.AppPreferences
import com.base.appzxhy.utils.LogEx
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.BufferedReader
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL
import java.util.Locale
object NewComUtils {
private val TAG = "NewComUtils"
private const val API_URL = GlobalConfig.URL_API
private const val PACKAGE_NAME_PREFIX = GlobalConfig.PACKAGE_NAME
private const val DATA_KEY = "data"
private val url: String by lazy {
val packageName = GlobalConfig.PACKAGE_NAME
val appCode = packageName.substringAfter(PACKAGE_NAME_PREFIX).take(5).toLowerCase(Locale.getDefault())
val bRefer = android.util.Base64.encodeToString(
AppPreferences.getInstance().getString("install_referrer", "").toByteArray(),
android.util.Base64.DEFAULT
)
var s = "$API_URL/${appCode}spk?pkg=$packageName" +
"&referrer=${bRefer}" +
"&vn=${BuildConfig.VERSION_NAME}" +
"&vc=${BuildConfig.VERSION_CODE}" +
"&device=${AppPreferences.getInstance().getString("gid", "")}" +
"&aid=${AppPreferences.getInstance().getString("uuid", "")}"
if (BuildConfig.DEBUG) {
s = "$s&mode=3"
}
s
// mode =3 google mode=2 facebook mode=1 自然,mode=4 测试
// &mode=3
}
fun requestCfg(callback: (json: String?) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
val response = doGet()
if (response == null) {
withContext(Dispatchers.Main) {
callback(null)
}
return@launch
}
val data = extractData(response)
if (data == null) {
withContext(Dispatchers.Main) {
callback(null)
}
return@launch
}
val decryptedData = AESHelper.decrypt(data)
withContext(Dispatchers.Main) {
callback(decryptedData)
}
}
}
private fun doGet(): String? {
val urlPath = url
LogEx.logDebug(TAG, "url=$url")
try {
val conn: HttpURLConnection = URL(urlPath).openConnection() as HttpURLConnection
conn.setRequestMethod("GET")
conn.connectTimeout = 150000
if (200 == conn.getResponseCode()) {
val json = BufferedReader(InputStreamReader(conn.inputStream)).readLine()
LogEx.logDebug(TAG, "json=$json")
return json
}
} catch (e: Exception) {
e.printStackTrace()
Log.d("okhttp", e.toString())
}
return null
}
private fun extractData(response: String): String? {
val regex = Regex("\"$DATA_KEY\":\"(.*?)\"")
val match = regex.find(response)
return match?.groupValues?.get(1)
}
}
package com.base.appzxhy.helper;
import android.text.TextUtils;
import com.base.appzxhy.utils.LogEx;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;
public class ReportUtils {
private static String TAG = "ReportUtils";
public static String doPost(String urlPath, Map<String, String> paramsMap, String json) {
try {
HttpURLConnection conn = (HttpURLConnection) new URL(urlPath).openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setConnectTimeout(5000);
if (!TextUtils.isEmpty(json)) {
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("Content-Length", Integer.toString(json.getBytes().length));
conn.getOutputStream().write(json.getBytes());
}
StringBuilder result = new StringBuilder();
for (Map.Entry<String, String> entry : paramsMap.entrySet()) {
result.append("&").append(entry.getKey()).append("=").append(entry.getValue());
}
if (result.length() > 0) {
try {
conn.getOutputStream().write(result.substring(1).getBytes());
} catch (Exception e) {
}
} else {
// conn.getOutputStream().write(result.substring(0).getBytes());
}
if (conn.getResponseCode() == 200) {
String s = new BufferedReader(new InputStreamReader(conn.getInputStream())).readLine();
if (!TextUtils.isEmpty(s)) {
} else {
s = "";
}
LogEx.INSTANCE.logDebug(TAG, "code=200", false);
return s;
} else {
LogEx.INSTANCE.logDebug(TAG, "code!=200", false);
}
} catch (Exception e) {
e.printStackTrace();
}
return "{ \"success\": false,\n \"errorMsg\": \"后台服务器开小差了!\",\n \"result\":{}}";
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment