Initial commit

This commit is contained in:
2021-09-04 04:26:07 +08:00
commit d4202e605a
49 changed files with 2543 additions and 0 deletions

1
app/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

70
app/build.gradle Normal file
View File

@@ -0,0 +1,70 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
}
android {
signingConfigs {
debug {
storeFile file('/Users/fankes/ProjectPath/AndroidStudioProjects/TSBattery/keystore/public')
storePassword '123456'
keyAlias 'public'
keyPassword '123456'
v1SigningEnabled true
v2SigningEnabled true
}
}
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.fankes.tsbattery"
minSdkVersion 22
//noinspection ExpiredTargetSdkVersion,OldTargetApi
targetSdkVersion 26
versionCode 5
versionName "2.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled true
signingConfig signingConfigs.debug
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
compileOnly 'de.robv.android.xposed:api:82'
// 基础依赖包,必须要依赖
implementation 'com.gyf.immersionbar:immersionbar:3.0.0'
// fragment快速实现可选
implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0'
// kotlin扩展可选
implementation 'com.gyf.immersionbar:immersionbar-ktx:3.0.0'
//noinspection GradleDependency,DifferentStdlibGradleVersion
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
//noinspection GradleDependency
implementation 'androidx.core:core-ktx:1.5.0'
//noinspection GradleDependency
implementation 'androidx.appcompat:appcompat:1.3.0'
//noinspection GradleDependency
implementation 'com.google.android.material:material:1.3.0'
//noinspection GradleDependency
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.13.2'
//noinspection GradleDependency
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
//noinspection GradleDependency
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}

48
app/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,48 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-dontwarn
-ignorewarnings
-optimizationpasses 10
-dontusemixedcaseclassnames
-dontoptimize
-verbose
-overloadaggressively
-repackageclasses o
-allowaccessmodification
-adaptclassstrings
-adaptresourcefilenames
-adaptresourcefilecontents
#-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
-renamesourcefileattribute H
-keepattributes SourceFile,LineNumberTable
-keep class android.support**
-keep class androidx**
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference

BIN
app/release/app-release.apk Normal file

Binary file not shown.

View File

@@ -0,0 +1,18 @@
{
"version": 2,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "com.fankes.tsbattery",
"variantName": "processReleaseResources",
"elements": [
{
"type": "SINGLE",
"filters": [],
"versionCode": 5,
"versionName": "2.2",
"outputFile": "app-release.apk"
}
]
}

View File

@@ -0,0 +1,24 @@
package com.fankes.tsbattery
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.fankes.tsbattery", appContext.packageName)
}
}

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.fankes.tsbattery">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.TSBattery"
tools:ignore="AllowBackup">
<!-- 是否是xposed模块 -->
<meta-data
android:name="xposedmodule"
android:value="true" />
<!-- 模块描述 -->
<meta-data
android:name="xposeddescription"
android:value="抵制毒瘤拒绝疯狂耗电Tencent 社交毒瘤一键省电模块(目前支持 QQ、TIM)通过干掉电源锁常驻减少电量消耗理论支持最新版本。by 酷安 @星夜不荟" />
<!-- 最低xposed版本号 -->
<meta-data
android:name="xposedminversion"
android:value="82" />
<activity
android:name="com.fankes.tsbattery.MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="de.robv.android.xposed.category.MODULE_SETTINGS" />
</intent-filter>
</activity>
<activity-alias
android:name="com.fankes.tsbattery.Home"
android:enabled="true"
android:label="@string/app_name"
android:targetActivity="com.fankes.tsbattery.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
</application>
</manifest>

View File

