Commit 30c6fcf6 authored by Your Name's avatar Your Name

..

parent 730a6b81
Pipeline #1605 canceled with stages
*.iml
.gradle
/local.properties
.idea
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
/build
\ No newline at end of file
plugins {
id("com.android.application")
id("com.google.gms.google-services")
alias(libs.plugins.kotlin.android)
id("com.google.firebase.crashlytics")
}
android {
namespace = "com.file.clean.q"
compileSdk = 34
defaultConfig {
applicationId = "confine.scream"
minSdk = 28
targetSdk = 34
versionCode = 3
versionName = "1.2"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
// 添加签名配置
signingConfigs {
create("release") {
storeFile = file("../my-release-key.keystore")
storePassword = "123456"
keyAlias = "my_alias"
keyPassword = "123456"
}
}
buildTypes {
release {
isMinifyEnabled = false
signingConfig = signingConfigs.getByName("release")
// 设置是否要自动上传
firebaseCrashlytics {
mappingFileUploadEnabled = true
}
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
buildFeatures {
viewBinding = true
buildConfig = true
}
dependenciesInfo {
includeInApk = true
}
kotlinOptions {
jvmTarget = "1.8"
}
}
gradle.taskGraph.whenReady {
tasks.forEach { task ->
if (task.name.contains("uploadCrashlyticsMappingFile")) {
task.enabled = false
}
}
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.material)
implementation(libs.androidx.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)
implementation(libs.applovin)
implementation(libs.applovin.google)
implementation(libs.applovin.admob)
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": "135677224109",
"project_id": "scream-1bc70",
"storage_bucket": "scream-1bc70.firebasestorage.app"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:135677224109:android:5e3fd3c8cfce99d40a1224",
"android_client_info": {
"package_name": "confine.scream"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyBIUMrPv6OOQWBheBAylXU94bkwvZgBFsg"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}
\ No newline at end of file
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
\ No newline at end of file
package com.scream.qq;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.scream.scream.qq", appContext.getPackageName());
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<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.POST_NOTIFICATIONS" />
<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>
<action android:name="android.intent.action.MAIN" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
</intent>
</queries>
<application
android:name="com.file.clean.q.CleanApplication"
android:requestLegacyExternalStorage="true"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/logo2"
android:label="@string/app_name"
android:roundIcon="@mipmap/logo2"
android:supportsRtl="true"
android:theme="@style/Theme.SwiftCleanerPhoneHelper"
tools:targetApi="31">
<!-- <receiver-->
<!-- android:name="自定义 Receiver"-->
<!-- android:enabled="true"-->
<!-- android:exported="false" >-->
<!-- <intent-filter>-->
<!-- <action android:name="cn.jpush.android.intent.RECEIVER_MESSAGE" />-->
<!-- <category android:name="com.clean_swift" />-->
<!-- </intent-filter>-->
<!-- </receiver>-->
<activity
android:name=".view.activity.StartActivity"
android:exported="true"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".view.activity.SimilarPhotosActivity"
android:exported="false" />
<activity
android:name=".view.activity.WhatsAppActivity"
android:exported="false" />
<activity
android:name=".view.activity.ScreenShotActivity"
android:exported="false" />
<activity
android:name=".view.activity.LargeFileActivity"
android:exported="false" />
<activity
android:name=".view.activity.AppProcessActivity"
android:exported="false" />
<activity
android:name=".view.activity.InfoActivity"
android:exported="false" />
<activity
android:name=".view.activity.BatteryInfoActivity"
android:exported="false" />
<activity
android:name=".view.activity.EndCleanJunkActivity"
android:exported="false" />
<activity
android:name=".view.activity.SettingActivity"
android:exported="false" />
<activity
android:name=".view.activity.CleanJunkActivity"
android:exported="false" />
<activity
android:name=".view.MainActivity"
android:exported="false"
android:launchMode="singleTop" /> <!-- 今日头条autosize 屏幕适配 -->
<meta-data
android:name="design_width_in_dp"
android:value="360" />
<meta-data
android:name="design_height_in_dp"
android:value="640" /> <!-- 适配华为(huawei)刘海屏 沉浸式 -->
<meta-data
android:name="android.notch_support"
android:value="true" /> <!-- 适配小米(xiaomi)刘海屏 -->
<meta-data
android:name="notch.config"
android:value="portrait|landscape" />
<meta-data
android:name="android.max_aspect"
android:value="2.4" />
<receiver android:name=".business.push.NotificationCancelReceiver" />
<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=".business.push.FirebaseService"
android:directBootAware="true"
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=".business.push.FirebaseReceiver"
android:directBootAware="true"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<category android:name="com.tool.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-9723053978060994~5799767410" />-->
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-3940256099942544~3347511713" />
<meta-data
android:name="com.facebook.sdk.ApplicationId"
android:value="@string/facebook_app_id" />
</application>
</manifest>
\ No newline at end of file
package com.file.clean.q
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.bumptech.glide.Glide
import com.reyun.solar.engine.OnAttributionListener
import com.reyun.solar.engine.SolarEngineConfig
import com.reyun.solar.engine.SolarEngineManager
import com.tool.elite.battery.business.push.BadgeUtils
import com.file.clean.q.GlobalConfig.KEY_APP_BACKGROUND
import com.file.clean.q.GlobalConfig.KEY_INIT
import com.file.clean.q.GlobalConfig.KEY_INSTALL_TIME
import com.file.clean.q.GlobalConfig.KEY_SOLAR
import com.file.clean.q.GlobalConfig.KEY_UUID
import com.file.clean.q.business.BlacklistUtils
import com.file.clean.q.business.DeviceUtils
import com.file.clean.q.business.push.FirebaseUtils
import com.file.clean.q.business.InstallReferrerUtils
import com.file.clean.q.business.EventUtils
import com.file.clean.q.business.admob.AdmobManager
import com.file.clean.q.business.push.Alarm2Receiver
import com.file.clean.q.business.push.AlarmReceiver
import com.file.clean.q.business.push.MyWorker
import com.file.clean.q.business.push.NotificationJobService.Companion.startNotification
import com.file.clean.q.business.push.PackageReceiver
import com.file.clean.q.business.push.UnlockReceiver
import com.file.clean.q.util.SPUtils
import com.file.clean.q.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()
Thread { Glide.get(this).clearDiskCache() }.start()
}
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.file.clean.q
import com.file.clean.q.util.SPUtils
import com.file.clean.q.view.activity.StartActivity
object GlobalConfig {
// 包名
const val PACKAGE_NAME = "com.cleaner.scream.qq.bb"
// 域名
/**
* Url Event 上报接口
*/
const val URL_EVENT = "https://rp.sekanderali32.xyz"
/**
* Url Api 业务接口
*/
const val URL_API = "https://api.sekanderali32.xyz"
/**
* Url Privacy 隐私链接
*/
const val URL_PRIVACY = "https://sites.google.com/view/ppsitese/home"
/**
* Url Agreement 用户协议
*/
const val URL_AGREEMENT = "https://sites.google.com/view/terms-junk-for/home"
/**
* Url Use 使用条款
*/
const val URL_USE = "https://sites.google.com/view/terms-junk-for/home"
/**
* Key Aes 加密key
*/
const val KEY_AES = "cuymzfujthzsz1xo"
/**
* Key solar 归因key
*/
const val KEY_SOLAR = "7733f8bfcbde264c"
//测试广告id
// admob广告位id
inline val ID_ADMOB_OPEN get() = "/6499/example/app-open"
inline val ID_ADMOB_INTER get() = "ca-app-pub-3940256099942544/1033173712"
inline val ID_ADMOB_NATIVE get() = "ca-app-pub-3940256099942544/2247696110"
inline val ID_ADMOB_BANNER get() = "ca-app-pub-3940256099942544/9214589741"
inline val ID_ADMOB_REWARD get() = "None"
// max广告位id
inline val ID_MAX_OPEN get() = "None"
inline val ID_MAX_INTER get() = "None"
inline val ID_MAX_NATIVE get() = "None"
inline val ID_MAX_BANNER get() = "None"
inline val ID_MAX_REWARD get() = "None"
/**
* Key MAX MAX广告初始化key
*/
const val KEY_MAX = "GGPreND6SRmCt1zJgn5faiLGD8c2PVGPLgPpSg7cHanVTud1DhtuI9MmteTqlEviaJ57WnxW68kQDaATJ5z3cW"
//正式广告id
// // 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.file.clean.q.business
import android.util.Base64
import com.file.clean.q.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.file.clean.q.business
import com.file.clean.q.GlobalConfig.PACKAGE_NAME
import com.file.clean.q.GlobalConfig.URL_API
import com.file.clean.q.BuildConfig
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()
//url放在这里
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.file.clean.q.business
import android.util.Base64
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.file.clean.q.GlobalConfig.KEY_GOOGLE_ADVERTISER_ID
import com.file.clean.q.GlobalConfig.KEY_REFERRER
import com.file.clean.q.GlobalConfig.KEY_UUID
import com.file.clean.q.GlobalConfig.PACKAGE_NAME
import com.file.clean.q.GlobalConfig.URL_API
import com.file.clean.q.util.SPUtils
import com.file.clean.q.BuildConfig
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()
//url 放在这里
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.file.clean.q.business
import android.os.Build
import android.util.Log
import com.google.android.gms.ads.identifier.AdvertisingIdClient
import com.file.clean.q.CleanApplication
import com.file.clean.q.GlobalConfig.KEY_DEVICE_NAME
import com.file.clean.q.GlobalConfig.KEY_GOOGLE_ADVERTISER_ID
import com.file.clean.q.GlobalConfig.KEY_UUID
import com.file.clean.q.GlobalConfig.PACKAGE_NAME
import com.file.clean.q.util.SPUtils
import com.file.clean.q.BuildConfig
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.file.clean.q.business
import android.util.Log
import com.file.clean.q.GlobalConfig.PACKAGE_NAME
import com.file.clean.q.GlobalConfig.URL_EVENT
import com.file.clean.q.GlobalConfig.isInit
import com.file.clean.q.BuildConfig
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()
//url放在这里
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) {
Log.e("tttttttttttt",json)
}
})
}
}
\ No newline at end of file
package com.file.clean.q.business
import com.android.installreferrer.api.InstallReferrerClient
import com.android.installreferrer.api.InstallReferrerStateListener
import com.file.clean.q.CleanApplication
import com.file.clean.q.GlobalConfig.KEY_REFERRER
import com.file.clean.q.business.push.PushTimer
import com.file.clean.q.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.file.clean.q.business.admob
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.LayoutRes
import com.google.android.gms.ads.nativead.NativeAd
import com.google.android.gms.ads.nativead.NativeAdView
import com.file.clean.q.R
class NativeView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
): FrameLayout(context, attrs) {
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)
adView.iconView = adView.findViewById(R.id.ad_icon)
(adView.headlineView as TextView?)?.text = nativeAd.headline
adView.mediaView!!.mediaContent = nativeAd.mediaContent
if (nativeAd.callToAction != null) {
(adView.callToActionView as TextView?)?.text = nativeAd.callToAction
}
if (nativeAd.icon != null) {
(adView.iconView as ImageView?)?.setImageDrawable(nativeAd.icon!!.drawable)
}
adView.setNativeAd(nativeAd)
removeAllViews()
addView(adView)
}
}
\ No newline at end of file
package com.file.clean.q.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.file.clean.q.CleanApplication
import com.file.clean.q.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.file.clean.q.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.file.clean.q.CleanApplication
import com.file.clean.q.business.EventUtils
import com.file.clean.q.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.file.clean.q.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.file.clean.q.CleanApplication
import com.file.clean.q.util.SPUtils
import com.file.clean.q.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.file.clean.q.business.push
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import com.file.clean.q.CleanApplication
import com.file.clean.q.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.file.clean.q.business.push
import android.annotation.SuppressLint
import android.util.Log
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import com.file.clean.q.CleanApplication
import com.file.clean.q.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.file.clean.q.business.push
import android.content.Context
import android.util.Log
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
object FirebaseUtils {
fun initFirebase(context: Context) {
FirebaseApp.initializeApp(context)
}
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 { task ->
if (task.isSuccessful) {
} else {
}
}
}
fun getToken(complete: ((String) -> Unit?)? = null) {
FirebaseMessaging.getInstance().token
.addOnCompleteListener(object : OnCompleteListener<String> {
override fun onComplete(task: Task<String>) {
if (!task.isSuccessful) {
Log.d("FCM", "Fetching FCM registration token failed", task.exception)
return
}
// Get new FCM registration token
val token: String = task.result
complete?.invoke(token)
// Handle new token
Log.d("FCM", "FCM Registration Token: $token")
}
})
}
}
\ No newline at end of file
package com.file.clean.q.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.file.clean.q.CleanApplication
import com.file.clean.q.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.file.clean.q.business.push
import com.file.clean.q.CleanApplication
import com.file.clean.q.GlobalConfig.ID_NOT_CLEANED
import com.file.clean.q.GlobalConfig.KEY_CLEAN_LAST_TIME
import com.file.clean.q.GlobalConfig.KEY_INSTALL_TIME
import com.file.clean.q.GlobalConfig.KEY_NOT_CLEAN_TIME
import com.file.clean.q.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.file.clean.q.business.push
import android.app.NotificationManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.file.clean.q.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.file.clean.q.business.push
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.file.clean.q.business.push.NotificationJobService.Companion.startNotification
class NotificationJobReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action.isNullOrEmpty()) return
context?.startNotification()
}
}
\ No newline at end of file
package com.file.clean.q.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.file.clean.q.GlobalConfig.ID_APP_PROCESS
import com.file.clean.q.GlobalConfig.ID_BATTERY_INFO
import com.file.clean.q.GlobalConfig.ID_CLEAN_JUNK
import com.file.clean.q.GlobalConfig.ID_LARGE_FILE_CLEANER
import com.file.clean.q.R
import com.file.clean.q.view.activity.AppProcessActivity
import com.file.clean.q.view.activity.CleanJunkActivity
import com.file.clean.q.view.activity.InfoActivity
import com.file.clean.q.view.activity.LargeFileActivity
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.file.clean.q.business.push
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import com.file.clean.q.CleanApplication
import com.file.clean.q.GlobalConfig.ID_APP_INSTALL
import com.file.clean.q.GlobalConfig.ID_APP_UNINSTALL
import com.file.clean.q.util.SPUtils
import com.file.clean.q.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.file.clean.q.business.push
import com.file.clean.q.CleanApplication
import com.file.clean.q.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.file.clean.q.business.push
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import com.file.clean.q.CleanApplication
import com.file.clean.q.util.SPUtils
import com.file.clean.q.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.file.clean.q.model.bean;
import android.graphics.drawable.Drawable;
public class AppInfo {
private Drawable icon;
private String appName;
private long memoryUsage;
public AppInfo(Drawable icon, String appName, long memoryUsage) {
this.icon = icon;
this.appName = appName;
this.memoryUsage = memoryUsage;
}
public Drawable getIcon() {
return icon;
}
public void setIcon(Drawable icon) {
this.icon = icon;
}
public String getAppName() {
return appName;
}
public void setAppName(String appName) {
this.appName = appName;
}
public long getMemoryUsage() {
return memoryUsage;
}
public void setMemoryUsage(long memoryUsage) {
this.memoryUsage = memoryUsage;
}
}
package com.file.clean.q.model.bean;
import android.graphics.drawable.Drawable;
public class AppInfoBean {
private String appName;
private Drawable appIcon;
private String packageName;
public AppInfoBean(String appName, Drawable appIcon, String packageName) {
this.appName = appName;
this.appIcon = appIcon;
this.packageName = packageName;
}
// Getters and setters
public String getAppName() {
return appName;
}
public void setAppName(String appName) {
this.appName = appName;
}
public Drawable getAppIcon() {
return appIcon;
}
public void setAppIcon(Drawable appIcon) {
this.appIcon = appIcon;
}
public String getPackageName() {
return packageName;
}
public void setPackageName(String packageName) {
this.packageName = packageName;
}
}
package com.file.clean.q.model.bean;
import android.graphics.Bitmap;
import java.util.List;
public class CleanFileBean {
private String name;
private String path;
private int icon;
private Bitmap icon1;
private long length;
private long lastModified;
private String mineType;
private boolean isSelected;
private boolean isScanning;
private int state;
private int type;
private List<CleanFileBean> items;
public CleanFileBean(String name) {
this(name, "", 0, 0, 0, "", false, true, 0, 0, null);
}
public CleanFileBean() {
}
public CleanFileBean(String name, String path, String mineType) {
this.name = name;
this.path = path;
this.mineType = mineType;
}
public CleanFileBean(String name, String path, long length) {
this.name = name;
this.path = path;
this.length = length;
}
public CleanFileBean(String name, String path, Bitmap icon1, long length) {
this.name = name;
this.path = path;
this.icon1 = icon1;
this.length = length;
}
public CleanFileBean(String name, Bitmap icon1, long length) {
this.name = name;
this.icon1 = icon1;
this.length = length;
}
public CleanFileBean(String name, String path, int icon1, long length, long lastModified, String mineType, boolean isSelected, boolean isScanning, int state, int type, List<CleanFileBean> items) {
this.name = name;
this.path = path;
this.icon = icon1;
this.length = length;
this.lastModified = lastModified;
this.mineType = mineType;
this.isSelected = isSelected;
this.isScanning = isScanning;
this.state = state;
this.type = type;
this.items = items;
}
public CleanFileBean(String name, String path, int icon, long length, long lastModified, String mineType) {
this.name = name;
this.path = path;
this.icon = icon;
this.length = length;
this.lastModified = lastModified;
this.mineType = mineType;
}
public CleanFileBean(Bitmap icon1, Long length,String path) {
this.icon1=icon1;
this.length = length;
this.path = path;
}
// public CleanFileBean(String displayName, String filePath, Bitmap icon1, long size, long dateModified, String mimeType) {
// this.name = displayName;
// this.path = filePath;
// this.icon1 = icon1;
// this.lastModified = dateModified;
// this.mineType = mimeType;
// }
public CleanFileBean(String name, String path, Bitmap icon1, long length, long lastModified, String mineType) {
this.name = name;
this.path = path;
this.icon1 = icon1;
this.length = length;
this.lastModified = lastModified;
this.mineType = mineType;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public int getIcon() {
return icon;
}
public void setIcon(int icon) {
this.icon = icon;
}
public long getLength() {
return length;
}
public void setLength(long length) {
this.length = length;
}
public long getLastModified() {
return lastModified;
}
public void setLastModified(long lastModified) {
this.lastModified = lastModified;
}
public String getMineType() {
return mineType;
}
public void setMineType(String mineType) {
this.mineType = mineType;
}
public boolean isSelected() {
return isSelected;
}
public void setSelected(boolean selected) {
isSelected = selected;
}
public boolean isScanning() {
return isScanning;
}
public void setScanning(boolean scanning) {
isScanning = scanning;
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public List<CleanFileBean> getItems() {
return items;
}
public void setItems(List<CleanFileBean> items) {
this.items = items;
}
@Override
public String toString() {
return "FileBean{" +
"name='" + name + '\'' +
", path='" + path + '\'' +
", icon=" + icon +
", length=" + length +
", lastModified=" + lastModified +
", mineType='" + mineType + '\'' +
", isSelected=" + isSelected +
", isScanning=" + isScanning +
", state=" + state +
", type=" + type +
", items=" + items +
'}';
}
public Bitmap getIcon1() {
return icon1;
}
public void setIcon1(Bitmap icon1) {
this.icon1 = icon1;
}
}
package com.file.clean.q.model.bean;
public class DialogLargeFileBean {
String name;
Boolean isChecked;
public DialogLargeFileBean(String name, Boolean isChecked) {
this.name = name;
this.isChecked = isChecked;
}
public DialogLargeFileBean() {
}
public DialogLargeFileBean(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Boolean getChecked() {
return isChecked;
}
public void setChecked(Boolean checked) {
isChecked = checked;
}
}
package com.file.clean.q.model.bean;
public class Global {
public static String FUNCTION_CLEAN = "function_clean";
public static String FUNCTION_BATTERY = "function_battery";
public static String FUNCTION_LARGE_FILE = "function_large_file";
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_SCREENSHOT = "function_screenshot";
public static String NOTIFICATION_WHERE_UNLOCK = "notification_where_unlock";
public static String NOTIFICATION_WHERE_FCM = "notification_where_fcm";
public static String NOTIFICATION_WHERE_INSTALL = "notification_where_install";
public static String NOTIFICATION_WHERE_TIMBER = "notification_where_timber";
public static String NOTIFICATION_WHERE_BATTERY = "notification_where_battery";
//配置
public static String OpenLoadTime = "OpenLoadTime";
public static String NotificationStayStatus = "NotificationStayStatus";//int
public static String NotificationStayCount = "NotificationStayCount";//int
public static String NotificationStayDelay = "NotificationStayDelay";//int
public static String timerStatus = "timerStatus";//int
public static String timerDelay = "timerDelay";//int
public static String timerInterval = "timerInterval";//int
public static String lockNotificationStatus = "lockNotificationStatus";
public static String batteryNotificationStatus = "batteryNotificationStatus";
public static String NotificationInterval = "notificationInterval";
}
package com.file.clean.q.model.bean;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
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;
/**
* 作者:马海钊
* 时间:2024/12/18 16:47
* 功能:
*/
public class ImageHelpers {
// 加载 Bitmap 从文件路径
private static Bitmap loadBitmapFromFile(String path) {
Bitmap bitmap = BitmapFactory.decodeFile(path);
if (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 static 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));
}
}
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;
}
}
StringBuilder hashCode = new StringBuilder();
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);
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;
}
}
// 将 RGB 值转换为灰度值
private static int rgbToGray(int pixels) {
int red = Color.red(pixels);
int green = Color.green(pixels);
int blue = Color.blue(pixels);
return (int) (0.3 * red + 0.59 * green + 0.11 * blue);
}
// 计算像素数组的平均值
private static int average(int[] pixels) {
int sum = 0;
for (int pixel : pixels) {
sum += pixel;
}
return sum / pixels.length;
}
// 回收 Bitmap
private static void recycleBitmap(Bitmap thumb) {
if (thumb != null && !thumb.isRecycled()) {
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;
}
// 计算汉明距离
private static 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;
}
// 将二进制转换为十六进制
private static 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 ' ';
}
}
}
package com.file.clean.q.model.bean;
import android.graphics.Bitmap;
public class MeCleanBean {
private int id;
private int icon;
private Bitmap icon1;
private String name;
private String path;
private String size;
private boolean isChecked;
public MeCleanBean(int icon, String name, String size) {
this.icon = icon;
this.name = name;
this.size = size;
}
public MeCleanBean(Bitmap icon1, String name, String size) {
this.icon1 = icon1;
this.name = name;
this.size = size;
}
public MeCleanBean(int icon, String name, String path, String size, boolean isChecked) {
this.icon = icon;
this.name = name;
this.path = path;
this.size = size;
this.isChecked = isChecked;
}
public MeCleanBean(int id, boolean isChecked) {
this.id = id;
this.isChecked = isChecked;
}
public MeCleanBean(String name, String path, String size) {
this.name = name;
this.path = path;
this.size = size;
}
public Bitmap getIcon1() {
return icon1;
}
public void setIcon1(Bitmap icon1) {
this.icon1 = icon1;
}
public int getIcon() {
return icon;
}
public void setIcon(int icon) {
this.icon = icon;
}
public MeCleanBean(int icon, String name, String path, String size) {
this.icon = icon;
this.name = name;
this.path = path;
this.size = size;
}
public MeCleanBean(int icon, String name) {
this.icon = icon;
this.name = name;
}
public MeCleanBean(String size, boolean isChecked) {
this.size = size;
this.isChecked = isChecked;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getSize() {
return size;
}
public void setSize(String size) {
this.size = size;
}
public boolean isChecked() {
return isChecked;
}
public void setChecked(boolean checked) {
isChecked = checked;
}
@Override
public String toString() {
return "MeCleanBean{" +
"id=" + id +
", isChecked=" + isChecked +
'}';
}
}
package com.file.clean.q.model.bean;
import android.graphics.Bitmap;
public class ScreenShotBean {
private int icon;
private String size;
private Boolean isChecked = false;
private Bitmap icon1;
private String path;
public ScreenShotBean(int icon, String size, Boolean isChecked) {
this.icon = icon;
this.size = size;
this.isChecked = isChecked;
}
public ScreenShotBean(int icon, String size) {
this.icon = icon;
this.size = size;
}
public ScreenShotBean(String size, Bitmap icon1,String path) {
this.size = size;
this.icon1 = icon1;
this.path=path;
}
public Bitmap getIcon1() {
return icon1;
}
public void setIcon1(Bitmap icon1) {
this.icon1 = icon1;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public int getIcon() {
return icon;
}
public void setIcon(int icon) {
this.icon = icon;
}
public String getSize() {
return size;
}
public void setSize(String size) {
this.size = size;
}
public Boolean getChecked() {
return isChecked;
}
public void setChecked(Boolean checked) {
isChecked = checked;
}
}
package com.file.clean.q.model.bean;
import java.util.ArrayList;
import java.util.List;
public class SimilarBean {
private String url;
private String hashCode;
private int avgPixel;
private long size;
private boolean isSelect;
private String groupID; // 新增字段,用于标识分组
private List<SimilarBean> items;
// Constructor
public SimilarBean(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;
this.items = new ArrayList<>();
}
public void addItem(SimilarBean item) {
this.items.add(item);
};
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;
}
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;
}
public List<SimilarBean> getItems() {
return items;
}
public void setItems(List<SimilarBean> items) {
this.items = items;
}
// Method to add a SimilarBean to the list
@Override
public String toString() {
return "SimilarBean{" +
"id=" + getGroupID() +
", items=" + (items != null ? items.size() : "null") + // 避免递归
"}";
}
}
\ No newline at end of file
package com.file.clean.q.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.file.clean.q.model.bean;
public class bean {
String name;
int num;
public bean(String name, int num) {
this.name = name;
this.num = num;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
}
package com.file.clean.q.model.tool;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import java.util.List;
public class AppUtils {
public static int getNonSystemAppCount(Context context) {
PackageManager packageManager = context.getPackageManager();
List<ApplicationInfo> installedApps = packageManager.getInstalledApplications(PackageManager.GET_META_DATA);
int nonSystemAppCount = 0;
for (ApplicationInfo appInfo : installedApps) {
if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
// 非系统应用
nonSystemAppCount++;
}
}
return nonSystemAppCount;
}
}
package com.file.clean.q.model.tool;
import android.os.Environment;
import com.file.clean.q.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.file.clean.q.model.tool;
/**
* 作者:马海钊
* 时间:2024/12/11 21:45
* 功能:
*/
import android.os.Handler;
import android.os.Looper;
import java.io.File;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FileDeleter {
private ExecutorService executorService;
public FileDeleter() {
this.executorService = Executors.newCachedThreadPool();
}
public FileDeleter deleteFiles(List<String> filePaths, OnFilesDeletedListener listener) {
executorService.execute(() -> {
for (String filePath : filePaths) {
File file = new File(filePath);
if (file.exists() && file.delete()) {
// 文件删除成功,立即回调
new Handler(Looper.getMainLooper()).post(() -> {
if (listener != null) {
listener.onFileDeleted(true, filePath);
}
});
} else {
// 文件删除失败,立即回调
new Handler(Looper.getMainLooper()).post(() -> {
if (listener != null) {
listener.onFileDeleted(false, filePath);
}
});
}
}
});
return this; // 返回当前实例
}
public void shutdown() {
executorService.shutdown(); // 关闭线程池
}
public interface OnFilesDeletedListener {
void onFileDeleted(boolean success, String filePath);
}
}
\ No newline at end of file
package com.file.clean.q.model.tool;
import android.os.Environment;
import android.util.Log;
import com.file.clean.q.model.bean.CleanFileBean;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
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);
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) {
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 {
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 fileBeans;
}
private static boolean endsWith(String fileName, String[] suffixes) {
for (String suffix : suffixes) {
if (fileName.toLowerCase().endsWith(suffix.toLowerCase())) {
return true;
}
}
return suffixes.length == 0;
}
private static String getAppSpecificDirPath(String relativePath) {
return Environment.getExternalStorageDirectory() + "/" + relativePath;
}
private static List<File> getAllEmptyFolders(File root, String[] filters) {
List<File> emptyFolders = new ArrayList<>();
File[] files = root.listFiles();
if (files == null) {
return emptyFolders;
}
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)) {
return true;
}
}
return false;
}
}
\ No newline at end of file
package com.file.clean.q.model.tool;
public class FileSizeUtil {
// 定义单位
private static final long KB = 1024;
private static final long MB = KB * 1024;
private static final long GB = MB * 1024;
public static String getAutoFileSize(long length) {
if (length < KB) {
return length + " B";
} else if (length < MB) {
return String.format("%.2f KB", (double) length / KB);
} else if (length < GB) {
return String.format("%.2f MB", (double) length / MB);
} else {
return String.format("%.2f GB", (double) length / GB);
}
}
}
package com.file.clean.q.model.tool;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 作者:马海钊
* 时间:2024/12/17 19:12
* 功能:
*/
public class FileTypes {
// 图像后缀列表(可变)
public static final List<String> images = new ArrayList<>(Arrays.asList(".jpeg", ".jpg", ".png", ".gif", ".webp", ".ico", ".raw"));
// 视频后缀列表(可变)
public static final List<String> videos = new ArrayList<>(Arrays.asList(".mp4", ".avi", ".mov", ".wmv", ".flv"));
// 音频后缀列表(可变)
public static final List<String> audios = new ArrayList<>(Arrays.asList(".mp3", ".wav", ".m4a", ".ncm"));
}
This diff is collapsed.
package com.file.clean.q.model.tool;
import android.content.Context;
import android.os.StatFs;
import java.io.File;
public class MemoryUtils {
// 获取手机磁盘总内存
public static String getTotalDiskMemory(Context context) {
File path = context.getFilesDir().getAbsoluteFile();
StatFs statFs = new StatFs(path.getPath());
long blockSize = statFs.getBlockSizeLong();
long totalBlocks = statFs.getBlockCountLong();
long totalMemory = totalBlocks * blockSize;
return String.format("%.1f", totalMemory / 1024f / 1024f / 1024f) + " GB"; // Convert to GB and format to one decimal place
}
// 获取手机磁盘已用内存
public static String getUsedDiskMemory(Context context) {
File path = context.getFilesDir().getAbsoluteFile();
StatFs statFs = new StatFs(path.getPath());
long blockSize = statFs.getBlockSizeLong();
long totalBlocks = statFs.getBlockCountLong();
long availableBlocks = statFs.getAvailableBlocksLong();
long totalMemory = totalBlocks * blockSize;
long availableMemory = availableBlocks * blockSize;
long usedMemory = totalMemory - availableMemory; // Calculate used memory
return String.format("%.1f", usedMemory / 1024f / 1024f / 1024f) + " GB"; // Convert to GB and format to one decimal place
}
public static String getUsedDiskMemoryPercent(Context context) {
File path = context.getFilesDir().getAbsoluteFile();
StatFs statFs = new StatFs(path.getPath());
long blockSize = statFs.getBlockSizeLong();
long totalBlocks = statFs.getBlockCountLong();
long availableBlocks = statFs.getAvailableBlocksLong();
long totalMemory = totalBlocks * blockSize;
long availableMemory = availableBlocks * blockSize;
long usedMemory = totalMemory - availableMemory; // Calculate used memory
// Calculate the percentage
double usedMemoryPercent = ((double) usedMemory / (double) totalMemory) * 100;
// Format the percentage to an integer
return String.format("%d", (int) usedMemoryPercent);
}
}
This diff is collapsed.
package com.file.clean.q.model.tool;
import android.content.Context;
import android.widget.Toast;
public class ToastUtil {
public static void showShortToast(FileDeleter.OnFilesDeletedListener context, String message) {
Toast.makeText((Context) context, message, Toast.LENGTH_SHORT).show();
}
public static void showLongToast(Context context, String message) {
Toast.makeText(context, message, Toast.LENGTH_LONG).show();
}
}
This diff is collapsed.
package com.file.clean.q.model.util
import android.app.Activity
import android.content.Intent
import com.file.clean.q.view.MainActivity
object FinishMain {
fun finishToMain(activity: Activity) {
activity.startActivity(Intent(activity, MainActivity::class.java))
activity.finish()
}
}
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
package com.file.clean.q.util;
public interface TimeOut {
abstract void timeOut(String where);
}
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment