├── .gitignore ├── .idea ├── .gitignore ├── compiler.xml ├── gradle.xml ├── misc.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── release │ └── output-metadata.json └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ └── xposed_init │ ├── java │ └── cn │ │ └── myflv │ │ └── android │ │ └── noactive │ │ ├── Hook.java │ │ ├── app │ │ ├── Android.java │ │ ├── IAppHook.java │ │ └── PowerKeeper.java │ │ ├── entity │ │ ├── ClassEnum.java │ │ ├── FieldEnum.java │ │ ├── MemData.java │ │ └── MethodEnum.java │ │ ├── hook │ │ ├── ANRHook.java │ │ ├── AppSwitchHook.java │ │ ├── BroadcastDeliverHook.java │ │ ├── CacheFreezerHook.java │ │ ├── MilletHook.java │ │ ├── OomAdjHook.java │ │ └── TestHook.java │ │ ├── server │ │ ├── ActiveServices.java │ │ ├── ActivityManagerService.java │ │ ├── AnrHelper.java │ │ ├── ApplicationInfo.java │ │ ├── BroadcastFilter.java │ │ ├── BroadcastQueue.java │ │ ├── ComponentName.java │ │ ├── Event.java │ │ ├── ProcessList.java │ │ ├── ProcessRecord.java │ │ ├── ProcessStateRecord.java │ │ └── ReceiverList.java │ │ └── utils │ │ ├── ConfigFileObserver.java │ │ ├── FreezeUtils.java │ │ ├── FreezerConfig.java │ │ ├── Log.java │ │ └── ThreadUtil.java │ └── res │ ├── mipmap-anydpi-v26 │ ├── ic_logo.xml │ └── ic_logo_round.xml │ ├── mipmap-hdpi │ ├── ic_logo.png │ ├── ic_logo_foreground.png │ └── ic_logo_round.png │ ├── mipmap-mdpi │ ├── ic_logo.png │ ├── ic_logo_foreground.png │ └── ic_logo_round.png │ ├── mipmap-xhdpi │ ├── ic_logo.png │ ├── ic_logo_foreground.png │ └── ic_logo_round.png │ ├── mipmap-xxhdpi │ ├── ic_logo.png │ ├── ic_logo_foreground.png │ └── ic_logo_round.png │ ├── mipmap-xxxhdpi │ ├── ic_logo.png │ ├── ic_logo_foreground.png │ └── ic_logo_round.png │ └── values │ ├── array.xml │ ├── color.xml │ └── strings.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | NoActive无图形版本 2 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | } 4 | 5 | android { 6 | compileSdk 32 7 | 8 | defaultConfig { 9 | applicationId "cn.myflv.android.noactive" 10 | minSdk 23 11 | targetSdk 32 12 | versionCode 128 13 | versionName "0.9.6" 14 | 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | compileOptions { 25 | sourceCompatibility JavaVersion.VERSION_1_8 26 | targetCompatibility JavaVersion.VERSION_1_8 27 | } 28 | 29 | android.applicationVariants.all { 30 | variant -> 31 | variant.outputs.all { 32 | //这里修改apk文件名 33 | outputFileName = "NoActive-v${variant.versionName}.apk" 34 | } 35 | } 36 | } 37 | 38 | dependencies { 39 | compileOnly 'org.projectlombok:lombok:1.18.24' 40 | annotationProcessor 'org.projectlombok:lombok:1.18.24' 41 | compileOnly 'de.robv.android.xposed:api:82' 42 | compileOnly 'de.robv.android.xposed:api:82:sources' 43 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/release/output-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "artifactType": { 4 | "type": "APK", 5 | "kind": "Directory" 6 | }, 7 | "applicationId": "cn.myflv.android.noactive", 8 | "variantName": "release", 9 | "elements": [ 10 | { 11 | "type": "SINGLE", 12 | "filters": [], 13 | "attributes": [], 14 | "versionCode": 127, 15 | "versionName": "0.9.6", 16 | "outputFile": "NoActive-v0.9.6.apk" 17 | } 18 | ], 19 | "elementType": "File" 20 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 13 | 16 | 17 | 20 | 21 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/assets/xposed_init: -------------------------------------------------------------------------------- 1 | cn.myflv.android.noactive.Hook -------------------------------------------------------------------------------- /app/src/main/java/cn/myflv/android/noactive/Hook.java: -------------------------------------------------------------------------------- 1 | package cn.myflv.android.noactive; 2 | 3 | import cn.myflv.android.noactive.app.Android; 4 | import cn.myflv.android.noactive.app.PowerKeeper; 5 | import de.robv.android.xposed.IXposedHookLoadPackage; 6 | import de.robv.android.xposed.callbacks.XC_LoadPackage; 7 | 8 | public class Hook implements IXposedHookLoadPackage { 9 | 10 | @Override 11 | public void handleLoadPackage(XC_LoadPackage.LoadPackageParam packageParam) throws Throwable { 12 | if (packageParam.packageName.equals("android")) { 13 | Android android = new Android(); 14 | android.hook(packageParam); 15 | return; 16 | } 17 | // 禁用 millet 18 | if (packageParam.packageName.equals("com.miui.powerkeeper")) { 19 | PowerKeeper powerKeeper = new PowerKeeper(); 20 | powerKeeper.hook(packageParam); 21 | } 22 | 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/cn/myflv/android/noactive/app/Android.java: -------------------------------------------------------------------------------- 1 | package cn.myflv.android.noactive.app; 2 | 3 | import android.content.Intent; 4 | import android.os.Build; 5 | 6 | import cn.myflv.android.noactive.entity.ClassEnum; 7 | import cn.myflv.android.noactive.entity.MemData; 8 | import cn.myflv.android.noactive.entity.MethodEnum; 9 | import cn.myflv.android.noactive.hook.ANRHook; 10 | import cn.myflv.android.noactive.hook.AppSwitchHook; 11 | import cn.myflv.android.noactive.hook.BroadcastDeliverHook; 12 | import cn.myflv.android.noactive.hook.CacheFreezerHook; 13 | import cn.myflv.android.noactive.hook.OomAdjHook; 14 | import cn.myflv.android.noactive.utils.FreezerConfig; 15 | import cn.myflv.android.noactive.utils.Log; 16 | import de.robv.android.xposed.XC_MethodReplacement; 17 | import de.robv.android.xposed.XposedHelpers; 18 | import de.robv.android.xposed.callbacks.XC_LoadPackage; 19 | 20 | public class Android implements IAppHook { 21 | @Override 22 | public void hook(XC_LoadPackage.LoadPackageParam packageParam) { 23 | // 类加载器 24 | ClassLoader classLoader = packageParam.classLoader; 25 | 26 | // 加载内存配置 27 | MemData memData = new MemData(); 28 | 29 | // 打印设备厂商 30 | Log.i(Build.MANUFACTURER + " device"); 31 | 32 | // 禁用暂停执行已缓存 33 | if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) { 34 | XposedHelpers.findAndHookMethod(ClassEnum.CachedAppOptimizer, classLoader, MethodEnum.useFreezer, new CacheFreezerHook()); 35 | Log.i("Disable cache freezer"); 36 | } 37 | 38 | // Hook 切换事件 39 | if (Build.MANUFACTURER.equals("samsung")) { 40 | XposedHelpers.findAndHookMethod(ClassEnum.ActivityManagerService, classLoader, 41 | MethodEnum.updateActivityUsageStats, 42 | ClassEnum.ComponentName, int.class, int.class, 43 | ClassEnum.IBinder, ClassEnum.ComponentName, Intent.class, new AppSwitchHook(classLoader, memData, AppSwitchHook.DIFFICULT)); 44 | } else { 45 | XposedHelpers.findAndHookMethod(ClassEnum.ActivityManagerService, classLoader, 46 | MethodEnum.updateActivityUsageStats, 47 | ClassEnum.ComponentName, int.class, int.class, 48 | ClassEnum.IBinder, ClassEnum.ComponentName, new AppSwitchHook(classLoader, memData, AppSwitchHook.DIFFICULT)); 49 | } 50 | 51 | // Hook 广播分发 52 | XposedHelpers.findAndHookMethod(ClassEnum.BroadcastQueue, classLoader, MethodEnum.deliverToRegisteredReceiverLocked, 53 | ClassEnum.BroadcastRecord, 54 | ClassEnum.BroadcastFilter, boolean.class, int.class, new BroadcastDeliverHook(memData)); 55 | 56 | // Hook oom_adj 57 | if (!FreezerConfig.isConfigOn(FreezerConfig.disableOOM)) { 58 | boolean colorOs = FreezerConfig.isColorOs(); 59 | if (!colorOs && (Build.MANUFACTURER.equals("OPPO") || Build.MANUFACTURER.equals("OnePlus"))) { 60 | Log.w("If you are using ColorOS"); 61 | Log.w("You can create file color.os"); 62 | } 63 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { 64 | if (colorOs) { 65 | Log.i("Hello ColorOS"); 66 | XposedHelpers.findAndHookMethod(ClassEnum.OomAdjuster, classLoader, MethodEnum.computeOomAdjLSP, ClassEnum.ProcessRecord, int.class, ClassEnum.ProcessRecord, boolean.class, long.class, boolean.class, boolean.class, new OomAdjHook(classLoader, memData, OomAdjHook.Color)); 67 | } else { 68 | XposedHelpers.findAndHookMethod(ClassEnum.ProcessStateRecord, classLoader, MethodEnum.setCurAdj, int.class, new OomAdjHook(classLoader, memData, OomAdjHook.Android_S)); 69 | } 70 | Log.i("Auto lmk"); 71 | } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R || Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) { 72 | XposedHelpers.findAndHookMethod(ClassEnum.OomAdjuster, classLoader, MethodEnum.applyOomAdjLocked, ClassEnum.ProcessRecord, boolean.class, long.class, long.class, new OomAdjHook(classLoader, memData, OomAdjHook.Android_Q_R)); 73 | Log.i("Auto lmk"); 74 | } 75 | } 76 | 77 | // Hook ANR 78 | if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) { 79 | XposedHelpers.findAndHookMethod(ClassEnum.AnrHelper, classLoader, MethodEnum.appNotResponding, 80 | ClassEnum.ProcessRecord, 81 | String.class, 82 | ClassEnum.ApplicationInfo, 83 | String.class, 84 | ClassEnum.WindowProcessController, 85 | boolean.class, 86 | String.class, new ANRHook(classLoader, memData)); 87 | Log.i("Auto keep process"); 88 | } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) { 89 | XposedHelpers.findAndHookMethod(ClassEnum.ProcessRecord, classLoader, MethodEnum.appNotResponding, 90 | String.class, ClassEnum.ApplicationInfo, String.class, ClassEnum.WindowProcessController, boolean.class, String.class, XC_MethodReplacement.DO_NOTHING); 91 | Log.i("Android Q"); 92 | Log.i("Force keep process"); 93 | } else { 94 | XposedHelpers.findAndHookMethod(ClassEnum.AppErrors, classLoader, MethodEnum.appNotResponding, 95 | ClassEnum.ProcessRecord, ClassEnum.ActivityRecord, ClassEnum.ActivityRecord, boolean.class, String.class, 96 | XC_MethodReplacement.DO_NOTHING 97 | ); 98 | Log.i("Android N-P"); 99 | Log.i("Force keep process"); 100 | } 101 | 102 | Log.i("Load success"); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /app/src/main/java/cn/myflv/android/noactive/app/IAppHook.java: -------------------------------------------------------------------------------- 1 | package cn.myflv.android.noactive.app; 2 | 3 | import de.robv.android.xposed.callbacks.XC_LoadPackage; 4 | 5 | public interface IAppHook { 6 | void hook(XC_LoadPackage.LoadPackageParam packageParam); 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/cn/myflv/android/noactive/app/PowerKeeper.java: -------------------------------------------------------------------------------- 1 | package cn.myflv.android.noactive.app; 2 | 3 | import android.content.Context; 4 | 5 | import cn.myflv.android.noactive.entity.ClassEnum; 6 | import cn.myflv.android.noactive.entity.MethodEnum; 7 | import cn.myflv.android.noactive.hook.MilletHook; 8 | import de.robv.android.xposed.XC_MethodReplacement; 9 | import de.robv.android.xposed.XposedBridge; 10 | import de.robv.android.xposed.XposedHelpers; 11 | import de.robv.android.xposed.callbacks.XC_LoadPackage; 12 | 13 | public class PowerKeeper implements IAppHook { 14 | @Override 15 | public void hook(XC_LoadPackage.LoadPackageParam packageParam) { 16 | try { 17 | XposedHelpers.findAndHookMethod(ClassEnum.PowerStateMachine, packageParam.classLoader, MethodEnum.clearAppWhenScreenOffTimeOut, XC_MethodReplacement.DO_NOTHING); 18 | XposedHelpers.findAndHookMethod(ClassEnum.PowerStateMachine, packageParam.classLoader, MethodEnum.clearAppWhenScreenOffTimeOutInNight, XC_MethodReplacement.DO_NOTHING); 19 | XposedHelpers.findAndHookMethod(ClassEnum.PowerStateMachine, packageParam.classLoader, MethodEnum.clearUnactiveApps, Context.class, XC_MethodReplacement.DO_NOTHING); 20 | XposedBridge.log("NoActive(info) -> Disable MIUI clearApp"); 21 | } catch (Throwable throwable) { 22 | XposedBridge.log("NoActive(error) -> Disable MIUI clearApp failed: " + throwable.getMessage()); 23 | } 24 | 25 | try { 26 | XposedHelpers.findAndHookMethod(ClassEnum.MilletConfig, packageParam.classLoader, MethodEnum.getEnable, Context.class, new MilletHook()); 27 | XposedBridge.log("NoActive(info) -> Disable millet"); 28 | } catch (Throwable throwable) { 29 | XposedBridge.log("NoActive(error) -> Disable millet failed: " + throwable.getMessage()); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/cn/myflv/android/noactive/entity/ClassEnum.java: -------------------------------------------------------------------------------- 1 | package cn.myflv.android.noactive.entity; 2 | 3 | public class ClassEnum { 4 | public final static String ActivityManagerService = "com.android.server.am.ActivityManagerService"; 5 | public final static String ComponentName = "android.content.ComponentName"; 6 | public final static String IBinder = "android.os.IBinder"; 7 | public final static String BroadcastQueue = "com.android.server.am.BroadcastQueue"; 8 | public final static String BroadcastRecord = "com.android.server.am.BroadcastRecord"; 9 | public final static String BroadcastFilter = "com.android.server.am.BroadcastFilter"; 10 | public final static String AnrHelper = "com.android.server.am.AnrHelper"; 11 | public final static String ProcessRecord = "com.android.server.am.ProcessRecord"; 12 | public final static String ApplicationInfo = "android.content.pm.ApplicationInfo"; 13 | public final static String WindowProcessController = "com.android.server.wm.WindowProcessController"; 14 | public final static String AnrRecord = AnrHelper + "$AnrRecord"; 15 | public final static String AppErrors = "com.android.server.am.AppErrors"; 16 | public final static String ActivityRecord = "com.android.server.am.ActivityRecord"; 17 | 18 | 19 | public final static String ProcessStateRecord = "com.android.server.am.ProcessStateRecord"; 20 | public final static String OomAdjuster = "com.android.server.am.OomAdjuster"; 21 | 22 | public final static String MilletConfig = "com.miui.powerkeeper.millet.MilletConfig"; 23 | public final static String CachedAppOptimizer = "com.android.server.am.CachedAppOptimizer"; 24 | public final static String ProcessList = "com.android.server.am.ProcessList"; 25 | public final static String PowerStateMachine = "com.miui.powerkeeper.statemachine.PowerStateMachine"; 26 | public final static String Process = "android.os.Process"; 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/cn/myflv/android/noactive/entity/FieldEnum.java: -------------------------------------------------------------------------------- 1 | package cn.myflv.android.noactive.entity; 2 | 3 | public class FieldEnum { 4 | public final static String packageName = "packageName"; 5 | public final static String uid = "uid"; 6 | public final static String pid = "pid"; 7 | public final static String flags = "flags"; 8 | public final static String info = "info"; 9 | public final static String mProcessList = "mProcessList"; 10 | public final static String mLruProcesses = "mLruProcesses"; 11 | public final static String mPackage = "mPackage"; 12 | public final static String processName = "processName"; 13 | public static String mPid = "mPid"; 14 | 15 | 16 | public final static String mServices = "mServices"; 17 | public final static String mService = "mService"; 18 | public final static String receiverList = "receiverList"; 19 | public final static String app = "app"; 20 | public final static String mAnrRecords = "mAnrRecords"; 21 | public final static String mContext = "mContext"; 22 | public final static String userId = "userId"; 23 | public final static String mApp = "mApp"; 24 | public final static String curAdj = "curAdj"; 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/cn/myflv/android/noactive/entity/MemData.java: -------------------------------------------------------------------------------- 1 | package cn.myflv.android.noactive.entity; 2 | 3 | import android.os.FileObserver; 4 | 5 | import java.util.Collection; 6 | import java.util.Collections; 7 | import java.util.HashSet; 8 | import java.util.LinkedHashSet; 9 | import java.util.Set; 10 | 11 | import cn.myflv.android.noactive.utils.ConfigFileObserver; 12 | import lombok.Data; 13 | 14 | @Data 15 | public class MemData { 16 | private Set whiteApps = new HashSet<>(); 17 | private Set blackSystemApps = new HashSet<>(); 18 | private Set whiteProcessList = new HashSet<>(); 19 | private Set killProcessList = new HashSet<>(); 20 | private Set appBackgroundSet = Collections.synchronizedSet(new LinkedHashSet<>()); 21 | private final FileObserver fileObserver = new ConfigFileObserver(this); 22 | 23 | public MemData() { 24 | fileObserver.startWatching(); 25 | } 26 | 27 | public int getBackgroundIndex(String packageName) { 28 | int total = appBackgroundSet.size(); 29 | for (String pkg : appBackgroundSet) { 30 | if (whiteApps.contains(pkg)) { 31 | continue; 32 | } 33 | if (packageName.equals(pkg)) { 34 | return total; 35 | } else { 36 | total -= 1; 37 | } 38 | } 39 | return total; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/cn/myflv/android/noactive/entity/MethodEnum.java: -------------------------------------------------------------------------------- 1 | package cn.myflv.android.noactive.entity; 2 | 3 | public class MethodEnum { 4 | public final static String updateActivityUsageStats = "updateActivityUsageStats"; 5 | 6 | 7 | public final static String deliverToRegisteredReceiverLocked = "deliverToRegisteredReceiverLocked"; 8 | public final static String appNotResponding = "appNotResponding"; 9 | public final static String startAnrConsumerIfNeeded = "startAnrConsumerIfNeeded"; 10 | public final static String isAppForeground = "isAppForeground"; 11 | public final static String add = "add"; 12 | public final static String killServicesLocked = "killServicesLocked"; 13 | public final static String setCurAdj = "setCurAdj"; 14 | 15 | public final static String applyOomAdjLocked = "applyOomAdjLocked"; 16 | public final static String getEnable = "getEnable"; 17 | public final static String isFreezerSupported = "isFreezerSupported"; 18 | public final static String setProcessFrozen = "setProcessFrozen"; 19 | public final static String useFreezer = "useFreezer"; 20 | public final static String computeOomAdjLSP = "computeOomAdjLSP"; 21 | public final static String setOomAdj = "setOomAdj"; 22 | public final static String clearAppWhenScreenOffTimeOutInNight = "clearAppWhenScreenOffTimeOutInNight"; 23 | public final static String clearAppWhenScreenOffTimeOut = "clearAppWhenScreenOffTimeOut"; 24 | public final static String clearUnactiveApps = "clearUnactiveApps"; 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/cn/myflv/android/noactive/hook/ANRHook.java: -------------------------------------------------------------------------------- 1 | package cn.myflv.android.noactive.hook; 2 | 3 | import cn.myflv.android.noactive.entity.MemData; 4 | import cn.myflv.android.noactive.server.ActivityManagerService; 5 | import cn.myflv.android.noactive.server.AnrHelper; 6 | import cn.myflv.android.noactive.server.ProcessRecord; 7 | import cn.myflv.android.noactive.utils.Log; 8 | import de.robv.android.xposed.XC_MethodReplacement; 9 | 10 | public class ANRHook extends XC_MethodReplacement { 11 | private final ClassLoader classLoader; 12 | private final MemData memData; 13 | 14 | public ANRHook(ClassLoader classLoader, MemData memData) { 15 | this.classLoader = classLoader; 16 | this.memData = memData; 17 | } 18 | 19 | @Override 20 | protected Object replaceHookedMethod(MethodHookParam param) throws Throwable { 21 | // 获取方法参数 22 | Object[] args = param.args; 23 | AnrHelper anrHelper = new AnrHelper(classLoader, param.thisObject); 24 | // ANR进程为空就不处理 25 | if (args[0] == null) return null; 26 | // ANR进程 27 | ProcessRecord processRecord = new ProcessRecord(args[0]); 28 | // 是否系统进程 29 | boolean isSystem = processRecord.getApplicationInfo().isSystem(); 30 | // 进程对应包名 31 | String packageName = processRecord.getApplicationInfo().getPackageName(); 32 | boolean isNotBlackSystem = !memData.getBlackSystemApps().contains(packageName); 33 | 34 | // 系统应用并且不是系统黑名单 35 | if ((isSystem && isNotBlackSystem) || processRecord.getUserId() != ActivityManagerService.MAIN_USER) { 36 | synchronized (anrHelper.getAnrRecords()) { 37 | // 添加ANR记录 38 | anrHelper.add(args); 39 | } 40 | // 开启ANR消费如果需要 41 | anrHelper.startAnrConsumerIfNeeded(); 42 | } 43 | Log.d("Keep " + (processRecord.getProcessName() != null ? processRecord.getProcessName() : packageName)); 44 | // 不处理 45 | return null; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/cn/myflv/android/noactive/hook/AppSwitchHook.java: -------------------------------------------------------------------------------- 1 | package cn.myflv.android.noactive.hook; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | import cn.myflv.android.noactive.entity.MemData; 10 | import cn.myflv.android.noactive.server.ActivityManagerService; 11 | import cn.myflv.android.noactive.server.ApplicationInfo; 12 | import cn.myflv.android.noactive.server.ComponentName; 13 | import cn.myflv.android.noactive.server.Event; 14 | import cn.myflv.android.noactive.server.ProcessList; 15 | import cn.myflv.android.noactive.server.ProcessRecord; 16 | import cn.myflv.android.noactive.utils.FreezeUtils; 17 | import cn.myflv.android.noactive.utils.Log; 18 | import cn.myflv.android.noactive.utils.ThreadUtil; 19 | import de.robv.android.xposed.XC_MethodHook; 20 | 21 | public class AppSwitchHook extends XC_MethodHook { 22 | private final int ACTIVITY_RESUMED; 23 | private final int ACTIVITY_PAUSED; 24 | 25 | public final static int SIMPLE = 1; 26 | public final static int DIFFICULT = 2; 27 | private final int type; 28 | 29 | private final MemData memData; 30 | private final FreezeUtils freezeUtils; 31 | private final Map freezerTokenMap = Collections.synchronizedMap(new HashMap<>()); 32 | 33 | public AppSwitchHook(ClassLoader classLoader, MemData memData, int type) { 34 | this.ACTIVITY_RESUMED = Event.ACTIVITY_RESUMED(classLoader); 35 | this.ACTIVITY_PAUSED = Event.ACTIVITY_PAUSED(classLoader); 36 | this.memData = memData; 37 | this.freezeUtils = new FreezeUtils(classLoader); 38 | this.type = type; 39 | } 40 | 41 | /** 42 | * Hook APP 切换事件 43 | * 44 | * @param param 方法参数 45 | * @throws Throwable 异常 46 | */ 47 | public void beforeHookedMethod(MethodHookParam param) throws Throwable { 48 | // 开启一个新线程防止避免阻塞主线程 49 | new Thread(() -> { 50 | // 获取方法参数 51 | Object[] args = param.args; 52 | // 获取切换事件 53 | int event = (int) args[2]; 54 | // AMS有两个方法,但参数不同 55 | String packageName = type == SIMPLE ? (String) args[0] : new ComponentName(args[0]).getPackageName(); 56 | int userId = (int) args[1]; 57 | if (userId != ActivityManagerService.MAIN_USER) { 58 | return; 59 | } 60 | // 如果是进入前台 61 | if (event == ACTIVITY_RESUMED) { 62 | // 后台APP移除 63 | memData.getAppBackgroundSet().remove(packageName); 64 | } else if (event != ACTIVITY_PAUSED) { 65 | // 不是进入前台或者后台就不处理 66 | return; 67 | } 68 | 69 | // 获取AMS 70 | ActivityManagerService activityManagerService = new ActivityManagerService(param.thisObject); 71 | // 重要系统APP 72 | boolean isImportantSystemApp = activityManagerService.isImportantSystemApp(packageName); 73 | if (isImportantSystemApp) { 74 | Log.d(packageName + " is important system app"); 75 | return; 76 | } 77 | // 系统APP 78 | boolean isSystem = activityManagerService.isSystem(packageName); 79 | // 判断是否白名单系统APP 80 | if (isSystem && !memData.getBlackSystemApps().contains(packageName)) { 81 | Log.d(packageName + " is white system app"); 82 | return; 83 | } 84 | if (event == ACTIVITY_PAUSED) { 85 | //暂停事件 86 | onPause(activityManagerService, packageName); 87 | } else { 88 | //继续事件 89 | onResume(activityManagerService, packageName); 90 | } 91 | }).start(); 92 | } 93 | 94 | /** 95 | * 获取目标进程 96 | * 97 | * @param activityManagerService AMS 98 | * @param packageName 包名 99 | * @return 目标进程列表 100 | */ 101 | public List getTargetProcessRecords(ActivityManagerService activityManagerService, String packageName) { 102 | // 从AMS获取进程列表对象 103 | ProcessList processList = activityManagerService.getProcessList(); 104 | // 从进程列表对象获取所有进程 105 | List processRecords = processList.getProcessRecords(); 106 | // 存放需要冻结/解冻的 processRecord 107 | List targetProcessRecords = new ArrayList<>(); 108 | // 对进程列表加锁 109 | synchronized (processList.getProcessList()) { 110 | // 遍历进程列表 111 | for (ProcessRecord processRecord : processRecords) { 112 | if (processRecord.getUserId() != ActivityManagerService.MAIN_USER) { 113 | continue; 114 | } 115 | ApplicationInfo applicationInfo = processRecord.getApplicationInfo(); 116 | // 如果包名和事件的包名不同就不处理 117 | if (!applicationInfo.getPackageName().equals(packageName)) { 118 | continue; 119 | } 120 | // 获取进程名 121 | String processName = processRecord.getProcessName(); 122 | // 如果进程名称不是包名开头就跳过 123 | if (!processName.startsWith(packageName)) { 124 | continue; 125 | } 126 | // 如果白名单进程包含进程则跳过 127 | if (memData.getWhiteProcessList().contains(processName)) { 128 | Log.d("white process " + processName); 129 | continue; 130 | } 131 | // 如果白名单APP包含包名并且杀死进程不包含进程名就跳过 132 | if (memData.getWhiteApps().contains(packageName) && !memData.getKillProcessList().contains(processName)) { 133 | Log.d("white app process " + processName); 134 | continue; 135 | } 136 | // 添加目标进程 137 | targetProcessRecords.add(processRecord); 138 | } 139 | } 140 | return targetProcessRecords; 141 | } 142 | 143 | /** 144 | * APP切换至前台 145 | * 146 | * @param packageName 包名 147 | * @param activityManagerService AMS 148 | */ 149 | public void onResume(ActivityManagerService activityManagerService, String packageName) { 150 | List targetProcessRecords = getTargetProcessRecords(activityManagerService, packageName); 151 | // 如果目标进程为空就不处理 152 | if (targetProcessRecords.isEmpty()) { 153 | return; 154 | } 155 | Log.d(packageName + " resumed"); 156 | // 遍历目标进程列表 157 | for (ProcessRecord targetProcessRecord : targetProcessRecords) { 158 | // 确保APP不在后台 159 | if (memData.getAppBackgroundSet().contains(packageName)) { 160 | return; 161 | } 162 | // 解冻进程 163 | freezeUtils.unFreezer(targetProcessRecord); 164 | } 165 | } 166 | 167 | /** 168 | * APP切换至后台 169 | * 170 | * @param activityManagerService AMS 171 | * @param packageName 包名 172 | */ 173 | public void onPause(ActivityManagerService activityManagerService, String packageName) { 174 | Log.d(packageName + " paused"); 175 | long token = System.currentTimeMillis(); 176 | setToken(packageName, token); 177 | // 休眠3s 178 | ThreadUtil.sleep(3000); 179 | if (!isCorrectToken(packageName, token)) { 180 | return; 181 | } 182 | 183 | // 应用是否前台 184 | boolean isAppForeground = activityManagerService.isAppForeground(packageName); 185 | // 如果是前台应用就不处理 186 | if (isAppForeground) { 187 | Log.d(packageName + " is in foreground"); 188 | return; 189 | } 190 | 191 | // 后台应用添加包名 192 | memData.getAppBackgroundSet().add(packageName); 193 | 194 | List targetProcessRecords = getTargetProcessRecords(activityManagerService, packageName); 195 | // 如果目标进程为空就不处理 196 | if (targetProcessRecords.isEmpty()) { 197 | return; 198 | } 199 | 200 | // 遍历目标进程 201 | for (ProcessRecord targetProcessRecord : targetProcessRecords) { 202 | // 应用又进入前台了 203 | if (!memData.getAppBackgroundSet().contains(packageName)) { 204 | // 为保证解冻顺利 205 | return; 206 | } 207 | // 目标进程名 208 | String processName = targetProcessRecord.getProcessName(); 209 | // 目标进程PID 210 | int pid = targetProcessRecord.getPid(); 211 | // 如果杀死进程列表包含进程名 212 | if (memData.getKillProcessList().contains(processName)) { 213 | Log.d(processName + " kill"); 214 | // 杀死进程 215 | FreezeUtils.kill(pid); 216 | } else { 217 | Log.d(processName + " freezer"); 218 | freezeUtils.freezer(targetProcessRecord); 219 | } 220 | } 221 | } 222 | 223 | 224 | public void setToken(String packageName, long token) { 225 | freezerTokenMap.put(packageName, token); 226 | } 227 | 228 | public boolean isCorrectToken(String packageName, long value) { 229 | Long token = freezerTokenMap.get(packageName); 230 | return token != null && value == token; 231 | } 232 | 233 | } 234 | -------------------------------------------------------------------------------- /app/src/main/java/cn/myflv/android/noactive/hook/BroadcastDeliverHook.java: -------------------------------------------------------------------------------- 1 | package cn.myflv.android.noactive.hook; 2 | 3 | import cn.myflv.android.noactive.entity.FieldEnum; 4 | import cn.myflv.android.noactive.entity.MemData; 5 | import cn.myflv.android.noactive.server.ActivityManagerService; 6 | import cn.myflv.android.noactive.server.ApplicationInfo; 7 | import cn.myflv.android.noactive.server.BroadcastFilter; 8 | import cn.myflv.android.noactive.server.ProcessRecord; 9 | import cn.myflv.android.noactive.server.ReceiverList; 10 | import cn.myflv.android.noactive.utils.Log; 11 | import de.robv.android.xposed.XC_MethodHook; 12 | import de.robv.android.xposed.XposedHelpers; 13 | 14 | public class BroadcastDeliverHook extends XC_MethodHook { 15 | private final MemData memData; 16 | 17 | public BroadcastDeliverHook(MemData memData) { 18 | this.memData = memData; 19 | } 20 | 21 | @Override 22 | public void beforeHookedMethod(MethodHookParam param) throws Throwable { 23 | Object[] args = param.args; 24 | if (args[1] == null) { 25 | return; 26 | } 27 | BroadcastFilter broadcastFilter = new BroadcastFilter(args[1]); 28 | ReceiverList receiverList = broadcastFilter.getReceiverList(); 29 | // 如果广播为空就不处理 30 | if (receiverList == null) { 31 | return; 32 | } 33 | ProcessRecord processRecord = receiverList.getProcessRecord(); 34 | // 如果进程或者应用信息为空就不处理 35 | if (processRecord == null || processRecord.getApplicationInfo() == null) { 36 | return; 37 | } 38 | if (processRecord.getUserId() != ActivityManagerService.MAIN_USER) { 39 | return; 40 | } 41 | ApplicationInfo applicationInfo = processRecord.getApplicationInfo(); 42 | String packageName = processRecord.getApplicationInfo().getPackageName(); 43 | // 如果包名为空就不处理(猜测系统进程可能为空) 44 | if (packageName == null) { 45 | return; 46 | } 47 | String processName = processRecord.getProcessName(); 48 | // 如果进程名称不是包名开头就跳过 49 | if (!processName.startsWith(packageName)) { 50 | return; 51 | } 52 | // 如果是系统应用并且不是系统黑名单就不处理 53 | if (applicationInfo.getUid() < 10000 || (applicationInfo.isSystem() && !memData.getBlackSystemApps().contains(packageName))) { 54 | return; 55 | } 56 | // 如果是前台应用就不处理 57 | if (!memData.getAppBackgroundSet().contains(packageName)) { 58 | return; 59 | } 60 | // 如果白名单应用或者进程就不处理 61 | if (memData.getWhiteApps().contains(packageName) || memData.getWhiteProcessList().contains(processName)) { 62 | return; 63 | } 64 | // 暂存 65 | Object app = processRecord.getProcessRecord(); 66 | param.setObjectExtra(FieldEnum.app, app); 67 | Log.d(processRecord.getProcessName() + " clear broadcast"); 68 | // 清楚广播 69 | receiverList.clear(); 70 | } 71 | 72 | @Override 73 | protected void afterHookedMethod(MethodHookParam param) throws Throwable { 74 | super.afterHookedMethod(param); 75 | 76 | // 获取进程 77 | Object app = param.getObjectExtra(FieldEnum.app); 78 | if (app == null) { 79 | return; 80 | } 81 | 82 | Object[] args = param.args; 83 | if (args[1] == null) { 84 | return; 85 | } 86 | Object receiverList = XposedHelpers.getObjectField(args[1], FieldEnum.receiverList); 87 | if (receiverList == null) { 88 | return; 89 | } 90 | // 还原修改 91 | XposedHelpers.setObjectField(receiverList, FieldEnum.app, app); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /app/src/main/java/cn/myflv/android/noactive/hook/CacheFreezerHook.java: -------------------------------------------------------------------------------- 1 | package cn.myflv.android.noactive.hook; 2 | 3 | import de.robv.android.xposed.XC_MethodReplacement; 4 | 5 | public class CacheFreezerHook extends XC_MethodReplacement { 6 | @Override 7 | protected Object replaceHookedMethod(MethodHookParam param) throws Throwable { 8 | return false; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/cn/myflv/android/noactive/hook/MilletHook.java: -------------------------------------------------------------------------------- 1 | package cn.myflv.android.noactive.hook; 2 | 3 | import de.robv.android.xposed.XC_MethodReplacement; 4 | 5 | public class MilletHook extends XC_MethodReplacement { 6 | @Override 7 | protected Object replaceHookedMethod(MethodHookParam param) throws Throwable { 8 | return false; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/cn/myflv/android/noactive/hook/OomAdjHook.java: -------------------------------------------------------------------------------- 1 | package cn.myflv.android.noactive.hook; 2 | 3 | import cn.myflv.android.noactive.entity.MemData; 4 | import cn.myflv.android.noactive.server.ActivityManagerService; 5 | import cn.myflv.android.noactive.server.ApplicationInfo; 6 | import cn.myflv.android.noactive.server.ProcessList; 7 | import cn.myflv.android.noactive.server.ProcessRecord; 8 | import cn.myflv.android.noactive.server.ProcessStateRecord; 9 | import cn.myflv.android.noactive.utils.Log; 10 | import de.robv.android.xposed.XC_MethodHook; 11 | 12 | public class OomAdjHook extends XC_MethodHook { 13 | private final ClassLoader classLoader; 14 | private final MemData memData; 15 | private final int type; 16 | public final static int Android_S = 1; 17 | public final static int Android_Q_R = 2; 18 | public final static int Color = 3; 19 | 20 | public OomAdjHook(ClassLoader classLoader, MemData memData, int type) { 21 | this.classLoader = classLoader; 22 | this.memData = memData; 23 | this.type = type; 24 | } 25 | 26 | 27 | public void computeOomAdj(MethodHookParam param) { 28 | ProcessRecord processRecord; 29 | switch (type) { 30 | case Android_S: 31 | processRecord = new ProcessStateRecord(param.thisObject).getProcessRecord(); 32 | break; 33 | case Android_Q_R: 34 | case Color: 35 | processRecord = new ProcessRecord(param.args[0]); 36 | break; 37 | default: 38 | return; 39 | } 40 | // 如果进程或者应用信息为空就不处理 41 | if (processRecord == null || processRecord.getApplicationInfo() == null) { 42 | return; 43 | } 44 | if (processRecord.getUserId()!= ActivityManagerService.MAIN_USER){ 45 | return; 46 | } 47 | ApplicationInfo applicationInfo = processRecord.getApplicationInfo(); 48 | String packageName = processRecord.getApplicationInfo().getPackageName(); 49 | // 如果包名为空就不处理(猜测系统进程可能为空) 50 | if (packageName == null) { 51 | return; 52 | } 53 | String processName = processRecord.getProcessName(); 54 | // 如果进程名称等于包名就跳过 55 | if (!processName.startsWith(packageName)) { 56 | return; 57 | } 58 | // 如果是系统应用并且不是系统黑名单就不处理 59 | if (applicationInfo.getUid() < 10000 || (applicationInfo.isSystem() && !memData.getBlackSystemApps().contains(packageName))) { 60 | return; 61 | } 62 | // 如果是前台应用就不处理 63 | if (!memData.getAppBackgroundSet().contains(packageName)) { 64 | return; 65 | } 66 | int finalCurlAdj; 67 | 68 | // 如果白名单应用或者进程就不处理 69 | if (memData.getWhiteApps().contains(packageName) || memData.getWhiteProcessList().contains(processName)) { 70 | finalCurlAdj = processName.equals(packageName) ? 500 : 700; 71 | } else { 72 | int curAdj = processName.equals(packageName) ? 700 : 900; 73 | finalCurlAdj = curAdj + memData.getBackgroundIndex(packageName); 74 | } 75 | 76 | Log.d(processName + " -> " + finalCurlAdj); 77 | 78 | 79 | switch (type) { 80 | case Android_S: 81 | param.args[0] = finalCurlAdj; 82 | break; 83 | case Android_Q_R: 84 | processRecord.setCurAdj(finalCurlAdj); 85 | break; 86 | case Color: 87 | ProcessList.setOomAdj(classLoader, processRecord.getPid(), processRecord.getUid(), finalCurlAdj); 88 | break; 89 | default: 90 | } 91 | 92 | } 93 | 94 | @Override 95 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 96 | super.beforeHookedMethod(param); 97 | if (type != Color) { 98 | computeOomAdj(param); 99 | } 100 | } 101 | 102 | @Override 103 | protected void afterHookedMethod(MethodHookParam param) throws Throwable { 104 | super.afterHookedMethod(param); 105 | if (type == Color) { 106 | computeOomAdj(param); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /app/src/main/java/cn/myflv/android/noactive/hook/TestHook.java: -------------------------------------------------------------------------------- 1 | package cn.myflv.android.noactive.hook; 2 | 3 | import android.os.Process; 4 | 5 | import cn.myflv.android.noactive.server.ProcessRecord; 6 | import cn.myflv.android.noactive.utils.Log; 7 | import de.robv.android.xposed.XC_MethodHook; 8 | 9 | public class TestHook extends XC_MethodHook { 10 | private final String TAG; 11 | 12 | public TestHook(String TAG) { 13 | this.TAG = TAG; 14 | } 15 | 16 | @Override 17 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 18 | super.beforeHookedMethod(param); 19 | Log.d(TAG); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/cn/myflv/android/noactive/server/ActiveServices.java: -------------------------------------------------------------------------------- 1 | package cn.myflv.android.noactive.server; 2 | 3 | import cn.myflv.android.noactive.entity.FieldEnum; 4 | import cn.myflv.android.noactive.entity.MethodEnum; 5 | import de.robv.android.xposed.XposedHelpers; 6 | import lombok.Data; 7 | 8 | @Data 9 | public class ActiveServices { 10 | private final Object activeServices; 11 | 12 | public ActiveServices(Object activeServices) { 13 | this.activeServices = activeServices; 14 | } 15 | 16 | public void killServicesLocked(ProcessRecord processRecord) { 17 | XposedHelpers.callMethod(activeServices, MethodEnum.killServicesLocked, processRecord.getProcessRecord(), false); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/cn/myflv/android/noactive/server/ActivityManagerService.java: -------------------------------------------------------------------------------- 1 | package cn.myflv.android.noactive.server; 2 | 3 | 4 | import android.content.Context; 5 | import android.content.pm.ApplicationInfo; 6 | import android.content.pm.PackageManager; 7 | 8 | import java.lang.reflect.InvocationTargetException; 9 | 10 | import cn.myflv.android.noactive.entity.ClassEnum; 11 | import cn.myflv.android.noactive.entity.FieldEnum; 12 | import cn.myflv.android.noactive.entity.MethodEnum; 13 | import cn.myflv.android.noactive.utils.Log; 14 | import de.robv.android.xposed.XposedHelpers; 15 | import lombok.Data; 16 | 17 | @Data 18 | public class ActivityManagerService { 19 | public final static int MAIN_USER = 0; 20 | private final Object activityManagerService; 21 | private final ProcessList processList; 22 | private final ActiveServices activeServices; 23 | private final Context context; 24 | 25 | public ActivityManagerService(Object activityManagerService) { 26 | this.activityManagerService = activityManagerService; 27 | this.processList = new ProcessList(XposedHelpers.getObjectField(activityManagerService, FieldEnum.mProcessList)); 28 | this.activeServices = new ActiveServices(XposedHelpers.getObjectField(activityManagerService, FieldEnum.mServices)); 29 | this.context = (Context) XposedHelpers.getObjectField(activityManagerService, FieldEnum.mContext); 30 | } 31 | 32 | public boolean isAppForeground(String packageName) { 33 | ApplicationInfo applicationInfo = getApplicationInfo(packageName); 34 | if (applicationInfo == null) { 35 | return true; 36 | } 37 | int uid = applicationInfo.uid; 38 | Class clazz = activityManagerService.getClass(); 39 | while (clazz != null && !clazz.getName().equals(Object.class.getName()) && !clazz.getName().equals(ClassEnum.ActivityManagerService)) { 40 | clazz = clazz.getSuperclass(); 41 | } 42 | if (clazz == null || !clazz.getName().equals(ClassEnum.ActivityManagerService)) { 43 | Log.d("super activityManagerService is not found"); 44 | return true; 45 | } 46 | try { 47 | return (boolean) XposedHelpers.findMethodBestMatch(clazz, MethodEnum.isAppForeground, uid).invoke(activityManagerService, uid); 48 | } catch (IllegalAccessException | InvocationTargetException e) { 49 | Log.d("call isAppForeground method error"); 50 | } 51 | return true; 52 | } 53 | 54 | public boolean isSystem(String packageName) { 55 | ApplicationInfo applicationInfo = getApplicationInfo(packageName); 56 | if (applicationInfo == null) { 57 | return true; 58 | } 59 | return (applicationInfo.flags & (ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) != 0; 60 | } 61 | 62 | public boolean isImportantSystemApp(String packageName) { 63 | ApplicationInfo applicationInfo = getApplicationInfo(packageName); 64 | if (applicationInfo == null) { 65 | return true; 66 | } 67 | return applicationInfo.uid < 10000; 68 | } 69 | 70 | public ApplicationInfo getApplicationInfo(String packageName) { 71 | try { 72 | PackageManager packageManager = context.getPackageManager(); 73 | return packageManager.getApplicationInfo(packageName, PackageManager.GET_UNINSTALLED_PACKAGES); 74 | } catch (PackageManager.NameNotFoundException e) { 75 | Log.d(packageName + " not found"); 76 | } 77 | return null; 78 | } 79 | 80 | 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/java/cn/myflv/android/noactive/server/AnrHelper.java: -------------------------------------------------------------------------------- 1 | package cn.myflv.android.noactive.server; 2 | 3 | import cn.myflv.android.noactive.entity.ClassEnum; 4 | import cn.myflv.android.noactive.entity.FieldEnum; 5 | import cn.myflv.android.noactive.entity.MethodEnum; 6 | import de.robv.android.xposed.XposedHelpers; 7 | import lombok.Data; 8 | 9 | @Data 10 | public class AnrHelper { 11 | private final ClassLoader classLoader; 12 | private final Object anrHelper; 13 | private final Object anrRecords; 14 | // private final ActivityManagerService activityManagerService; 15 | 16 | public AnrHelper(ClassLoader classLoader, Object anrHelper) { 17 | this.classLoader = classLoader; 18 | this.anrHelper = anrHelper; 19 | this.anrRecords = XposedHelpers.getObjectField(anrHelper, FieldEnum.mAnrRecords); 20 | // this.activityManagerService = new ActivityManagerService(XposedHelpers.getObjectField(anrHelper, FieldEnum.mService)); 21 | } 22 | 23 | 24 | public void startAnrConsumerIfNeeded() { 25 | XposedHelpers.callMethod(anrHelper, MethodEnum.startAnrConsumerIfNeeded); 26 | } 27 | 28 | public void add(Object... args) { 29 | Class AnrRecord = XposedHelpers.findClass(ClassEnum.AnrRecord, classLoader); 30 | Object anrRecord = XposedHelpers.newInstance(AnrRecord, args); 31 | XposedHelpers.callMethod(anrRecords, MethodEnum.add, anrRecord); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/cn/myflv/android/noactive/server/ApplicationInfo.java: -------------------------------------------------------------------------------- 1 | package cn.myflv.android.noactive.server; 2 | 3 | import cn.myflv.android.noactive.entity.FieldEnum; 4 | import cn.myflv.android.noactive.utils.Log; 5 | import de.robv.android.xposed.XposedHelpers; 6 | import lombok.Data; 7 | 8 | @Data 9 | public class ApplicationInfo { 10 | private final int FLAG_SYSTEM; 11 | private final int FLAG_UPDATED_SYSTEM_APP; 12 | private final int flags; 13 | private final int uid; 14 | private final String processName; 15 | private final String packageName; 16 | private Object applicationInfo; 17 | 18 | public ApplicationInfo(Object applicationInfo) { 19 | this.applicationInfo = applicationInfo; 20 | this.processName = (String) XposedHelpers.getObjectField(applicationInfo, FieldEnum.processName); 21 | this.packageName = (String) XposedHelpers.getObjectField(applicationInfo, FieldEnum.packageName); 22 | this.flags = XposedHelpers.getIntField(applicationInfo, FieldEnum.flags); 23 | this.uid = XposedHelpers.getIntField(applicationInfo, FieldEnum.uid); 24 | this.FLAG_SYSTEM = XposedHelpers.getStaticIntField(applicationInfo.getClass(), "FLAG_SYSTEM"); 25 | this.FLAG_UPDATED_SYSTEM_APP = XposedHelpers.getStaticIntField(applicationInfo.getClass(), "FLAG_UPDATED_SYSTEM_APP"); 26 | } 27 | 28 | 29 | public boolean isSystem() { 30 | return (flags & (FLAG_SYSTEM | FLAG_UPDATED_SYSTEM_APP)) != 0; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/cn/myflv/android/noactive/server/BroadcastFilter.java: -------------------------------------------------------------------------------- 1 | package cn.myflv.android.noactive.server; 2 | 3 | import cn.myflv.android.noactive.entity.FieldEnum; 4 | import de.robv.android.xposed.XposedHelpers; 5 | import lombok.Data; 6 | 7 | @Data 8 | public class BroadcastFilter { 9 | private final Object broadcastFilter; 10 | private final ReceiverList receiverList; 11 | 12 | 13 | public BroadcastFilter(Object broadcastFilter) { 14 | this.broadcastFilter = broadcastFilter; 15 | this.receiverList = new ReceiverList(XposedHelpers.getObjectField(broadcastFilter, FieldEnum.receiverList)); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/cn/myflv/android/noactive/server/BroadcastQueue.java: -------------------------------------------------------------------------------- 1 | package cn.myflv.android.noactive.server; 2 | 3 | import cn.myflv.android.noactive.entity.FieldEnum; 4 | import de.robv.android.xposed.XposedHelpers; 5 | import lombok.Data; 6 | 7 | @Data 8 | public class BroadcastQueue { 9 | private final Object broadcastQueue; 10 | private ActivityManagerService activityManagerService; 11 | 12 | public BroadcastQueue(Object broadcastQueue) { 13 | this.broadcastQueue = broadcastQueue; 14 | this.activityManagerService = new ActivityManagerService(XposedHelpers.getObjectField(broadcastQueue, FieldEnum.mService)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/cn/myflv/android/noactive/server/ComponentName.java: -------------------------------------------------------------------------------- 1 | package cn.myflv.android.noactive.server; 2 | 3 | import cn.myflv.android.noactive.entity.FieldEnum; 4 | import de.robv.android.xposed.XposedHelpers; 5 | import lombok.Data; 6 | 7 | @Data 8 | public class ComponentName { 9 | private final Object componentName; 10 | private final String packageName; 11 | 12 | public ComponentName(Object componentName) { 13 | this.componentName = componentName; 14 | this.packageName = (String) XposedHelpers.getObjectField(componentName, FieldEnum.mPackage); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/cn/myflv/android/noactive/server/Event.java: -------------------------------------------------------------------------------- 1 | package cn.myflv.android.noactive.server; 2 | 3 | import de.robv.android.xposed.XposedHelpers; 4 | 5 | public class Event { 6 | 7 | public final static String Event = "android.app.usage.UsageEvents.Event"; 8 | public final static String ACTIVITY_RESUMED = "ACTIVITY_RESUMED"; 9 | public final static String ACTIVITY_PAUSED = "ACTIVITY_PAUSED"; 10 | public final static String ACTIVITY_STOPPED = "ACTIVITY_STOPPED"; 11 | public final static String ACTIVITY_DESTROYED = "ACTIVITY_DESTROYED"; 12 | 13 | public static Class getEvent(ClassLoader classLoader) { 14 | return XposedHelpers.findClass(Event, classLoader); 15 | } 16 | 17 | public static int ACTIVITY_RESUMED(ClassLoader classLoader) { 18 | Class Event = getEvent(classLoader); 19 | return XposedHelpers.getStaticIntField(Event, ACTIVITY_RESUMED); 20 | } 21 | 22 | public static int ACTIVITY_PAUSED(ClassLoader classLoader) { 23 | Class Event = getEvent(classLoader); 24 | return XposedHelpers.getStaticIntField(Event, ACTIVITY_PAUSED); 25 | } 26 | public static int ACTIVITY_STOPPED(ClassLoader classLoader) { 27 | Class Event = getEvent(classLoader); 28 | return XposedHelpers.getStaticIntField(Event, ACTIVITY_STOPPED); 29 | } 30 | 31 | public static int ACTIVITY_DESTROYED(ClassLoader classLoader) { 32 | Class Event = getEvent(classLoader); 33 | return XposedHelpers.getStaticIntField(Event, ACTIVITY_DESTROYED); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/cn/myflv/android/noactive/server/ProcessList.java: -------------------------------------------------------------------------------- 1 | package cn.myflv.android.noactive.server; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import cn.myflv.android.noactive.entity.ClassEnum; 7 | import cn.myflv.android.noactive.entity.FieldEnum; 8 | import cn.myflv.android.noactive.entity.MethodEnum; 9 | import de.robv.android.xposed.XposedHelpers; 10 | import lombok.Data; 11 | 12 | @Data 13 | public class ProcessList { 14 | private final Object processList; 15 | private final List processRecords = new ArrayList<>(); 16 | 17 | public ProcessList(Object processList) { 18 | this.processList = processList; 19 | try { 20 | List processRecordList = (List) XposedHelpers.getObjectField(processList, FieldEnum.mLruProcesses); 21 | for (Object proc : processRecordList) { 22 | ProcessRecord processRecord = new ProcessRecord(proc); 23 | processRecords.add(processRecord); 24 | } 25 | } catch (Exception ignored) { 26 | 27 | } 28 | } 29 | 30 | public static void setOomAdj(ClassLoader classLoader,int pid, int uid, int oomAdj) { 31 | Class ProcessList = XposedHelpers.findClass(ClassEnum.ProcessList, classLoader); 32 | XposedHelpers.callStaticMethod(ProcessList, MethodEnum.setOomAdj,pid,uid,oomAdj); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/cn/myflv/android/noactive/server/ProcessRecord.java: -------------------------------------------------------------------------------- 1 | package cn.myflv.android.noactive.server; 2 | 3 | import android.annotation.TargetApi; 4 | import android.os.Build; 5 | 6 | import cn.myflv.android.noactive.entity.FieldEnum; 7 | import de.robv.android.xposed.XposedHelpers; 8 | import lombok.Data; 9 | 10 | @Data 11 | public class ProcessRecord { 12 | private final int uid; 13 | private final int pid; 14 | private final String processName; 15 | private final int userId; 16 | private final ApplicationInfo applicationInfo; 17 | private Object processRecord; 18 | 19 | 20 | public ProcessRecord(Object processRecord) { 21 | this.processRecord = processRecord; 22 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { 23 | this.pid = XposedHelpers.getIntField(processRecord, FieldEnum.mPid); 24 | } else { 25 | this.pid = XposedHelpers.getIntField(processRecord, FieldEnum.pid); 26 | } 27 | this.uid = XposedHelpers.getIntField(processRecord, FieldEnum.uid); 28 | this.processName = (String) XposedHelpers.getObjectField(processRecord, FieldEnum.processName); 29 | this.userId = XposedHelpers.getIntField(processRecord, FieldEnum.userId); 30 | this.applicationInfo = new ApplicationInfo(XposedHelpers.getObjectField(processRecord, FieldEnum.info)); 31 | } 32 | 33 | public void setCurAdj(int curAdj) { 34 | XposedHelpers.setIntField(processRecord, FieldEnum.curAdj, curAdj); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/cn/myflv/android/noactive/server/ProcessStateRecord.java: -------------------------------------------------------------------------------- 1 | package cn.myflv.android.noactive.server; 2 | 3 | import cn.myflv.android.noactive.entity.FieldEnum; 4 | import de.robv.android.xposed.XposedHelpers; 5 | import lombok.Data; 6 | 7 | @Data 8 | public class ProcessStateRecord { 9 | private final Object processStateRecord; 10 | private final ProcessRecord processRecord; 11 | 12 | public ProcessStateRecord(Object processStateRecord) { 13 | this.processStateRecord = processStateRecord; 14 | this.processRecord = new ProcessRecord(XposedHelpers.getObjectField(processStateRecord, FieldEnum.mApp)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/cn/myflv/android/noactive/server/ReceiverList.java: -------------------------------------------------------------------------------- 1 | package cn.myflv.android.noactive.server; 2 | 3 | import cn.myflv.android.noactive.entity.FieldEnum; 4 | import cn.myflv.android.noactive.utils.Log; 5 | import de.robv.android.xposed.XC_MethodHook; 6 | import de.robv.android.xposed.XposedHelpers; 7 | import lombok.Data; 8 | 9 | @Data 10 | public class ReceiverList { 11 | private final Object receiverList; 12 | private ProcessRecord processRecord; 13 | 14 | public ReceiverList(Object receiverList) { 15 | this.receiverList = receiverList; 16 | try { 17 | this.processRecord = new ProcessRecord(XposedHelpers.getObjectField(receiverList, FieldEnum.app)); 18 | } catch (Exception ignored) { 19 | } 20 | } 21 | 22 | 23 | public void clear() { 24 | XposedHelpers.setObjectField(receiverList, FieldEnum.app, null); 25 | } 26 | 27 | 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/cn/myflv/android/noactive/utils/ConfigFileObserver.java: -------------------------------------------------------------------------------- 1 | package cn.myflv.android.noactive.utils; 2 | 3 | import android.os.FileObserver; 4 | 5 | import java.util.HashSet; 6 | import java.util.Set; 7 | 8 | import cn.myflv.android.noactive.entity.MemData; 9 | 10 | public class ConfigFileObserver extends FileObserver { 11 | private final MemData memData; 12 | 13 | public ConfigFileObserver(MemData memData) { 14 | super(FreezerConfig.ConfigDir); 15 | this.memData = memData; 16 | FreezerConfig.checkAndInit(); 17 | reload(); 18 | } 19 | 20 | @Override 21 | public void startWatching() { 22 | super.startWatching(); 23 | for (String file : FreezerConfig.listenConfig) { 24 | Log.d("Start monitor " + file); 25 | } 26 | } 27 | 28 | @Override 29 | public void onEvent(int event, String path) { 30 | int e = event & ALL_EVENTS; 31 | switch (e) { 32 | case DELETE: 33 | case DELETE_SELF: 34 | FreezerConfig.checkAndInit(); 35 | break; 36 | case MODIFY: 37 | case MOVE_SELF: 38 | ThreadUtil.sleep(2000); 39 | reload(); 40 | } 41 | } 42 | 43 | public void reload() { 44 | for (String file : FreezerConfig.listenConfig) { 45 | Log.d("Reload " + file); 46 | Set newConfig = new HashSet<>(FreezerConfig.get(file)); 47 | switch (file) { 48 | case FreezerConfig.whiteAppConfig: 49 | memData.setWhiteApps(newConfig); 50 | break; 51 | case FreezerConfig.whiteProcessConfig: 52 | memData.setWhiteProcessList(newConfig); 53 | break; 54 | case FreezerConfig.killProcessConfig: 55 | memData.setKillProcessList(newConfig); 56 | break; 57 | case FreezerConfig.blackSystemAppConfig: 58 | memData.setBlackSystemApps(newConfig); 59 | break; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/cn/myflv/android/noactive/utils/FreezeUtils.java: -------------------------------------------------------------------------------- 1 | // 2 | // Decompiled by Jadx - 917ms 3 | // 4 | package cn.myflv.android.noactive.utils; 5 | 6 | import android.os.Process; 7 | 8 | import java.io.BufferedReader; 9 | import java.io.FileReader; 10 | import java.io.IOException; 11 | import java.io.PrintWriter; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | import cn.myflv.android.noactive.entity.ClassEnum; 16 | import cn.myflv.android.noactive.entity.MethodEnum; 17 | import cn.myflv.android.noactive.server.ProcessRecord; 18 | import de.robv.android.xposed.XposedHelpers; 19 | 20 | public class FreezeUtils { 21 | 22 | private final static int CONT = 18; 23 | 24 | private static final int FREEZE_ACTION = 1; 25 | private static final int UNFREEZE_ACTION = 0; 26 | 27 | private static final String V1_FREEZER_FROZEN_PORCS = "/sys/fs/cgroup/freezer/perf/frozen/cgroup.procs"; 28 | private static final String V1_FREEZER_THAWED_PORCS = "/sys/fs/cgroup/freezer/perf/thawed/cgroup.procs"; 29 | 30 | private final boolean freezerApi; 31 | private final int freezerVersion; 32 | private final int stopSignal; 33 | private final boolean useKill; 34 | private final ClassLoader classLoader; 35 | 36 | 37 | public FreezeUtils(ClassLoader classLoader) { 38 | this.classLoader = classLoader; 39 | String freezerVersion = FreezerConfig.getFreezerVersion(classLoader); 40 | switch (freezerVersion) { 41 | case FreezerConfig.API: 42 | this.freezerApi = true; 43 | this.freezerVersion = 2; 44 | break; 45 | case FreezerConfig.V2: 46 | this.freezerApi = false; 47 | this.freezerVersion = 2; 48 | break; 49 | case FreezerConfig.V1: 50 | default: 51 | this.freezerApi = false; 52 | this.freezerVersion = 1; 53 | } 54 | this.stopSignal = FreezerConfig.getKillSignal(); 55 | this.useKill = FreezerConfig.isUseKill(); 56 | if (useKill) { 57 | Log.i("Kill -" + stopSignal); 58 | } else { 59 | Log.i("Freezer " + freezerVersion); 60 | } 61 | } 62 | 63 | 64 | public static List getFrozenPids() { 65 | List pids = new ArrayList<>(); 66 | try { 67 | BufferedReader reader = new BufferedReader(new FileReader(V1_FREEZER_FROZEN_PORCS)); 68 | while (true) { 69 | String line = reader.readLine(); 70 | if (line == null) { 71 | break; 72 | } 73 | try { 74 | pids.add(Integer.parseInt(line)); 75 | } catch (NumberFormatException ignored) { 76 | } 77 | } 78 | reader.close(); 79 | } catch (IOException ignored) { 80 | } 81 | return pids; 82 | } 83 | 84 | public void freezer(ProcessRecord processRecord) { 85 | if (useKill) { 86 | Process.sendSignal(processRecord.getPid(), stopSignal); 87 | } else { 88 | if (freezerVersion == 2) { 89 | if (freezerApi) { 90 | setProcessFrozen(processRecord.getPid(), processRecord.getUid(), true); 91 | } else { 92 | freezePid(processRecord.getPid(), processRecord.getUid()); 93 | } 94 | } else { 95 | freezePid(processRecord.getPid()); 96 | } 97 | } 98 | } 99 | 100 | public void unFreezer(ProcessRecord processRecord) { 101 | if (useKill) { 102 | Process.sendSignal(processRecord.getPid(), CONT); 103 | } else { 104 | if (freezerVersion == 2) { 105 | if (freezerApi) { 106 | setProcessFrozen(processRecord.getPid(), processRecord.getUid(), false); 107 | } else { 108 | thawPid(processRecord.getPid(), processRecord.getUid()); 109 | } 110 | } else { 111 | thawPid(processRecord.getPid()); 112 | } 113 | } 114 | } 115 | 116 | public static boolean isFrozonPid(int pid) { 117 | return getFrozenPids().contains(pid); 118 | } 119 | 120 | 121 | public static void freezePid(int pid) { 122 | writeNode(V1_FREEZER_FROZEN_PORCS, pid); 123 | } 124 | 125 | 126 | public static void thawPid(int pid) { 127 | writeNode(V1_FREEZER_THAWED_PORCS, pid); 128 | } 129 | 130 | 131 | private static void writeNode(String path, int val) { 132 | try { 133 | PrintWriter writer = new PrintWriter(path); 134 | writer.write(Integer.toString(val)); 135 | writer.close(); 136 | } catch (IOException e) { 137 | Log.e("Freezer V1 failed: " + e.getMessage()); 138 | } 139 | } 140 | 141 | 142 | private static void setFreezeAction(int pid, int uid, boolean action) { 143 | String path = "/sys/fs/cgroup/uid_" + uid + "/pid_" + pid + "/cgroup.freeze"; 144 | try { 145 | PrintWriter writer = new PrintWriter(path); 146 | if (action) { 147 | writer.write(Integer.toString(FREEZE_ACTION)); 148 | } else { 149 | writer.write(Integer.toString(UNFREEZE_ACTION)); 150 | } 151 | writer.close(); 152 | } catch (IOException e) { 153 | Log.e("Freezer V2 failed: " + e.getMessage()); 154 | } 155 | } 156 | 157 | public static void thawPid(int pid, int uid) { 158 | setFreezeAction(pid, uid, false); 159 | } 160 | 161 | 162 | public static void freezePid(int pid, int uid) { 163 | setFreezeAction(pid, uid, true); 164 | } 165 | 166 | public static void kill(int pid) { 167 | Process.killProcess(pid); 168 | } 169 | 170 | public void setProcessFrozen(int pid, int uid, boolean frozen) { 171 | Class Process = XposedHelpers.findClass(ClassEnum.Process, classLoader); 172 | XposedHelpers.callStaticMethod(Process, MethodEnum.setProcessFrozen, pid, uid, frozen); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /app/src/main/java/cn/myflv/android/noactive/utils/FreezerConfig.java: -------------------------------------------------------------------------------- 1 | package cn.myflv.android.noactive.utils; 2 | 3 | import android.os.Build; 4 | 5 | import java.io.BufferedReader; 6 | import java.io.File; 7 | import java.io.FileNotFoundException; 8 | import java.io.FileReader; 9 | import java.io.IOException; 10 | import java.util.HashSet; 11 | import java.util.Set; 12 | 13 | import cn.myflv.android.noactive.entity.ClassEnum; 14 | import cn.myflv.android.noactive.entity.MethodEnum; 15 | import de.robv.android.xposed.XposedHelpers; 16 | 17 | public class FreezerConfig { 18 | 19 | 20 | public final static String ConfigDir = "/data/system/NoActive"; 21 | public final static String whiteAppConfig = "whiteApp.conf"; 22 | public final static String blackSystemAppConfig = "blackSystemApp.conf"; 23 | public final static String whiteProcessConfig = "whiteProcess.conf"; 24 | public final static String killProcessConfig = "killProcess.conf"; 25 | public final static String disableOOM = "disable.oom"; 26 | public final static String kill19 = "kill.19"; 27 | public final static String kill20 = "kill.20"; 28 | public final static String freezerV1 = "freezer.v1"; 29 | public final static String freezerV2 = "freezer.v2"; 30 | public final static String freezerApi = "freezer.api"; 31 | public final static String colorOs = "color.os"; 32 | public final static String API = "Api"; 33 | public final static String V2 = "V2"; 34 | public final static String V1 = "V1"; 35 | 36 | 37 | public final static String[] listenConfig = {whiteAppConfig, whiteProcessConfig, 38 | killProcessConfig, blackSystemAppConfig}; 39 | 40 | 41 | public static boolean isConfigOn(String configName) { 42 | File config = new File(ConfigDir, configName); 43 | return config.exists(); 44 | } 45 | 46 | public static int getKillSignal() { 47 | if (isConfigOn(kill19)) { 48 | return 19; 49 | } 50 | if (isConfigOn(kill20)) { 51 | return 20; 52 | } 53 | return 19; 54 | } 55 | 56 | 57 | public static String getFreezerVersion(ClassLoader classLoader) { 58 | if (isConfigOn(freezerV2)) { 59 | return V2; 60 | } 61 | if (isConfigOn(freezerV1)) { 62 | return V1; 63 | } 64 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { 65 | if (isConfigOn(freezerApi)) { 66 | return API; 67 | } 68 | Class CachedAppOptimizer = XposedHelpers.findClass(ClassEnum.CachedAppOptimizer, classLoader); 69 | boolean isSupportV2 = (boolean) XposedHelpers.callStaticMethod(CachedAppOptimizer, MethodEnum.isFreezerSupported); 70 | if (isSupportV2) { 71 | return V2; 72 | } 73 | } 74 | return V1; 75 | } 76 | 77 | 78 | public static boolean isUseKill() { 79 | return isConfigOn(kill19) || isConfigOn(kill20); 80 | } 81 | 82 | public static boolean isColorOs() { 83 | return isConfigOn(colorOs); 84 | } 85 | 86 | 87 | public static void checkAndInit() { 88 | File configDir = new File(ConfigDir); 89 | if (!configDir.exists()) { 90 | boolean mkdir = configDir.mkdir(); 91 | if (!mkdir) return; 92 | Log.i("Init config dir"); 93 | } 94 | for (String configName : listenConfig) { 95 | File config = new File(configDir, configName); 96 | if (!config.exists()) { 97 | createFile(config); 98 | Log.i("Init " + configName); 99 | } 100 | } 101 | } 102 | 103 | 104 | public static Set get(String name) { 105 | Set set = new HashSet<>(); 106 | try { 107 | File file = new File(ConfigDir, name); 108 | BufferedReader bufferedReader = new BufferedReader(new FileReader(file)); 109 | String line; 110 | while ((line = bufferedReader.readLine()) != null) { 111 | if ("".equals(line.trim())) continue; 112 | if (line.startsWith("#")) continue; 113 | set.add(line.trim()); 114 | Log.i(name.replace(".conf", "") + " add " + line); 115 | } 116 | bufferedReader.close(); 117 | } catch (FileNotFoundException fileNotFoundException) { 118 | Log.e(name + " file not found"); 119 | } catch (IOException ioException) { 120 | Log.e(name + " file read filed"); 121 | } 122 | return set; 123 | } 124 | 125 | public static void createFile(File file) { 126 | try { 127 | boolean newFile = file.createNewFile(); 128 | if (!newFile) { 129 | throw new IOException(); 130 | } 131 | } catch (IOException e) { 132 | Log.e(file.getName() + " file create filed"); 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /app/src/main/java/cn/myflv/android/noactive/utils/Log.java: -------------------------------------------------------------------------------- 1 | package cn.myflv.android.noactive.utils; 2 | 3 | import java.io.File; 4 | 5 | import de.robv.android.xposed.XposedBridge; 6 | 7 | public class Log { 8 | private final static String TAG = "NoActive"; 9 | private static final boolean isDebug; 10 | private final static String ERROR = "error"; 11 | private final static String WARN = "warn"; 12 | private final static String INFO = "info"; 13 | private final static String DEBUG = "debug"; 14 | 15 | static { 16 | File config = new File(FreezerConfig.ConfigDir, "debug"); 17 | isDebug = config.exists(); 18 | i("Debug " + (isDebug ? "on" : "off")); 19 | } 20 | 21 | public static void d(String msg) { 22 | if (isDebug) { 23 | unify(DEBUG, msg); 24 | } 25 | } 26 | 27 | public static void i(String msg) { 28 | unify(INFO, msg); 29 | } 30 | 31 | public static void w(String msg) { 32 | unify(WARN, msg); 33 | } 34 | 35 | public static void e(String msg) { 36 | unify(ERROR, msg); 37 | } 38 | 39 | 40 | public static void e(String msg, Throwable throwable) { 41 | unify(ERROR, msg + ": " + throwable.getMessage()); 42 | } 43 | 44 | public static void unify(String level, String msg) { 45 | XposedBridge.log(TAG + "(" + level + ") -> " + msg); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/cn/myflv/android/noactive/utils/ThreadUtil.java: -------------------------------------------------------------------------------- 1 | package cn.myflv.android.noactive.utils; 2 | 3 | public class ThreadUtil { 4 | public static void sleep(int ms) { 5 | try { 6 | Thread.sleep(ms); 7 | } catch (InterruptedException ignored) { 8 | Log.w("Thread sleep failed"); 9 | } 10 | } 11 | 12 | public static void sleep(long ms) { 13 | try { 14 | Thread.sleep(ms); 15 | } catch (InterruptedException ignored) { 16 | Log.w("Thread sleep failed"); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_logo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_logo_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myflavor/NoActive/ec57340fd2749d3e026e46a04bff6305f3977a78/app/src/main/res/mipmap-hdpi/ic_logo.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_logo_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myflavor/NoActive/ec57340fd2749d3e026e46a04bff6305f3977a78/app/src/main/res/mipmap-hdpi/ic_logo_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_logo_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myflavor/NoActive/ec57340fd2749d3e026e46a04bff6305f3977a78/app/src/main/res/mipmap-hdpi/ic_logo_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myflavor/NoActive/ec57340fd2749d3e026e46a04bff6305f3977a78/app/src/main/res/mipmap-mdpi/ic_logo.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_logo_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myflavor/NoActive/ec57340fd2749d3e026e46a04bff6305f3977a78/app/src/main/res/mipmap-mdpi/ic_logo_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_logo_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myflavor/NoActive/ec57340fd2749d3e026e46a04bff6305f3977a78/app/src/main/res/mipmap-mdpi/ic_logo_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myflavor/NoActive/ec57340fd2749d3e026e46a04bff6305f3977a78/app/src/main/res/mipmap-xhdpi/ic_logo.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_logo_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myflavor/NoActive/ec57340fd2749d3e026e46a04bff6305f3977a78/app/src/main/res/mipmap-xhdpi/ic_logo_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_logo_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myflavor/NoActive/ec57340fd2749d3e026e46a04bff6305f3977a78/app/src/main/res/mipmap-xhdpi/ic_logo_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myflavor/NoActive/ec57340fd2749d3e026e46a04bff6305f3977a78/app/src/main/res/mipmap-xxhdpi/ic_logo.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_logo_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myflavor/NoActive/ec57340fd2749d3e026e46a04bff6305f3977a78/app/src/main/res/mipmap-xxhdpi/ic_logo_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_logo_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myflavor/NoActive/ec57340fd2749d3e026e46a04bff6305f3977a78/app/src/main/res/mipmap-xxhdpi/ic_logo_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myflavor/NoActive/ec57340fd2749d3e026e46a04bff6305f3977a78/app/src/main/res/mipmap-xxxhdpi/ic_logo.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_logo_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myflavor/NoActive/ec57340fd2749d3e026e46a04bff6305f3977a78/app/src/main/res/mipmap-xxxhdpi/ic_logo_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_logo_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myflavor/NoActive/ec57340fd2749d3e026e46a04bff6305f3977a78/app/src/main/res/mipmap-xxxhdpi/ic_logo_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/array.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | android 5 | com.miui.powerkeeper 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3DDC84 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | NoActive 3 | Let app no active in background 4 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | plugins { 3 | id 'com.android.application' version '7.1.3' apply false 4 | id 'com.android.library' version '7.1.3' apply false 5 | } 6 | 7 | task clean(type: Delete) { 8 | delete rootProject.buildDir 9 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Enables namespacing of each library's R class so that its R class includes only the 19 | # resources declared in the library itself and none from the library's dependencies, 20 | # thereby reducing the size of the R class for that library 21 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myflavor/NoActive/ec57340fd2749d3e026e46a04bff6305f3977a78/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed May 18 23:46:06 CST 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | maven { url "https://api.xposed.info/" } 14 | } 15 | } 16 | rootProject.name = "NoActive" 17 | include ':app' 18 | --------------------------------------------------------------------------------