This commit is contained in:
Administrator 2025-07-10 16:19:47 +08:00
parent 5bcecde83d
commit 0e252b3751
6 changed files with 30 additions and 58 deletions

View File

@ -14,9 +14,12 @@ import androidx.core.view.WindowInsetsCompat
import com.android.grape.databinding.ActivityMainBinding import com.android.grape.databinding.ActivityMainBinding
import com.android.grape.job.MonitorService import com.android.grape.job.MonitorService
import com.android.grape.util.ClashUtil import com.android.grape.util.ClashUtil
import com.android.grape.util.MockTools
import com.android.grape.util.NotificationPermissionHandler import com.android.grape.util.NotificationPermissionHandler
import com.android.grape.util.ScriptUtil import com.android.grape.util.ScriptUtil
import com.android.grape.util.ShellUtils
import com.android.grape.util.StoragePermissionHelper import com.android.grape.util.StoragePermissionHelper
import com.android.grape.util.Util
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
private val viewModel by viewModels<MainViewModel>() private val viewModel by viewModels<MainViewModel>()
@ -25,6 +28,7 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enableEdgeToEdge() enableEdgeToEdge()
init()
viewBinding = ActivityMainBinding.inflate(layoutInflater) viewBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(viewBinding.root) setContentView(viewBinding.root)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
@ -36,28 +40,19 @@ class MainActivity : AppCompatActivity() {
ScriptUtil.registerScriptResultReceiver() ScriptUtil.registerScriptResultReceiver()
viewBinding.start.setOnClickListener { viewBinding.start.setOnClickListener {
MonitorService.onEvent(MainApplication.instance) MonitorService.onEvent(MainApplication.instance)
// try {
// ClashUtil.startProxy(this)
// ClashUtil.switchProxyGroup(
// "GLOBAL",
// ClashUtil.getRandomLocale(),
// "http://127.0.0.1:6170"
// )
// } catch (e: Exception) {
// LogUtils.e("startProxyVpn: Failed to start VPN", e)
// Toast.makeText(
// this,
// "Failed to start VPN: " + (if (e.message != null) e.message else "Unknown error"),
// Toast.LENGTH_SHORT
// ).show()
// }
} }
viewBinding.stop.setOnClickListener { viewBinding.stop.setOnClickListener {
ClashUtil.stopProxy(this) val isRoot = ShellUtils.hasRootAccess()
// AutoJsUtil.stopAutojsScript(this) Log.d("TAG", "onCreate: isRoot $isRoot")
} }
} }
fun init() {
MockTools.exec("pm grant com.android.grape android.permission.INTERACT_ACROSS_USERS")
MockTools.exec("pm grant com.android.grape android.permission.WRITE_SECURE_SETTINGS")
MockTools.exec("pm setenforce 0")
}
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
ClashUtil.unregisterReceiver(this) ClashUtil.unregisterReceiver(this)
@ -65,7 +60,6 @@ class MainActivity : AppCompatActivity() {
} }
private fun checkPermission() { private fun checkPermission() {
Log.d("TAG", "checkPermission: ")
permissionHandler = NotificationPermissionHandler(this) { result -> permissionHandler = NotificationPermissionHandler(this) { result ->
handlePermissionResult(result) handlePermissionResult(result)
} }

View File

@ -36,7 +36,7 @@ class StartVpnPortJobService : JobIntentService() {
do { do {
val url = val url =
"http://39.103.73.250/tt/test/testProxy.jsp?port=$port&country=" + Util.proxyCountry "http://39.103.73.250/tt/test/testProxy.jsp?port=$port&country=" + Util.proxyCountry
?.lowercase(Locale.getDefault()) ?.uppercase(Locale.getDefault())
val result: String = MyGet.get(url) val result: String = MyGet.get(url)
Log.d(TAG, "request url == $url result$result") Log.d(TAG, "request url == $url result$result")
if (result.contains("ok")) { if (result.contains("ok")) {

View File

@ -39,6 +39,10 @@ object ChangeDeviceInfoUtil {
put("propertiesName", "MCCMNC") put("propertiesName", "MCCMNC")
put("propertiesValue", "${device.mcc},${device.mnc}") put("propertiesValue", "${device.mcc},${device.mnc}")
}) })
put(JSONObject().apply {
put("propertiesName", "OpName")
put("propertiesValue", device.operator)
})
}) })
put("systemPropertiesList", JSONArray().apply { put("systemPropertiesList", JSONArray().apply {
put(JSONObject().apply { put(JSONObject().apply {
@ -187,7 +191,7 @@ object ChangeDeviceInfoUtil {
vcloudsettingsPut("$currentPkgName.vendor", device.expand.glVendor, context) vcloudsettingsPut("$currentPkgName.vendor", device.expand.glVendor, context)
// callVCloudSettings_put("$currentPkgName.battery_scale", batteryScale.toString(), context) // callVCloudSettings_put("$currentPkgName.battery_scale", batteryScale.toString(), context)
vcloudsettingsPut("$currentPkgName.os_lang", device.lang, context) vcloudsettingsPut("$currentPkgName.os_lang", device.lang, context)
vcloudsettingsPut("$currentPkgName.model", device.model, context); vcloudsettingsPut("$currentPkgName.model", device.model, context)
vcloudsettingsPut("$currentPkgName.net", device.network, context) vcloudsettingsPut("$currentPkgName.net", device.network, context)
vcloudsettingsPut("$currentPkgName.dpi", device.expand.dPI.toString(), context) vcloudsettingsPut("$currentPkgName.dpi", device.expand.dPI.toString(), context)
// callVCloudSettings_put( // callVCloudSettings_put(
@ -295,7 +299,7 @@ object ChangeDeviceInfoUtil {
} }
private fun vcloudsettingsPut(key: String, value: String, context: Context) { fun vcloudsettingsPut(key: String, value: String, context: Context) {
if (key.isEmpty()) { if (key.isEmpty()) {
LogUtils.e(Log.ERROR, "ChangeDeviceInfoUtil", "Key cannot be null or empty", null) LogUtils.e(Log.ERROR, "ChangeDeviceInfoUtil", "Key cannot be null or empty", null)
throw IllegalArgumentException("Key cannot be null or empty") throw IllegalArgumentException("Key cannot be null or empty")

View File

@ -107,7 +107,6 @@ class NotificationPermissionHandler(
fun requestNotificationPermission() { fun requestNotificationPermission() {
// 保存当前权限状态,用于比较设置后是否发生变化 // 保存当前权限状态,用于比较设置后是否发生变化
lastPermissionState = areNotificationsEnabled() lastPermissionState = areNotificationsEnabled()
Log.d(TAG, "requestNotificationPermission: ")
when { when {
// Android 13+ - 使用运行时权限请求 // Android 13+ - 使用运行时权限请求
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> { Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> {
@ -199,7 +198,6 @@ class NotificationPermissionHandler(
} }
val activity = context as? AppCompatActivity ?: return val activity = context as? AppCompatActivity ?: return
Log.d(TAG, "requestPermissionApi33: ")
when { when {
// 已经拥有权限 // 已经拥有权限
areNotificationsEnabled() -> { areNotificationsEnabled() -> {
@ -215,7 +213,6 @@ class NotificationPermissionHandler(
// 请求权限 // 请求权限
else -> { else -> {
permissionRequestPending = true permissionRequestPending = true
Log.d(TAG, "requestPermissionApi33: launch")
permissionLauncher?.launch(Manifest.permission.POST_NOTIFICATIONS) permissionLauncher?.launch(Manifest.permission.POST_NOTIFICATIONS)
} }
} }
@ -284,30 +281,23 @@ class NotificationPermissionHandler(
*/ */
@JvmStatic @JvmStatic
fun checkPermissionState(context: Context): PermissionResult { fun checkPermissionState(context: Context): PermissionResult {
Log.d(TAG, "checkPermissionState: --------->")
return when { return when {
// Android 13+ 需要运行时权限 // Android 13+ 需要运行时权限
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> { Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> {
Log.d(TAG, "checkPermissionState: Android 13+")
if (ContextCompat.checkSelfPermission( if (ContextCompat.checkSelfPermission(
context, context,
Manifest.permission.POST_NOTIFICATIONS Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED) { ) == PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "checkPermissionState: Granted")
PermissionResult.GRANTED PermissionResult.GRANTED
} else { } else {
Log.d(TAG, "checkPermissionState: Denied")
PermissionResult.DENIED PermissionResult.DENIED
} }
} }
// Android 8.0+ 检查通知设置 // Android 8.0+ 检查通知设置
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O -> { Build.VERSION.SDK_INT >= Build.VERSION_CODES.O -> {
Log.d(TAG, "checkPermissionState: Android 8.0+")
if (NotificationManagerCompat.from(context).areNotificationsEnabled()) { if (NotificationManagerCompat.from(context).areNotificationsEnabled()) {
Log.d(TAG, "checkPermissionState: Granted")
PermissionResult.GRANTED PermissionResult.GRANTED
} else { } else {
Log.d(TAG, "checkPermissionState: Denied")
PermissionResult.NEEDS_SETTINGS PermissionResult.NEEDS_SETTINGS
} }
} }

View File

@ -102,32 +102,23 @@ public class ShellUtils {
} }
public static String execRootCmdAndGetResult(String cmd) { public static String execRootCmdAndGetResult(String cmd) {
// Log.d("ShellUtils", "execRootCmdAndGetResult - Started execution for command: " + cmd); Log.d("ShellUtils", "execRootCmdAndGetResult - Started execution for command: " + cmd);
if (cmd == null || cmd.trim().isEmpty()) { if (!isCommandSafe(cmd)) { // 检查命令的合法性
LogUtils.e(Log.ERROR, "ShellUtils", "Unsafe or empty command. Aborting execution.", null); Log.e("ShellUtils", "Detected unsafe command. Aborting execution.");
throw new IllegalArgumentException("Unsafe or empty command."); throw new IllegalArgumentException("Detected unsafe command.");
} }
// if (!isCommandSafe(cmd)) { // 检查命令的合法性
// Log.e("ShellUtils", "Detected unsafe command. Aborting execution.");
// throw new IllegalArgumentException("Detected unsafe command.");
// }
Process process = null; Process process = null;
ExecutorService executor = Executors.newFixedThreadPool(2); ExecutorService executor = Executors.newFixedThreadPool(2);
try { try {
// Log.d("ShellUtils", "Determining appropriate shell for execution...");
if (hasBin("su")) { if (hasBin("su")) {
// Log.d("ShellUtils", "'su' binary found, using 'su' shell.");
process = Runtime.getRuntime().exec("su"); process = Runtime.getRuntime().exec("su");
} else if (hasBin("xu")) { } else if (hasBin("xu")) {
// Log.d("ShellUtils", "'xu' binary found, using 'xu' shell.");
process = Runtime.getRuntime().exec("xu"); process = Runtime.getRuntime().exec("xu");
} else if (hasBin("vu")) { } else if (hasBin("vu")) {
// Log.d("ShellUtils", "'vu' binary found, using 'vu' shell.");
process = Runtime.getRuntime().exec("vu"); process = Runtime.getRuntime().exec("vu");
} else { } else {
// Log.d("ShellUtils", "No specific binary found, using 'sh' shell.");
process = Runtime.getRuntime().exec("sh"); process = Runtime.getRuntime().exec("sh");
} }
@ -137,7 +128,6 @@ public class ShellUtils {
BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)); BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
BufferedReader errorReader = new BufferedReader(new InputStreamReader(errorStream, StandardCharsets.UTF_8))) { BufferedReader errorReader = new BufferedReader(new InputStreamReader(errorStream, StandardCharsets.UTF_8))) {
// Log.d("ShellUtils", "Starting separate thread to process error stream...");
executor.submit(() -> { executor.submit(() -> {
String line; String line;
try { try {
@ -149,28 +139,21 @@ public class ShellUtils {
} }
}); });
// Log.d("ShellUtils", "Writing the command to the shell...");
os.write((cmd + "\n").getBytes()); os.write((cmd + "\n").getBytes());
os.write("exit\n".getBytes()); os.write("exit\n".getBytes());
os.flush(); os.flush();
// Log.d("ShellUtils", "Command written to shell. Waiting for process to complete.");
StringBuilder output = new StringBuilder(); StringBuilder output = new StringBuilder();
String line; String line;
while ((line = br.readLine()) != null) { while ((line = br.readLine()) != null) {
// Log.d("ShellUtils", "Shell Output: " + line);
output.append(line).append("\n"); output.append(line).append("\n");
} }
// Log.d("ShellUtils", "Awaiting process termination...");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (!process.waitFor(10, TimeUnit.SECONDS)) { if (!process.waitFor(10, TimeUnit.SECONDS)) {
// LogUtils.e(Log.ERROR, "ShellUtils", "Process execution timed out. Destroying process.", null);
process.destroyForcibly(); process.destroyForcibly();
throw new RuntimeException("Shell command execution timeout."); throw new RuntimeException("Shell command execution timeout.");
} }
} else { } else {
// Log.d("ShellUtils", "Using manual time tracking method for process termination (API < 26).");
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
while (true) { while (true) {
try { try {
@ -178,7 +161,6 @@ public class ShellUtils {
break; break;
} catch (IllegalThreadStateException e) { } catch (IllegalThreadStateException e) {
if (System.currentTimeMillis() - startTime > 10000) { // 10 seconds if (System.currentTimeMillis() - startTime > 10000) { // 10 seconds
// LogUtils.e(Log.ERROR, "ShellUtils", "Process execution timed out (manual tracking). Destroying process.", null);
process.destroy(); process.destroy();
throw new RuntimeException("Shell command execution timeout."); throw new RuntimeException("Shell command execution timeout.");
} }
@ -186,8 +168,6 @@ public class ShellUtils {
} }
} }
} }
Log.d("ShellUtils", "Process terminated successfully. Returning result.");
return output.toString().trim(); return output.toString().trim();
} }
} catch (IOException | InterruptedException e) { } catch (IOException | InterruptedException e) {
@ -196,7 +176,6 @@ public class ShellUtils {
return "Error: " + e.getMessage(); return "Error: " + e.getMessage();
} finally { } finally {
if (process != null) { if (process != null) {
Log.d("ShellUtils", "Finalizing process. Attempting to destroy it.");
process.destroy(); process.destroy();
} }
executor.shutdown(); executor.shutdown();
@ -207,7 +186,6 @@ public class ShellUtils {
public static void execRootCmd(String cmd) { public static void execRootCmd(String cmd) {
// 校验命令是否安全 // 校验命令是否安全
if (!isCommandSafe(cmd)) { if (!isCommandSafe(cmd)) {
LogUtils.e(Log.ERROR, "ShellUtils", "Unsafe command, aborting.", null);
return; return;
} }
List<String> cmds = new ArrayList<>(); List<String> cmds = new ArrayList<>();

View File

@ -1,6 +1,7 @@
package com.android.grape.util package com.android.grape.util
import android.content.Context import android.content.Context
import com.android.grape.util.TimeZoneUtils.formatTimeZoneOffset
import java.util.* import java.util.*
import kotlin.math.abs import kotlin.math.abs
@ -67,3 +68,8 @@ object TimeZoneUtils {
return Date(time.time + diff) return Date(time.time + diff)
} }
} }
fun main() {
val offset = formatTimeZoneOffset(19800000)
println("当前时区偏移:$offset")
}