@@ -0,0 +1 @@
com.fankes.tsbattery.hook.HookMain

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,234 @@
/*
* Copyright (C) 2021. Fankes Studio(qzmmcn@163.com)
*
* This file is part of TSBattery.
*
* TSBattery is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* TSBattery is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* This file is Created by fankes on 2021/9/4.
*/
@file:Suppress(
"DEPRECATION", "SetTextI18n", "SetWorldReadable", "WorldReadableFiles",
"LocalVariableName"
)
package com.fankes.tsbattery
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.util.Log
import android.view.View
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.annotation.Keep
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.widget.SwitchCompat
import androidx.constraintlayout.utils.widget.ImageFilterView
import com.fankes.tsbattery.hook.HookMain
import com.fankes.tsbattery.utils.FileUtils
import com.gyf.immersionbar.ImmersionBar
import java.io.File
@Keep
class MainActivity : AppCompatActivity() {
companion object {
private const val moduleVersion = BuildConfig.VERSION_NAME
private const val moduleSupport = "QQ 8.5.5~8.8.23、TIM 2+"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
/*禁止系统夜间模式对自己造成干扰*/
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
/*隐藏系统的标题栏*/
supportActionBar?.hide()
/*初始化沉浸状态栏*/
ImmersionBar.with(this)
.statusBarColor("#FFFFFFFF")
.autoDarkModeEnable(false)
.statusBarDarkFont(true)
.navigationBarColor("#FFFFFFFF")
.navigationBarDarkIcon(true)
.fitsSystemWindows(true)
.init()
/*判断 Hook 状态*/
if (isHooked()) {
findViewById<LinearLayout>(R.id.main_lin_status).setBackgroundResource(R.drawable.green_round)
findViewById<ImageFilterView>(R.id.main_img_status).setImageResource(R.mipmap.succcess)
findViewById<TextView>(R.id.main_text_status).text = "模块已激活"
} else
AlertDialog.Builder(this)
.setTitle("模块没有激活")
.setMessage(
"检测到模块没有激活,模块需要 Xposed 环境依赖,同时需要系统拥有 Root 权限(太极阴可以免 Root),请自行查看本页面使用帮助与说明第三条。\n" +
"太极、应用转生、梦境(Pine)和第三方 Xposed 激活后可能不会提示激活,若想验证是否激活请打开“提示模块运行信息”自行检查,如果生效就代表模块运行正常,这里的激活状态只是一个显示意义上的存在。"
)
.setPositiveButton("我知道了", null)
.setCancelable(false)
.show()
/*设置文本*/
findViewById<TextView>(R.id.main_text_version).text = "当前版本:$moduleVersion"
findViewById<TextView>(R.id.main_text_support).text = "支持 $moduleSupport"
/*初始化 View*/
val protectModeSwitch = findViewById<SwitchCompat>(R.id.protect_mode_switch)
val hideIconInLauncherSwitch = findViewById<SwitchCompat>(R.id.hide_icon_in_launcher_switch)
val notifyModuleInfoSwitch = findViewById<SwitchCompat>(R.id.notify_module_info_switch)
/*获取 Sp 存储的信息*/
protectModeSwitch.isChecked = getBoolean("_white_mode")
hideIconInLauncherSwitch.isChecked = getBoolean("_hide_icon")
notifyModuleInfoSwitch.isChecked = getBoolean("_tip_run_info")
protectModeSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener
putBoolean("_white_mode", b)
}
hideIconInLauncherSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener
putBoolean("_hide_icon", b)
packageManager.setComponentEnabledSetting(
ComponentName(this@MainActivity, "com.fankes.tsbattery.Home"),
if (b) PackageManager.COMPONENT_ENABLED_STATE_DISABLED else PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP
)
}
notifyModuleInfoSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener
putBoolean("_tip_run_info", b)
}
/*项目地址点击事件*/
findViewById<View>(R.id.link_with_project_address).setOnClickListener {
try {
val intent = Intent()
intent.action = "android.intent.action.VIEW"
val content_url = Uri.parse("https://github.com/fankes/TSBattery")
intent.data = content_url
startActivity(intent)
} catch (e: Exception) {
Toast.makeText(this, "无法启动系统默认浏览器", Toast.LENGTH_SHORT).show()
}
}
}
/**
* 判断模块是否激活
* 在 [HookMain] 中 Hook 掉此方法
* @return 激活状态
*/
private fun isHooked(): Boolean {
Log.d("TSBattery", "isHooked: true")
return isExpModuleActive()
}
/**
* 新增太极判断方式
* @return 是否激活
*/
private fun isExpModuleActive(): Boolean {
var isExp = false
try {
val uri = Uri.parse("content://me.weishu.exposed.CP/")
var result: Bundle? = null
try {
result = contentResolver.call(uri, "active", null, null)
} catch (e: RuntimeException) {
// TaiChi is killed, try invoke
try {
val intent = Intent("me.weishu.exp.ACTION_ACTIVE")
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
} catch (e1: Throwable) {
return false
}
}
if (result == null) result = contentResolver.call(uri, "active", null, null)
if (result == null) return false
isExp = result.getBoolean("active", false)
} catch (ignored: Throwable) {
}
return isExp
}
override fun onResume() {
super.onResume()
setWorldReadable()
}
override fun onRestart() {
super.onRestart()
setWorldReadable()
}
override fun onPause() {
super.onPause()
setWorldReadable()
}
/**
* 获取保存的值
* @param key 名称
* @return 保存的值
*/
private fun getBoolean(key: String) =
getSharedPreferences(
packageName + "_preferences",
Context.MODE_PRIVATE
).getBoolean(key, false)
/**
* 保存值
* @param key 名称
* @param bool 值
*/
private fun putBoolean(key: String, bool: Boolean) {
getSharedPreferences(
packageName + "_preferences",
Context.MODE_PRIVATE
).edit().putBoolean(key, bool).apply()
setWorldReadable()
Handler().postDelayed({ setWorldReadable() }, 500)
Handler().postDelayed({ setWorldReadable() }, 1000)
Handler().postDelayed({ setWorldReadable() }, 1500)
}
/**
* 强制设置 Sp 存储为全局可读可写
* 以供模块使用
*/
private fun setWorldReadable() {
try {
if (FileUtils.getDefaultPrefFile(this).exists()) {
for (file in arrayOf<File>(
FileUtils.getDataDir(this),
FileUtils.getPrefDir(this),
FileUtils.getDefaultPrefFile(this)
)) {
file.setReadable(true, false)
file.setExecutable(true, false)
}
}
} catch (e: Exception) {
Toast.makeText(this, "无法写入模块设置,请检查权限\n如果此提示一直显示,请不要双开模块", Toast.LENGTH_SHORT).show()
}
}
}

