├── .gitignore ├── .idea ├── .gitignore ├── compiler.xml ├── deploymentTargetDropDown.xml ├── gradle.xml ├── jarRepositories.xml ├── misc.xml └── vcs.xml ├── app ├── .gitignore ├── build.gradle ├── libs │ └── XposedBridgeApi-82.jar ├── proguard-rules.pro ├── release │ ├── app-release.apk │ └── output-metadata.json └── src │ ├── androidTest │ └── java │ │ └── indi │ │ └── conastin │ │ └── textmodify │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ └── xposed_init │ ├── ic_launcher-playstore.png │ ├── java │ │ └── indi │ │ │ └── conastin │ │ │ └── textmodify │ │ │ ├── HookEntrance.java │ │ │ ├── MainActivity.kt │ │ │ ├── activity │ │ │ ├── AboutActivity.kt │ │ │ ├── AddRule.kt │ │ │ ├── AppListActivity.kt │ │ │ ├── AppMethodActivity.kt │ │ │ └── GlobalMethodActivity.kt │ │ │ ├── adapter │ │ │ ├── AppListAdapter.kt │ │ │ └── AppMethodAdapter.kt │ │ │ ├── databse │ │ │ ├── AppInfo.kt │ │ │ ├── HookMethodDao.kt │ │ │ ├── HookMethodDatabase.kt │ │ │ ├── HookMethodEntity.kt │ │ │ ├── MethodInfo.kt │ │ │ └── RuleInfo.kt │ │ │ ├── log │ │ │ └── NewLog.kt │ │ │ └── viewmodel │ │ │ ├── InventoryViewModel.kt │ │ │ └── RulesViewModel.kt │ └── res │ │ ├── drawable │ │ ├── bg_gray_bottom_solid.xml │ │ ├── bg_gray_solid.xml │ │ ├── bg_green_solid.xml │ │ ├── bg_red_solid.xml │ │ ├── bg_white_solid.xml │ │ ├── bt_gray_solid.xml │ │ ├── donation.png │ │ ├── ic_about.xml │ │ ├── ic_add.xml │ │ ├── ic_app_lst.xml │ │ ├── ic_global_method.xml │ │ ├── ic_is_active.xml │ │ ├── ic_is_not_active.xml │ │ ├── ic_launcher_foreground.xml │ │ ├── ic_log.xml │ │ ├── ic_refresh.xml │ │ ├── ic_setting.xml │ │ └── ic_textmodify.xml │ │ ├── layout │ │ ├── activity_about.xml │ │ ├── activity_add_rule.xml │ │ ├── activity_app_method.xml │ │ ├── activity_app_method_info.xml │ │ ├── activity_main.xml │ │ ├── app_list_listview_item.xml │ │ ├── app_method_listview_item.xml │ │ └── global_method.xml │ │ ├── menu │ │ └── app_method_topbar_menu.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values-night │ │ └── themes.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── ic_launcher_background.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ └── data_extraction_rules.xml │ └── test │ └── java │ └── indi │ └── conastin │ └── textmodify │ └── ExampleUnitTest.kt ├── 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 | # 默认忽略的文件 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/deploymentTargetDropDown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'org.jetbrains.kotlin.android' 4 | id 'kotlin-kapt' 5 | } 6 | 7 | android { 8 | compileSdkVersion 31 9 | buildToolsVersion "30.0.3" 10 | 11 | defaultConfig { 12 | applicationId "indi.conastin.textmodify" 13 | minSdkVersion 26 14 | targetSdkVersion 31 15 | versionCode 1 16 | versionName "1.0" 17 | 18 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 19 | javaCompileOptions { 20 | annotationProcessorOptions { 21 | arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]//指定数据库schema导出的位置 22 | } 23 | } 24 | } 25 | 26 | buildTypes { 27 | release { 28 | // 开启会导致XposedBridgeApi无法加载 29 | // minifyEnabled true 30 | // shrinkResources true 31 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 32 | } 33 | } 34 | compileOptions { 35 | sourceCompatibility JavaVersion.VERSION_1_8 36 | targetCompatibility JavaVersion.VERSION_1_8 37 | } 38 | kotlinOptions { 39 | jvmTarget = '1.8' 40 | } 41 | 42 | buildFeatures { 43 | viewBinding = true 44 | } 45 | } 46 | 47 | dependencies { 48 | 49 | implementation "androidx.core:core-ktx:$core_ktx_version" 50 | implementation "androidx.appcompat:appcompat:$appcompat_version" 51 | implementation "com.google.android.material:material:$material_version" 52 | implementation "androidx.constraintlayout:constraintlayout:$constraintlayout_version" 53 | 54 | // Fragment libraries 55 | implementation "androidx.fragment:fragment-ktx:$fragment_version" 56 | 57 | // Room libraries 58 | implementation "androidx.room:room-runtime:$room_version" 59 | // annotationProcessor "androidx.room:room-compiler:$room_version" 60 | kapt "androidx.room:room-compiler:$room_version" 61 | implementation "androidx.room:room-ktx:$room_version" 62 | 63 | // Lifecycle libraries 64 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" 65 | implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" 66 | 67 | // Navigation libraries 68 | implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" 69 | implementation "androidx.navigation:navigation-ui-ktx:$nav_version" 70 | 71 | // Xposed libraries 72 | compileOnly files('libs/XposedBridgeApi-82.jar') 73 | 74 | // AndroidTest libraries 75 | testImplementation "junit:junit:4.13.2" 76 | androidTestImplementation "androidx.test.ext:junit:1.1.3" 77 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 78 | } -------------------------------------------------------------------------------- /app/libs/XposedBridgeApi-82.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Conastin/TextModify/e0238e43bd89d8eb750fa96418dd42b1b15b961d/app/libs/XposedBridgeApi-82.jar -------------------------------------------------------------------------------- /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/app-release.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Conastin/TextModify/e0238e43bd89d8eb750fa96418dd42b1b15b961d/app/release/app-release.apk -------------------------------------------------------------------------------- /app/release/output-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "artifactType": { 4 | "type": "APK", 5 | "kind": "Directory" 6 | }, 7 | "applicationId": "indi.conastin.textmodify", 8 | "variantName": "release", 9 | "elements": [ 10 | { 11 | "type": "SINGLE", 12 | "filters": [], 13 | "attributes": [], 14 | "versionCode": 1, 15 | "versionName": "1.0", 16 | "outputFile": "app-release.apk" 17 | } 18 | ], 19 | "elementType": "File" 20 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/indi/conastin/textmodify/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package indi.conastin.textmodify 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import org.junit.Assert.assertEquals 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Instrumented test, which will execute on an Android device. 11 | * 12 | * See [testing documentation](http://d.android.com/tools/testing). 13 | */ 14 | @RunWith(AndroidJUnit4::class) 15 | class ExampleInstrumentedTest { 16 | @Test 17 | fun useAppContext() { 18 | // Context of the app under test. 19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 20 | assertEquals("indi.conastin.textmodify", appContext.packageName) 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | 11 | 14 | 15 | 25 | 28 | 31 | 34 | 37 | 40 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 52 | 55 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /app/src/main/assets/xposed_init: -------------------------------------------------------------------------------- 1 | indi.conastin.textmodify.HookEntrance -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Conastin/TextModify/e0238e43bd89d8eb750fa96418dd42b1b15b961d/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/indi/conastin/textmodify/HookEntrance.java: -------------------------------------------------------------------------------- 1 | package indi.conastin.textmodify; 2 | 3 | import android.text.SpannableString; 4 | import android.text.SpannableStringBuilder; 5 | import android.widget.TextView; 6 | 7 | import java.util.ArrayList; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | import de.robv.android.xposed.IXposedHookLoadPackage; 12 | import de.robv.android.xposed.XC_MethodHook; 13 | import de.robv.android.xposed.XC_MethodReplacement; 14 | import de.robv.android.xposed.XSharedPreferences; 15 | import de.robv.android.xposed.XposedHelpers; 16 | import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam; 17 | import indi.conastin.textmodify.databse.RuleInfo; 18 | import indi.conastin.textmodify.log.NewLog; 19 | 20 | public class HookEntrance implements IXposedHookLoadPackage { 21 | 22 | // 存放全部规则 23 | private final ArrayList allRules = new ArrayList<>(); 24 | 25 | @Override 26 | public void handleLoadPackage(final LoadPackageParam lpparam) { 27 | if (lpparam.packageName.equals("indi.conastin.textmodify")) { 28 | // hook checkModelActive 29 | XposedHelpers.findAndHookMethod("indi.conastin.textmodify.MainActivity", lpparam.classLoader, "checkModelActive", XC_MethodReplacement.returnConstant(true)); 30 | } else { 31 | // 消除附加进程干扰(可能导致异常) 32 | if (lpparam.isFirstApplication) { 33 | NewLog log = new NewLog(); 34 | log.xposedLog("【HookEntrance】 | packageName: " + lpparam.packageName + " | try to init file"); 35 | // 初始化配置文件 36 | // XSharedPreferences globalSps = new XSharedPreferences(lpparam.packageName, "global_TextModify"); 37 | XSharedPreferences globalSps = new XSharedPreferences("indi.conastin.textmodify", "global_TextModify"); 38 | globalSps.makeWorldReadable(); 39 | log.xposedLog("【HookEntrance】 | globalFilePath: " + globalSps.getFile() + " | globalFileValues: " + globalSps.getAll()); 40 | XSharedPreferences packageSps = new XSharedPreferences(lpparam.packageName, lpparam.packageName + "_TextModify"); 41 | log.xposedLog("【HookEntrance】 | packageFilePath: " + packageSps.getFile() + " | packageFileValues: " + packageSps.getAll()); 42 | // 配置文件读取到globalRules和packageRules 43 | allRules.addAll(loadRuleList(globalSps)); 44 | allRules.addAll(loadRuleList(packageSps)); 45 | log.xposedLog("【HookEntrance】 | allRules: " + allRules.toString()); 46 | // hook text TextView.class method 47 | XC_MethodHook textviewHook = new XC_MethodHook() { 48 | @Override 49 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 50 | super.beforeHookedMethod(param); 51 | if (param.args[0] != null) { 52 | if (param.args[0] instanceof String) { 53 | String finalText = param.args[0].toString(); 54 | for (int i = 0; i < allRules.size(); i++) { 55 | if (param.args[0].toString().contains(allRules.get(i).getOriginText())) { 56 | log.xposedLog("【HookEntrance】 | String finalText init: " + finalText); 57 | finalText = finalText.replace(allRules.get(i).getOriginText(), allRules.get(i).getNewText()); 58 | log.xposedLog("【HookEntrance】 | origin: " + allRules.get(i).getOriginText() + " | new: " + allRules.get(i).getNewText() + " | finalText: " + finalText); 59 | } 60 | } 61 | param.args[0] = finalText; 62 | } else if (param.args[0] instanceof SpannableString) { 63 | String finalText = param.args[0].toString(); 64 | for (int i = 0; i < allRules.size(); i++) { 65 | if (param.args[0].toString().contains(allRules.get(i).getOriginText())) { 66 | log.xposedLog("【HookEntrance】 | SpannableString finalText init: " + finalText); 67 | finalText = finalText.replace(allRules.get(i).getOriginText(), allRules.get(i).getNewText()); 68 | log.xposedLog("【HookEntrance】 | origin: " + allRules.get(i).getOriginText() + " | new: " + allRules.get(i).getNewText() + " | finalText: " + finalText); 69 | } 70 | } 71 | param.args[0] = new SpannableString(finalText); 72 | } else if (param.args[0] instanceof SpannableStringBuilder) { 73 | SpannableStringBuilder finalText = (SpannableStringBuilder) param.args[0]; 74 | for (int i = 0; i < allRules.size(); i++) { 75 | if (param.args[0].toString().contains(allRules.get(i).getOriginText())) { 76 | log.xposedLog("【HookEntrance】 | SpannableStringBuilder finalText init: " + finalText.toString()); 77 | // 获取起始位置 78 | int start = param.args[0].toString().indexOf(allRules.get(i).getOriginText()); 79 | // 计算替换文字大小 80 | if (allRules.get(i).getOriginText().length() == allRules.get(i).getNewText().length()) { 81 | // 相等 82 | finalText.replace(start, allRules.get(i).getOriginText().length(), allRules.get(i).getNewText()); 83 | } else if (allRules.get(i).getOriginText().length() > allRules.get(i).getNewText().length()) { 84 | // 删减了 85 | finalText.replace(start, allRules.get(i).getNewText().length() + 1, allRules.get(i).getNewText()); 86 | finalText.delete(start + allRules.get(i).getNewText().length(), start + allRules.get(i).getOriginText().length() - allRules.get(i).getNewText().length() + 1); 87 | } else { 88 | // 增加了 89 | finalText.replace(start, allRules.get(i).getOriginText().length() + 1, allRules.get(i).getNewText().subSequence(0, allRules.get(i).getOriginText().length())); 90 | finalText.insert(start + allRules.get(i).getOriginText().length(), allRules.get(i).getNewText().subSequence(allRules.get(i).getOriginText().length(), allRules.get(i).getNewText().length())); 91 | } 92 | log.xposedLog("【HookEntrance】 | origin: " + allRules.get(i).getOriginText() + " | new: " + allRules.get(i).getNewText() + " | finalText: " + finalText); 93 | } 94 | } 95 | param.args[0] = finalText; 96 | } else if (param.args[0] instanceof StringBuffer) { 97 | String finalText = param.args[0].toString(); 98 | for (int i = 0; i < allRules.size(); i++) { 99 | if (param.args[0].toString().contains(allRules.get(i).getOriginText())) { 100 | log.xposedLog("【HookEntrance】 | SpannableString finalText init: " + finalText); 101 | finalText = finalText.replace(allRules.get(i).getOriginText(), allRules.get(i).getNewText()); 102 | log.xposedLog("【HookEntrance】 | origin: " + allRules.get(i).getOriginText() + " | new: " + allRules.get(i).getNewText() + " | finalText: " + finalText); 103 | } 104 | } 105 | param.args[0] = new StringBuffer(finalText); 106 | } else { 107 | String origin = param.args[0].toString(); 108 | log.xposedLog("【HookEntrance】 | 未知的字符串类型 | 文字: " + origin + " | 类: " + param.args[0].getClass()); 109 | } 110 | } 111 | } 112 | }; 113 | XposedHelpers.findAndHookMethod(TextView.class, "setText", CharSequence.class, TextView.BufferType.class, textviewHook); 114 | } 115 | } 116 | } 117 | 118 | private ArrayList loadRuleList(XSharedPreferences sps) { 119 | // temp中介 ruleList存放成对的列表 120 | ArrayList ruleList = new ArrayList<>(); 121 | Map temp = new HashMap<>(); 122 | Map map = sps.getAll(); 123 | // map按配对存至temp 124 | for (String key : map.keySet()) { 125 | // 排除num的干扰 在HookEntrance里面没什么用 126 | if (!key.equals("num")) { 127 | // log.xposedLog("【HookEntrance】 | key: " + key + " | key.startsWith('o'): " + key.startsWith("o")); 128 | if (key.startsWith("o")) { 129 | // o 130 | // log.xposedLog("【HookEntrance】 | key.substring(1): " + key.substring(1)); 131 | // log.xposedLog("【HookEntrance】 | map.get(key): " + map.get(key).toString()); 132 | // log.xposedLog("【HookEntrance】 | temp.get(key.substring(1)): " + temp.get(key.substring(1))); 133 | if (temp.get(key.substring(1)) == null) { 134 | temp.put(key.substring(1), new RuleInfo(map.get(key).toString(), "")); 135 | } else { 136 | temp.put(key.substring(1), new RuleInfo(map.get(key).toString(), temp.get(key.substring(1)).getNewText())); 137 | } 138 | // log.xposedLog("【HookEntrance】 | temp: " + temp.toString()); 139 | } else { 140 | // log.xposedLog("【HookEntrance】 | key.substring(1): " + key.substring(1)); 141 | // log.xposedLog("【HookEntrance】 | map.get(key): " + map.get(key).toString()); 142 | // log.xposedLog("【HookEntrance】 | temp.get(key.substring(1)): " + temp.get(key.substring(1))); 143 | // n 144 | if (temp.get(key.substring(1)) == null) { 145 | temp.put(key.substring(1), new RuleInfo("", map.get(key).toString())); 146 | } else { 147 | temp.put(key.substring(1), new RuleInfo(temp.get(key.substring(1)).getOriginText(), map.get(key).toString())); 148 | } 149 | // log.xposedLog("【HookEntrance】 | temp: " + temp.toString()); 150 | } 151 | } 152 | } 153 | // log.xposedLog("【HookEntrance】 | temp: " + temp.toString()); 154 | // temp键值对转List至ruleList 155 | for (String key : temp.keySet()) { 156 | ruleList.add(temp.get(key)); 157 | } 158 | // log.xposedLog("【HookEntrance】 | ruleList: " + ruleList.toString()); 159 | return ruleList; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /app/src/main/java/indi/conastin/textmodify/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package indi.conastin.textmodify 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import android.os.Process 7 | import android.widget.Toast 8 | import androidx.appcompat.app.AppCompatActivity 9 | import indi.conastin.textmodify.activity.AboutActivity 10 | import indi.conastin.textmodify.activity.AppListActivity 11 | import indi.conastin.textmodify.activity.GlobalMethodActivity 12 | import indi.conastin.textmodify.databinding.ActivityMainBinding 13 | 14 | 15 | class MainActivity : AppCompatActivity() { 16 | 17 | private lateinit var binding: ActivityMainBinding 18 | 19 | @SuppressLint("UseCompatLoadingForDrawables") 20 | override fun onCreate(savedInstanceState: Bundle?) { 21 | super.onCreate(savedInstanceState) 22 | // binding创建layout 23 | binding = ActivityMainBinding.inflate(layoutInflater) 24 | val view = binding.root 25 | setContentView(view) 26 | // 检查root权限 27 | Runtime.getRuntime().exec("su") 28 | // 检查xposed激活状态 29 | if (checkModelActive()) { 30 | binding.mainCheckXposedActiveBackground.setBackgroundResource(R.drawable.bg_green_solid) 31 | binding.mainCheckXposedActiveText.setText(R.string.main_xposed_is_active) 32 | binding.mainCheckXposedActiveIcon.setImageDrawable( 33 | resources.getDrawable(R.drawable.ic_is_active, null) 34 | ) 35 | } 36 | // 重启按钮监听 37 | binding.mainCheckXposedActiveBackground.setOnClickListener { 38 | // 重启 39 | val intent = Intent(this, MainActivity::class.java) 40 | intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) 41 | this.startActivity(intent) 42 | Process.killProcess(Process.myPid()) 43 | } 44 | // 应用列表按钮监听 45 | binding.appMethod.setOnClickListener { 46 | startActivity(Intent(view.context, AppListActivity::class.java)) 47 | } 48 | // 全局替换按钮监听 49 | binding.globalMethod.setOnClickListener { 50 | startActivity(Intent(view.context, GlobalMethodActivity::class.java)) 51 | } 52 | 53 | binding.setting.setOnClickListener { 54 | Toast.makeText(this, "憋催了,没写呢!", Toast.LENGTH_SHORT).show() 55 | // 隐藏图标 56 | // val componentName = ComponentName(this, MainActivity::class.java) 57 | // val res = packageManager.getComponentEnabledSetting(componentName); 58 | // Log.d("TextModify", (res == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT).toString() + (res == PackageManager.COMPONENT_ENABLED_STATE_ENABLED)); 59 | // if (res == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT 60 | // || res == PackageManager.COMPONENT_ENABLED_STATE_ENABLED 61 | // ) { 62 | // // 隐藏应用图标 63 | // packageManager.setComponentEnabledSetting( 64 | // componentName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 65 | // PackageManager.DONT_KILL_APP 66 | // ); 67 | // } else { 68 | // // 显示应用图标 69 | // packageManager.setComponentEnabledSetting( 70 | // componentName, PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, 71 | // PackageManager.DONT_KILL_APP 72 | // ); 73 | // } 74 | } 75 | // 关于按钮监听 76 | binding.info.setOnClickListener { 77 | startActivity(Intent(view.context, AboutActivity::class.java)) 78 | } 79 | } 80 | 81 | private fun checkModelActive(): Boolean { 82 | return false 83 | } 84 | 85 | } -------------------------------------------------------------------------------- /app/src/main/java/indi/conastin/textmodify/activity/AboutActivity.kt: -------------------------------------------------------------------------------- 1 | package indi.conastin.textmodify.activity 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import indi.conastin.textmodify.databinding.ActivityAboutBinding 6 | 7 | class AboutActivity : AppCompatActivity() { 8 | override fun onCreate(savedInstanceState: Bundle?) { 9 | super.onCreate(savedInstanceState) 10 | // binding创建layout 11 | val binding = ActivityAboutBinding.inflate(layoutInflater) 12 | setContentView(binding.root) 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/java/indi/conastin/textmodify/activity/AddRule.kt: -------------------------------------------------------------------------------- 1 | package indi.conastin.textmodify.activity 2 | 3 | import android.content.SharedPreferences 4 | import android.os.Bundle 5 | import android.widget.Toast 6 | import androidx.appcompat.app.AppCompatActivity 7 | import indi.conastin.textmodify.databinding.ActivityAddRuleBinding 8 | import indi.conastin.textmodify.databse.HookMethodDatabase 9 | import indi.conastin.textmodify.databse.RuleInfo 10 | import indi.conastin.textmodify.viewmodel.InventoryViewModel 11 | import java.io.BufferedReader 12 | import java.io.InputStreamReader 13 | 14 | class AddRule : AppCompatActivity() { 15 | 16 | private lateinit var binding: ActivityAddRuleBinding 17 | private lateinit var viewModel: InventoryViewModel 18 | 19 | override fun onCreate(savedInstanceState: Bundle?) { 20 | super.onCreate(savedInstanceState) 21 | // binding创建layout 22 | binding = ActivityAddRuleBinding.inflate(layoutInflater) 23 | setContentView(binding.root) 24 | // 接收Intent参数 25 | val bundle = this.intent.extras 26 | val id = bundle?.get("id") 27 | val packageName = bundle?.get("packageName") 28 | val originText = bundle?.get("originText") 29 | val newText = bundle?.get("newText") 30 | // Log.d("TextModify", "【AddRule】 | id: $id | packageName: $packageName | originText: $originText | newText: $newText") 31 | // 配置SharedPreference对象 32 | val sps = getSharedPreferences("${packageName}_TextModify", MODE_PRIVATE) 33 | // 初始化存储数量变量 34 | var num = 0 35 | // 获取SharedPreference的所有键值对 36 | val all = readSharedPreference(sps) 37 | // Log.d("TextModify", "【AddRule】 | all: $all") 38 | // map按id存储originText和newText 39 | val map = mutableMapOf() 40 | // map赋值 41 | if (all != null) { 42 | for ((k, v) in all) { 43 | if (k == "num") { 44 | // 获取总共的originText和newText数量 45 | num = v as Int 46 | // Log.d("TextModify", "【AddRule】 | num: $num") 47 | } else { 48 | // 获取originText和newText给map 49 | // Log.d("TextModify", "【AddRule】 | k.drop(1): ${k.drop(1)}") 50 | if ("o" in k) { 51 | // k是原文字 52 | map[k.drop(1)] = RuleInfo(v as String, map[k.drop(1)]?.newText ?: "") 53 | } else { 54 | // k是新文字 55 | map[k.drop(1)] = RuleInfo(map[k.drop(1)]?.originText ?: "", v as String) 56 | } 57 | } 58 | } 59 | } 60 | // Log.d("TextModify", "【AddRule】 | map: $map") 61 | // room对象 62 | val database = HookMethodDatabase.getDatabase(this) 63 | // 初始化id对象 64 | var maxId = 0 65 | // viewModel对象 66 | viewModel = InventoryViewModel(database.HookMethodDao()) 67 | // 获取最大id 68 | viewModel.allMethod.observe(this) { 69 | it.let { 70 | maxId = it.count() 71 | } 72 | } 73 | // Log.d("TextModify", "【AddRule】 | maxId: $maxId | packageName: $packageName | originText: $originText | newText: $newText") 74 | // 更改时将原值显示 75 | if (id != null) { 76 | if (originText != null) { 77 | binding.originText.setText(originText.toString()) 78 | } 79 | if (newText != null) { 80 | binding.newText.setText(newText.toString()) 81 | } 82 | } 83 | 84 | // 保存按钮监听 85 | binding.save.setOnClickListener { 86 | if (id == null) { 87 | // 新建 88 | viewModel.addPackageMethod( 89 | maxId, 90 | packageName.toString(), 91 | binding.originText.text.toString(), 92 | binding.newText.text.toString() 93 | ) 94 | addRuleToSharedPreference( 95 | sps, 96 | num, 97 | maxId, 98 | binding.originText.text.toString(), 99 | binding.newText.text.toString() 100 | ) 101 | Toast.makeText( 102 | this, 103 | "添加规则成功\n原文字:" + binding.originText.text.toString() + "\n替换文字:" + binding.newText.text.toString(), 104 | Toast.LENGTH_SHORT 105 | ).show() 106 | this.finish() 107 | } else { 108 | // 更新 109 | viewModel.updatePackageMethod( 110 | id as Int, 111 | packageName.toString(), 112 | binding.originText.text.toString(), 113 | binding.newText.text.toString() 114 | ) 115 | updateRuleToSharedPreference( 116 | sps, 117 | id.toInt(), 118 | binding.originText.text.toString(), 119 | binding.newText.text.toString() 120 | ) 121 | Toast.makeText( 122 | this, 123 | "更新规则成功\n原文字:" + binding.originText.text.toString() + "\n替换文字:" + binding.newText.text.toString(), 124 | Toast.LENGTH_SHORT 125 | ).show() 126 | this.finish() 127 | } 128 | } 129 | 130 | // 删除按钮监听 131 | binding.delete.setOnClickListener { 132 | if (id == null) { 133 | // Log.d("TextModify", "【AddRule】 | maxId: $maxId") 134 | Toast.makeText(this, "添加规则你都还没添加\n删个屁的", Toast.LENGTH_SHORT).show() 135 | } else { 136 | // 删除 137 | viewModel.deletePackageMethod( 138 | id as Int, 139 | packageName.toString(), 140 | binding.originText.text.toString(), 141 | binding.newText.text.toString() 142 | ) 143 | deleteRuleToSharedPreference(sps, num, id.toInt()) 144 | Toast.makeText( 145 | this, 146 | "删了就删了,世上没有后悔药\n但我可以给你瞅一眼让你记下来\n原文字:" + binding.originText.text.toString() + "\n替换文字:" + binding.newText.text.toString(), 147 | Toast.LENGTH_SHORT 148 | ).show() 149 | this.finish() 150 | } 151 | } 152 | } 153 | 154 | private fun addRuleToSharedPreference( 155 | sps: SharedPreferences, 156 | num: Int, 157 | id: Int, 158 | originText: String, 159 | newText: String 160 | ) { 161 | val edit = sps.edit() 162 | edit.putInt("num", num + 1) 163 | edit.putString("o$id", originText) 164 | edit.putString("n$id", newText) 165 | edit.apply() 166 | } 167 | 168 | private fun updateRuleToSharedPreference( 169 | sps: SharedPreferences, 170 | id: Int, 171 | originText: String, 172 | newText: String 173 | ) { 174 | val edit = sps.edit() 175 | edit.putString("o$id", originText) 176 | edit.putString("n$id", newText) 177 | edit.apply() 178 | } 179 | 180 | private fun deleteRuleToSharedPreference(sps: SharedPreferences, num: Int, id: Int) { 181 | val edit = sps.edit() 182 | edit.remove("o$id") 183 | edit.remove("n$id") 184 | edit.putInt("num", num - 1) 185 | edit.apply() 186 | } 187 | 188 | private fun getCountShared(sps: SharedPreferences): Int { 189 | return sps.getInt("num", 0) 190 | } 191 | 192 | private fun readSharedPreference(sps: SharedPreferences): MutableMap? { 193 | return sps.all 194 | } 195 | 196 | private fun cpSharedPreference(packageName: String) { 197 | if (packageName != "global") { 198 | // 获取root 199 | val process = Runtime.getRuntime().exec("su") 200 | val out = process.outputStream 201 | var cmd = 202 | "cp -f /data/data/indi.conastin.textmodify/shared_prefs/global_TextModify.xml /data/data/indi.conastin.textmodify/shared_prefs/${packageName}_TextModify.xml /data/data/$packageName/shared_prefs/ \n" 203 | out.write(cmd.toByteArray()) 204 | out.flush() 205 | cmd = 206 | "chmod 777 /data/data/$packageName/shared_prefs/global_TextModify.xml /data/data/$packageName/shared_prefs/${packageName}_TextModify.xml \n" 207 | out.write(cmd.toByteArray()) 208 | out.flush() 209 | out.write("exit \n".toByteArray()) 210 | out.close() 211 | val fis = process.inputStream 212 | val isr = InputStreamReader(fis) 213 | val br = BufferedReader(isr) 214 | var line: String? = br.readLine() 215 | while (line != null) { 216 | line = br.readLine() 217 | } 218 | } 219 | } 220 | 221 | // private fun rubbish() { 222 | //private fun initFile(file: File) { 223 | // if (!file.parentFile.exists()) { 224 | // file.parentFile.mkdir() 225 | // } 226 | // if (!file.exists()) { 227 | // file.createNewFile() 228 | // val writer = JsonWriter(OutputStreamWriter(FileOutputStream(file), "utf-8")) 229 | // writer.setIndent(" ") 230 | // writer.beginArray() 231 | // writer.endArray() 232 | // writer.close() 233 | // } 234 | //} 235 | //// private fun readJson(file: File): MutableMap { 236 | //// var reader = JsonReader(InputStreamReader(FileInputStream(file), "utf-8")) 237 | //// val appsMethod: MutableMap = mutableMapOf() 238 | //// reader.beginArray() 239 | //// while (reader.hasNext()) { 240 | //// var id = "" 241 | //// var packageName = "" 242 | //// var originText = "" 243 | //// var newText = "" 244 | //// reader.beginObject() 245 | //// while (reader.hasNext()) { 246 | //// var field = reader.nextName() 247 | //// if (field.equals("id")) { 248 | //// id = reader.nextString() 249 | //// } else if (field.equals("packageName")) { 250 | //// packageName = reader.nextString() 251 | //// } else if (field.equals("originText")) { 252 | //// originText = reader.nextString() 253 | //// } else if (field.equals("newText")) { 254 | //// newText = reader.nextString() 255 | //// } else { 256 | //// reader.skipValue() 257 | //// } 258 | //// } 259 | //// reader.endObject() 260 | //// appsMethod.put(id.toInt(), MethodInfo(packageName, originText, newText)) 261 | //// } 262 | //// reader.endArray() 263 | //// reader.close() 264 | //// return appsMethod 265 | //// } 266 | //// 267 | //// private fun writeJson(file: File, map: MutableMap) { 268 | //// val writer = JsonWriter(OutputStreamWriter(FileOutputStream(file), "utf-8")) 269 | //// writer.setIndent(" ") 270 | //// writer.beginArray() 271 | //// for ((m, k) in map) { 272 | //// writer.beginObject() 273 | //// writer.name("id").value(m) 274 | //// writer.name("packageName").value(k.packageName) 275 | //// writer.name("originText").value(k.originText) 276 | //// writer.name("newText").value(k.newText) 277 | //// writer.endObject() 278 | //// } 279 | //// writer.endArray() 280 | //// writer.close() 281 | //// } 282 | // 283 | // 284 | // } 285 | } -------------------------------------------------------------------------------- /app/src/main/java/indi/conastin/textmodify/activity/AppListActivity.kt: -------------------------------------------------------------------------------- 1 | package indi.conastin.textmodify.activity 2 | 3 | import android.content.Intent 4 | import android.content.pm.ApplicationInfo 5 | import android.graphics.drawable.Drawable 6 | import android.os.Bundle 7 | import android.view.inputmethod.EditorInfo 8 | import android.widget.SearchView 9 | import androidx.appcompat.app.AppCompatActivity 10 | import androidx.recyclerview.widget.LinearLayoutManager 11 | import indi.conastin.textmodify.R 12 | import indi.conastin.textmodify.adapter.AppListAdapter 13 | import indi.conastin.textmodify.databinding.ActivityAppMethodBinding 14 | import indi.conastin.textmodify.databse.AppInfo 15 | 16 | class AppListActivity : AppCompatActivity() { 17 | 18 | private lateinit var binding: ActivityAppMethodBinding 19 | lateinit var appsInfo: List 20 | lateinit var searchInfo: List 21 | 22 | override fun onCreate(savedInstanceState: Bundle?) { 23 | super.onCreate(savedInstanceState) 24 | val adapter = AppListAdapter { 25 | var intent = Intent(binding.root.context, AppMethodActivity::class.java) 26 | intent.putExtra("appName", it.appName) 27 | intent.putExtra("packageName", it.packageName) 28 | startActivity(intent) 29 | } 30 | binding = ActivityAppMethodBinding.inflate(layoutInflater) 31 | setContentView(binding.root) 32 | appsInfo = getPackageList() 33 | binding.recyclerView.layoutManager = LinearLayoutManager(this) 34 | binding.recyclerView.adapter = adapter 35 | adapter.submitList(appsInfo) 36 | binding.searchView.imeOptions = EditorInfo.IME_ACTION_DONE 37 | binding.searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { 38 | override fun onQueryTextSubmit(p0: String?): Boolean { 39 | return false 40 | } 41 | 42 | override fun onQueryTextChange(p0: String?): Boolean { 43 | searchInfo = arrayListOf() 44 | for (appInfo in appsInfo) { 45 | if (p0.toString() in appInfo.appName || p0.toString() in appInfo.packageName) { 46 | searchInfo += appInfo 47 | } 48 | } 49 | adapter.submitList(searchInfo) 50 | return false 51 | } 52 | 53 | }) 54 | 55 | // 刷新功能 56 | binding.topBar.setOnMenuItemClickListener { 57 | when (it.itemId) { 58 | R.id.menu_refresh -> { 59 | appsInfo = getPackageList() 60 | } 61 | } 62 | true 63 | } 64 | 65 | } 66 | 67 | private fun getAppInfo( 68 | packageName: String, 69 | name: String, 70 | icon: Drawable, 71 | isSystem: Boolean 72 | ): AppInfo { 73 | return AppInfo( 74 | packageName = packageName, 75 | appName = name, 76 | icon = icon, 77 | isSystem = isSystem 78 | ) 79 | } 80 | 81 | private fun getPackageList(): List { 82 | var list: MutableList = mutableListOf() 83 | var packagesInfo = packageManager.getInstalledPackages(0) 84 | for (packageInfo in packagesInfo) { 85 | var isSystem: Boolean = false 86 | var packageName: String = packageInfo.packageName 87 | var name: String = packageInfo.applicationInfo.loadLabel(packageManager).toString() 88 | var icon: Drawable = packageInfo.applicationInfo.loadIcon(packageManager) 89 | // check if system app 90 | if ((packageInfo.applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) != 0) { 91 | // if system app 92 | isSystem = true 93 | } 94 | list.add(getAppInfo(packageName, name, icon, isSystem)) 95 | } 96 | return list 97 | } 98 | 99 | } -------------------------------------------------------------------------------- /app/src/main/java/indi/conastin/textmodify/activity/AppMethodActivity.kt: -------------------------------------------------------------------------------- 1 | package indi.conastin.textmodify.activity 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import androidx.appcompat.app.AppCompatActivity 6 | import androidx.recyclerview.widget.LinearLayoutManager 7 | import indi.conastin.textmodify.adapter.AppMethodAdapter 8 | import indi.conastin.textmodify.databinding.ActivityAppMethodInfoBinding 9 | import indi.conastin.textmodify.databse.HookMethodDatabase 10 | import indi.conastin.textmodify.viewmodel.InventoryViewModel 11 | import java.io.BufferedReader 12 | import java.io.InputStreamReader 13 | 14 | 15 | class AppMethodActivity : AppCompatActivity() { 16 | 17 | // private lateinit var application: InventoryApplication 18 | private lateinit var viewModel: InventoryViewModel 19 | private lateinit var binding: ActivityAppMethodInfoBinding 20 | 21 | override fun onCreate(savedInstanceState: Bundle?) { 22 | super.onCreate(savedInstanceState) 23 | binding = ActivityAppMethodInfoBinding.inflate(layoutInflater) 24 | setContentView(binding.root) 25 | val bundle = this.intent.extras 26 | binding.topBar.title = bundle?.get("appName").toString() 27 | val database = HookMethodDatabase.getDatabase(this) 28 | // application = InventoryApplication() 29 | // viewModel = InventoryViewModel((application as InventoryApplication).database.HookMethodDao()) 30 | viewModel = InventoryViewModel(database.HookMethodDao()) 31 | viewModel.getPackageMethod(bundle?.get("packageName").toString()) 32 | val adapter = AppMethodAdapter { 33 | val intent = Intent(binding.root.context, AddRule::class.java) 34 | intent.putExtra("id", it.id) 35 | intent.putExtra("packageName", it.packageName) 36 | intent.putExtra("originText", it.originText) 37 | intent.putExtra("newText", it.newText) 38 | startActivity(intent) 39 | } 40 | binding.recyclerView.layoutManager = LinearLayoutManager(this) 41 | binding.recyclerView.adapter = adapter 42 | viewModel.packageMethod.observe(this) { 43 | it.let { 44 | adapter.submitList(it) 45 | } 46 | } 47 | 48 | // 添加规则 49 | binding.addRule.setOnClickListener { 50 | val intent = Intent(binding.root.context, AddRule::class.java) 51 | intent.putExtra("packageName", bundle?.get("packageName").toString()) 52 | startActivity(intent) 53 | } 54 | 55 | // 重启应用 56 | binding.rebootApp.setOnClickListener { 57 | // 关闭应用 58 | // 获取root权限 59 | val process = Runtime.getRuntime().exec("su") 60 | val out = process.outputStream 61 | // 移动配置文件至目标应用私有目录 62 | var cmd = 63 | "cp -f /data/data/indi.conastin.textmodify/shared_prefs/global_TextModify.xml /data/data/indi.conastin.textmodify/shared_prefs/${ 64 | bundle?.get("packageName").toString() 65 | }_TextModify.xml /data/data/${ 66 | bundle?.get("packageName").toString() 67 | }/shared_prefs/\n" 68 | out.write(cmd.toByteArray()) 69 | out.flush() 70 | // 因为移动过去的文件归属用户还是归TextModify 目标应用无法读取 所以需要提权使目标应用可以正常访问 71 | cmd = "chmod 777 /data/data/${ 72 | bundle?.get("packageName").toString() 73 | }/shared_prefs/global_TextModify.xml /data/data/${ 74 | bundle?.get("packageName").toString() 75 | }/shared_prefs/${bundle?.get("packageName").toString()}_TextModify.xml\n" 76 | out.write(cmd.toByteArray()) 77 | out.flush() 78 | // 强行关闭目标应用 79 | cmd = "am force-stop ${bundle?.get("packageName").toString()}\n" 80 | out.write(cmd.toByteArray()) 81 | out.flush() 82 | out.close() 83 | // 等待完成 84 | val fis = process.inputStream 85 | val isr = InputStreamReader(fis) 86 | val br = BufferedReader(isr) 87 | var line: String? = br.readLine() 88 | while (line != null) { 89 | line = br.readLine() 90 | } 91 | // 开启应用 92 | startActivity( 93 | Intent( 94 | packageManager.getLaunchIntentForPackage( 95 | bundle?.get("packageName").toString() 96 | ) 97 | ) 98 | ) 99 | } 100 | } 101 | } -------------------------------------------------------------------------------- /app/src/main/java/indi/conastin/textmodify/activity/GlobalMethodActivity.kt: -------------------------------------------------------------------------------- 1 | package indi.conastin.textmodify.activity 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import androidx.appcompat.app.AppCompatActivity 6 | import androidx.recyclerview.widget.LinearLayoutManager 7 | import indi.conastin.textmodify.adapter.AppMethodAdapter 8 | import indi.conastin.textmodify.databinding.GlobalMethodBinding 9 | import indi.conastin.textmodify.databse.HookMethodDatabase 10 | import indi.conastin.textmodify.viewmodel.InventoryViewModel 11 | 12 | class GlobalMethodActivity : AppCompatActivity() { 13 | 14 | private lateinit var binding: GlobalMethodBinding 15 | private lateinit var viewModel: InventoryViewModel 16 | 17 | override fun onCreate(savedInstanceState: Bundle?) { 18 | super.onCreate(savedInstanceState) 19 | binding = GlobalMethodBinding.inflate(layoutInflater) 20 | setContentView(binding.root) 21 | val database = HookMethodDatabase.getDatabase(this) 22 | viewModel = InventoryViewModel(database.HookMethodDao()) 23 | val adapter = AppMethodAdapter { 24 | var intent = Intent(binding.root.context, AddRule::class.java) 25 | intent.putExtra("id", it.id) 26 | intent.putExtra("packageName", "global") 27 | intent.putExtra("originText", it.originText) 28 | intent.putExtra("newText", it.newText) 29 | startActivity(intent) 30 | } 31 | binding.recyclerView.layoutManager = LinearLayoutManager(this) 32 | binding.recyclerView.adapter = adapter 33 | viewModel.globalMethod.observe(this) { 34 | it.let { 35 | adapter.submitList(it) 36 | } 37 | } 38 | 39 | binding.addRule.setOnClickListener { 40 | var intent = Intent(binding.root.context, AddRule::class.java) 41 | intent.putExtra("packageName", "global") 42 | startActivity(intent) 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /app/src/main/java/indi/conastin/textmodify/adapter/AppListAdapter.kt: -------------------------------------------------------------------------------- 1 | package indi.conastin.textmodify.adapter 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.DiffUtil 6 | import androidx.recyclerview.widget.ListAdapter 7 | import androidx.recyclerview.widget.RecyclerView 8 | import indi.conastin.textmodify.databinding.AppListListviewItemBinding 9 | import indi.conastin.textmodify.databse.AppInfo 10 | 11 | class AppListAdapter(private val onPackageClicked: (AppInfo) -> Unit) : 12 | ListAdapter(DiffCallback) { 13 | 14 | override fun onCreateViewHolder( 15 | parent: ViewGroup, 16 | viewType: Int 17 | ): PackageViewHolder { 18 | return PackageViewHolder( 19 | AppListListviewItemBinding.inflate( 20 | LayoutInflater.from( 21 | parent.context 22 | ), 23 | parent, 24 | false 25 | ) 26 | ) 27 | } 28 | 29 | override fun onBindViewHolder(holder: PackageViewHolder, position: Int) { 30 | val current = getItem(position) 31 | holder.itemView.setOnClickListener { 32 | onPackageClicked(current) 33 | } 34 | holder.bind(current) 35 | } 36 | 37 | class PackageViewHolder(private var binding: AppListListviewItemBinding) : 38 | RecyclerView.ViewHolder(binding.root) { 39 | 40 | fun bind(appInfo: AppInfo) { 41 | binding.appListItemName.text = appInfo.appName 42 | binding.appListItemPackageName.text = appInfo.packageName 43 | binding.appListItemIcon.setImageDrawable(appInfo.icon) 44 | } 45 | 46 | } 47 | 48 | companion object { 49 | private val DiffCallback = object : DiffUtil.ItemCallback() { 50 | override fun areItemsTheSame(oldItem: AppInfo, newItem: AppInfo): Boolean { 51 | return oldItem === newItem 52 | } 53 | 54 | override fun areContentsTheSame(oldItem: AppInfo, newItem: AppInfo): Boolean { 55 | return oldItem.packageName == newItem.packageName 56 | } 57 | } 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /app/src/main/java/indi/conastin/textmodify/adapter/AppMethodAdapter.kt: -------------------------------------------------------------------------------- 1 | package indi.conastin.textmodify.adapter 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.DiffUtil 6 | import androidx.recyclerview.widget.ListAdapter 7 | import androidx.recyclerview.widget.RecyclerView 8 | import indi.conastin.textmodify.databinding.AppMethodListviewItemBinding 9 | import indi.conastin.textmodify.databse.HookMethodEntity 10 | 11 | class AppMethodAdapter(private val onRuleLongClicked: (HookMethodEntity) -> Unit) : 12 | ListAdapter(DiffCallback) { 13 | 14 | override fun onCreateViewHolder( 15 | parent: ViewGroup, 16 | viewType: Int 17 | ): PackageViewHolder { 18 | return PackageViewHolder( 19 | AppMethodListviewItemBinding.inflate( 20 | LayoutInflater.from( 21 | parent.context 22 | ), 23 | parent, 24 | false 25 | ) 26 | ) 27 | } 28 | 29 | override fun onBindViewHolder(holder: PackageViewHolder, position: Int) { 30 | val current = getItem(position) 31 | holder.itemView.setOnClickListener { 32 | onRuleLongClicked(current) 33 | } 34 | holder.bind(current) 35 | } 36 | 37 | class PackageViewHolder(private var binding: AppMethodListviewItemBinding) : 38 | RecyclerView.ViewHolder(binding.root) { 39 | 40 | fun bind(packageInfoEntity: HookMethodEntity) { 41 | binding.originText.text = packageInfoEntity.originText 42 | binding.newText.text = packageInfoEntity.newText 43 | } 44 | } 45 | 46 | companion object { 47 | private val DiffCallback = object : DiffUtil.ItemCallback() { 48 | override fun areItemsTheSame( 49 | oldItem: HookMethodEntity, 50 | newItem: HookMethodEntity 51 | ): Boolean { 52 | return oldItem === newItem 53 | } 54 | 55 | override fun areContentsTheSame( 56 | oldItem: HookMethodEntity, 57 | newItem: HookMethodEntity 58 | ): Boolean { 59 | return oldItem.originText == newItem.originText 60 | } 61 | } 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /app/src/main/java/indi/conastin/textmodify/databse/AppInfo.kt: -------------------------------------------------------------------------------- 1 | package indi.conastin.textmodify.databse 2 | 3 | import android.graphics.drawable.Drawable 4 | 5 | data class AppInfo( 6 | // 数据类用以生成应用列表 7 | val appName: String, 8 | val packageName: String, 9 | val icon: Drawable, 10 | val isSystem: Boolean 11 | ) -------------------------------------------------------------------------------- /app/src/main/java/indi/conastin/textmodify/databse/HookMethodDao.kt: -------------------------------------------------------------------------------- 1 | package indi.conastin.textmodify.databse 2 | 3 | 4 | import androidx.room.* 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | @Dao 8 | interface HookMethodDao { 9 | 10 | // 获取所有方法 11 | @Query("select * from app_method order by id desc") 12 | fun getAllMethod(): Flow> 13 | 14 | // 获取全局方法 15 | @Query("select * from app_method where packageName = 'global' order by id desc") 16 | fun getGlobalMethod(): Flow> 17 | 18 | // 获取包名的所有方法 19 | @Query("select * from app_method where packageName = :packageName order by id desc") 20 | fun getPackageMethod(packageName: String): Flow> 21 | 22 | // 新增方法 23 | @Insert 24 | suspend fun addMethod(method: HookMethodEntity) 25 | 26 | // 删除方法 27 | @Delete 28 | suspend fun deleteMethod(method: HookMethodEntity) 29 | 30 | // 更新方法 31 | @Update 32 | suspend fun updateMethod(method: HookMethodEntity) 33 | } -------------------------------------------------------------------------------- /app/src/main/java/indi/conastin/textmodify/databse/HookMethodDatabase.kt: -------------------------------------------------------------------------------- 1 | package indi.conastin.textmodify.databse 2 | 3 | import android.content.Context 4 | import androidx.room.Database 5 | import androidx.room.Room 6 | import androidx.room.RoomDatabase 7 | 8 | 9 | @Database(entities = [HookMethodEntity::class], version = 1, exportSchema = false) 10 | abstract class HookMethodDatabase : RoomDatabase() { 11 | 12 | abstract fun HookMethodDao(): HookMethodDao 13 | 14 | companion object { 15 | @Volatile 16 | private var INSTANCE: HookMethodDatabase? = null 17 | 18 | fun getDatabase(context: Context): HookMethodDatabase { 19 | return INSTANCE ?: synchronized(this) { 20 | val instance = Room.databaseBuilder( 21 | context.applicationContext, 22 | HookMethodDatabase::class.java, 23 | "database.db" 24 | ).fallbackToDestructiveMigration().build() 25 | INSTANCE = instance 26 | instance 27 | } 28 | } 29 | 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/java/indi/conastin/textmodify/databse/HookMethodEntity.kt: -------------------------------------------------------------------------------- 1 | package indi.conastin.textmodify.databse 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | 7 | @Entity(tableName = "app_method") 8 | data class HookMethodEntity( 9 | @PrimaryKey 10 | val id: Int, 11 | 12 | @ColumnInfo(name = "packageName") 13 | val packageName: String, 14 | 15 | @ColumnInfo(name = "originText") 16 | val originText: String, 17 | 18 | @ColumnInfo(name = "newText") 19 | val newText: String 20 | ) -------------------------------------------------------------------------------- /app/src/main/java/indi/conastin/textmodify/databse/MethodInfo.kt: -------------------------------------------------------------------------------- 1 | package indi.conastin.textmodify.databse 2 | 3 | data class MethodInfo( 4 | val packageName: String, 5 | val originText: String, 6 | val newText: String 7 | ) -------------------------------------------------------------------------------- /app/src/main/java/indi/conastin/textmodify/databse/RuleInfo.kt: -------------------------------------------------------------------------------- 1 | package indi.conastin.textmodify.databse 2 | 3 | data class RuleInfo( 4 | val originText: String, 5 | val newText: String 6 | ) -------------------------------------------------------------------------------- /app/src/main/java/indi/conastin/textmodify/log/NewLog.kt: -------------------------------------------------------------------------------- 1 | package indi.conastin.textmodify.log 2 | 3 | import android.util.Log 4 | import de.robv.android.xposed.XposedBridge 5 | 6 | class NewLog { 7 | 8 | fun systemLog(string: String) { 9 | Log.d("TextModify", string) 10 | } 11 | 12 | fun xposedLog(string: String) { 13 | XposedBridge.log(string.replace("【", "【TextModify|")) 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/java/indi/conastin/textmodify/viewmodel/InventoryViewModel.kt: -------------------------------------------------------------------------------- 1 | package indi.conastin.textmodify.viewmodel 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.ViewModel 5 | import androidx.lifecycle.asLiveData 6 | import androidx.lifecycle.viewModelScope 7 | import indi.conastin.textmodify.databse.HookMethodDao 8 | import indi.conastin.textmodify.databse.HookMethodEntity 9 | import kotlinx.coroutines.launch 10 | 11 | class InventoryViewModel(private val hookMethodDao: HookMethodDao) : ViewModel() { 12 | 13 | var allMethod: LiveData> = hookMethodDao.getAllMethod().asLiveData() 14 | val globalMethod: LiveData> = 15 | hookMethodDao.getGlobalMethod().asLiveData() 16 | lateinit var packageMethod: LiveData> 17 | 18 | fun getPackageMethod(packageName: String) { 19 | viewModelScope.launch { 20 | packageMethod = hookMethodDao.getPackageMethod(packageName).asLiveData() 21 | } 22 | } 23 | 24 | // private fun getMethodEntry( 25 | // packageName: String, 26 | // originText: String, 27 | // newText: String, 28 | // ): HookMethodEntity { 29 | // return HookMethodEntity( 30 | // packageName = packageName, 31 | // originText = originText, 32 | // newText = newText 33 | // ) 34 | // } 35 | 36 | // 新增规则 37 | fun addPackageMethod( 38 | id: Int, 39 | packageName: String, 40 | originText: String, 41 | newText: String 42 | ) { 43 | // val packageMethod = getMethodEntry(packageName, originText, newText) 44 | viewModelScope.launch { 45 | hookMethodDao.addMethod(HookMethodEntity(id, packageName, originText, newText)) 46 | } 47 | } 48 | 49 | // 删除规则 50 | fun deletePackageMethod( 51 | id: Int, 52 | packageName: String, 53 | originText: String, 54 | newText: String 55 | ) { 56 | // val packageMethod = getMethodEntry(packageName, originText, newText) 57 | viewModelScope.launch { 58 | hookMethodDao.deleteMethod(HookMethodEntity(id, packageName, originText, newText)) 59 | } 60 | } 61 | 62 | // 更新规则 63 | fun updatePackageMethod( 64 | id: Int, 65 | packageName: String, 66 | originText: String, 67 | newText: String 68 | ) { 69 | viewModelScope.launch { 70 | hookMethodDao.updateMethod(HookMethodEntity(id, packageName, originText, newText)) 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /app/src/main/java/indi/conastin/textmodify/viewmodel/RulesViewModel.kt: -------------------------------------------------------------------------------- 1 | package indi.conastin.textmodify.viewmodel 2 | 3 | import android.annotation.SuppressLint 4 | import android.util.JsonReader 5 | import android.util.JsonWriter 6 | import androidx.lifecycle.ViewModel 7 | import androidx.lifecycle.viewModelScope 8 | import indi.conastin.textmodify.databse.RuleInfo 9 | import kotlinx.coroutines.launch 10 | import java.io.* 11 | 12 | class RulesViewModel : ViewModel() { 13 | @SuppressLint("SdCardPath") 14 | val file = File("/data/user/0/indi.conastin.textmodify/files", "rules.json") 15 | lateinit var methods: HashMap> 16 | 17 | fun getMethod() { 18 | viewModelScope.launch { 19 | initFIle() 20 | methods = readJson() 21 | } 22 | } 23 | 24 | private fun initFIle() { 25 | if (!file.exists()) { 26 | // 初始化 27 | file.createNewFile() 28 | val writer = JsonWriter(OutputStreamWriter(FileOutputStream(file), "utf-8")) 29 | writer.setIndent(" ") 30 | writer.beginArray() 31 | writer.endArray() 32 | writer.close() 33 | } 34 | } 35 | 36 | private fun readJson(): HashMap> { 37 | var reader = JsonReader(InputStreamReader(FileInputStream(file), "utf-8")) 38 | val appsMethod: HashMap> = hashMapOf() 39 | reader.beginArray() 40 | while (reader.hasNext()) { 41 | var id = "" 42 | var packageName = "" 43 | var originText = "" 44 | var newText = "" 45 | reader.beginObject() 46 | while (reader.hasNext()) { 47 | var field = reader.nextName() 48 | if (field.equals("packageName")) { 49 | packageName = reader.nextString() 50 | } else if (field.equals("originText")) { 51 | originText = reader.nextString() 52 | } else if (field.equals("newText")) { 53 | newText = reader.nextString() 54 | } else { 55 | reader.skipValue() 56 | } 57 | } 58 | reader.endObject() 59 | if (appsMethod[packageName] == null) { 60 | appsMethod[packageName] = arrayListOf(RuleInfo(originText, newText)) 61 | } else { 62 | appsMethod[packageName] = arrayListOf( 63 | appsMethod[packageName], 64 | RuleInfo(originText, newText) 65 | ) as java.util.ArrayList 66 | } 67 | } 68 | reader.endArray() 69 | reader.close() 70 | return appsMethod 71 | } 72 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_gray_bottom_solid.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 12 | 13 | 14 | 19 | 20 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_gray_solid.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | 12 | 17 | 18 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_green_solid.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 17 | 18 | 19 | 20 | 25 | 26 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_red_solid.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | 12 | 17 | 18 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_white_solid.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | 12 | 17 | 18 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bt_gray_solid.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | 12 | 17 | 18 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/donation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Conastin/TextModify/e0238e43bd89d8eb750fa96418dd42b1b15b961d/app/src/main/res/drawable/donation.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_about.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_app_lst.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_global_method.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_is_active.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_is_not_active.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 14 | 17 | 20 | 23 | 28 | 31 | 34 | 37 | 40 | 43 | 46 | 47 | 48 | 51 | 52 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_log.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_refresh.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_setting.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_textmodify.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 18 | 23 | 26 | 29 | 32 | 35 | 38 | 41 | 42 | 43 | 46 | 47 | 52 | 53 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_about.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 23 | 24 | 25 | 26 | 31 | 32 | 37 | 38 | 48 | 49 | 53 | 54 | 61 | 62 | 70 | 71 | 79 | 80 | 81 | 82 | 83 | 84 | 94 | 95 | 99 | 100 | 107 | 108 | 116 | 117 | 125 | 126 | 134 | 135 | 143 | 144 | 145 | 146 | 147 | 148 | 158 | 159 | 163 | 164 | 171 | 172 | 180 | 181 | 189 | 190 | 191 | 192 | 193 | 194 | 204 | 205 | 209 | 210 | 217 | 218 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_add_rule.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 25 | 26 | 37 | 38 | 43 | 44 |