From 7e7b38caf6225ba9ea2d559ed7dae48eda69a872 Mon Sep 17 00:00:00 2001 From: jiqiu2021 Date: Fri, 27 Jun 2025 17:31:01 +0800 Subject: [PATCH] =?UTF-8?q?feat:gadget=E5=85=A8=E5=B1=80=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/jiqiu/configapp/AppListFragment.java | 85 +++++++++++++------ .../com/jiqiu/configapp/ConfigManager.java | 49 +++++++++-- .../jiqiu/configapp/GadgetConfigDialog.java | 64 +++++++++++++- .../com/jiqiu/configapp/SettingsFragment.java | 44 ++++++++++ .../src/main/res/layout/dialog_app_config.xml | 66 ++++++++++---- .../src/main/res/layout/fragment_settings.xml | 47 ++++++++++ 6 files changed, 307 insertions(+), 48 deletions(-) diff --git a/configapp/src/main/java/com/jiqiu/configapp/AppListFragment.java b/configapp/src/main/java/com/jiqiu/configapp/AppListFragment.java index 99e753b..1e2e1ca 100644 --- a/configapp/src/main/java/com/jiqiu/configapp/AppListFragment.java +++ b/configapp/src/main/java/com/jiqiu/configapp/AppListFragment.java @@ -140,7 +140,11 @@ public class AppListFragment extends Fragment implements AppListAdapter.OnAppTog RadioButton radioStandardInjection = dialogView.findViewById(R.id.radioStandardInjection); RadioButton radioRiruInjection = dialogView.findViewById(R.id.radioRiruInjection); RadioButton radioCustomLinkerInjection = dialogView.findViewById(R.id.radioCustomLinkerInjection); - CheckBox checkboxEnableGadget = dialogView.findViewById(R.id.checkboxEnableGadget); + RadioGroup gadgetConfigGroup = dialogView.findViewById(R.id.gadgetConfigGroup); + RadioButton radioNoGadget = dialogView.findViewById(R.id.radioNoGadget); + RadioButton radioUseGlobalGadget = dialogView.findViewById(R.id.radioUseGlobalGadget); + RadioButton radioUseCustomGadget = dialogView.findViewById(R.id.radioUseCustomGadget); + TextView tvGlobalGadgetInfo = dialogView.findViewById(R.id.tvGlobalGadgetInfo); com.google.android.material.button.MaterialButton btnConfigureGadget = dialogView.findViewById(R.id.btnConfigureGadget); appIcon.setImageDrawable(appInfo.getAppIcon()); @@ -158,30 +162,71 @@ public class AppListFragment extends Fragment implements AppListAdapter.OnAppTog } // Load gadget config - ConfigManager.GadgetConfig gadgetConfig = configManager.getAppGadgetConfig(appInfo.getPackageName()); - checkboxEnableGadget.setChecked(gadgetConfig != null); - btnConfigureGadget.setEnabled(gadgetConfig != null); + boolean useGlobalGadget = configManager.getAppUseGlobalGadget(appInfo.getPackageName()); + ConfigManager.GadgetConfig appSpecificGadget = configManager.getAppGadgetConfig(appInfo.getPackageName()); + ConfigManager.GadgetConfig globalGadget = configManager.getGlobalGadgetConfig(); - // Setup gadget listeners - checkboxEnableGadget.setOnCheckedChangeListener((buttonView, isChecked) -> { - btnConfigureGadget.setEnabled(isChecked); - if (!isChecked) { - // Remove gadget config when unchecked + // Update global gadget info + if (globalGadget != null) { + String info = "全局: " + globalGadget.gadgetName; + if (globalGadget.mode.equals("server")) { + info += " (端口: " + globalGadget.port + ")"; + } + tvGlobalGadgetInfo.setText(info); + } else { + tvGlobalGadgetInfo.setText("未配置全局Gadget"); + } + + // Set initial radio selection + if (!useGlobalGadget && appSpecificGadget != null) { + radioUseCustomGadget.setChecked(true); + btnConfigureGadget.setVisibility(View.VISIBLE); + btnConfigureGadget.setEnabled(true); + } else if (useGlobalGadget && globalGadget != null) { + radioUseGlobalGadget.setChecked(true); + btnConfigureGadget.setVisibility(View.GONE); + } else { + radioNoGadget.setChecked(true); + btnConfigureGadget.setVisibility(View.GONE); + } + + // Setup gadget radio group listener + gadgetConfigGroup.setOnCheckedChangeListener((group, checkedId) -> { + if (checkedId == R.id.radioNoGadget) { + btnConfigureGadget.setVisibility(View.GONE); + configManager.setAppUseGlobalGadget(appInfo.getPackageName(), false); configManager.setAppGadgetConfig(appInfo.getPackageName(), null); + } else if (checkedId == R.id.radioUseGlobalGadget) { + btnConfigureGadget.setVisibility(View.GONE); + configManager.setAppUseGlobalGadget(appInfo.getPackageName(), true); + configManager.setAppGadgetConfig(appInfo.getPackageName(), null); + } else if (checkedId == R.id.radioUseCustomGadget) { + btnConfigureGadget.setVisibility(View.VISIBLE); + btnConfigureGadget.setEnabled(true); + configManager.setAppUseGlobalGadget(appInfo.getPackageName(), false); } }); + // Configure button listener btnConfigureGadget.setOnClickListener(v -> { - ConfigManager.GadgetConfig currentConfig = configManager.getAppGadgetConfig(appInfo.getPackageName()); + ConfigManager.GadgetConfig currentConfig = null; + if (!useGlobalGadget) { + currentConfig = configManager.getAppGadgetConfig(appInfo.getPackageName()); + } if (currentConfig == null) { currentConfig = new ConfigManager.GadgetConfig(); } - GadgetConfigDialog gadgetDialog = GadgetConfigDialog.newInstance(currentConfig); - gadgetDialog.setOnGadgetConfigListener(config -> { - configManager.setAppGadgetConfig(appInfo.getPackageName(), config); - }); - gadgetDialog.show(getParentFragmentManager(), "gadget_config"); + GadgetConfigDialog dialog = new GadgetConfigDialog( + getContext(), + "配置" + appInfo.getAppName() + "的Gadget", + currentConfig, + config -> { + configManager.setAppUseGlobalGadget(appInfo.getPackageName(), false); + configManager.setAppGadgetConfig(appInfo.getPackageName(), config); + } + ); + dialog.show(); }); // Setup SO list @@ -216,16 +261,6 @@ public class AppListFragment extends Fragment implements AppListAdapter.OnAppTog } configManager.setAppInjectionMethod(appInfo.getPackageName(), selectedMethod); - // Save gadget config if enabled - if (checkboxEnableGadget.isChecked()) { - ConfigManager.GadgetConfig currentGadgetConfig = configManager.getAppGadgetConfig(appInfo.getPackageName()); - if (currentGadgetConfig == null) { - // Create default config if not already configured - currentGadgetConfig = new ConfigManager.GadgetConfig(); - configManager.setAppGadgetConfig(appInfo.getPackageName(), currentGadgetConfig); - } - } - // Save SO selection if (soListRecyclerView.getAdapter() != null) { SoSelectionAdapter adapter = (SoSelectionAdapter) soListRecyclerView.getAdapter(); diff --git a/configapp/src/main/java/com/jiqiu/configapp/ConfigManager.java b/configapp/src/main/java/com/jiqiu/configapp/ConfigManager.java index 103c434..36f707e 100644 --- a/configapp/src/main/java/com/jiqiu/configapp/ConfigManager.java +++ b/configapp/src/main/java/com/jiqiu/configapp/ConfigManager.java @@ -265,11 +265,46 @@ public class ConfigManager { public GadgetConfig getAppGadgetConfig(String packageName) { AppConfig appConfig = config.perAppConfig.get(packageName); if (appConfig == null) { - return null; + // If no app config, return global gadget config + return config.globalGadgetConfig; } + + // If app is set to use global gadget, return global config + if (appConfig.useGlobalGadget) { + return config.globalGadgetConfig; + } + + // Otherwise return app-specific gadget config return appConfig.gadgetConfig; } + public GadgetConfig getGlobalGadgetConfig() { + return config.globalGadgetConfig; + } + + public void setGlobalGadgetConfig(GadgetConfig gadgetConfig) { + config.globalGadgetConfig = gadgetConfig; + saveConfig(); + } + + public boolean getAppUseGlobalGadget(String packageName) { + AppConfig appConfig = config.perAppConfig.get(packageName); + if (appConfig == null) { + return true; // Default to use global + } + return appConfig.useGlobalGadget; + } + + public void setAppUseGlobalGadget(String packageName, boolean useGlobal) { + AppConfig appConfig = config.perAppConfig.get(packageName); + if (appConfig == null) { + appConfig = new AppConfig(); + config.perAppConfig.put(packageName, appConfig); + } + appConfig.useGlobalGadget = useGlobal; + saveConfig(); + } + public void setAppGadgetConfig(String packageName, GadgetConfig gadgetConfig) { AppConfig appConfig = config.perAppConfig.get(packageName); if (appConfig == null) { @@ -478,8 +513,9 @@ public class ConfigManager { Log.i(TAG, "Deployment complete for: " + packageName); // Deploy gadget config if configured - if (appConfig.gadgetConfig != null) { - deployGadgetConfigFile(packageName, appConfig.gadgetConfig); + ConfigManager.GadgetConfig gadgetToUse = getAppGadgetConfig(packageName); + if (gadgetToUse != null) { + deployGadgetConfigFile(packageName, gadgetToUse); } } @@ -527,8 +563,9 @@ public class ConfigManager { } // Clean up gadget config file if exists - if (appConfig.gadgetConfig != null) { - String gadgetConfigName = appConfig.gadgetConfig.gadgetName.replace(".so", ".config.so"); + ConfigManager.GadgetConfig gadgetToUse = getAppGadgetConfig(packageName); + if (gadgetToUse != null) { + String gadgetConfigName = gadgetToUse.gadgetName.replace(".so", ".config.so"); String configPath = filesDir + "/" + gadgetConfigName; Shell.Result checkConfigResult = Shell.cmd("test -f \"" + configPath + "\" && echo 'exists'").exec(); @@ -562,6 +599,7 @@ public class ConfigManager { public int injectionDelay = 2; // Default 2 seconds public List globalSoFiles = new ArrayList<>(); public Map perAppConfig = new HashMap<>(); + public GadgetConfig globalGadgetConfig = null; // Global gadget configuration } public static class AppConfig { @@ -569,6 +607,7 @@ public class ConfigManager { public List soFiles = new ArrayList<>(); public String injectionMethod = "standard"; // "standard", "riru" or "custom_linker" public GadgetConfig gadgetConfig = null; + public boolean useGlobalGadget = true; // Whether to use global gadget settings } public static class SoFile { diff --git a/configapp/src/main/java/com/jiqiu/configapp/GadgetConfigDialog.java b/configapp/src/main/java/com/jiqiu/configapp/GadgetConfigDialog.java index ed56bca..b45ea04 100644 --- a/configapp/src/main/java/com/jiqiu/configapp/GadgetConfigDialog.java +++ b/configapp/src/main/java/com/jiqiu/configapp/GadgetConfigDialog.java @@ -57,6 +57,7 @@ public class GadgetConfigDialog extends DialogFragment { // Configuration data private ConfigManager.GadgetConfig config; private OnGadgetConfigListener listener; + private String customTitle; // Flag to prevent recursive updates private boolean isUpdatingUI = false; @@ -75,6 +76,21 @@ public class GadgetConfigDialog extends DialogFragment { return dialog; } + // Constructor for non-fragment usage + public GadgetConfigDialog(Context context, String title, ConfigManager.GadgetConfig config, OnGadgetConfigListener listener) { + // This constructor is for compatibility with direct dialog creation + // The actual dialog will be created in show() method + this.savedContext = context; + this.customTitle = title; + this.config = config != null ? config : new ConfigManager.GadgetConfig(); + this.listener = listener; + } + + // Default constructor required for DialogFragment + public GadgetConfigDialog() { + // Empty constructor required + } + public void setOnGadgetConfigListener(OnGadgetConfigListener listener) { this.listener = listener; } @@ -129,8 +145,10 @@ public class GadgetConfigDialog extends DialogFragment { setupListeners(); updateJsonPreview(); + String title = customTitle != null ? customTitle : "Gadget 配置"; + return new MaterialAlertDialogBuilder(getContext()) - .setTitle("Gadget 配置") + .setTitle(title) .setView(view) .setPositiveButton("保存", (dialog, which) -> saveConfig()) .setNegativeButton("取消", null) @@ -567,6 +585,50 @@ public class GadgetConfigDialog extends DialogFragment { .show(); } + // Show method for non-fragment usage + public void show() { + if (getContext() == null) { + throw new IllegalStateException("Context is required for non-fragment usage"); + } + + View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_gadget_config, null); + initViews(view); + + // Initialize config if null + if (config == null) { + config = new ConfigManager.GadgetConfig(); + } + + loadConfig(); + setupListeners(); + updateJsonPreview(); + + String title = customTitle != null ? customTitle : "Gadget 配置"; + + new MaterialAlertDialogBuilder(getContext()) + .setTitle(title) + .setView(view) + .setPositiveButton("保存", (dialog, which) -> saveConfig()) + .setNegativeButton("取消", null) + .show(); + } + + private Context savedContext; + + @Override + public Context getContext() { + Context context = super.getContext(); + if (context == null) { + return savedContext; + } + return context; + } + + // Constructor for non-fragment usage needs to save context + public void setContext(Context context) { + this.savedContext = context; + } + private String getPathFromUri(Uri uri) { String path = null; diff --git a/configapp/src/main/java/com/jiqiu/configapp/SettingsFragment.java b/configapp/src/main/java/com/jiqiu/configapp/SettingsFragment.java index 310fb29..5187a1d 100644 --- a/configapp/src/main/java/com/jiqiu/configapp/SettingsFragment.java +++ b/configapp/src/main/java/com/jiqiu/configapp/SettingsFragment.java @@ -11,6 +11,8 @@ import android.widget.RadioGroup; import android.widget.EditText; import android.text.TextWatcher; import android.text.Editable; +import android.widget.TextView; +import android.widget.Button; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -28,6 +30,8 @@ public class SettingsFragment extends Fragment { private RadioButton radioShowAll; private RadioButton radioHideSystem; private EditText editInjectionDelay; + private TextView tvGlobalGadgetStatus; + private Button btnConfigureGlobalGadget; private ConfigManager configManager; private SharedPreferences sharedPreferences; @@ -59,6 +63,8 @@ public class SettingsFragment extends Fragment { radioShowAll = view.findViewById(R.id.radio_show_all); radioHideSystem = view.findViewById(R.id.radio_hide_system); editInjectionDelay = view.findViewById(R.id.editInjectionDelay); + tvGlobalGadgetStatus = view.findViewById(R.id.tvGlobalGadgetStatus); + btnConfigureGlobalGadget = view.findViewById(R.id.btnConfigureGlobalGadget); configManager = new ConfigManager(getContext()); } @@ -79,6 +85,9 @@ public class SettingsFragment extends Fragment { // Load injection delay int injectionDelay = configManager.getInjectionDelay(); editInjectionDelay.setText(String.valueOf(injectionDelay)); + + // Load global gadget status + updateGlobalGadgetStatus(); } private void setupListeners() { @@ -124,6 +133,11 @@ public class SettingsFragment extends Fragment { } } }); + + // Global gadget configuration button + btnConfigureGlobalGadget.setOnClickListener(v -> { + showGlobalGadgetConfigDialog(); + }); } public void setOnSettingsChangeListener(OnSettingsChangeListener listener) { @@ -133,4 +147,34 @@ public class SettingsFragment extends Fragment { public boolean isHideSystemApps() { return sharedPreferences.getBoolean(KEY_HIDE_SYSTEM_APPS, false); } + + private void updateGlobalGadgetStatus() { + ConfigManager.GadgetConfig globalGadget = configManager.getGlobalGadgetConfig(); + if (globalGadget != null) { + String status = "已配置: " + globalGadget.gadgetName; + if (globalGadget.mode.equals("server")) { + status += " (Server模式, 端口: " + globalGadget.port + ")"; + } else { + status += " (Script模式)"; + } + tvGlobalGadgetStatus.setText(status); + } else { + tvGlobalGadgetStatus.setText("未配置"); + } + } + + private void showGlobalGadgetConfigDialog() { + // Use existing GadgetConfigDialog + GadgetConfigDialog dialog = new GadgetConfigDialog( + getContext(), + "全局Gadget配置", + configManager.getGlobalGadgetConfig(), + gadgetConfig -> { + // Save global gadget configuration + configManager.setGlobalGadgetConfig(gadgetConfig); + updateGlobalGadgetStatus(); + } + ); + dialog.show(); + } } diff --git a/configapp/src/main/res/layout/dialog_app_config.xml b/configapp/src/main/res/layout/dialog_app_config.xml index 5b867bd..373d512 100644 --- a/configapp/src/main/res/layout/dialog_app_config.xml +++ b/configapp/src/main/res/layout/dialog_app_config.xml @@ -88,35 +88,67 @@ android:layout_marginTop="16dp" android:layout_marginBottom="16dp" /> - + + - + + + + + android:layout_marginStart="32dp" + android:layout_marginBottom="4dp" + android:text="未配置全局Gadget" + android:textColor="?android:attr/textColorSecondary" + android:textSize="12sp" /> - + - + + + diff --git a/configapp/src/main/res/layout/fragment_settings.xml b/configapp/src/main/res/layout/fragment_settings.xml index 953b6cc..227dece 100644 --- a/configapp/src/main/res/layout/fragment_settings.xml +++ b/configapp/src/main/res/layout/fragment_settings.xml @@ -143,6 +143,53 @@ + + + + + + + + + + + + + + + + +