View File

@@ -0,0 +1,366 @@
/*
* Copyright (C) 2021. Fankes Studio(qzmmcn@163.com)
*
* This file is part of TSBattery.
*
* TSBattery is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* TSBattery is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* This file is Created by fankes on 2021/9/4.
*/
@file:Suppress("DEPRECATION", "SameParameterValue")
package com.fankes.tsbattery.hook
import android.app.Activity
import android.app.AlertDialog
import android.os.Build
import android.os.Bundle
import android.util.Log
import androidx.annotation.Keep
import com.fankes.tsbattery.utils.XPrefUtils
import de.robv.android.xposed.*
import de.robv.android.xposed.callbacks.XC_LoadPackage
import java.util.*
@Keep
class HookMain : IXposedHookLoadPackage {
/**
* Print the log
* @param content
*/
private fun logD(content: String) {
XposedBridge.log(content)
Log.d("TSBattery", content)
}
/**
* Print the log
* @param content
*/
private fun logE(content: String, e: Throwable? = null) {
XposedBridge.log(content)
Log.e("TSBattery", content, e)
}
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam?) {
if (lpparam == null) return
when (lpparam.packageName) {
/*Hook 自身*/
"com.fankes.tsbattery" ->
XposedHelpers.findAndHookMethod(
"com.fankes.tsbattery.MainActivity",
lpparam.classLoader,
"isHooked",
object : XC_MethodReplacement() {
override fun replaceHookedMethod(param: MethodHookParam?): Any {
return true
}
})
/*经过测试 QQ 与 TIM 这两个是一个模子里面的东西,所以他们的类名也基本上是一样的*/
"com.tencent.mobileqq", "com.tencent.tim" -> {
try {
XposedHelpers.findAndHookMethod(
"android.os.PowerManager\$WakeLock",
lpparam.classLoader,
"acquire",
object : XC_MethodReplacement() {
override fun replaceHookedMethod(param: MethodHookParam?): Any? {
return null
}
})
} catch (e: Throwable) {
logE("handleLoadPackage: hook wakeLock acquire() Failed", e)
}
try {
XposedHelpers.findAndHookMethod(
"android.os.PowerManager\$WakeLock",
lpparam.classLoader,
"acquire",
Long::class.java,
object : XC_MethodReplacement() {
override fun replaceHookedMethod(param: MethodHookParam?): Any? {
return null
}
})
} catch (e: Throwable) {
logE("handleLoadPackage: hook wakeLock acquire(time) Failed", e)
}
/*判断是否开启提示模块运行信息*/
if (XPrefUtils.getBoolean("_tip_run_info"))
try {
/**
* Hook 启动界面的第一个 [Activity]
* QQ 和 TIM 都是一样的类
* 在里面加入提示运行信息的对话框测试模块是否激活
*/
XposedHelpers.findAndHookMethod(
"com.tencent.mobileqq.activity.SplashActivity",
lpparam.classLoader,
"doOnCreate",
Bundle::class.java,
object : XC_MethodHook() {
override fun afterHookedMethod(param: MethodHookParam?) {
val self = param!!.thisObject as Activity
AlertDialog.Builder(
self,
android.R.style.Theme_Material_Dialog_Alert
).setCancelable(false)
.setTitle("TSBattery 已激活")
.setMessage(
"模块工作看起来一切正常,请自行测试是否能达到省电效果。\n\n" +
"当前模式:${if (XPrefUtils.getBoolean("_white_mode")) "保守模式" else "完全模式"}" +
"\n\n包名:${self.packageName}\n版本:${
self.packageManager.getPackageInfo(
self.packageName,
0
).versionName
}(${
self.packageManager.getPackageInfo(
self.packageName,
0
).versionCode
})" + "\n\nPS模块只对挂后台锁屏情况下有省电效果请不要将过多的群提醒消息通知打开这样子在使用过程时照样会极其耗电\n" +
"如果你不想看到此提示。请在模块设置中关闭运行信息提醒,此设置默认关闭。\n" +
"开发者 酷安 @星夜不荟\n未经允许禁止转载、修改或复制我的劳动成果。"
)
.setPositiveButton("我知道了", null)
.show()
}
})
} catch (e: Exception) {
logE("handleLoadPackage: hook SplashActivity Failed", e)
}
/*关闭保守模式后不再仅仅作用于系统电源锁*/
if (!XPrefUtils.getBoolean("_white_mode")) {
val replaceMent = object : XC_MethodReplacement() {
override fun replaceHookedMethod(param: MethodHookParam?): Any? {
return null
}
}
/**
* 这个类 BaseChatPie 是控制聊天界面的
* 里面有两个随机混淆的方法
* 这两个方法一个是挂起电源锁常驻亮屏
* 一个是停止常驻亮屏
* 不由分说每个版本混淆的方法名都会变
* 所以说每个版本重新适配 - 也可以提交分支帮我适配
* 8.8.17 版本是 bd be
* 8.8.23 版本是 bf bg
* ⚠️ Hook 错了方法会造成闪退!
*/
try {
/*通过在 SplashActivity 里取到应用的版本号*/
XposedHelpers.findAndHookMethod(
"com.tencent.mobileqq.activity.SplashActivity",
lpparam.classLoader,
"doOnCreate",
Bundle::class.java,
object : XC_MethodHook() {
override fun beforeHookedMethod(param: MethodHookParam?) {
val self = param!!.thisObject as Activity
val name = self.packageName
val version =
self.packageManager.getPackageInfo(name, 0).versionName
/*这个地方我们只处理 QQ*/
try {
if (name == "com.tencent.mobileqq") {
when (version) {
"8.8.17" -> {
XposedHelpers.findAndHookMethod(
"com.tencent.mobileqq.activity.aio.core.BaseChatPie",
lpparam.classLoader,
"bd",
replaceMent
)
XposedHelpers.findAndHookMethod(
"com.tencent.mobileqq.activity.aio.core.BaseChatPie",
lpparam.classLoader,
"be",
replaceMent
)
}
"8.8.23" -> {
XposedHelpers.findAndHookMethod(
"com.tencent.mobileqq.activity.aio.core.BaseChatPie",
lpparam.classLoader,
"bf",
replaceMent
)
XposedHelpers.findAndHookMethod(
"com.tencent.mobileqq.activity.aio.core.BaseChatPie",
lpparam.classLoader,
"bg",
replaceMent
)
}
//TODO 后面的版本逐个适配 此方法没封装 目前比较笨蛋 主要是我懒得写
}
}
} catch (e: Exception) {
logE("handleLoadPackage: hook BaseChatPie Failed", e)
}
}
})
} catch (e: Exception) {
logE("handleLoadPackage: hook BaseChatPie(first time) Failed", e)
}
try {
/**
* 一个不知道是什么作用的电源锁
* 同样直接干掉
*/
XposedHelpers.findAndHookMethod(
"com.tencent.mars.ilink.comm.WakerLock",
lpparam.classLoader,
"lock", Long::class.java,
replaceMent
)
} catch (e: Exception) {
logE("handleLoadPackage: hook WakerLock Failed", e)
}
try {
/**
* Hook 掉一个一像素保活 [Activity] 真的我怎么都想不到讯哥的程序员做出这种事情
* 这个东西经过测试会在锁屏的时候吊起来,解锁的时候自动 finish(),无限耍流氓耗电
*/
XposedHelpers.findAndHookMethod(
"com.tencent.mobileqq.activity.QQLSUnlockActivity",
lpparam.classLoader,
"onCreate", Bundle::class.java,
object : XC_MethodHook() {
private var origDevice = ""
override fun beforeHookedMethod(param: MethodHookParam?) {
/*由于在 onCreate 里有一行判断只要型号是 xiaomi 的设备就开电源锁,所以说这里临时替换成菊花厂*/
origDevice = Build.MANUFACTURER
if (Build.MANUFACTURER.toLowerCase(Locale.ROOT) == "xiaomi")
XposedHelpers.setStaticObjectField(
Build::class.java,
"MANUFACTURER",
"HUAWEI"
)
}
override fun afterHookedMethod(param: MethodHookParam?) {
(param?.thisObject as? Activity)?.finish()
/*这里再把型号替换回去 - 不影响应用变量等 Xposed 模块修改的型号*/
XposedHelpers.setStaticObjectField(
Build::class.java,
"MANUFACTURER",
origDevice
)
}
}
)
/**
* 这个东西同上,不知道是啥时候调用
* 反正也是一个一像素保活的 [Activity]
* 讯哥的程序员真的有你的
*/
XposedHelpers.findAndHookMethod(
"com.tencent.mobileqq.activity.QQLSActivity\$14",
lpparam.classLoader,
"run",
replaceMent
)
} catch (e: Exception) {
logE("handleLoadPackage: hook QQLSActivity Failed", e)
}
try {
/**
* 这个是毒瘤核心类
* WakeLockMonitor
* 这个名字真的起的特别诗情画意
* 带给用户的却是 shit 一样的体验
* 里面有各种使用 Handler 和 Timer 的各种耗时常驻后台耗电办法持续接收消息
* 直接循环全部方法全部干掉
*/
lpparam.classLoader.loadClass("com.tencent.qapmsdk.qqbattery.monitor.WakeLockMonitor")
.apply {
val lockClazz =
lpparam.classLoader.loadClass("com.tencent.qapmsdk.qqbattery.monitor.WakeLockMonitor\$WakeLockEntity")
val hookClazz =
lpparam.classLoader.loadClass("com.tencent.qapmsdk.qqbattery.monitor.MethodHookParam")
val onHook = getDeclaredMethod(
"onHook",
String::class.java,
Any::class.java,
java.lang.reflect.Array.newInstance(
Any::class.java,
0
).javaClass,
Any::class.java
).apply { isAccessible = true }
val doReport =
getDeclaredMethod(
"doReport",
lockClazz,
Int::class.java
).apply {
isAccessible = true
}
val afterHookedMethod =
getDeclaredMethod(
"afterHookedMethod",
hookClazz
).apply { isAccessible = true }
val beforeHookedMethod =
getDeclaredMethod("beforeHookedMethod", hookClazz).apply {
isAccessible = true
}
val onAppBackground =
getDeclaredMethod("onAppBackground").apply {
isAccessible = true
}
val onOtherProcReport =
getDeclaredMethod(
"onOtherProcReport",
Bundle::class.java
).apply { isAccessible = true }
val onProcessRun30Min =
getDeclaredMethod("onProcessRun30Min").apply {
isAccessible = true
}
val onProcessBG5Min =
getDeclaredMethod("onProcessBG5Min").apply {
isAccessible = true
}
val writeReport =
getDeclaredMethod(
"writeReport",
Boolean::class.java
).apply { isAccessible = true }
XposedBridge.hookMethod(onHook, replaceMent)
XposedBridge.hookMethod(doReport, replaceMent)
XposedBridge.hookMethod(afterHookedMethod, replaceMent)
XposedBridge.hookMethod(beforeHookedMethod, replaceMent)
XposedBridge.hookMethod(onAppBackground, replaceMent)
XposedBridge.hookMethod(onOtherProcReport, replaceMent)
XposedBridge.hookMethod(onProcessRun30Min, replaceMent)
XposedBridge.hookMethod(onProcessBG5Min, replaceMent)
XposedBridge.hookMethod(writeReport, replaceMent)
}
} catch (e: Throwable) {
logE("handleLoadPackage: hook WakerLockMonitor Failed", e)
}
logD("handleLoadPackage: hook Complete!")
}
}
}
}
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright (C) 2021. Fankes Studio(qzmmcn@163.com)
*
* This file is part of TSBattery.
*
* TSBattery is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* TSBattery is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* This file is Created by zpp0196 on 2019/2/9.
*/
package com.fankes.tsbattery.utils;
import android.content.Context;
import android.os.Environment;
import com.fankes.tsbattery.BuildConfig;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
@SuppressWarnings("ALL")
public class FileUtils {
private static final String FILE_PREF_NAME = BuildConfig.APPLICATION_ID + "_preferences.xml";
public static boolean copyFile(File srcFile, File targetFile) {
FileInputStream ins = null;
FileOutputStream out = null;
try {
if (targetFile.exists()) {
targetFile.delete();
}
File targetParent = targetFile.getParentFile();
if (!targetParent.exists()) {
targetParent.mkdirs();
}
targetFile.createNewFile();
ins = new FileInputStream(srcFile);
out = new FileOutputStream(targetFile);
byte[] b = new byte[1024];
int n;
while ((n = ins.read(b)) != -1) {
out.write(b, 0, n);
}
} catch (IOException e) {
e.printStackTrace();
return false;
} finally {
try {
if (ins != null) {
ins.close();
}
if (out != null) {
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
public static File getDataDir(Context context) {
return new File(context.getApplicationInfo().dataDir);
}
public static File getPrefDir(Context context) {
return new File(getDataDir(context), "shared_prefs");
}
public static File getDefaultPrefFile(Context context) {
return new File(getPrefDir(context), FILE_PREF_NAME);
}
public static File getBackupPrefsFile() {
return new File(getBackupDir(), FILE_PREF_NAME);
}
private static File getBackupDir() {
return new File(Environment.getExternalStorageDirectory(), "QQPurify");
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (C) 2021. Fankes Studio(qzmmcn@163.com)
*
* This file is part of TSBattery.
*
* TSBattery is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* TSBattery is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* This file is Created by zpp0196 on 2018/4/11.
*/
package com.fankes.tsbattery.utils
import de.robv.android.xposed.XSharedPreferences
object XPrefUtils {
fun getBoolean(key: String) = pref.getBoolean(key, false)
private val pref: XSharedPreferences
get() {
val preferences = XSharedPreferences("com.fankes.tsbattery")
preferences.makeWorldReadable()
preferences.reload()
return preferences
}
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#6A6A6A" />
<corners android:radius="15dp" />
</shape>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#26A69A" />
<corners android:radius="15dp" />
</shape>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="150dp"
android:height="150dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#FFffff"
android:pathData="M717.48,354.1a33.64,33.64 0,0 0,-17.41 -21.56,37.17 37.17,0 0,0 -28.42,-3.38c-65.54,17.77 -123.8,43.16 -174.69,76.19 -50.89,33.02 -93.9,70.76 -129.02,113.15 -35.12,42.5 -69.48,94.52 -102.96,156.11 -5.22,9.57 -6.66,18.69 -4.35,27.44a33.64,33.64 0,0 0,17.36 21.5,37.17 37.17,0 0,0 28.47,3.43 40.4,40.4 0,0 0,22.94 -17.36c7.99,-11.47 18.69,-28.16 32.1,-50.07 13.41,-21.91 23.04,-37.38 28.77,-46.39 40.29,-59.14 83.05,-104.96 128.15,-137.42 45.16,-32.46 102.4,-58.06 171.78,-76.85a37.58,37.58 0,0 0,22.99 -17.31,34 34,0 0,0 4.35,-27.44l-0.05,-0.05zM928.53,194c9.47,35.58 15.36,72.76 17.82,111.51 5.38,88.52 -9.37,169.47 -44.34,242.84 -35.02,73.37 -87.24,141.31 -156.67,203.93 -67.74,61.75 -142.64,103.73 -224.77,125.95a490.8,490.8 0,0 1,-164.25 16.13c-5.99,-0.36 -24.22,-3.89 -54.68,-10.5 -30.41,-6.66 -49.87,-8.81 -58.27,-6.55 -5.89,1.64 -11.52,9.57 -16.95,23.91 -5.43,14.34 -10.24,29.7 -14.34,46.08 -4.1,16.33 -10.24,32.05 -18.43,47.1 -8.19,15.1 -17.61,24.06 -28.26,26.93a60.47,60.47 0,0 1,-29.7 1.43,52.99 52.99,0 0,1 -20.58,-8.91 245.91,245.91 0,0 1,-26.06 -24.93,30.31 30.31,0 0,1 -7.53,-9.73 34.66,34.66 0,0 1,-2.87 -7.37c-3.48,-13.11 -1.43,-28.42 6.14,-45.93a209.92,209.92 0,0 1,27.6 -46.95c10.85,-13.82 20.58,-27.65 29.03,-41.63 8.5,-13.93 11.78,-24.47 9.93,-31.64 -0.41,-1.48 -4.86,-7.94 -13.36,-19.25a197.63,197.63 0,0 1,-15.36 -22.37,355.48 355.48,0 0,1 -20.48,-57.14 338.43,338.43 0,0 1,-8.86 -130.15,349.9 349.9,0 0,1 37.89,-121.5A504.68,504.68 0,0 1,204.3 351.74a531.35,531.35 0,0 1,97.95 -84.07c18.43,-12.24 43.72,-24.22 75.93,-35.94 32.1,-11.72 64.56,-22.32 97.38,-31.74 32.56,-9.42 65.02,-19.46 97.23,-30a455.37,455.37 0,0 0,86.32 -37.89c25.4,-14.64 43.42,-30.87 54.02,-48.64l11.78,-20.99 12.08,-20.17c7.99,-13.41 11.98,-18.48 11.83,-15.26 -0.1,3.28 5.79,-1.54 17.66,-14.44 11.93,-12.9 19.66,-15.87 23.24,-8.96 14.34,-3.89 29.49,1.23 45.62,15.36 16.13,14.08 30.41,32.72 42.8,55.86 12.44,23.19 23.04,45.21 31.74,66.15 8.7,20.94 14.9,38.55 18.64,52.74l0.05,0.26z"/>
</vector>

View File

@@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group android:scaleX="0.03609375"
android:scaleY="0.03609375"
android:translateX="35.52"
android:translateY="35.52">
<path
android:fillColor="#FFffff"
android:pathData="M717.48,354.1a33.64,33.64 0,0 0,-17.41 -21.56,37.17 37.17,0 0,0 -28.42,-3.38c-65.54,17.77 -123.8,43.16 -174.69,76.19 -50.89,33.02 -93.9,70.76 -129.02,113.15 -35.12,42.5 -69.48,94.52 -102.96,156.11 -5.22,9.57 -6.66,18.69 -4.35,27.44a33.64,33.64 0,0 0,17.36 21.5,37.17 37.17,0 0,0 28.47,3.43 40.4,40.4 0,0 0,22.94 -17.36c7.99,-11.47 18.69,-28.16 32.1,-50.07 13.41,-21.91 23.04,-37.38 28.77,-46.39 40.29,-59.14 83.05,-104.96 128.15,-137.42 45.16,-32.46 102.4,-58.06 171.78,-76.85a37.58,37.58 0,0 0,22.99 -17.31,34 34,0 0,0 4.35,-27.44l-0.05,-0.05zM928.53,194c9.47,35.58 15.36,72.76 17.82,111.51 5.38,88.52 -9.37,169.47 -44.34,242.84 -35.02,73.37 -87.24,141.31 -156.67,203.93 -67.74,61.75 -142.64,103.73 -224.77,125.95a490.8,490.8 0,0 1,-164.25 16.13c-5.99,-0.36 -24.22,-3.89 -54.68,-10.5 -30.41,-6.66 -49.87,-8.81 -58.27,-6.55 -5.89,1.64 -11.52,9.57 -16.95,23.91 -5.43,14.34 -10.24,29.7 -14.34,46.08 -4.1,16.33 -10.24,32.05 -18.43,47.1 -8.19,15.1 -17.61,24.06 -28.26,26.93a60.47,60.47 0,0 1,-29.7 1.43,52.99 52.99,0 0,1 -20.58,-8.91 245.91,245.91 0,0 1,-26.06 -24.93,30.31 30.31,0 0,1 -7.53,-9.73 34.66,34.66 0,0 1,-2.87 -7.37c-3.48,-13.11 -1.43,-28.42 6.14,-45.93a209.92,209.92 0,0 1,27.6 -46.95c10.85,-13.82 20.58,-27.65 29.03,-41.63 8.5,-13.93 11.78,-24.47 9.93,-31.64 -0.41,-1.48 -4.86,-7.94 -13.36,-19.25a197.63,197.63 0,0 1,-15.36 -22.37,355.48 355.48,0 0,1 -20.48,-57.14 338.43,338.43 0,0 1,-8.86 -130.15,349.9 349.9,0 0,1 37.89,-121.5A504.68,504.68 0,0 1,204.3 351.74a531.35,531.35 0,0 1,97.95 -84.07c18.43,-12.24 43.72,-24.22 75.93,-35.94 32.1,-11.72 64.56,-22.32 97.38,-31.74 32.56,-9.42 65.02,-19.46 97.23,-30a455.37,455.37 0,0 0,86.32 -37.89c25.4,-14.64 43.42,-30.87 54.02,-48.64l11.78,-20.99 12.08,-20.17c7.99,-13.41 11.98,-18.48 11.83,-15.26 -0.1,3.28 5.79,-1.54 17.66,-14.44 11.93,-12.9 19.66,-15.87 23.24,-8.96 14.34,-3.89 29.49,1.23 45.62,15.36 16.13,14.08 30.41,32.72 42.8,55.86 12.44,23.19 23.04,45.21 31.74,66.15 8.7,20.94 14.9,38.55 18.64,52.74l0.05,0.26z"/>
</group>
</vector>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FFFFFF" />
<corners android:radius="15dp" />
</shape>

View File

@@ -0,0 +1,384 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity"
tools:ignore="HardcodedText">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:elevation="5dp"
android:padding="15dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="TSBattery"
android:textColor="#FF323B42"
android:textSize="18sp"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:id="@+id/main_lin_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:background="@drawable/dark_round"
android:elevation="3dp"
android:gravity="center">
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/main_img_status"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginStart="25dp"
android:layout_marginEnd="5dp"
android:src="@mipmap/warn"
android:tint="@color/white" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
<TextView
android:id="@+id/main_text_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:text="模块未激活"
android:textColor="@color/white"
android:textSize="18sp" />
<TextView
android:id="@+id/main_text_version"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="3dp"
android:alpha="0.8"
android:text="当前版本:%1"
android:textColor="@color/white"
android:textSize="13sp" />
<TextView
android:id="@+id/main_text_support"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="3dp"
android:alpha="0.8"
android:text="支持 %1"
android:textColor="@color/white"
android:textSize="12sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.6"
android:text="理论在小更新内还会生效,如果失效请看下方的联系方式"
android:textColor="@color/white"
android:textSize="10sp"
tools:ignore="SmallSp" />
</LinearLayout>
</LinearLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="10dp"
android:fadingEdgeLength="10dp"
android:fillViewport="true"
android:overScrollMode="never"
android:requiresFadingEdge="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginTop="10dp"
android:layout_marginRight="15dp"
android:background="@drawable/white_round"
android:elevation="2dp"
android:gravity="center"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingRight="15dp">
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/protect_mode_switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="启用保守模式"
android:textColor="#FF323B42"
android:textSize="15sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.6"
android:lineSpacingExtra="6dp"
android:text="此选项默认关闭,默认情况下模块将会干掉 QQ 和 TIM 自身的电源锁控制类,开启后模块将只对系统电源锁生效,如果你的 QQ 或 TIM 视频通话等设置发生了故障,可以尝试开启这个功能,开启后请重启 QQ 或 TIM。"
android:textColor="#777777"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginTop="15dp"
android:layout_marginRight="15dp"
android:background="@drawable/white_round"
android:elevation="2dp"
android:gravity="center"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingRight="15dp">
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/notify_module_info_switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="提示模块运行信息"
android:textColor="#FF323B42"
android:textSize="15sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.6"
android:lineSpacingExtra="6dp"
android:text="模块工作正常情况下不要开启,如果你想测试模块是否正常激活,可以打开此提示,开启后将会在启动 QQ 或 TIM 的时候提示运行信息。"
android:textColor="#777777"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginTop="15dp"
android:layout_marginRight="15dp"
android:background="@drawable/white_round"
android:elevation="2dp"
android:gravity="center"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingRight="15dp">
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/hide_icon_in_launcher_switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="在桌面隐藏模块图标"
android:textColor="#FF323B42"
android:textSize="15sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.6"
android:lineSpacingExtra="6dp"
android:text="隐藏模块图标后,模块不会再在桌面显示,你可以在 EdXposed、太极、LsPosed 中找到模块设置并打开。"
android:textColor="#777777"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginTop="15dp"
android:layout_marginRight="15dp"
android:background="@drawable/white_round"
android:elevation="2dp"
android:gravity="center"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingRight="15dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:gravity="center|start">
<androidx.constraintlayout.utils.widget.ImageFilterView
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginEnd="5dp"
android:alpha="0.85"
android:src="@mipmap/about"
android:tint="#FF323B42" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:alpha="0.85"
android:singleLine="true"
android:text="使用帮助与说明"
android:textColor="#FF323B42"
android:textSize="12sp" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.8"
android:lineSpacingExtra="6dp"
android:text="Q.这个模块是做什么的?\nA.此模块的诞生来源于国内厂商毒瘤 APP 强行霸占后台耗电QQ 在 8.6.0 版本以后也只是接入了 HMS 推送,但是可笑的是开发组却并没有删除之前疯狂耗电的接收消息方法,于是这个模块就诞生了。"
android:textColor="#777777"
android:textSize="12sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.8"
android:lineSpacingExtra="6dp"
android:text="Q.原理是什么?\nA.模块有两套工作方式,一种是针对 QQ、TIM Hook 掉系统自身的电源锁“WakeLock”使其不能影响系统休眠这样子在锁屏的时候 QQ、TIM 就可以进入睡眠状态。第二种就是针对 QQ、TIM 删除其自身的无用耗电疯狂循环检测后台强行保活服务。"
android:textColor="#777777"
android:textSize="12sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.8"
android:lineSpacingExtra="6dp"
android:text="Q.如何使用?\nA.目前模块支持 EdXposed、LsPosed 以及太极(无极)框架,在太极和 LsPosed 的作用域中,只需勾选 QQ 和 TIM 即可,模块可以做到即插即用,激活后无需重启手机,重启 QQ 或 TIM 就可以了。"
android:textColor="#777777"
android:textSize="12sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.8"
android:lineSpacingExtra="6dp"
android:text="Q.激活后一定可以非常省电吗?\nA.并不,模块只能减少 QQ、TIM 的耗电,但是请务必记住这一点,省电只是一个理论上的东西,实际水平由你使用的系统和硬件决定,如果你在前台疯狂使用 QQ、TIM那么照样会耗电模块只能保证后台运行和锁屏时毒瘤不会消耗过多的无用的电量仅此而已。"
android:textColor="#777777"
android:textSize="12sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.8"
android:lineSpacingExtra="6dp"
android:text="Q.如何反馈问题?\nA.酷安关注 @星夜不荟"
android:textColor="#777777"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/link_with_project_address"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginTop="15dp"
android:layout_marginRight="15dp"
android:background="@drawable/white_round"
android:elevation="2dp"
android:gravity="center"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingRight="15dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:gravity="center|start">
<androidx.constraintlayout.utils.widget.ImageFilterView
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginEnd="5dp"
android:alpha="0.85"
android:src="@mipmap/about"
android:tint="#FF323B42" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:alpha="0.85"
android:singleLine="true"
android:text="项目地址"
android:textColor="#FF323B42"
android:textSize="12sp" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.8"
android:lineSpacingExtra="6dp"
android:text="本软件是免费开源项目,遵循 GPL 协议,你可以点击这里前往 Github 查看源码以及获取模块更新。\n严禁以任何形式贩卖、商用本软件否则开发者有权追究其法律责任。"
android:textColor="#777777"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginTop="15dp"
android:layout_marginRight="15dp"
android:layout_marginBottom="10dp"
android:background="@drawable/white_round"
android:elevation="2dp"
android:gravity="center"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingRight="15dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:gravity="center"
android:lineSpacingExtra="6dp"
android:text="恰饭时间\n酷安关注我获取我的更多应用"
android:textColor="#FF323B42"
android:textSize="16sp" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:src="@mipmap/qr_pay"
tools:ignore="ContentDescription" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:gravity="center"
android:lineSpacingExtra="6dp"
android:text="开发者 酷安 @星夜不荟\n未经允许不得转载、修改复制我的劳动成果"
android:textColor="#FF323B42"
android:textSize="16sp" />
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</LinearLayout>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.TSBattery" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#656565</color>
<color name="purple_500">#656565</color>
<color name="purple_700">#656565</color>
<color name="teal_200">#656565</color>
<color name="teal_700">#656565</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#26A69A</color>
</resources>

View File

@@ -0,0 +1,3 @@
<resources>
<string name="app_name">TSBattery</string>
</resources>

View File

@@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.TSBattery" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/teal_700</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@@ -0,0 +1,17 @@
package com.fankes.tsbattery
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}