Commit b8543ad4 authored by Your Name's avatar Your Name

正式包

parent 03e43d2c
......@@ -11,10 +11,10 @@ android {
defaultConfig {
applicationId = "com.tool.advanced.cleaner"
applicationId = "com.swiftcleaner.chovey"
minSdk = 28
targetSdk = 34
versionCode = 1
versionCode = 9
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
......@@ -22,7 +22,7 @@ android {
// 添加签名配置
signingConfigs {
create("release") {
storeFile = file("../my-key.keystore")
storeFile = file("../my-release-key.keystore")
storePassword = "123456"
keyAlias = "my_alias"
keyPassword = "123456"
......@@ -32,6 +32,10 @@ android {
release {
isMinifyEnabled = false
signingConfig = signingConfigs.getByName("release")
// 设置是否要自动上传
firebaseCrashlytics {
mappingFileUploadEnabled = true
}
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
......@@ -54,53 +58,50 @@ android {
}
}
gradle.taskGraph.whenReady {
tasks.forEach { task ->
if (task.name.contains("uploadCrashlyticsMappingFile")) {
task.enabled = false
}
}
}
dependencies {
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.8.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation(libs.core.ktx)
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
//底部弹窗
api("com.google.android.material:material:1.4.0")
api("com.github.bumptech.glide:glide:4.15.1")
api("com.geyifeng.immersionbar:immersionbar:3.2.2")
api("com.airbnb.android:lottie:5.2.0")
implementation("com.google.firebase:firebase-messaging:23.2.1")
//solar 归因
implementation("com.reyun.solar.engine.oversea:solar-engine-core:1.2.8.3")
implementation("com.squareup.okhttp3:okhttp:4.12.0")
//admob渠道
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.material)
implementation(libs.androidx.constraintlayout)
implementation(libs.gson)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.glide)
annotationProcessor(libs.glide.compiler)
implementation(libs.lottie)
implementation(libs.facebook.android.sdk)
implementation(libs.user.messaging.platform)
implementation(libs.expandablerecyclerview)
implementation(platform(libs.firebase.bom))
implementation(libs.firebase.analytics.ktx)
implementation(libs.firebase.messaging.ktx)
implementation(libs.firebase.messaging.directboot)
implementation(libs.firebase.crashlytics)
implementation(libs.androidx.lifecycle.process)
implementation(libs.okhttp)
implementation(libs.logging.interceptor)
implementation(libs.solar.engine)
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
//firebase
implementation(platform("com.google.firebase:firebase-bom:32.3.1"))
implementation("com.google.firebase:firebase-messaging")
implementation("com.google.firebase:firebase-analytics-ktx")
implementation("com.google.firebase:firebase-crashlytics")
implementation("com.google.firebase:firebase-config")
implementation("com.google.firebase:firebase-messaging-directboot")
//facebook
implementation("com.facebook.android:facebook-android-sdk:[8,9)")
implementation(libs.applovin.facebook)
implementation(libs.applovin.mintegral)
implementation(libs.applovin.pangle)
implementation(libs.applovin.vungle)
implementation(libs.androidx.work.runtime.ktx)
api("com.geyifeng.immersionbar:immersionbar:3.2.2")
implementation ("io.reactivex.rxjava3:rxjava:3.1.5")
implementation ("io.reactivex.rxjava3:rxandroid:3.0.0")
}
\ No newline at end of file
{
"project_info": {
"project_number": "755421476297",
"project_id": "comtooladvancedcleaner",
"storage_bucket": "comtooladvancedcleaner.firebasestorage.app"
"project_number": "847944465890",
"project_id": "swift-cleaner-phone-helper",
"storage_bucket": "swift-cleaner-phone-helper.firebasestorage.app"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:755421476297:android:c66d8129d83b17e8af6418",
"mobilesdk_app_id": "1:847944465890:android:e1c07e767666a42c4ccdbb",
"android_client_info": {
"package_name": "com.tool.advanced.cleaner"
"package_name": "com.swiftcleaner.chovey"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyAhCB4trMZPymkWCNl_m-bFGPpASG6bsQI"
"current_key": "AIzaSyAwWyLHdMr4RaScAPts91N_VM_1lKjBMr8"
}
],
"services": {
......
......@@ -7,15 +7,17 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
<uses-permission android:name="android.permission.READ_NOTIFICATIONS" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<!-- fcm 添加以下权限 -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<!--三星和 LG 厂商通知角标权限-->
<uses-permission android:name="com.sec.android.provider.badge.permission.READ" />
<uses-permission android:name="com.sec.android.provider.badge.permission.WRITE" />
<queries>
<intent>
......@@ -27,7 +29,8 @@
</queries>
<application
android:name="com.zxdemo.ZxApplication"
android:name=".CleanApplication"
android:requestLegacyExternalStorage="true"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
......@@ -109,23 +112,32 @@
<meta-data
android:name="android.max_aspect"
android:value="2.4" />
<receiver android:name=".business.push.NotificationCancelReceiver" />
<service
android:name="com.zxdemo.service.PermanentNotifyService"
android:foregroundServiceType="dataSync" />
<receiver
android:enabled="true"
android:name=".business.push.AlarmReceiver"
android:exported="true"
android:permission="android.permission.RECEIVE_BOOT_COMPLETED" />
<receiver
android:enabled="true"
android:name=".business.push.Alarm2Receiver"
android:exported="true"
android:permission="android.permission.RECEIVE_BOOT_COMPLETED" />
<service
android:name="com.zxdemo.service.FcmService"
android:name=".business.push.FirebaseService"
android:directBootAware="true"
android:exported="false">
android:exported="false"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<receiver
android:name="com.zxdemo.receiver.FcmReceiver"
android:name=".business.push.FirebaseReceiver"
android:directBootAware="true"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
......@@ -133,14 +145,52 @@
<action android:name="com.google.firebase.MESSAGING_EVENT" />
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<category android:name="com.tool.advanced.cleaner" />
<category android:name="com.tool.elite.cleaner" />
</intent-filter>
</receiver>
<service
android:name=".business.push.NotificationJobService"
android:exported="true"
android:foregroundServiceType="dataSync"
android:permission="android.permission.BIND_JOB_SERVICE" />
<receiver
android:enabled="true"
android:name=".business.push.NotificationJobReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.USER_PRESENT" />
<action android:name="android.intent.action.TIME_SET" />
<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>
<!-- 广告的appId每次打包正式需要改 -->
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-3940256099942544~3347511713" />
android:value="ca-app-pub-9723053978060994~5799767410" />
<meta-data
android:name="com.facebook.sdk.ApplicationId"
android:value="@string/facebook_app_id" />
......
package com.swiftcleaner.chovey
import android.app.Activity
import android.app.AlarmManager
import android.app.Application
import android.app.PendingIntent
import android.app.job.JobService
import android.content.Intent
import android.os.Bundle
import android.util.Log
import androidx.work.Constraints
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.NetworkType
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import com.reyun.solar.engine.OnAttributionListener
import com.reyun.solar.engine.SolarEngineConfig
import com.reyun.solar.engine.SolarEngineManager
import com.swiftcleaner.chovey.business.push.NotificationJobService.Companion.startNotification
import com.tool.elite.battery.business.push.BadgeUtils
import com.swiftcleaner.chovey.GlobalConfig.KEY_APP_BACKGROUND
import com.swiftcleaner.chovey.GlobalConfig.KEY_INIT
import com.swiftcleaner.chovey.GlobalConfig.KEY_INSTALL_TIME
import com.swiftcleaner.chovey.GlobalConfig.KEY_SOLAR
import com.swiftcleaner.chovey.GlobalConfig.KEY_UUID
import com.swiftcleaner.chovey.business.BlacklistUtils
import com.swiftcleaner.chovey.business.DeviceUtils
import com.swiftcleaner.chovey.business.push.FirebaseUtils
import com.swiftcleaner.chovey.business.InstallReferrerUtils
import com.swiftcleaner.chovey.business.EventUtils
import com.swiftcleaner.chovey.business.admob.AdmobManager
import com.swiftcleaner.chovey.business.push.Alarm2Receiver
import com.swiftcleaner.chovey.business.push.AlarmReceiver
import com.swiftcleaner.chovey.business.push.BatteryReceiver
import com.swiftcleaner.chovey.business.push.MyWorker
import com.swiftcleaner.chovey.business.push.PackageReceiver
import com.swiftcleaner.chovey.business.push.UnlockReceiver
import com.swiftcleaner.chovey.util.SPUtils
import com.swiftcleaner.chovey.view.activity.StartActivity
import com.tool.elite.cleaner.utils.ActivityManagerUtils
import org.json.JSONObject
import java.util.Calendar
import java.util.UUID
import java.util.concurrent.TimeUnit
class CleanApplication : Application() {
companion object {
lateinit var context: CleanApplication
var APP_STATE = 0
fun init() {
context.init()
}
fun startNotification() {
context.startNotification()
}
}
override fun onCreate() {
super.onCreate()
context = this
DeviceUtils.init()
registerActivityLifecycle()
if (SPUtils.getInstance().getString(KEY_UUID).isEmpty())
SPUtils.getInstance().putString(KEY_UUID, UUID.randomUUID().toString() + System.currentTimeMillis())
if (SPUtils.getInstance().getLong(KEY_INSTALL_TIME) == 0L)
SPUtils.getInstance().putLong(KEY_INSTALL_TIME, System.currentTimeMillis())
if (SPUtils.getInstance().getBoolean(KEY_INIT)) init()
}
private fun init() {
startNotification()
InstallReferrerUtils.init()
BlacklistUtils.requestBlacklist { AdmobManager.blacklist = it }
initSolar()
initFirebase()
initAlarm(1, 1800000, AlarmReceiver::class.java)
initAlarm(2, 1800000 * 2 * 48, Alarm2Receiver::class.java)
initAlarm(3, 1800000 * 2 * 72, Alarm2Receiver::class.java)
initWorkManager()
// BatteryReceiver.register(this)
PackageReceiver.register(this)
UnlockReceiver.register(this)
}
private fun initSolar() {
SolarEngineManager.getInstance().preInit(this, KEY_SOLAR)
val config = SolarEngineConfig.Builder().build()
SolarEngineManager.getInstance().initialize(this, KEY_SOLAR, config) { code ->
if (code == 0) {
config.setOnAttributionListener(object : OnAttributionListener {
override fun onAttributionSuccess(p0: JSONObject?) {
Log.d("TAG", "onAttributionSuccess: 获取归因结果成功 $p0 ")
EventUtils.event("source_atrribute", ext = p0)
}
override fun onAttributionFail(p0: Int) {
Log.d("TAG", "onAttributionSuccess: 获取归因结果失败 $p0 ")
EventUtils.event("source_atrribute_error")
}
})
}
}
}
private fun initFirebase() {
FirebaseUtils.initFirebase(this)
val topic = "${packageName}_push"
FirebaseUtils.subscribeToTopic(topic) {
EventUtils.event("FCM_Topic_${topic}")
}
FirebaseUtils.getToken {
val json = JSONObject()
json.put("token", it)
EventUtils.event("fcm_message_received", ext = json)
SPUtils.getInstance().putString("token", it)
}
}
private fun initAlarm(code: Int, intervalMillis: Long, cls: Class<*>) {
val alarmManager = getSystemService(JobService.ALARM_SERVICE) as AlarmManager
val pendingIntent = PendingIntent.getBroadcast(
this, code,
Intent(this, cls),
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)
alarmManager.setRepeating(
AlarmManager.RTC_WAKEUP,
calendar.timeInMillis,
intervalMillis,
pendingIntent
)
}
private fun initWorkManager() {
val tag = "${packageName}_unique_periodic_work"
val periodicRequest = PeriodicWorkRequestBuilder<MyWorker>(15, TimeUnit.MINUTES)
.setConstraints(
Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()
)
.addTag(tag)
.build()
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
tag,
ExistingPeriodicWorkPolicy.KEEP,
periodicRequest)
}
private var lastTimePause = 0L
private var lastTimeResume = 0L
private fun isHotLaunch(): Boolean {
if ((lastTimeResume - lastTimePause) > 5000) {
return true
}
return false
}
private fun registerActivityLifecycle() {
registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
private var count = 0
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
ActivityManagerUtils.add(activity)
}
override fun onActivityStarted(activity: Activity) {
count++
lastTimeResume = System.currentTimeMillis()
if (count == 1 && isHotLaunch()) {
val topActivity = ActivityManagerUtils.topActivity
val flag = if (topActivity == null) {
true
} else {
GlobalConfig.noLoadingActivities.all {
!topActivity.localClassName.contains(
it,
true
)
}
}
if (flag) {
topActivity?.startActivity(
Intent(topActivity, StartActivity::class.java).apply {
putExtra(KEY_APP_BACKGROUND, 1)
}
)
}
}
lastTimeResume = 0
}
override fun onActivityResumed(activity: Activity) {
APP_STATE = 1
}
override fun onActivityPaused(activity: Activity) {
APP_STATE = 2
lastTimePause = System.currentTimeMillis()
}
override fun onActivityStopped(activity: Activity) {
count--
if (count == 0) {
BadgeUtils.setBadgeNum(context, 1)
}
}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
}
override fun onActivityDestroyed(activity: Activity) {
ActivityManagerUtils.remove(activity)
}
})
}
}
\ No newline at end of file
package com.swiftcleaner.chovey
import com.swiftcleaner.chovey.util.SPUtils
import com.swiftcleaner.chovey.view.activity.StartActivity
object GlobalConfig {
// 包名
const val PACKAGE_NAME = "com.swiftcleaner.chovey"
// 域名
/**
* Url Event 上报接口
*/
const val URL_EVENT = "https://rp.sifatkhan95840.xyz"
/**
* Url Api 业务接口
*/
const val URL_API = "https://api.sifatkhan95840.xyz"
/**
* Url Privacy 隐私链接
*/
const val URL_PRIVACY = "https://sites.google.com/view/elite-cleaner/home"
/**
* Url Use 使用条款
*/
const val URL_USE = ""
/**
* Key Aes 加密key
*/
const val KEY_AES = "j2xlm90mibmbpekw"
/**
* Key solar 归因key
*/
const val KEY_SOLAR = "17697786c4fcd7e9"
// admob广告位id
inline val ID_ADMOB_OPEN get() = "ca-app-pub-9723053978060994/2762540539"
inline val ID_ADMOB_INTER get() = "ca-app-pub-9723053978060994/3372651292"
inline val ID_ADMOB_NATIVE get() = "ca-app-pub-9723053978060994/4075622203"
inline val ID_ADMOB_BANNER get() = "ca-app-pub-9723053978060994/8272758212"
inline val ID_ADMOB_REWARD get() = ""
// max广告位id
inline val ID_MAX_OPEN get() = "d1d943cdd3127c90"
inline val ID_MAX_INTER get() = "b31e7f6d11ee659e"
inline val ID_MAX_NATIVE get() = "96e8fe78b0efc5d1"
inline val ID_MAX_BANNER get() = "ca-app-pub-3940256099942544/9214589741"
inline val ID_MAX_REWARD get() = ""
/**
* Key MAX MAX广告初始化key
*/
const val KEY_MAX =
"GGPreND6SRmCt1zJgn5faiLGD8c2PVGPLgPpSg7cHanVTud1DhtuI9MmteTqlEviaJ57WnxW68kQDaATJ5z3cW"
//
const val KEY_INIT = "key_init"
const val KEY_CLEANUP_SIZE = "key_cleanup_size"
const val KEY_SCORE = "key_score"
const val KEY_APP_BACKGROUND = "key_app_background"
const val KEY_NOTIFICATION_ID = "key_notification_id"
const val KEY_HOME_COUNT = "key_home_count"
const val KEY_INSTALL_TIME = "key_install_time"
const val KEY_CLEAN_LAST_TIME = "key_clean_last_time"
const val KEY_NOT_CLEAN_TIME = "key_not_clean_time"
const val KEY_UUID = "key_uuid"
const val KEY_GOOGLE_ADVERTISER_ID = "key_google_advertiser_id"
const val KEY_DEVICE_NAME = "key_device_name"
const val KEY_REFERRER = "referrer"
const val ID_CLEAN_JUNK = 12000
const val ID_WHATSAPP = 12010
const val ID_APPMANAGER = 12010
const val ID_BATTERY_INFO = 12020
const val ID_LARGE_FILE_CLEANER = 12030
const val ID_SIMILAR_PHOTOS = 12040
const val ID_SCREENSHOT_CLEAN = 12050
const val ID_APP_INSTALL = 12060
const val ID_APP_UNINSTALL = 12070
const val ID_NOT_CLEANED = 12080
const val ID_BATTERY_LEVEL = 12090
const val ID_APP_PROCESS = 12100
val noLoadingActivities = listOf(
"full", // 过滤全屏广告
"adActivity",
"AppLovinFullscreenActivity",
StartActivity::class.java.simpleName,
// 返回前台时不跳转启动页的 activity
)
inline var isInit
get() = SPUtils.getInstance().getBoolean(KEY_INIT)
set(value) = SPUtils.getInstance().putBoolean(KEY_INIT, value)
}
\ No newline at end of file
package com.swiftcleaner.chovey.business
import android.util.Base64
import com.swiftcleaner.chovey.GlobalConfig.KEY_AES
import java.nio.charset.Charset
import java.security.SecureRandom
import javax.crypto.Cipher
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.SecretKeySpec
object AESUtils {
private val AES_CIPHER_TRANSFORMATION = "AES/GCM/NoPadding" // 明确指定加密算法
// 使用单例模式初始化Cipher,避免每次加密解密都重新创建
private val cipher: Cipher by lazy {
Cipher.getInstance(AES_CIPHER_TRANSFORMATION)
}
// 使用单例模式初始化SecretKeySpec,避免每次加密解密都重新创建
private val secretKey: SecretKeySpec by lazy {
SecretKeySpec(KEY_AES.toByteArray(Charset.defaultCharset()), "AES")
}
fun encrypt(content: String): String {
try {
// 初始化向量IV,确保每次加密都是随机的
val iv = generateIv()
// 初始化GCM参数规范
val gcmParameterSpec = GCMParameterSpec(128, iv)
// 初始化Cipher为加密模式
cipher.init(Cipher.ENCRYPT_MODE, secretKey, gcmParameterSpec)
// 加密内容
val encryptedBytes = cipher.doFinal(content.toByteArray(Charset.defaultCharset()))
// 将IV和加密数据合并,并进行Base64编码
val message = iv + encryptedBytes
return Base64.encodeToString(message, Base64.NO_WRAP)
} catch (_: Exception) {
return content
}
}
fun decrypt(content: String): String {
try {
// 将Base64编码的字符串解码为字节数组
val contentBytes = Base64.decode(content, Base64.NO_WRAP)
// 提取IV
val iv = contentBytes.copyOfRange(0, 12)
// 提取加密数据
val encryptedBytes = contentBytes.copyOfRange(12, contentBytes.size)
// 初始化GCM参数规范
val gcmParameterSpec = GCMParameterSpec(128, iv)
// 初始化Cipher为解密模式
cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmParameterSpec)
// 解密内容
val decryptedBytes = cipher.doFinal(encryptedBytes)
// 将解密后的字节数组转换为字符串
return String(decryptedBytes, Charset.defaultCharset())
} catch (_: Exception) {
return content
}
}
// 生成随机的初始化向量IV
private fun generateIv(): ByteArray {
val iv = ByteArray(12)
SecureRandom().nextBytes(iv)
return iv
}
}
\ No newline at end of file
package com.swiftcleaner.chovey.business
import com.swiftcleaner.chovey.BuildConfig
import com.swiftcleaner.chovey.GlobalConfig.PACKAGE_NAME
import com.swiftcleaner.chovey.GlobalConfig.URL_API
import okhttp3.Call
import okhttp3.Callback
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import okhttp3.logging.HttpLoggingInterceptor
import org.json.JSONObject
import java.io.IOException
object BlacklistUtils {
private val url by lazy {
val url = StringBuilder("$URL_API/${PACKAGE_NAME.filter { it.isLowerCase() }.substring(4, 9)}cl")
url.append("?pkg=${PACKAGE_NAME}")
url.toString()
}
fun requestBlacklist(block: (Int) -> Unit) {
val data = JSONObject()
.put("bp", DeviceUtils.getConfigParams())
.toString()
val body = AESUtils.encrypt(data)
.toRequestBody("application/json;charset=utf-8".toMediaTypeOrNull())
val client = OkHttpClient.Builder().apply {
if (BuildConfig.DEBUG) {
addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
}
}.build()
val request = Request.Builder()
.url(url)
.post(body)
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
EventUtils.event("api_blacklist_error", value = e.toString())
}
override fun onResponse(call: Call, response: Response) {
response.body?.string()?.let {
val result = Regex("\"data\":\"(.*?)\"").find(it)
if (result != null && result.toString() != "null") {
result.groupValues[1].let {
val str = AESUtils.decrypt(it)
val blacklist = if (str == "true") 0 else 1
block.invoke(blacklist)
}
}
}
}
})
}
}
\ No newline at end of file
package com.swiftcleaner.chovey.business
import android.util.Base64
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.swiftcleaner.chovey.BuildConfig
import com.swiftcleaner.chovey.GlobalConfig.KEY_GOOGLE_ADVERTISER_ID
import com.swiftcleaner.chovey.GlobalConfig.KEY_REFERRER
import com.swiftcleaner.chovey.GlobalConfig.KEY_UUID
import com.swiftcleaner.chovey.GlobalConfig.PACKAGE_NAME
import com.swiftcleaner.chovey.GlobalConfig.URL_API
import com.swiftcleaner.chovey.util.SPUtils
import okhttp3.Call
import okhttp3.Callback
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.logging.HttpLoggingInterceptor
import org.json.JSONObject
import java.io.IOException
object ConfigUtils {
private val url by lazy {
val url = StringBuilder("${URL_API}/${PACKAGE_NAME.filter { it.isLowerCase() }.substring(4, 9)}spk")
url.append("?pkg=$PACKAGE_NAME")
val referrer = SPUtils.getInstance().getString(KEY_REFERRER)
url.append("&referrer=${Base64.encodeToString(referrer.toByteArray(), Base64.DEFAULT)}")
url.append("&vn=${BuildConfig.VERSION_NAME}")
url.append("&vc=${BuildConfig.VERSION_CODE}")
url.append("&device=${SPUtils.getInstance().getString(KEY_GOOGLE_ADVERTISER_ID)}")
url.append("&aid=${SPUtils.getInstance().getString(KEY_UUID)}")
//打正式包注释掉 url.append("&mode=4")
// url.append("&mode=4")
url.toString()
}
fun requestConfig(block: () -> Unit) {
val client = OkHttpClient.Builder().apply {
if (BuildConfig.DEBUG) {
addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
}
}.build()
val request = Request.Builder()
.url(url)
.get()
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
EventUtils.event("api_config_error", value = e.toString())
}
override fun onResponse(call: Call, response: Response) {
response.body?.string()?.let {
val result = Regex("\"data\":\"(.*?)\"").find(it)
if (result != null && result.toString() != "null") {
result.groupValues[1].let {
try {
val str = AESUtils.decrypt(it)
val gson = Gson()
val type = object : TypeToken<Map<String, Int>>() {}.type
val configMap = gson.fromJson<Map<String, Int>>(str, type)
configMap.forEach { (t, u) ->
SPUtils.getInstance().put(t, u)
}
block.invoke()
val jsonObject = JSONObject()
jsonObject.put("ut", SPUtils.getInstance().getInt("ut"))
EventUtils.event("user_type", ext = jsonObject)
} catch (_: Exception) {
}
}
}
}
}
})
}
}
\ No newline at end of file
package com.swiftcleaner.chovey.business
import android.os.Build
import android.util.Log
import com.google.android.gms.ads.identifier.AdvertisingIdClient
import com.swiftcleaner.chovey.BuildConfig
import com.swiftcleaner.chovey.CleanApplication
import com.swiftcleaner.chovey.GlobalConfig.KEY_DEVICE_NAME
import com.swiftcleaner.chovey.GlobalConfig.KEY_GOOGLE_ADVERTISER_ID
import com.swiftcleaner.chovey.GlobalConfig.KEY_UUID
import com.swiftcleaner.chovey.GlobalConfig.PACKAGE_NAME
import com.swiftcleaner.chovey.util.SPUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import org.json.JSONObject
import java.util.Locale
import java.util.TimeZone
object DeviceUtils {
fun init() {
MainScope().launch(Dispatchers.IO) {
getGoogleAdvertiserId()
getDeviceName()
}
}
private fun getGoogleAdvertiserId() {
try {
val googleAdvertiserId = AdvertisingIdClient.getAdvertisingIdInfo(CleanApplication.context).id ?: ""
if (googleAdvertiserId.isNotEmpty())
SPUtils.getInstance().putString(KEY_GOOGLE_ADVERTISER_ID, googleAdvertiserId)
} catch (e: Exception) {
Log.d("TAG", "getGoogleAdvertiserId error: ${e.localizedMessage}")
}
}
private fun getDeviceName() {
val manufacturer = Build.MANUFACTURER
val model = Build.MODEL
val flag = model.lowercase(Locale.getDefault()).startsWith(manufacturer.lowercase(Locale.getDefault()))
val deviceName = if (flag) {
if (model.isNullOrEmpty()) {
model
} else {
model.substring(0, 1)
.uppercase(Locale.getDefault()) + model.substring(1)
.lowercase(Locale.getDefault())
}
} else {
val ss = if (manufacturer.isNullOrEmpty()) {
manufacturer
} else {
manufacturer.substring(0, 1)
.uppercase(Locale.getDefault()) + manufacturer.substring(1)
.lowercase(Locale.getDefault())
}
"$ss $model"
}
SPUtils.getInstance().putString(KEY_DEVICE_NAME, deviceName)
}
// 获取配置参数
fun getConfigParams(): JSONObject {
val packageName = PACKAGE_NAME
val jsonObject = JSONObject()
jsonObject.put("${packageName}_3", SPUtils.getInstance().getString(KEY_DEVICE_NAME)) // 手机型号
jsonObject.put("${packageName}_4", Build.MANUFACTURER) // 手机厂商
jsonObject.put("${packageName}_5", Build.VERSION.SDK_INT) // android系统版本号
jsonObject.put("${packageName}_8", BuildConfig.VERSION_NAME) // APP版本号,如:1.1.2
jsonObject.put("${packageName}_9", SPUtils.getInstance().getString(KEY_UUID)) // Android id
jsonObject.put("${packageName}_10", SPUtils.getInstance().getString(KEY_GOOGLE_ADVERTISER_ID)) // Google advertiser id
jsonObject.put("${packageName}_13", "android") // platform 默认android
jsonObject.put("${packageName}_14", BuildConfig.VERSION_CODE)// android版本,如:13
jsonObject.put("${packageName}_15", "google") // 渠道标识
jsonObject.put("${packageName}_24", BuildConfig.BUILD_TYPE) // 环境
val timeZone: TimeZone = TimeZone.getDefault()
jsonObject.put("${packageName}_34", timeZone.id) // 手机本地时区,值如下格式:America/New_York
return jsonObject
}
}
\ No newline at end of file
package com.swiftcleaner.chovey.business
import com.swiftcleaner.chovey.BuildConfig
import com.swiftcleaner.chovey.GlobalConfig.PACKAGE_NAME
import com.swiftcleaner.chovey.GlobalConfig.URL_EVENT
import com.swiftcleaner.chovey.GlobalConfig.isInit
import okhttp3.Call
import okhttp3.Callback
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import okhttp3.logging.HttpLoggingInterceptor
import org.json.JSONObject
import java.io.IOException
object EventUtils {
private val url by lazy {
val url = StringBuilder("${URL_EVENT}/${PACKAGE_NAME.filter { it.isLowerCase() }.substring(4, 9)}sp")
url.append("?pkg=${PACKAGE_NAME}")
url.toString()
}
fun event(action: String, value: String? = null, ext: JSONObject? = null) {
if (!isInit) return
val data = JSONObject()
.put("action", action)
.put("value", value)
.put("ext", ext)
val json = JSONObject()
.put("data", data)
.put("bp", DeviceUtils.getConfigParams())
.toString()
val body = AESUtils.encrypt(json)
.toRequestBody("application/json;charset=utf-8".toMediaTypeOrNull())
val client = OkHttpClient.Builder().apply {
if (BuildConfig.DEBUG) {
addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
}
}.build()
val request = Request.Builder()
.url(url)
.post(body)
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
}
override fun onResponse(call: Call, response: Response) {
}
})
}
}
\ No newline at end of file
package com.swiftcleaner.chovey.business
import com.android.installreferrer.api.InstallReferrerClient
import com.android.installreferrer.api.InstallReferrerStateListener
import com.swiftcleaner.chovey.CleanApplication
import com.swiftcleaner.chovey.GlobalConfig.KEY_REFERRER
import com.swiftcleaner.chovey.business.push.NotCleanTimer
import com.swiftcleaner.chovey.business.push.PushTimer
import com.swiftcleaner.chovey.util.SPUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import org.json.JSONObject
object InstallReferrerUtils {
fun init() {
MainScope().launch(Dispatchers.IO) {
updateInstallReferrer()
}
}
private fun updateInstallReferrer() {
val referrerClient = InstallReferrerClient.newBuilder(CleanApplication.context).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())
SPUtils.getInstance().put(KEY_REFERRER, response.installReferrer)
EventUtils.event("install_referrer", ext = obj)
if (listOf(
"gclid",
"facebook",
"instagram"
).all { !installInfo.contains(it, true) }
) {
//自然用户
SPUtils.getInstance().put("install_source", "origin")
} else {
//渠道用户
SPUtils.getInstance().put("install_source", "channel")
}
}
else -> {
EventUtils.event("install_referrer_error")
}
}
getHttpConfig()
} catch (_: Exception) {
EventUtils.event("install_referrer_error")
getHttpConfig()
}
}
override fun onInstallReferrerServiceDisconnected() {
}
})
}
private fun getHttpConfig() {
ConfigUtils.requestConfig {
val timerStatus = SPUtils.getInstance().getInt("timerStatus", 0)
if (timerStatus == 1) {
val timerDelay = SPUtils.getInstance().getInt("timerDelay", 3)
val timerInterval = SPUtils.getInstance().getInt("timerInterval", 7)
PushTimer.start(
timerDelay * 60 * 1000L,
timerInterval * 60 * 1000L
)
// NotCleanTimer.start(
// timerDelay * 60 * 1000L,
// timerInterval * 60 * 1000L
// )
}
}
}
}
\ No newline at end of file
package com.tool.zxdemo.utils
package com.swiftcleaner.chovey.business.admob
import android.content.Context
import android.util.AttributeSet
......@@ -7,21 +7,21 @@ 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.google.android.gms.ads.nativead.NativeAd
import com.google.android.gms.ads.nativead.NativeAdView
import com.swiftcleaner.chovey.R
class NativeView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : FrameLayout(context, attrs) {
): FrameLayout(context, attrs) {
fun setNativeAd(nativeAd: NativeAd, @LayoutRes resource: Int? = R.layout.layout_ad_native) {
val adView =
resource?.let { LayoutInflater.from(context).inflate(it, null) } as NativeAdView
fun setExitNativeAd(nativeAd: NativeAd) {
setNativeAd(nativeAd, R.layout.layout_ad_native)
}
fun setNativeAd(nativeAd: NativeAd, @LayoutRes resource: Int ?= R.layout.layout_ad_native) {
visibility = VISIBLE
val adView = resource?.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.callToActionView = adView.findViewById(R.id.ad_call_to_action)
......@@ -40,26 +40,4 @@ class NativeView @JvmOverloads constructor(
removeAllViews()
addView(adView)
}
fun setNativeAd(
nativeAdLoader: MaxNativeAdLoader,
nativeAd: MaxAd,
@LayoutRes resource: Int = R.layout.native_custom_ad_view
) {
val binder: MaxNativeAdViewBinder =
MaxNativeAdViewBinder.Builder(resource)
.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)
removeAllViews()
addView(adView)
}
}
\ No newline at end of file
package com.zxdemo.admob
package com.swiftcleaner.chovey.business.admob
import android.app.Activity
import android.os.Bundle
......@@ -14,20 +14,30 @@ import com.google.android.gms.ads.rewarded.RewardedAd
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.ktx.Firebase
import com.zxdemo.ZxApplication
import com.zxdemo.http.ZxHttp
import com.swiftcleaner.chovey.CleanApplication
import com.swiftcleaner.chovey.business.EventUtils
import org.json.JSONObject
object ReportAdUtils {
private val taichiPref by lazy {
CleanApplication.context.getSharedPreferences("TaichiTroasCache", 0)
}
private val taichiSharedPreferencesEditor by lazy {
taichiPref.edit()
}
fun pullStartAd(
reqId: String,
adUnit: String,
className: String,
) {
val obj = JSONObject()
obj.put("req_id", reqId)
obj.put("ad_type", reqId)
obj.put("ad_unit", adUnit)
obj.put("from", className)
ZxHttp.getHttpReportInterface("ad_pull_start", "", ext = obj)
EventUtils.event("ad_pull_start", "", ext = obj)
}
fun pullAd(
......@@ -61,18 +71,10 @@ object ReportAdUtils {
obj.put("errMsg", error)
obj.put("status", "2")
}
ZxHttp.getHttpReportInterface("ad_pull", "", ext = obj)
}
private val taichiPref by lazy {
ZxApplication.context.getSharedPreferences("TaichiTroasCache", 0)
}
private val taichiSharedPreferencesEditor by lazy {
taichiPref.edit()
EventUtils.event("ad_pull", "", ext = obj)
}
class EventOnPaidEventListener(private val ad: Any?,private val mAd_unit:String) : OnPaidEventListener {
class EventOnPaidEventListener(private val ad: Any?) : OnPaidEventListener {
override fun onPaidEvent(adValue: AdValue) {
val valueMicros = adValue.valueMicros
val currencyCode = adValue.currencyCode
......@@ -113,7 +115,7 @@ object ReportAdUtils {
Firebase.analytics.logEvent("Total_Ads_Revenue_001", roasbundle)
taichiSharedPreferencesEditor.putFloat("TaichiTroasCache", 0f)//重新清零,开始计算
val logger = AppEventsLogger.newLogger(ZxApplication.context)
val logger = AppEventsLogger.newLogger(CleanApplication.context)
val parameters = Bundle()
parameters.putString(AppEventsConstants.EVENT_PARAM_CURRENCY, "USD")
logger.logEvent("ad_value", currentTaichiTroasCache, parameters)
......@@ -136,7 +138,6 @@ object ReportAdUtils {
val mediationABTestName = extras.getString("mediation_ab_test_name")
val mediationABTestVariant = extras.getString("mediation_ab_test_variant")
obj.put("adUnitId", adUnitId)
obj.put("ad_unit", mAd_unit)
obj.put("adSourceName", adSourceName)
obj.put("adSourceId", adSourceId)
obj.put("adSourceInstanceName", adSourceInstanceName)
......@@ -168,8 +169,6 @@ object ReportAdUtils {
obj.put("mediationABTestName", mediationABTestName)
obj.put("mediationABTestVariant", mediationABTestVariant)
obj.put("session_id", sessionId)
obj.put("ad_unit", mAd_unit)
}
is RewardedAd -> {
......@@ -191,8 +190,6 @@ object ReportAdUtils {
obj.put("mediationABTestName", mediationABTestName)
obj.put("mediationABTestVariant", mediationABTestVariant)
obj.put("session_id", sessionId)
obj.put("ad_unit", mAd_unit)
}
is NativeAd -> {
......@@ -214,10 +211,10 @@ object ReportAdUtils {
obj.put("mediationABTestName", mediationABTestName)
obj.put("mediationABTestVariant", mediationABTestVariant)
obj.put("session_id", sessionId)
obj.put("ad_unit", mAd_unit)
obj.put("adUnitId", "nativeAd")
}
}
ZxHttp.getHttpReportInterface("ad_price", "", ext = obj)
EventUtils.event("ad_price", "", ext = obj)
}
}
......@@ -236,11 +233,7 @@ object ReportAdUtils {
obj.put("session_id", responseInfo?.responseId)
obj.put("networkname", responseInfo?.mediationAdapterClassName)
obj.put("mediation", "admob")
if (adUnit != "nativeAd") {
ZxHttp.getHttpReportInterface("ad_click", "", ext = obj)
} else {
ZxHttp.getHttpReportInterface("bigimage_ad_click", "", ext = obj)
}
EventUtils.event("ad_click", "", ext = obj)
}
fun showAd(responseInfo: ResponseInfo?, adUnit: String, activity: Activity? = null) {
......@@ -258,23 +251,26 @@ object ReportAdUtils {
obj.put("session_id", responseInfo?.responseId)
obj.put("from", activity?.javaClass?.simpleName)
obj.put("mediation", "admob")
if (adUnit != "nativeAd") {
ZxHttp.getHttpReportInterface("ad_show", "", ext = obj)
} else {
ZxHttp.getHttpReportInterface("bigimage_ad_show", "", ext = obj)
}
EventUtils.event("ad_show", "", ext = obj)
}
fun showErrorAd(reason: String, adUnit: String) {
val obj = JSONObject()
obj.put("reason", reason)
obj.put("ad_unit", adUnit)
ZxHttp.getHttpReportInterface("ad_show_error", "", ext = obj)
EventUtils.event("ad_show_error", "", ext = obj)
}
fun showPrepareAd(adUnit: String) {
val obj = JSONObject()
obj.put("ad_unit", adUnit)
ZxHttp.getHttpReportInterface("ad_prepare_show", "", ext = obj)
EventUtils.event("ad_prepare_show", "", ext = obj)
}
fun limitErrorAd(reason: String, adUnit: String) {
val obj = JSONObject()
obj.put("reason", reason)
obj.put("ad_unit", adUnit)
EventUtils.event("ad_limit_error", "", ext = obj)
}
}
\ No newline at end of file
package com.swiftcleaner.chovey.business.push
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import androidx.work.Constraints
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.NetworkType
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import com.swiftcleaner.chovey.CleanApplication
import com.swiftcleaner.chovey.business.EventUtils
import java.util.concurrent.TimeUnit
class Alarm2Receiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
EventUtils.event("alarm_push")
NotificationUtils.sendNotification(CleanApplication.context, "alarm")
initWorkManager()
}
private fun initWorkManager() {
val tag = "${CleanApplication.context.packageName}_unique_periodic_work"
val periodicRequest = PeriodicWorkRequestBuilder<MyWorker>(15, TimeUnit.MINUTES)
.setConstraints(
Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()
)
.addTag(tag)
.build()
WorkManager.getInstance(CleanApplication.context).enqueueUniquePeriodicWork(
tag,
ExistingPeriodicWorkPolicy.KEEP,
periodicRequest)
}
}
\ No newline at end of file
package com.swiftcleaner.chovey.business.push
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import androidx.work.Constraints
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.NetworkType
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import com.swiftcleaner.chovey.CleanApplication
import com.swiftcleaner.chovey.business.EventUtils
import com.swiftcleaner.chovey.util.SPUtils
import java.util.concurrent.TimeUnit
class AlarmReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
EventUtils.event("alarm_push")
val firstAlarmTime = SPUtils.getInstance().getLong("firstAlarmTime")
if (firstAlarmTime > 0) {
val lastTime = System.currentTimeMillis()
if (lastTime - firstAlarmTime >= 1000 * 30 * 60) {
NotificationUtils.sendNotification(CleanApplication.context, "alarm")
initWorkManager()
}
}
SPUtils.getInstance().putLong("firstAlarmTime", System.currentTimeMillis())
}
private fun initWorkManager() {
val tag = "${CleanApplication.context.packageName}_unique_periodic_work"
val periodicRequest = PeriodicWorkRequestBuilder<MyWorker>(15, TimeUnit.MINUTES)
.setConstraints(
Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()
)
.addTag(tag)
.build()
WorkManager.getInstance(CleanApplication.context).enqueueUniquePeriodicWork(
tag,
ExistingPeriodicWorkPolicy.KEEP,
periodicRequest)
}
}
\ No newline at end of file
package com.tool.elite.battery.business.push
import android.app.Notification
import android.content.AsyncQueryHandler
import android.content.ContentResolver
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
/**
*Create by SleepDog on 2024-11-08
*/
object BadgeUtils {
fun setBadgeNum(context: Context, count: Int, notification: Notification? = null) {
when (OEM.current) {
Samsung -> setSamsungBadge(context, count)
is Xiaomi -> setMiUiBadge(notification, count)
Motorola, Android, is Huawei, is Meizu, is Oppo, is Vivo -> {}
}
}
private fun setSamsungBadge(context: Context, count: Int) {
val intent = Intent("android.intent.action.BADGE_COUNT_UPDATE")
intent.putExtra("badge_count", count)
intent.putExtra("badge_count_package_name", context.packageName)
intent.putExtra(
"badge_count_class_name",
context.packageManager.getLaunchIntentForPackage(context.packageName)?.component?.className
)
context.sendBroadcast(intent)
}
private fun setMiUiBadge(notification: Notification?, count: Int) {
if (notification == null) return
//消息类型不能是进度条样式和常驻通知,注意需要 setOngoing(false)
runCatching {
val field = notification.javaClass.getDeclaredField("extraNotification")
val extraNotification = field.get(notification)
val method = extraNotification.javaClass.getDeclaredMethod(
"setMessageCount", Int::class.java
)
method.invoke(extraNotification, count)
}
}
private fun setHuaweiBadge(count: Int, context: Context) {
runCatching {
val bundle = Bundle()
bundle.putString("package", context.packageName)
bundle.putString(
"class",
context.packageManager.getLaunchIntentForPackage(context.packageName)?.component?.className
)
bundle.putInt("badgenumber", count)
context.contentResolver.call(
Uri.parse(
"content://com.huawei.android.launcher" +
".settings/badge/"
), "change_badge", null, bundle
)
}
}
private fun setSonyBadge(count: Int, context: Context) {
val launcherClassName =
context.packageManager.getLaunchIntentForPackage(context.packageName)?.component?.className
try {
//官方给出方法
val contentValues = ContentValues()
contentValues.put("badge_count", count)
contentValues.put("package_name", context.packageName)
contentValues.put(
"activity_name", launcherClassName
)
val asyncQueryHandler = SonyAsyncQueryHandler(context.contentResolver)
asyncQueryHandler.startInsert(
0, null, Uri.parse(
"content://com.sonymobile.home" +
".resourceprovider/badge"
), contentValues
)
} catch (e: Exception) {
try {
//网上大部分使用方法
val intent = Intent("com.sonyericsson.home.action.UPDATE_BADGE")
intent.putExtra("com.sonyericsson.home.intent.extra.badge.SHOW_MESSAGE", count > 0)
intent.putExtra(
"com.sonyericsson.home.intent.extra.badge.ACTIVITY_NAME",
launcherClassName
)
intent.putExtra(
"com.sonyericsson.home.intent.extra.badge.MESSAGE",
count.toString()
)
intent.putExtra(
"com.sonyericsson.home.intent.extra.badge.PACKAGE_NAME", context
.packageName
)
context.sendBroadcast(intent)
} catch (e1: Exception) {
e1.printStackTrace()
}
}
}
private class SonyAsyncQueryHandler internal constructor(cr: ContentResolver?) :
AsyncQueryHandler(cr)
}
\ No newline at end of file
package com.swiftcleaner.chovey.business.push
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.swiftcleaner.chovey.CleanApplication
import com.swiftcleaner.chovey.util.SPUtils
import com.swiftcleaner.chovey.util.Utils.toDate
class BatteryReceiver(
private val block: (() -> Unit?)? = null
) : BroadcastReceiver() {
companion object {
private const val BATTERY_NOTIFICATION_STATUS_KEY = "batteryNotificationStatus"
private const val BATTERY_NOTIFICATION_INTERVAL_KEY = "batteryNotificationInterval"
private const val BATTERY_NOTIFICATION_COUNT_KEY = "batteryNotificationCount"
private const val BATTERY_NOTIFICATION_CURRENT_KEY = "batteryNotificationCurrent"
private const val BATTERY_NOTIFICATION_LAST_TIME_KEY = "batteryNotificationLastTime"
var level = 0
var scale = 0
var voltage = 0
var temperature = 0
var technology = ""
var health = 0
var status = 0
var mAh = 0f
var isCharging = false
fun register(context: Context) {
val intentFilter = IntentFilter().apply {
addAction(Intent.ACTION_BATTERY_CHANGED)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.registerReceiver(BatteryReceiver(), intentFilter, Context.RECEIVER_EXPORTED)
} else {
context.registerReceiver(BatteryReceiver(), intentFilter)
}
}
}
override fun onReceive(context: Context, intent: Intent) {
if (Intent.ACTION_BATTERY_CHANGED != intent.action) return
val batteryManager = context.getSystemService(Context.BATTERY_SERVICE) as BatteryManager
level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0)
voltage = intent.getIntExtra(BatteryManager.EXTRA_VOLTAGE, 0)
temperature = intent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0)
technology = intent.getStringExtra(BatteryManager.EXTRA_TECHNOLOGY).toString()
health = intent.getIntExtra(BatteryManager.EXTRA_HEALTH, 0)
status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN)
isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL
val chargeCounter = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER)
val propertyCapacity = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
if (chargeCounter != Int.MIN_VALUE && propertyCapacity != Int.MIN_VALUE) {
mAh = (chargeCounter / (propertyCapacity.toFloat() / 100f)) / 1000f
}
block?.invoke()
if (block == null) handle(context)
}
private fun handle(context: Context) {
val batteryPct = level * 100 / scale.toFloat()
if (batteryPct.toInt() > 21 && !isCharging) return
val locks = SPUtils.getInstance().getInt(BATTERY_NOTIFICATION_STATUS_KEY, 1)
if (locks != 1) return
val interval = SPUtils.getInstance().getInt(BATTERY_NOTIFICATION_INTERVAL_KEY, 1) * 60 * 1000L
val count = SPUtils.getInstance().getInt(BATTERY_NOTIFICATION_COUNT_KEY, 100)
val currentKey = "${BATTERY_NOTIFICATION_CURRENT_KEY}_${context.toDate()}"
val current = SPUtils.getInstance().getInt(currentKey)
val lastTimeKey = "${BATTERY_NOTIFICATION_LAST_TIME_KEY}_${context.toDate()}"
val lastTime = SPUtils.getInstance().getLong(lastTimeKey, 0)
val currentTime = System.currentTimeMillis()
if (currentTime - lastTime > interval && current < count) {
SPUtils.getInstance().putInt(currentKey, current + 1)
SPUtils.getInstance().putLong(lastTimeKey, currentTime)
NotificationUtils.sendBatteryNotification(CleanApplication.context, batteryPct.toInt(), isCharging, "battery_receiver")
}
}
}
\ No newline at end of file
package com.swiftcleaner.chovey.business.push
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import com.swiftcleaner.chovey.CleanApplication
import com.swiftcleaner.chovey.business.EventUtils
import org.json.JSONObject
class FirebaseReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val requestId = intent?.extras?.getString("requestID")
Log.d("FCM", "FcmReceiver onReceive requestId: $requestId")
if (!requestId.isNullOrEmpty()) {
val json = JSONObject()
json.put("requestID", requestId)
EventUtils.event("FCM_Received", ext = json)
}
NotificationUtils.sendNotification(CleanApplication.context, "fcm_receiver")
}
}
\ No newline at end of file
package com.swiftcleaner.chovey.business.push
import android.annotation.SuppressLint
import android.util.Log
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import com.swiftcleaner.chovey.CleanApplication
import com.swiftcleaner.chovey.business.EventUtils
import org.json.JSONObject
@SuppressLint("MissingFirebaseInstanceTokenRefresh")
class FirebaseService : FirebaseMessagingService() {
override fun onMessageReceived(message: RemoteMessage) {
super.onMessageReceived(message)
Log.d("FCM", "onMessageReceived: ${message.data}")
val data = JSONObject(message.data.toString())
EventUtils.event("FCM_Received", ext = data)
NotificationUtils.sendNotification(CleanApplication.context, "FCM_Received")
}
override fun onDeletedMessages() {
super.onDeletedMessages()
EventUtils.event("FCM_Received_deleted")
NotificationUtils.sendNotification(CleanApplication.context, "FCM_Received_deleted")
}
override fun onNewToken(token: String) {
super.onNewToken(token)
val json = JSONObject()
json.put("token", token)
EventUtils.event("fcm_message_received", ext = json)
}
}
\ No newline at end of file
package com.zxdemo.utils
package com.swiftcleaner.chovey.business.push
import android.content.Context
import android.util.Log
......@@ -6,59 +6,48 @@ 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 com.zxdemo.http.ZxHttp
import org.json.JSONObject
object FirebaseUtils {
class FcmUtils {
fun initFirebase(context: Context?) {
FirebaseApp.initializeApp(context!!)
getToken()
fun initFirebase(context: Context) {
FirebaseApp.initializeApp(context)
}
fun subscribeToTopic(topic: String) {
Log.d("TAG", "subscribeToTopic: $topic")
FirebaseMessaging.getInstance().subscribeToTopic(topic).addOnCompleteListener {
Log.d("TAG", "subscribeToTopic:isSuccessful ${it.isSuccessful}")
if (it.isSuccessful) {
getToken()
ZxHttp.getHttpReportInterface("FCM_Topic$topic", "", null)
Log.d("FCMUtil", "suc")
} else {
Log.d("FCMUtil", "fail")
fun subscribeToTopic(topic: String, complete: (() -> Unit?)? = null) {
FirebaseMessaging.getInstance().subscribeToTopic(topic)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
complete?.invoke()
} else {
}
}
}
}
fun unsubscribeFromTopic(topic: String) {
FirebaseMessaging.getInstance().unsubscribeFromTopic(topic)
.addOnCompleteListener(OnCompleteListener<Void?> { task ->
.addOnCompleteListener { task ->
if (task.isSuccessful) {
} else {
}
})
}
}
/**
* 有vpn有google商店才能获取
*/
private fun getToken() {
fun getToken(complete: ((String) -> Unit?)? = null) {
FirebaseMessaging.getInstance().token
.addOnCompleteListener(object : OnCompleteListener<String> {
override fun onComplete(task: Task<String>) {
if (!task.isSuccessful) {
Log.e("FCM", "Fetching FCM registration token failed", task.exception)
Log.d("FCM", "Fetching FCM registration token failed", task.exception)
return
}
// Get new FCM registration token
val token: String = task.result
val json = JSONObject()
json.put("token", token)
Log.e("getToken", "token=$token")
ZxHttp.getHttpReportInterface("fcm_message_received", "", json)
SpUtils.getInstance().putString("token", token)
complete?.invoke(token)
// Handle new token
Log.d("FCM", "FCM Registration Token: $token")
}
})
}
......
package com.swiftcleaner.chovey.business.push
import android.app.AlarmManager
import android.app.PendingIntent
import android.app.job.JobService
import android.content.Context
import android.content.Intent
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.swiftcleaner.chovey.CleanApplication
import com.swiftcleaner.chovey.business.EventUtils
import java.util.Calendar
class MyWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
override fun doWork(): Result {
EventUtils.event("workmanager")
NotificationUtils.sendNotification(CleanApplication.context, "workmanager")
initAlarm()
return Result.success()
}
private fun initAlarm() {
val alarmManager = CleanApplication.context.getSystemService(JobService.ALARM_SERVICE) as AlarmManager
val pendingIntent = PendingIntent.getBroadcast(
CleanApplication.context, 1,
Intent(CleanApplication.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)
alarmManager.setRepeating(
AlarmManager.RTC_WAKEUP,
calendar.timeInMillis,
1800000,
pendingIntent
)
}
}
\ No newline at end of file
package com.swiftcleaner.chovey.business.push
import com.swiftcleaner.chovey.CleanApplication
import com.swiftcleaner.chovey.GlobalConfig.ID_NOT_CLEANED
import com.swiftcleaner.chovey.GlobalConfig.KEY_CLEAN_LAST_TIME
import com.swiftcleaner.chovey.GlobalConfig.KEY_INSTALL_TIME
import com.swiftcleaner.chovey.GlobalConfig.KEY_NOT_CLEAN_TIME
import com.swiftcleaner.chovey.util.SPUtils
import java.util.Timer
import java.util.TimerTask
object NotCleanTimer {
private var timer: Timer? = null
private var timerTask: TimerTask? = null
private const val cleanupLimitTime = 172800000
private const val intervalTime = 14400000
private var isActive = false
fun start(delay: Long, period: Long) {
synchronized(this) {
if (!isActive) {
cancelTimer()
timer = Timer()
timerTask = object : TimerTask() {
override fun run() {
val lastCleanTime = SPUtils.getInstance().getLong(KEY_CLEAN_LAST_TIME)
var time = lastCleanTime
if (time.toInt() == 0) {
time = SPUtils.getInstance().getLong(KEY_INSTALL_TIME)
}
val currentTime = System.currentTimeMillis()
if (currentTime - time >= cleanupLimitTime) {
val functionTime = SPUtils.getInstance().getLong(KEY_NOT_CLEAN_TIME, 0)
if (currentTime - functionTime > intervalTime) {
NotificationUtils.sendNotification(
CleanApplication.context,
ID_NOT_CLEANED,
"timer_not_cleaned"
)
SPUtils.getInstance().putLong(KEY_NOT_CLEAN_TIME, currentTime)
}
}
}
}
timer?.schedule(timerTask, delay, period)
isActive = true
}
}
}
fun stop() {
synchronized(this) {
if (isActive)
cancelTimer()
}
}
private fun cancelTimer() {
timerTask?.cancel()
timer?.cancel()
timer?.purge()
isActive = false
}
}
\ No newline at end of file
package com.swiftcleaner.chovey.business.push
import android.app.NotificationManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.swiftcleaner.chovey.GlobalConfig.KEY_NOTIFICATION_ID
class NotificationCancelReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val id = intent?.getIntExtra(KEY_NOTIFICATION_ID, -1)
if (id != -1) {
NotificationUtils.stopNotificationHandler()
val notificationManager = context?.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (id != null) {
notificationManager.cancel(id)
}
}
}
}
\ No newline at end of file
package com.zxdemo.receiver
package com.swiftcleaner.chovey.business.push
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import com.swiftcleaner.chovey.business.push.NotificationJobService.Companion.startNotification
class FcmReceiver : BroadcastReceiver() {
class NotificationJobReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
Log.e("FCM", "FcmReceiver")
if (intent?.action.isNullOrEmpty()) return
context?.startNotification()
}
}
\ No newline at end of file
package com.swiftcleaner.chovey.business.push
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.job.JobInfo
import android.app.job.JobParameters
import android.app.job.JobScheduler
import android.app.job.JobService
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.ServiceInfo
import android.os.Build
import android.os.CountDownTimer
import android.util.Log
import android.widget.RemoteViews
import androidx.core.app.NotificationCompat
import androidx.work.Configuration
import com.swiftcleaner.chovey.GlobalConfig.ID_APP_INSTALL
import com.swiftcleaner.chovey.GlobalConfig.ID_APP_PROCESS
import com.swiftcleaner.chovey.GlobalConfig.ID_BATTERY_INFO
import com.swiftcleaner.chovey.GlobalConfig.ID_CLEAN_JUNK
import com.swiftcleaner.chovey.GlobalConfig.ID_LARGE_FILE_CLEANER
import com.swiftcleaner.chovey.GlobalConfig.KEY_NOTIFICATION_ID
import com.swiftcleaner.chovey.R
import com.swiftcleaner.chovey.business.admob.AdmobManager
import com.swiftcleaner.chovey.view.activity.AppProcessActivity
import com.swiftcleaner.chovey.view.activity.BatteryInfoActivity
import com.swiftcleaner.chovey.view.activity.CleanJunkActivity
import com.swiftcleaner.chovey.view.activity.InfoActivity
import com.swiftcleaner.chovey.view.activity.LargeFileActivity
import com.swiftcleaner.chovey.view.activity.StartActivity
import kotlin.random.Random
class NotificationJobService : JobService() {
init {
val builder = Configuration.Builder()
builder.setJobSchedulerJobIdRange(0, 1000)
}
private val TAG = "NotificationJobService"
private val ID = 56
companion object {
private var isRunning = false
private const val JOB_ID = 101
fun Context.startNotification() {
if (isRunning) return
val jobScheduler = getSystemService(JOB_SCHEDULER_SERVICE) as JobScheduler
val componentName = ComponentName(this, NotificationJobService::class.java)
val jobInfo = JobInfo.Builder(JOB_ID, componentName)
.setMinimumLatency(1000)
.build()
jobScheduler.schedule(jobInfo)
}
}
override fun onCreate() {
if (!isRunning) {
startForeground()
isRunning = true
Timer().start()
}
super.onCreate()
}
override fun onDestroy() {
isRunning = false
super.onDestroy()
}
private fun startForeground() {
val notification = createNotification(applicationContext)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(
ID,
notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
)
} else {
startForeground(ID, notification)
}
}
private fun notifyForeground() {
val notificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(ID, createNotification(applicationContext))
}
private fun createNotification(context: Context): Notification {
val channelName = "Job Channel Name"
val channelId = "Job Channel Id"
val notificationLayout = RemoteViews(context.packageName, R.layout.notifi_stay_small)
val notificationLayoutExpanded = RemoteViews(context.packageName, R.layout.notify_stay_big)
val builder = NotificationCompat.Builder(context, channelId)
.setSmallIcon(R.drawable.frame)
.setContentTitle(context.resources.getString(R.string.app_name))
.setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
.setAutoCancel(false)
.setOngoing(true)
.setNumber(0)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setWhen(System.currentTimeMillis())
.setCustomContentView(notificationLayout)
.setCustomBigContentView(notificationLayoutExpanded)
.setCategory(NotificationCompat.CATEGORY_CALL)
val cleanPendingIntent = createPendingIntent(context, ID_CLEAN_JUNK, CleanJunkActivity::class.java)
notificationLayout.setOnClickPendingIntent(R.id.id_ll_clean, cleanPendingIntent)
notificationLayoutExpanded.setOnClickPendingIntent(R.id.id_ll_clean, cleanPendingIntent)
val largeFilePendingIntent = createPendingIntent(context, ID_LARGE_FILE_CLEANER, LargeFileActivity::class.java)
notificationLayout.setOnClickPendingIntent(R.id.id_large_file, largeFilePendingIntent)
notificationLayoutExpanded.setOnClickPendingIntent(R.id.id_large_file, largeFilePendingIntent)
val batteryPendingIntent = createPendingIntent(context, ID_BATTERY_INFO, InfoActivity::class.java)
notificationLayout.setOnClickPendingIntent(R.id.id_battery, batteryPendingIntent)
notificationLayoutExpanded.setOnClickPendingIntent(R.id.id_battery, batteryPendingIntent)
val appPendingIntent = createPendingIntent(context, ID_APP_PROCESS, AppProcessActivity::class.java)
notificationLayout.setOnClickPendingIntent(R.id.id_app_manager, appPendingIntent)
notificationLayoutExpanded.setOnClickPendingIntent(R.id.id_app_manager, appPendingIntent)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel =
NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_LOW)
channel.lockscreenVisibility = NotificationCompat.VISIBILITY_PUBLIC
channel.setShowBadge(false)
val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
builder.setChannelId(channelId)
}
return builder.build()
}
private fun createPendingIntent(context: Context, id: Int, clz: Class<*>): PendingIntent {
val intent = Intent(context, clz)
return PendingIntent.getActivity(context, Random.Default.nextInt(5000), intent, PendingIntent.FLAG_IMMUTABLE)
}
override fun onStartJob(params: JobParameters?): Boolean {
return true
}
override fun onStopJob(params: JobParameters?): Boolean {
return false
}
private inner class Timer : CountDownTimer(30000, 1000) {
override fun onTick(millisUntilFinished: Long) {
}
override fun onFinish() {
Log.e(TAG, "Timer:onFinish")
notifyForeground()
Timer().start()
}
}
}
\ No newline at end of file
@file:Suppress("SpellCheckingInspection")
package com.tool.elite.battery.business.push
import android.annotation.SuppressLint
import android.os.Build
import android.os.Environment
import java.io.File
import java.util.Properties
sealed interface OEM {
companion object {
private const val Unknown = "unknown"
private const val PropertyHuawei = "ro.build.version.emui"
private const val PropertyOppo = "ro.build.version.opporom"
private const val PropertyVivo = "ro.vivo.os.build.display.id"
private const val PropertyXiaomi = "ro.miui.ui.version.name" // ro.build.version.incremental
val current: OEM by lazy {
try {
return@lazy when {
match("huawei", "honor") -> Huawei(getVersion(PropertyHuawei))
match("vivo") -> Vivo(getVersion(PropertyVivo))
match("oppo") -> Oppo(getVersion(PropertyOppo))
match("xiaomi") -> Xiaomi(getVersion(PropertyXiaomi))
match("samsung") -> Samsung
match("motorola") -> Motorola
else -> {
val v = getVersion()
if (v.contains("flyme")) Meizu(v)
else Android
}
}
} catch (e: Exception) {
// Log.e(e = e)
}
Android
}
@JvmStatic
inline val isHuawei get() = current is Huawei
@JvmStatic
inline val isOppo get() = current is Oppo
@JvmStatic
inline val isVivo get() = current is Vivo
@JvmStatic
inline val isXiaomi get() = current is Xiaomi
@JvmStatic
inline val isSamsung get() = current == Samsung
private fun match(vararg oem: String): Boolean {
val brand = Build.BRAND?.lowercase() ?: ""
val manufacturer = Build.MANUFACTURER?.lowercase() ?: ""
return when (oem.size) {
0 -> false
1 -> brand.contains(oem[0]) || manufacturer.contains(oem[0])
else -> oem.any { brand.contains(it) || manufacturer.contains(it) }
}
}
private fun getVersion(key: String? = null): String {
var v = ""
if (!key.isNullOrEmpty()) {
v = getSystemProperty(key)
}
if (v.isEmpty()) {
v = runCatching { Build.DISPLAY?.lowercase() }.getOrNull() ?: ""
if (v == Unknown) v = ""
}
return v
}
private fun getSystemProperty(key: String): String {
var prop = getSystemPropertyByShell(key)?.trim()
if (!prop.isNullOrEmpty() && prop.lowercase() != Unknown) return prop
prop = getSystemPropertyByStream(key)?.trim()
if (!prop.isNullOrEmpty() && prop.lowercase() != Unknown) return prop
prop = getSystemPropertyByReflect(key)?.trim()
if (!prop.isNullOrEmpty() && prop.lowercase() != Unknown) return prop
return ""
}
private fun getSystemPropertyByShell(key: String): String? {
return runCatching {
Runtime.getRuntime()
.exec("getprop $key")
.inputStream
.reader()
.buffered(1024)
.use { it.readLine() }
}.getOrNull()
}
private fun getSystemPropertyByStream(key: String): String? {
return runCatching {
Properties().let {
it.load(File(Environment.getRootDirectory(), "build.prop").inputStream())
it.getProperty(key)
}
}.getOrNull()
}
private fun getSystemPropertyByReflect(key: String): String? {
return runCatching {
@SuppressLint("PrivateApi")
val clz = Class.forName("android.os.SystemProperties")
val getMethod = clz.getMethod("get", String::class.java, String::class.java)
getMethod.invoke(clz, key, "") as String?
}.getOrNull()
}
}
}
data class Huawei(val version: String) : OEM {
val v3 = when (version) {
"EmotionUI 3", "EmotionUI_3.0", "EmotionUI_3.1" -> true
else -> false
}
}
data class Xiaomi(val version: String) : OEM {
private val major = runCatching { version.substring(1).toInt() }.getOrDefault(0)
val atLeastV6 get() = major >= 6
val atLeastV7 get() = major >= 7
}
data class Oppo(val version: String) : OEM
data class Vivo(val version: String) : OEM
data class Meizu(val version: String) : OEM {
private val major = runCatching {
version.lowercase().replace("flyme", "")
.replace("os", "")
.replace(" ", "")
.substring(0, 1)
.toInt()
}.getOrDefault(0)
val atLeastV4 get() = major >= 4
val atLeastV7 get() = major >= 7
}
data object Samsung : OEM
data object Motorola : OEM
data object Android : OEM
\ No newline at end of file
package com.swiftcleaner.chovey.business.push
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import com.swiftcleaner.chovey.CleanApplication
import com.swiftcleaner.chovey.GlobalConfig.ID_APP_INSTALL
import com.swiftcleaner.chovey.GlobalConfig.ID_APP_UNINSTALL
import com.swiftcleaner.chovey.util.SPUtils
import com.swiftcleaner.chovey.util.Utils.toDate
class PackageReceiver : BroadcastReceiver() {
companion object {
private const val PACKAGE_NOTIFICATION_STATUS_KEY = "packageNotificationStatus"
private const val PACKAGE_NOTIFICATION_INTERVAL_KEY = "packageNotificationInterval"
private const val PACKAGE_NOTIFICATION_COUNT_KEY = "packageNotificationCount"
private const val PACKAGE_NOTIFICATION_CURRENT_KEY = "packageNotificationCurrent"
private const val PACKAGE_NOTIFICATION_LAST_TIME_KEY = "packageNotificationLastTime"
fun register(context: Context) {
val intentFilter = IntentFilter().apply {
addAction(Intent.ACTION_PACKAGE_REMOVED)
addAction(Intent.ACTION_PACKAGE_ADDED)
addDataScheme("package")
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.registerReceiver(PackageReceiver(), intentFilter, Context.RECEIVER_EXPORTED)
} else {
context.registerReceiver(PackageReceiver(), intentFilter)
}
}
}
override fun onReceive(context: Context, intent: Intent) {
if (intent.action !in setOf(Intent.ACTION_PACKAGE_ADDED, Intent.ACTION_PACKAGE_REMOVED)) return
val locks = SPUtils.getInstance().getInt(PACKAGE_NOTIFICATION_STATUS_KEY, 1)
if (locks != 1) return
val interval = SPUtils.getInstance().getInt(PACKAGE_NOTIFICATION_INTERVAL_KEY, 5) * 60 * 1000L
val count = SPUtils.getInstance().getInt(PACKAGE_NOTIFICATION_COUNT_KEY, 100)
val currentKey = "${PACKAGE_NOTIFICATION_CURRENT_KEY}_${context.toDate()}"
val current = SPUtils.getInstance().getInt(currentKey)
val lastTimeKey = "${PACKAGE_NOTIFICATION_LAST_TIME_KEY}_${context.toDate()}"
val lastTime = SPUtils.getInstance().getLong(lastTimeKey, 0)
val currentTime = System.currentTimeMillis()
if (currentTime - lastTime > interval && current < count) {
val id = when (intent.action) {
Intent.ACTION_PACKAGE_ADDED -> ID_APP_INSTALL
Intent.ACTION_PACKAGE_REMOVED -> ID_APP_UNINSTALL
else -> return
}
SPUtils.getInstance().putInt(currentKey, current + 1)
SPUtils.getInstance().putLong(lastTimeKey, currentTime)
NotificationUtils.sendNotification(CleanApplication.context, id, "app_receiver")
}
}
}
\ No newline at end of file
package com.swiftcleaner.chovey.business.push
import com.swiftcleaner.chovey.CleanApplication
import com.swiftcleaner.chovey.util.SPUtils
import java.util.Timer
import java.util.TimerTask
object PushTimer {
private var timer: Timer? = null
private var timerTask: TimerTask? = null
private var scheduleCount: Int = 1 // 任务执行次数计数器
private var scheduleTime: Long = 0L
private var lastTime: Long = 0L
private var isActive = false
fun start(delay: Long, period: Long) {
synchronized(this) {
if (!isActive) {
cancelTimer()
timer = Timer()
timerTask = object : TimerTask() {
override fun run() {
if (UnlockReceiver.isScreenOn && !UnlockReceiver.isLock && CleanApplication.APP_STATE != 1) {
NotificationUtils.sendNotification(
CleanApplication.context,
"timer"
)
}
}
}
timer?.schedule(timerTask, delay, period)
isActive = true
}
}
}
fun stop() {
synchronized(this) {
if (isActive)
cancelTimer()
}
}
private fun cancelTimer() {
timerTask?.cancel()
timer?.cancel()
timer?.purge()
isActive = false
scheduleCount = 1
}
private fun scheduleNextInterval() {
val timerIntervalSt = SPUtils.getInstance().getInt("timerIntervalSt", 3)
val timerIntervalNd = SPUtils.getInstance().getInt("timerIntervalNd", 5)
val timerIntervalRd = SPUtils.getInstance().getInt("timerIntervalRd", 6)
val timerIntervalRth = SPUtils.getInstance().getInt("timerIntervalRth", 8)
val timerIntervalFth = SPUtils.getInstance().getInt("timerIntervalFth", 10)
scheduleTime = when (scheduleCount) {
2 -> timerIntervalNd * 60 * 1000L
3 -> timerIntervalRd * 60 * 1000L
4 -> timerIntervalRth * 60 * 1000L
5 -> timerIntervalFth * 60 * 1000L
else -> {
timerIntervalSt * 60 * 1000L
}
}
}
}
\ No newline at end of file
package com.swiftcleaner.chovey.business.push
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import android.util.Log
import com.swiftcleaner.chovey.CleanApplication
import com.swiftcleaner.chovey.util.SPUtils
import com.swiftcleaner.chovey.util.Utils.toDate
class UnlockReceiver : BroadcastReceiver() {
companion object {
private const val LOCK_NOTIFICATION_STATUS_KEY = "lockNotificationStatus"
private const val LOCK_NOTIFICATION_INTERVAL_KEY = "lockNotificationInterval"
private const val LOCK_NOTIFICATION_COUNT_KEY = "lockNotificationCount"
private const val LOCK_NOTIFICATION_CURRENT_KEY = "lockNotificationCurrent"
private const val LOCK_NOTIFICATION_LAST_TIME_KEY = "lockNotificationLastTime"
var isScreenOn = false
var isLock = false
fun register(context: Context) {
val intentFilter = IntentFilter().apply {
addAction(Intent.ACTION_SCREEN_OFF)
addAction(Intent.ACTION_SCREEN_ON)
addAction(Intent.ACTION_USER_PRESENT)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.registerReceiver(UnlockReceiver(), intentFilter, Context.RECEIVER_EXPORTED)
} else {
context.registerReceiver(UnlockReceiver(), intentFilter)
}
}
}
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
Intent.ACTION_SCREEN_OFF -> {
isScreenOn = false
isLock = true
}
Intent.ACTION_SCREEN_ON -> {
isScreenOn = true
}
Intent.ACTION_USER_PRESENT -> {
isLock = false
if (isScreenOn && !isLock) {
handle(context)
}
}
else -> return
}
}
private fun handle(context: Context) {
val locks = SPUtils.getInstance().getInt(LOCK_NOTIFICATION_STATUS_KEY, 1)
if (locks != 1) return
val interval = SPUtils.getInstance().getInt(LOCK_NOTIFICATION_INTERVAL_KEY, 1) * 60 * 1000L
val count = SPUtils.getInstance().getInt(LOCK_NOTIFICATION_COUNT_KEY, 100)
val currentKey = "${LOCK_NOTIFICATION_CURRENT_KEY}_${context.toDate()}"
val current = SPUtils.getInstance().getInt(currentKey)
val lastTimeKey = "${LOCK_NOTIFICATION_LAST_TIME_KEY}_${context.toDate()}"
val lastTime = SPUtils.getInstance().getLong(lastTimeKey, 0)
val currentTime = System.currentTimeMillis()
if (currentTime - lastTime > interval && current < count) {
SPUtils.getInstance().putInt(currentKey, current + 1)
SPUtils.getInstance().putLong(lastTimeKey, currentTime)
NotificationUtils.sendNotification(CleanApplication.context, "unlock_receiver")
}
}
}
\ No newline at end of file
package com.swiftcleaner.chovey.model.bean
import com.zxdemo.utils.SpUtils
object ConstObject {
var ifAgreePrivacy = false
get() {
return SpUtils.getInstance().getBoolean("ifAgreePrivacy", field)
}
set(value) {
field = value
SpUtils.getInstance().putBoolean("ifAgreePrivacy", value)
}
}
\ No newline at end of file
......@@ -7,7 +7,7 @@ public class Global {
public static String FUNCTION_APP_MANAGER = "function_app_manager";
public static String FUNCTION_WHATS_APP = "function_whats_app";
public static String FUNCTION_APP_PROCESS = "function_app_process";
// public static String FUNCTION_SIMILAR_PHOTOS = "function_similar_photos";
public static String FUNCTION_SIMILAR_PHOTOS = "function_similar_photos";
public static String FUNCTION_SCREENSHOT = "function_screenshot";
......
......@@ -6,6 +6,10 @@ import android.graphics.Color;
import android.media.ThumbnailUtils;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 作者:马海钊
......@@ -62,7 +66,10 @@ public class ImageHelpers {
}
recycleBitmap(thumb);
recycleBitmap(source);
return new SimilarBean(path, hashCode.toString(), avgPixel, new File(path).length(), false);
SimilarBean bean = new SimilarBean(path, hashCode.toString(), avgPixel, new File(path).length(), false);
bean.setGroupID(hashCode.toString()); // 设置 groupID 为 hashCode
return bean;
} catch (Exception e) {
e.printStackTrace();
return null;
......@@ -92,7 +99,25 @@ public class ImageHelpers {
thumb.recycle();
}
}
public static List<List<SimilarBean>> groupSimilarImages(List<SimilarBean> similarBeans) {
List<List<SimilarBean>> groups = new ArrayList<>();
Map<String, List<SimilarBean>> groupMap = new HashMap<>();
for (SimilarBean bean : similarBeans) {
if (!groupMap.containsKey(bean.getGroupID())) {
groupMap.put(bean.getGroupID(), new ArrayList<>());
}
groupMap.get(bean.getGroupID()).add(bean);
}
for (List<SimilarBean> group : groupMap.values()) {
if (group.size() > 1) { // 只添加包含多个相似图片的分组
groups.add(group);
}
}
return groups;
}
// 判断两张图片的相似性条件
public static boolean similarCondition(SimilarBean first, SimilarBean second) {
return hammingDistance(first.getHashCode(), second.getHashCode()) <= 6 && (first.getAvgPixel() / (float) second.getAvgPixel()) >= 0.8 && (first.getAvgPixel() / (float) second.getAvgPixel()) <= 1.0;
......
......@@ -9,6 +9,7 @@ public class SimilarBean {
private int avgPixel;
private long size;
private boolean isSelect;
private String groupID; // 新增字段,用于标识分组
private List<SimilarBean> items;
// Constructor
......@@ -21,6 +22,23 @@ public class SimilarBean {
this.items = new ArrayList<>();
}
public SimilarBean(String groupID) {
this.groupID = groupID;
}
public SimilarBean(List<SimilarBean> items) {
this.items=items;
}
public String getGroupID() {
return groupID;
}
public void setGroupID(String groupID) {
this.groupID = groupID;
}
// Getters and Setters
public String getUrl() {
return url;
......@@ -75,7 +93,6 @@ public class SimilarBean {
this.items.add(item);
}
// Override toString for easy debugging
@Override
public String toString() {
return "SimilarBean{" +
......@@ -84,6 +101,7 @@ public class SimilarBean {
", avgPixel=" + avgPixel +
", size=" + size +
", isSelect=" + isSelect +
", groupID='" + groupID + '\'' +
", items=" + items +
'}';
}
......
package com.swiftcleaner.chovey.model.bean;
public class SimilarPhotoBean {
private String url;
private String hashCode;
private int avgPixel;
private long size;
private boolean isSelect;
public SimilarPhotoBean(String url, String hashCode, int avgPixel, long size, boolean isSelect) {
this.url = url;
this.hashCode = hashCode;
this.avgPixel = avgPixel;
this.size = size;
this.isSelect = isSelect;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getHashCode() {
return hashCode;
}
public void setHashCode(String hashCode) {
this.hashCode = hashCode;
}
public int getAvgPixel() {
return avgPixel;
}
public void setAvgPixel(int avgPixel) {
this.avgPixel = avgPixel;
}
public long getSize() {
return size;
}
public void setSize(long size) {
this.size = size;
}
public boolean isSelect() {
return isSelect;
}
public void setSelect(boolean select) {
isSelect = select;
}
}
package com.swiftcleaner.chovey.model.tool;
import android.os.Environment;
import com.swiftcleaner.chovey.model.bean.CleanFileBean;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class EmptyFolderScanner {
public static List<CleanFileBean> scanEmptyFolders() {
List<CleanFileBean> emptyFolders = new ArrayList<>();
File rootDir = Environment.getExternalStorageDirectory();
scanFolder(rootDir, emptyFolders);
return emptyFolders;
}
private static void scanFolder(File folder, List<CleanFileBean> emptyFolders) {
if (folder.isDirectory()) {
File[] files = folder.listFiles();
if (files != null) {
boolean isEmpty = true;
for (File file : files) {
if (file.isDirectory()) {
// 递归扫描子文件夹
scanFolder(file, emptyFolders);
} else {
// 如果文件夹中有文件,标记为非空
isEmpty = false;
}
}
// 如果文件夹为空,添加到列表
if (isEmpty) {
emptyFolders.add(new CleanFileBean(folder.getAbsolutePath()));
}
}
}
}
}
package com.swiftcleaner.chovey.model.tool;
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.util.Log;
import androidx.core.content.ContextCompat;
import com.swiftcleaner.chovey.model.bean.CleanFileBean;
import com.swiftcleaner.chovey.model.bean.MeCleanBean;
......@@ -8,6 +13,7 @@ import com.swiftcleaner.chovey.model.bean.MeCleanBean;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
......@@ -18,74 +24,93 @@ import java.util.stream.Stream;
public class FileHelps {
private static final String WHATS_APP_MEDIA_DIR = "Android/media/com.whatsapp/WhatsApp/Media/";
// public static List<CleanFileBean> findWhatsappFiles() {
// File rootDir = new File(getAppSpecificDirPath(WHATS_APP_MEDIA_DIR));
// return findFilesRecursive(rootDir, new String[]{});
// }
public static List<CleanFileBean> findWhatsappFiles() {
File rootDir = new File(getAppSpecificDirPath(WHATS_APP_MEDIA_DIR));
if (!rootDir.exists() || !rootDir.isDirectory()) {
Log.e("FilePath", "Directory does not exist or is not a directory: " + rootDir.getAbsolutePath());
return new ArrayList<>();
}
// Check for read storage permission
return findFilesRecursive(rootDir, new String[]{});
}
//查找外部存储中的空文件夹
public static List<CleanFileBean> findEmptyFolders() {
File externalStorageDir = Environment.getExternalStorageDirectory();
String[] filters = new String[]{"/storage/emulated/0/Android/data", "/storage/emulated/0/Android/obb"};
List<File> emptyFolders = getAllEmptyFolders(externalStorageDir, filters);
return emptyFolders.stream()
.map(folder -> new CleanFileBean(folder.getName(), folder.getPath(), 4 * 1024))
.collect(Collectors.toList());
List<CleanFileBean> fileBeans = new ArrayList<>();
for (File folder : emptyFolders) {
fileBeans.add(new CleanFileBean(folder.getName(), folder.getPath(), 4 * 1024));
}
return fileBeans;
}
//查找外部存储目录下以.temp 为后缀的文件。
public static List<CleanFileBean> findTempFiles() {
File rootDir = Environment.getExternalStorageDirectory();
return findFilesRecursive(rootDir, new String[]{".temp"});
}
//查找外部存储目录下以.apk 和.aab 为后缀的文件。
public static List<CleanFileBean> findApkFiles() {
File rootDir = Environment.getExternalStorageDirectory();
return findFilesRecursive(rootDir, new String[]{".apk", ".aab"});
}
//查找外部存储目录下以.log 为后缀的文件。
public static List<CleanFileBean> findLogFiles() {
File rootDir = Environment.getExternalStorageDirectory();
return findFilesRecursive(rootDir, new String[]{".log"});
}
public static void deleteFile(String filePath) {
public static void deleteFile(String filePath) {
File file = new File(filePath);
deleteIfExists(file);
}
private static boolean deleteIfExists(File file) {
if (file != null && file.exists()) {
Log.d("selectedFiles","exists");
return file.delete();
}
return false;
}
private static List<CleanFileBean> findFilesRecursive(File dir, String[] suffixes) {
List<CleanFileBean> fileBeans = new ArrayList<>();
try {
return Files.walk(dir.toPath())
.filter(Files::isRegularFile)
.filter(path -> Arrays.stream(suffixes).anyMatch(suffix -> path.getFileName().toString().endsWith(suffix)))
.map(path -> {
File file = path.toFile();
return new CleanFileBean(file.getName(), file.getPath(), file.length());
})
.collect(Collectors.toList());
} catch (IOException e) {
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
fileBeans.addAll(findFilesRecursive(file, suffixes));
} else if (file.isFile() && endsWith(file.getName(), suffixes)) {
fileBeans.add(new CleanFileBean(file.getName(), file.getPath(), file.length()));
}
}
}
} catch (Exception e) {
e.printStackTrace();
return Collections.emptyList();
}
return fileBeans;
}
private boolean endsWith(File file, String[] suffixes) {
if (suffixes.length == 0) {
return true;
}
String fileName = file.getName().toLowerCase();
private static boolean endsWith(String fileName, String[] suffixes) {
for (String suffix : suffixes) {
if (fileName.endsWith(suffix.toLowerCase())) {
if (fileName.toLowerCase().endsWith(suffix.toLowerCase())) {
return true;
}
}
return false;
return suffixes.length == 0;
}
private static String getAppSpecificDirPath(String relativePath) {
......@@ -93,15 +118,44 @@ public class FileHelps {
}
private static List<File> getAllEmptyFolders(File root, String[] filters) {
List<File> emptyFolders = new ArrayList<>();
File[] files = root.listFiles();
if (files == null) {
return Collections.emptyList();
return emptyFolders;
}
return Stream.of(files)
.flatMap(file -> getAllEmptyFolders(file, filters).stream())
.filter(folder -> folder.isDirectory() &&!contains(filters, folder.getPath()) &&!folder.isHidden() && folder.list()!= null && folder.list().length == 0)
.collect(Collectors.toList());
boolean isEmpty = true;
for (File file : files) {
if (file.isDirectory()) {
emptyFolders.addAll(getAllEmptyFolders(file, filters));
isEmpty = false; // 如果子文件夹不为空,当前文件夹也不为空
} else {
isEmpty = false; // 如果文件夹中有文件,标记为非空
}
}
if (isEmpty && isTargetEmptyFolder(root, filters)) {
emptyFolders.add(root);
}
return emptyFolders;
}
private static boolean isTargetEmptyFolder(File folder, String[] filters) {
if (!folder.isDirectory()) {
return false;
}
if (contains(filters, folder.getPath())) {
return false;
}
if (folder.isHidden()) {
return false;
}
String[] files = folder.list();
return files == null || files.length == 0;
}
private static boolean contains(String[] filters, String path) {
for (String filter : filters) {
if (filter.equals(path)) {
......@@ -110,6 +164,4 @@ public class FileHelps {
}
return false;
}
}
}
\ No newline at end of file
package com.swiftcleaner.chovey.model.tool;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import java.io.File;
import android.graphics.Color;
import android.media.ThumbnailUtils;
import com.swiftcleaner.chovey.model.bean.SimilarBean;
class SimilarBeanJava {
private String url;
private String hashCode;
private int avgPixel;
private long size;
private boolean isSelect;
public SimilarBeanJava(String url, String hashCode, int avgPixel, long size, boolean isSelect) {
this.url = url;
this.hashCode = hashCode;
this.avgPixel = avgPixel;
this.size = size;
this.isSelect = isSelect;
}
import java.io.File;
public String getUrl() {
return url;
class ImageHelpers {
private Bitmap loadBitmapFromFile(String path) {
Bitmap bitmap = BitmapFactory.decodeFile(path);
if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
return null;
}
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
if (options.outHeight == -1 || options.outWidth == -1) {
return null;
}
int inSampleSize = (int) (0.5f + options.outHeight / (float) bitmap.getHeight()) >= (int) (0.5f + options.outWidth / (float) bitmap.getWidth())?
(int) (0.5f + options.outHeight / (float) bitmap.getHeight()) : (int) (0.5f + options.outWidth / (float) bitmap.getWidth());
inSampleSize++;
options.inSampleSize = inSampleSize >= 1? inSampleSize : 1;
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(path, options);
}
public void setUrl(String url) {
this.url = url;
}
SimilarBean createImage(String path) {
try {
int width = 8;
int height = 8;
Bitmap source = loadBitmapFromFile(path);
Bitmap thumb = ThumbnailUtils.extractThumbnail(source, width, height);
int[] pixels = new int[width * height];
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
pixels[i * height + j] = rgbToGray(thumb.getPixel(i, j));
}
}
public String getHashCode() {
return hashCode;
}
int avgPixel = average(pixels);
int[] comps = new int[width * height];
for (int i = 0; i < comps.length; i++) {
if (pixels[i] >= avgPixel) {
comps[i] = 1;
} else {
comps[i] = 0;
}
}
public void setHashCode(String hashCode) {
this.hashCode = hashCode;
StringBuffer hashCode = new StringBuffer();
for (int i = 0; i < comps.length; i += 4) {
int result = comps[i] * (int) Math.pow(2, 3) + comps[i + 1] * (int) Math.pow(2, 2) +
comps[i + 2] * (int) Math.pow(2, 1) + comps[i + 3];
hashCode.append(binaryToHex(result));
}
recycleBitmap(thumb);
recycleBitmap(source);
return new SimilarBean(path, hashCode.toString(), avgPixel, new File(path).length(), false);
} catch (Exception e) {
return null;
}
}
public int getAvgPixel() {
return avgPixel;
private int rgbToGray(int pixels) {
int red = (pixels >> 16) & 0xff;
int green = (pixels >> 8) & 0xff;
int blue = pixels & 0xff;
return (int) (0.3 * red + 0.59 * green + 0.11 * blue);
}
public void setAvgPixel(int avgPixel) {
this.avgPixel = avgPixel;
private int average(int[] pixels) {
int sum = 0;
for (int pixel : pixels) {
sum += pixel;
}
return sum / pixels.length;
}
public long getSize() {
return size;
private void recycleBitmap(Bitmap thumb) {
if (thumb!= null &&!thumb.isRecycled()) {
thumb.recycle();
}
}
public void setSize(long size) {
this.size = size;
boolean similarCondition(SimilarBean first, SimilarBean second) {
return hammingDistance(first.getHashCode(), second.getHashCode()) <= 6 &&
(first.getAvgPixel() / (float) second.getAvgPixel()) >= 0.8 && (first.getAvgPixel() / (float) second.getAvgPixel()) <= 1.0;
}
public boolean isSelect() {
return isSelect;
private int hammingDistance(String sourceHashCode, String hashCode) {
int difference = 0;
for (int i = 0; i < sourceHashCode.length(); i++) {
if (sourceHashCode.charAt(i)!= hashCode.charAt(i)) {
difference++;
}
}
return difference;
}
public void setSelect(boolean select) {
isSelect = select;
private char binaryToHex(int binary) {
switch (binary) {
case 0:
return '0';
case 1:
return '1';
case 2:
return '2';
case 3:
return '3';
case 4:
return '4';
case 5:
return '5';
case 6:
return '6';
case 7:
return '7';
case 8:
return '8';
case 9:
return '9';
case 10:
return 'a';
case 11:
return 'b';
case 12:
return 'c';
case 13:
return 'd';
case 14:
return 'e';
case 15:
return 'f';
default:
return ' ';
}
}
}
\ No newline at end of file
......@@ -39,7 +39,9 @@ public class MediaHelps {
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public static void updateMedia(Context context, String[] paths) {
MediaScannerConnection.scanFile(context, paths, null, null);
if (paths != null && paths.length > 0) {
MediaScannerConnection.scanFile(context, paths, null, null);
}
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
......
package com.swiftcleaner.chovey.model.tool
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import android.media.ThumbnailUtils
import com.swiftcleaner.chovey.model.bean.SimilarBean
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
import kotlin.math.pow
object SimilarFindHelper {
private fun loadBitmapFromFile(path: String): Bitmap? {
val bitmap = BitmapFactory.decodeFile(path)
if (bitmap.width <= 0 || bitmap.height <= 0) {
return null
}
val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
BitmapFactory.decodeFile(path, options)
if (options.outHeight == -1 || options.outWidth == -1) {
return null
}
var inSampleSize =
(0.5f + options.outHeight.toFloat() / bitmap.height.toFloat()).coerceAtLeast(0.5f + options.outWidth.toFloat() / bitmap.width.toFloat())
.toInt()
inSampleSize += 1
options.inSampleSize = inSampleSize.coerceAtLeast(1)
options.inJustDecodeBounds = false
return BitmapFactory.decodeFile(path, options)
}
suspend fun createImage(path: String): SimilarBean? {
return withContext(Dispatchers.IO) {
try {
val width = 8
val height = 8
val source = loadBitmapFromFile(path)
val thumb = ThumbnailUtils.extractThumbnail(source, width, height)
val pixels = IntArray(width * height)
for (i in 0 until width) {
for (j in 0 until height) {
pixels[i * height + j] = rgbToGray(thumb.getPixel(i, j))
}
}
val avgPixel = average(pixels)
val comps = IntArray(width * height)
for (i in comps.indices) {
if (pixels[i] >= avgPixel) {
comps[i] = 1
} else {
comps[i] = 0
}
}
val hashCode = StringBuffer()
var i = 0
while (i < comps.size) {
val result = comps[i] * 2.0.pow(3.0).toInt() + (comps[i + 1] * 2.0.pow(2.0)
.toInt()) + (comps[i + 2] * 2.0.pow(1.0).toInt()) + comps[i + 3]
hashCode.append(binaryToHex(result))
i += 4
}
recycleBitmap(thumb)
recycleBitmap(source)
return@withContext SimilarBean(
path,
hashCode.toString(),
avgPixel,
File(path).length(),
false
)
} catch (_: Exception) {
return@withContext null
}
}
}
private fun rgbToGray(pixels: Int): Int {
val red = Color.red(pixels)
val green = Color.green(pixels)
val blue = Color.blue(pixels)
return (0.3 * red + 0.59 * green + 0.11 * blue).toInt()
}
private fun average(pixels: IntArray): Int {
return (pixels.sumOf { it }.toFloat() / pixels.size).toInt()
}
private fun recycleBitmap(thumb: Bitmap?) {
if (thumb?.isRecycled == false) {
thumb.recycle()
}
}
fun similarCondition(first: SimilarBean, second: SimilarBean): Boolean {
return hammingDistance(
first.hashCode,
second.hashCode
) <= 6 && (first.avgPixel.toFloat() / second.avgPixel) in 0.8..1.0
}
private fun hammingDistance(sourceHashCode: String, hashCode: String): Int {
var difference = 0
for (i in sourceHashCode.indices) {
if (sourceHashCode[i] != hashCode[i]) {
difference++
}
}
return difference
}
private fun binaryToHex(binary: Int) = when (binary) {
0 -> '0'
1 -> '1'
2 -> '2'
3 -> '3'
4 -> '4'
5 -> '5'
6 -> '6'
7 -> '7'
8 -> '8'
9 -> '9'
10 -> 'a'
11 -> 'b'
12 -> 'c'
13 -> 'd'
14 -> 'e'
15 -> 'f'
else -> ' '
}
}
\ No newline at end of file
package com.swiftcleaner.chovey.model.util;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Scheduler;
import io.reactivex.rxjava3.schedulers.Schedulers;
/**
* 作者:马海钊
* 时间:2024/12/25 14:36
* 功能:
*/
public class AppSchedulers {
private static final Scheduler IO_SCHEDULER = Schedulers.io();
private static final Scheduler COMPUTATION_SCHEDULER = Schedulers.computation();
private static final Scheduler MAIN_THREAD_SCHEDULER = AndroidSchedulers.mainThread();
private AppSchedulers() {
// 避免外部实例化
}
public static Scheduler io() {
return IO_SCHEDULER;
}
public static Scheduler computation() {
return COMPUTATION_SCHEDULER;
}
public static Scheduler mainThread() {
return MAIN_THREAD_SCHEDULER;
}
}
package com.swiftcleaner.chovey.view.activity
package com.swiftcleaner.chovey.model.util
import android.app.Activity
import android.content.Intent
......
package com.zxdemo.utils
package com.swiftcleaner.chovey.util
import android.content.Intent
import androidx.activity.result.ActivityResult
......
package com.zxdemo.utils
package com.tool.elite.cleaner.utils
import android.app.Activity
import java.util.Stack
object ActivityCollector {
object ActivityManagerUtils {
private val activityStack = Stack<Activity>()
/**
......@@ -39,6 +40,17 @@ object ActivityCollector {
activityStack.clear()
}
fun finishAll(excludeCls: Class<*>?) {
val iterator = activityStack.iterator()
while (iterator.hasNext()) {
val item = iterator.next()
if (excludeCls != item.javaClass) {
item.finish()
iterator.remove()
}
}
}
/**
* 检查Activity是否存在于堆栈中
*/
......@@ -55,15 +67,13 @@ object ActivityCollector {
return isExist
}
fun getAll(): List<Activity> {
return activityStack
}
val all: List<Activity>
get() = activityStack
fun getTopActivity(): Activity? {
return if (activityStack.isEmpty()) {
val topActivity: Activity?
get() = if (activityStack.isEmpty()) {
null
} else {
activityStack.peek()
}
}
}
\ No newline at end of file
package com.zxdemo.utils;
package com.swiftcleaner.chovey.util;
import android.os.Handler;
import android.os.Looper;
......
package com.swiftcleaner.chovey.util
import android.content.Context
import android.content.SharedPreferences
import com.swiftcleaner.chovey.CleanApplication
import org.json.JSONObject
class SPUtils private constructor(context: Context) {
private val sharedPreferences: SharedPreferences = context.getSharedPreferences("app_preferences", Context.MODE_PRIVATE)
// 通用的保存数据方法
fun <T> put(key: String, value: T) {
when (value) {
is String -> putString(key, value)
is Int -> putInt(key, value)
is Long -> putLong(key, value)
is Boolean -> putBoolean(key, value)
is Float -> putFloat(key, value)
else -> throw IllegalArgumentException("Unsupported type")
}
}
// 通用的获取数据方法
fun <T> get(key: String, defaultValue: T): T {
return when (defaultValue) {
is String -> getString(key, defaultValue)
is Int -> getInt(key, defaultValue)
is Long -> getLong(key, defaultValue)
is Boolean -> getBoolean(key, defaultValue)
is Float -> getFloat(key, defaultValue)
else -> throw IllegalArgumentException("Unsupported type")
} as T
}
// 保存字符串
fun putString(key: String, value: String) {
sharedPreferences.edit().putString(getKey(key), value).apply()
}
// 获取字符串
fun getString(key: String, defaultValue: String = ""): String {
return sharedPreferences.getString(getKey(key), defaultValue) ?: defaultValue
}
// 保存整数
fun putInt(key: String, value: Int) {
sharedPreferences.edit().putInt(getKey(key), value).apply()
}
// 获取整数
fun getInt(key: String, defaultValue: Int = 0): Int {
return sharedPreferences.getInt(getKey(key), defaultValue)
}
// 保存长整数
fun putLong(key: String, value: Long) {
sharedPreferences.edit().putLong(getKey(key), value).apply()
}
// 获取长整数
fun getLong(key: String, defaultValue: Long = 0): Long {
return sharedPreferences.getLong(getKey(key), defaultValue)
}
// 保存布尔值
fun putBoolean(key: String, value: Boolean) {
sharedPreferences.edit().putBoolean(getKey(key), value).apply()
}
// 获取布尔值
fun getBoolean(key: String, defaultValue: Boolean = false): Boolean {
return sharedPreferences.getBoolean(getKey(key), defaultValue)
}
// 保存浮点数
fun putFloat(key: String, value: Float) {
sharedPreferences.edit().putFloat(getKey(key), value).apply()
}
// 获取浮点数
fun getFloat(key: String, defaultValue: Float = 0f): Float {
return sharedPreferences.getFloat(getKey(key), defaultValue)
}
// 删除键值对
fun remove(key: String) {
sharedPreferences.edit().remove(getKey(key)).apply()
}
// 清除所有数据
fun clear() {
sharedPreferences.edit().clear().apply()
}
// 检查键是否存在
fun contains(key: String): Boolean {
return sharedPreferences.contains(getKey(key))
}
// 保存 JSON 对象
fun saveJsonObjectToSp(json: String) {
try {
if (json.isEmpty()) return
val jsonObject = JSONObject(json)
sharedPreferences.edit().apply {
jsonObject.keys().forEachRemaining { key ->
val value = jsonObject.get(key)
val newKey = getKey(key)
when (value) {
is String -> putString(newKey, value)
is Int -> putInt(newKey, value)
is Long -> putLong(newKey, value)
is Boolean -> putBoolean(newKey, value)
else -> putString(newKey, value.toString())
}
}
apply()
}
} catch (_:Exception) { }
}
fun <T> getValue(key: String, default: T): T {
val newKey = getKey(key)
val value = get(newKey, default)
return value
}
private fun getKey(key: String): String = "${CleanApplication.context.packageName}_$key"
companion object {
@Volatile
private var instance: SPUtils? = null
// 单例模式获取实例
fun getInstance(): SPUtils {
return instance ?: synchronized(this) {
instance ?: SPUtils(CleanApplication.context).also {
instance = it
}
}
}
}
}
\ No newline at end of file
package com.zxdemo.utils;
package com.swiftcleaner.chovey.util;
public interface TimeOut {
abstract void timeOut(String where);
......
package com.swiftcleaner.chovey.view;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.viewbinding.ViewBinding;
import com.swiftcleaner.chovey.view.dialog.AppExitDialog;
import io.reactivex.rxjava3.annotations.Nullable;
/**
* 作者:马海钊
* 时间:2025/1/2 11:54
* 功能:
*/
public abstract class BaseActivity<VB extends ViewBinding> extends AppCompatActivity {
protected VB binding;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = getViewBinding();
setContentView(binding.getRoot());
initView();
initData();
}
protected abstract VB getViewBinding();
protected abstract void initView();
protected abstract void initData();
}
package com.swiftcleaner.chovey.view;
import android.app.Application;
public class ExampleApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
}
}
......@@ -46,7 +46,7 @@ public class ScreenShotAdapter extends RecyclerView.Adapter<ScreenShotAdapter.Vi
if (item != null && item.getPath() != null) {
Glide.with(holder.itemView.getContext())
.load(item.getIcon1())
.load(item.getPath())
.placeholder(R.mipmap.h_appmanager)
.error(R.mipmap.err_img)
.apply(RequestOptions.bitmapTransform(new RoundedCorners(20)))
......
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