diff --git a/configapp/src/main/AndroidManifest.xml b/configapp/src/main/AndroidManifest.xml
index 837a2c2..31c3194 100644
--- a/configapp/src/main/AndroidManifest.xml
+++ b/configapp/src/main/AndroidManifest.xml
@@ -20,6 +20,14 @@
+
+
+
+
\ No newline at end of file
diff --git a/configapp/src/main/java/com/jiqiu/configapp/AppSoConfigActivity.java b/configapp/src/main/java/com/jiqiu/configapp/AppSoConfigActivity.java
new file mode 100644
index 0000000..9199283
--- /dev/null
+++ b/configapp/src/main/java/com/jiqiu/configapp/AppSoConfigActivity.java
@@ -0,0 +1,164 @@
+package com.jiqiu.configapp;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CheckBox;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class AppSoConfigActivity extends AppCompatActivity {
+
+ public static final String EXTRA_PACKAGE_NAME = "package_name";
+ public static final String EXTRA_APP_NAME = "app_name";
+
+ private RecyclerView recyclerView;
+ private TextView emptyView;
+ private SoSelectionAdapter adapter;
+ private ConfigManager configManager;
+
+ private String packageName;
+ private String appName;
+ private List appSoFiles;
+ private List globalSoFiles;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_app_so_config);
+
+ packageName = getIntent().getStringExtra(EXTRA_PACKAGE_NAME);
+ appName = getIntent().getStringExtra(EXTRA_APP_NAME);
+
+ if (packageName == null) {
+ finish();
+ return;
+ }
+
+ configManager = new ConfigManager(this);
+
+ Toolbar toolbar = findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setTitle(appName != null ? appName : packageName);
+ getSupportActionBar().setSubtitle("配置SO文件");
+
+ recyclerView = findViewById(R.id.recyclerView);
+ emptyView = findViewById(R.id.emptyView);
+
+ adapter = new SoSelectionAdapter();
+ recyclerView.setLayoutManager(new LinearLayoutManager(this));
+ recyclerView.setAdapter(adapter);
+
+ loadData();
+ }
+
+ private void loadData() {
+ // Load app-specific SO files
+ appSoFiles = configManager.getAppSoFiles(packageName);
+
+ // Load global SO files
+ globalSoFiles = configManager.getAllSoFiles();
+
+ if (globalSoFiles.isEmpty()) {
+ emptyView.setVisibility(View.VISIBLE);
+ recyclerView.setVisibility(View.GONE);
+ } else {
+ emptyView.setVisibility(View.GONE);
+ recyclerView.setVisibility(View.VISIBLE);
+ adapter.setData(globalSoFiles, appSoFiles);
+ }
+ }
+
+ @Override
+ public boolean onSupportNavigateUp() {
+ onBackPressed();
+ return true;
+ }
+
+ class SoSelectionAdapter extends RecyclerView.Adapter {
+ private List availableSoFiles = new ArrayList<>();
+ private List selectedSoFiles = new ArrayList<>();
+
+ void setData(List available, List selected) {
+ this.availableSoFiles = available;
+ this.selectedSoFiles = selected;
+ notifyDataSetChanged();
+ }
+
+ @NonNull
+ @Override
+ public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.item_so_selection, parent, false);
+ return new ViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
+ holder.bind(availableSoFiles.get(position));
+ }
+
+ @Override
+ public int getItemCount() {
+ return availableSoFiles.size();
+ }
+
+ class ViewHolder extends RecyclerView.ViewHolder {
+ CheckBox checkBox;
+ TextView nameText;
+ TextView pathText;
+
+ ViewHolder(@NonNull View itemView) {
+ super(itemView);
+ checkBox = itemView.findViewById(R.id.checkBox);
+ nameText = itemView.findViewById(R.id.textName);
+ pathText = itemView.findViewById(R.id.textPath);
+ }
+
+ void bind(ConfigManager.SoFile soFile) {
+ nameText.setText(soFile.name);
+ pathText.setText(soFile.originalPath);
+
+ // Check if this SO is selected for the app
+ boolean isSelected = false;
+ for (ConfigManager.SoFile selected : selectedSoFiles) {
+ if (selected.storedPath.equals(soFile.storedPath)) {
+ isSelected = true;
+ break;
+ }
+ }
+
+ checkBox.setOnCheckedChangeListener(null);
+ checkBox.setChecked(isSelected);
+
+ checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ if (isChecked) {
+ // Add SO to app
+ configManager.addSoFileToApp(packageName, soFile.originalPath, false);
+ } else {
+ // Remove SO from app
+ configManager.removeSoFileFromApp(packageName, soFile);
+ }
+
+ // Reload data
+ loadData();
+ });
+
+ itemView.setOnClickListener(v -> checkBox.toggle());
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/configapp/src/main/java/com/jiqiu/configapp/ConfigManager.java b/configapp/src/main/java/com/jiqiu/configapp/ConfigManager.java
index 9e2b16a..5485e94 100644
--- a/configapp/src/main/java/com/jiqiu/configapp/ConfigManager.java
+++ b/configapp/src/main/java/com/jiqiu/configapp/ConfigManager.java
@@ -104,6 +104,48 @@ public class ConfigManager {
return new ArrayList<>(appConfig.soFiles);
}
+ public List getAllSoFiles() {
+ if (config.globalSoFiles == null) {
+ config.globalSoFiles = new ArrayList<>();
+ }
+ return new ArrayList<>(config.globalSoFiles);
+ }
+
+ public void addGlobalSoFile(String originalPath, boolean deleteOriginal) {
+ if (config.globalSoFiles == null) {
+ config.globalSoFiles = new ArrayList<>();
+ }
+
+ // Generate unique filename
+ String fileName = new File(originalPath).getName();
+ String storedPath = SO_STORAGE_DIR + "/" + System.currentTimeMillis() + "_" + fileName;
+
+ // Copy SO file to our storage
+ Shell.Result result = Shell.cmd("cp \"" + originalPath + "\" \"" + storedPath + "\"").exec();
+ if (result.isSuccess()) {
+ SoFile soFile = new SoFile();
+ soFile.name = fileName;
+ soFile.storedPath = storedPath;
+ soFile.originalPath = originalPath;
+ config.globalSoFiles.add(soFile);
+
+ if (deleteOriginal) {
+ Shell.cmd("rm \"" + originalPath + "\"").exec();
+ }
+
+ saveConfig();
+ }
+ }
+
+ public void removeGlobalSoFile(SoFile soFile) {
+ if (config.globalSoFiles == null) return;
+
+ config.globalSoFiles.remove(soFile);
+ // Delete the stored file
+ Shell.cmd("rm \"" + soFile.storedPath + "\"").exec();
+ saveConfig();
+ }
+
public void addSoFileToApp(String packageName, String originalPath, boolean deleteOriginal) {
AppConfig appConfig = config.perAppConfig.get(packageName);
if (appConfig == null) {
@@ -116,7 +158,7 @@ public class ConfigManager {
String storedPath = SO_STORAGE_DIR + "/" + System.currentTimeMillis() + "_" + fileName;
// Copy SO file to our storage
- Shell.Result result = Shell.cmd("cp " + originalPath + " " + storedPath).exec();
+ Shell.Result result = Shell.cmd("cp \"" + originalPath + "\" \"" + storedPath + "\"").exec();
if (result.isSuccess()) {
SoFile soFile = new SoFile();
soFile.name = fileName;
@@ -125,7 +167,7 @@ public class ConfigManager {
appConfig.soFiles.add(soFile);
if (deleteOriginal) {
- Shell.cmd("rm " + originalPath).exec();
+ Shell.cmd("rm \"" + originalPath + "\"").exec();
}
saveConfig();
@@ -155,6 +197,7 @@ public class ConfigManager {
public static class ModuleConfig {
public boolean enabled = true;
public boolean hideInjection = false;
+ public List globalSoFiles = new ArrayList<>();
public Map perAppConfig = new HashMap<>();
}
diff --git a/configapp/src/main/java/com/jiqiu/configapp/FileBrowserActivity.java b/configapp/src/main/java/com/jiqiu/configapp/FileBrowserActivity.java
new file mode 100644
index 0000000..725913d
--- /dev/null
+++ b/configapp/src/main/java/com/jiqiu/configapp/FileBrowserActivity.java
@@ -0,0 +1,287 @@
+package com.jiqiu.configapp;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.topjohnwu.superuser.Shell;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class FileBrowserActivity extends AppCompatActivity {
+
+ private static final String TAG = "FileBrowser";
+ public static final String EXTRA_START_PATH = "start_path";
+ public static final String EXTRA_FILE_FILTER = "file_filter";
+ public static final String EXTRA_SELECTED_PATH = "selected_path";
+
+ private RecyclerView recyclerView;
+ private TextView currentPathText;
+ private View emptyView;
+ private FileListAdapter adapter;
+
+ private String currentPath;
+ private String fileFilter = ".so";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_file_browser);
+
+ Toolbar toolbar = findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setTitle("选择SO文件");
+
+ currentPathText = findViewById(R.id.currentPath);
+ recyclerView = findViewById(R.id.recyclerView);
+ emptyView = findViewById(R.id.emptyView);
+
+ adapter = new FileListAdapter();
+ recyclerView.setLayoutManager(new LinearLayoutManager(this));
+ recyclerView.setAdapter(adapter);
+
+ // Get start path from intent
+ String startPath = getIntent().getStringExtra(EXTRA_START_PATH);
+ if (startPath == null) {
+ startPath = "/data/local/tmp";
+ }
+ fileFilter = getIntent().getStringExtra(EXTRA_FILE_FILTER);
+ if (fileFilter == null) {
+ fileFilter = ".so";
+ }
+
+ // Check if we have root access
+ if (!Shell.getShell().isRoot()) {
+ Toast.makeText(this, "需要Root权限才能浏览文件", Toast.LENGTH_LONG).show();
+ Log.e(TAG, "No root access");
+ }
+
+ currentPath = startPath;
+ loadFiles();
+ }
+
+ private void loadFiles() {
+ currentPathText.setText(currentPath);
+
+ List items = new ArrayList<>();
+
+ // Add parent directory if not root
+ if (!"/".equals(currentPath)) {
+ items.add(new FileItem("..", true, true));
+ }
+
+ // List files using root
+ Log.d(TAG, "Loading files from: " + currentPath);
+ Shell.Result result = Shell.cmd("ls -la " + currentPath + " 2>/dev/null").exec();
+ Log.d(TAG, "ls command success: " + result.isSuccess() + ", output lines: " + result.getOut().size());
+
+ if (result.isSuccess()) {
+ for (String line : result.getOut()) {
+ // Skip empty lines, total line, and symbolic links
+ if (line.trim().isEmpty() || line.startsWith("total") || line.contains("->")) {
+ continue;
+ }
+
+ // Try to parse ls output - handle different formats
+ String name = null;
+ boolean isDirectory = false;
+ boolean isReadable = true;
+
+ // Check if line starts with permissions (drwxr-xr-x format)
+ if (line.matches("^[dlrwxst-]{10}.*")) {
+ String[] parts = line.split("\\s+", 9);
+ if (parts.length >= 9) {
+ String permissions = parts[0];
+ name = parts[parts.length - 1];
+ isDirectory = permissions.startsWith("d");
+ isReadable = permissions.length() > 1 && permissions.charAt(1) == 'r';
+ }
+ } else {
+ // Simple format, just the filename
+ name = line.trim();
+ // Check if it's a directory by trying to list it
+ Shell.Result dirCheck = Shell.cmd("test -d \"" + currentPath + "/" + name + "\" && echo 'dir'").exec();
+ isDirectory = dirCheck.isSuccess() && !dirCheck.getOut().isEmpty();
+ }
+
+ if (name != null && !".".equals(name) && !"..".equals(name)) {
+ // Filter files by extension
+ if (!isDirectory && fileFilter != null && !name.endsWith(fileFilter)) {
+ continue;
+ }
+
+ items.add(new FileItem(name, isDirectory, isReadable));
+ }
+ }
+ } else {
+ // If ls fails, try a simpler approach
+ Shell.Result simpleResult = Shell.cmd("cd " + currentPath + " && for f in *; do echo \"$f\"; done").exec();
+ if (simpleResult.isSuccess()) {
+ for (String name : simpleResult.getOut()) {
+ if (!name.trim().isEmpty() && !"*".equals(name)) {
+ Shell.Result dirCheck = Shell.cmd("test -d \"" + currentPath + "/" + name + "\" && echo 'dir'").exec();
+ boolean isDirectory = dirCheck.isSuccess() && !dirCheck.getOut().isEmpty();
+
+ // Filter files by extension
+ if (!isDirectory && fileFilter != null && !name.endsWith(fileFilter)) {
+ continue;
+ }
+
+ items.add(new FileItem(name, isDirectory, true));
+ }
+ }
+ }
+ }
+
+ // If still no items and not root, add some common directories to try
+ if (items.size() <= 1 && "/data/local/tmp".equals(currentPath)) {
+ // Try to create a test file to verify access
+ Shell.cmd("touch /data/local/tmp/test_access.tmp && rm /data/local/tmp/test_access.tmp").exec();
+
+ // Add any .so files we can find
+ Shell.Result findResult = Shell.cmd("find " + currentPath + " -maxdepth 1 -name '*.so' -type f 2>/dev/null").exec();
+ if (findResult.isSuccess()) {
+ for (String path : findResult.getOut()) {
+ if (!path.trim().isEmpty()) {
+ String name = path.substring(path.lastIndexOf('/') + 1);
+ items.add(new FileItem(name, false, true));
+ }
+ }
+ }
+ }
+
+ Collections.sort(items, (a, b) -> {
+ if (a.isDirectory != b.isDirectory) {
+ return a.isDirectory ? -1 : 1;
+ }
+ return a.name.compareToIgnoreCase(b.name);
+ });
+
+ adapter.setItems(items);
+ emptyView.setVisibility(items.isEmpty() || (items.size() == 1 && "..".equals(items.get(0).name)) ? View.VISIBLE : View.GONE);
+ }
+
+ @Override
+ public boolean onSupportNavigateUp() {
+ onBackPressed();
+ return true;
+ }
+
+ class FileItem {
+ String name;
+ boolean isDirectory;
+ boolean isReadable;
+
+ FileItem(String name, boolean isDirectory, boolean isReadable) {
+ this.name = name;
+ this.isDirectory = isDirectory;
+ this.isReadable = isReadable;
+ }
+ }
+
+ class FileListAdapter extends RecyclerView.Adapter {
+ private List items = new ArrayList<>();
+
+ void setItems(List items) {
+ this.items = items;
+ notifyDataSetChanged();
+ }
+
+ @NonNull
+ @Override
+ public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.item_file, parent, false);
+ return new ViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
+ holder.bind(items.get(position));
+ }
+
+ @Override
+ public int getItemCount() {
+ return items.size();
+ }
+
+ class ViewHolder extends RecyclerView.ViewHolder {
+ ImageView icon;
+ TextView name;
+ TextView info;
+
+ ViewHolder(@NonNull View itemView) {
+ super(itemView);
+ icon = itemView.findViewById(R.id.fileIcon);
+ name = itemView.findViewById(R.id.fileName);
+ info = itemView.findViewById(R.id.fileInfo);
+ }
+
+ void bind(FileItem item) {
+ name.setText(item.name);
+
+ if (item.isDirectory) {
+ icon.setImageResource(android.R.drawable.ic_menu_agenda);
+ info.setText("文件夹");
+ } else {
+ icon.setImageResource(android.R.drawable.ic_menu_save);
+ info.setText("SO文件");
+ }
+
+ if (!item.isReadable) {
+ itemView.setAlpha(0.5f);
+ } else {
+ itemView.setAlpha(1.0f);
+ }
+
+ itemView.setOnClickListener(v -> {
+ if ("..".equals(item.name)) {
+ // Go to parent directory
+ int lastSlash = currentPath.lastIndexOf('/');
+ if (lastSlash > 0) {
+ currentPath = currentPath.substring(0, lastSlash);
+ } else {
+ currentPath = "/";
+ }
+ loadFiles();
+ } else if (item.isDirectory) {
+ if (!item.isReadable) {
+ Toast.makeText(FileBrowserActivity.this,
+ "没有权限访问此目录", Toast.LENGTH_SHORT).show();
+ return;
+ }
+ if ("/".equals(currentPath)) {
+ currentPath = "/" + item.name;
+ } else {
+ currentPath = currentPath + "/" + item.name;
+ }
+ loadFiles();
+ } else {
+ // File selected
+ String selectedPath = currentPath + "/" + item.name;
+ Intent resultIntent = new Intent();
+ resultIntent.putExtra(EXTRA_SELECTED_PATH, selectedPath);
+ setResult(Activity.RESULT_OK, resultIntent);
+ finish();
+ }
+ });
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/configapp/src/main/java/com/jiqiu/configapp/SoManagerFragment.java b/configapp/src/main/java/com/jiqiu/configapp/SoManagerFragment.java
index 70278e9..e996bbd 100644
--- a/configapp/src/main/java/com/jiqiu/configapp/SoManagerFragment.java
+++ b/configapp/src/main/java/com/jiqiu/configapp/SoManagerFragment.java
@@ -36,6 +36,7 @@ public class SoManagerFragment extends Fragment {
private List globalSoFiles = new ArrayList<>();
private ActivityResultLauncher filePickerLauncher;
+ private ActivityResultLauncher fileBrowserLauncher;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
@@ -55,6 +56,19 @@ public class SoManagerFragment extends Fragment {
}
}
);
+
+ // Initialize file browser
+ fileBrowserLauncher = registerForActivityResult(
+ new ActivityResultContracts.StartActivityForResult(),
+ result -> {
+ if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) {
+ String path = result.getData().getStringExtra(FileBrowserActivity.EXTRA_SELECTED_PATH);
+ if (path != null) {
+ showDeleteOriginalDialog(path);
+ }
+ }
+ }
+ );
}
@Nullable
@@ -87,32 +101,16 @@ public class SoManagerFragment extends Fragment {
Toast.makeText(getContext(), "需要Root权限", Toast.LENGTH_LONG).show();
} else {
configManager.ensureModuleDirectories();
+ // Also ensure common directories exist
+ Shell.cmd("mkdir -p /data/local/tmp").exec();
+ Shell.cmd("chmod 777 /data/local/tmp").exec();
loadSoFiles();
}
}
private void loadSoFiles() {
- // Load global SO files from the storage directory
- Shell.Result result = Shell.cmd("ls -la " + ConfigManager.SO_STORAGE_DIR).exec();
-
- globalSoFiles.clear();
- if (result.isSuccess()) {
- for (String line : result.getOut()) {
- if (line.contains(".so")) {
- // Parse file info
- String[] parts = line.split("\\s+");
- if (parts.length >= 9) {
- String fileName = parts[parts.length - 1];
- ConfigManager.SoFile soFile = new ConfigManager.SoFile();
- soFile.name = fileName;
- soFile.storedPath = ConfigManager.SO_STORAGE_DIR + "/" + fileName;
- soFile.originalPath = soFile.storedPath; // For display
- globalSoFiles.add(soFile);
- }
- }
- }
- }
-
+ // Load global SO files from config
+ globalSoFiles = configManager.getAllSoFiles();
updateUI();
}
@@ -128,12 +126,14 @@ public class SoManagerFragment extends Fragment {
}
private void showAddSoDialog() {
- String[] options = {"从文件管理器选择", "输入路径"};
+ String[] options = {"浏览文件系统", "从外部文件管理器选择", "手动输入路径"};
new MaterialAlertDialogBuilder(requireContext())
.setTitle("添加SO文件")
.setItems(options, (dialog, which) -> {
if (which == 0) {
+ openFileBrowser();
+ } else if (which == 1) {
openFilePicker();
} else {
showPathInputDialog();
@@ -142,6 +142,54 @@ public class SoManagerFragment extends Fragment {
.show();
}
+ private void openFileBrowser() {
+ // Show path selection dialog first
+ String[] paths = {
+ "/data/local/tmp",
+ "/sdcard",
+ "/sdcard/Download",
+ "/storage/emulated/0",
+ "自定义路径..."
+ };
+
+ new MaterialAlertDialogBuilder(requireContext())
+ .setTitle("选择起始目录")
+ .setItems(paths, (dialog, which) -> {
+ if (which == paths.length - 1) {
+ // Custom path
+ showCustomPathDialog();
+ } else {
+ Intent intent = new Intent(getContext(), FileBrowserActivity.class);
+ intent.putExtra(FileBrowserActivity.EXTRA_START_PATH, paths[which]);
+ intent.putExtra(FileBrowserActivity.EXTRA_FILE_FILTER, ".so");
+ fileBrowserLauncher.launch(intent);
+ }
+ })
+ .show();
+ }
+
+ private void showCustomPathDialog() {
+ View view = getLayoutInflater().inflate(R.layout.dialog_input, null);
+ android.widget.EditText editText = view.findViewById(android.R.id.edit);
+ editText.setText("/");
+ editText.setHint("输入起始路径");
+
+ new MaterialAlertDialogBuilder(requireContext())
+ .setTitle("自定义起始路径")
+ .setView(view)
+ .setPositiveButton("确定", (dialog, which) -> {
+ String path = editText.getText().toString().trim();
+ if (!path.isEmpty()) {
+ Intent intent = new Intent(getContext(), FileBrowserActivity.class);
+ intent.putExtra(FileBrowserActivity.EXTRA_START_PATH, path);
+ intent.putExtra(FileBrowserActivity.EXTRA_FILE_FILTER, ".so");
+ fileBrowserLauncher.launch(intent);
+ }
+ })
+ .setNegativeButton("取消", null)
+ .show();
+ }
+
private void openFilePicker() {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("*/*");
@@ -152,6 +200,7 @@ public class SoManagerFragment extends Fragment {
private void showPathInputDialog() {
View view = getLayoutInflater().inflate(R.layout.dialog_input, null);
android.widget.EditText editText = view.findViewById(android.R.id.edit);
+ editText.setText("/data/local/tmp/");
editText.setHint("/data/local/tmp/example.so");
new MaterialAlertDialogBuilder(requireContext())
@@ -160,7 +209,7 @@ public class SoManagerFragment extends Fragment {
.setPositiveButton("添加", (dialog, which) -> {
String path = editText.getText().toString().trim();
if (!path.isEmpty()) {
- addSoFile(path, false);
+ showDeleteOriginalDialog(path);
}
})
.setNegativeButton("取消", null)
@@ -175,40 +224,38 @@ public class SoManagerFragment extends Fragment {
if (path.startsWith("file://")) {
path = path.substring(7);
}
- addSoFile(path, false);
+ showDeleteOriginalDialog(path);
}
}
+ private void showDeleteOriginalDialog(String path) {
+ new MaterialAlertDialogBuilder(requireContext())
+ .setTitle("删除原文件")
+ .setMessage("是否删除原始SO文件?\n\n文件路径:" + path)
+ .setPositiveButton("删除原文件", (dialog, which) -> {
+ addSoFile(path, true);
+ })
+ .setNegativeButton("保留原文件", (dialog, which) -> {
+ addSoFile(path, false);
+ })
+ .setNeutralButton("取消", null)
+ .show();
+ }
+
private void addSoFile(String path, boolean deleteOriginal) {
// Verify file exists
- Shell.Result result = Shell.cmd("test -f " + path + " && echo 'exists'").exec();
+ Shell.Result result = Shell.cmd("test -f \"" + path + "\" && echo 'exists'").exec();
if (!result.isSuccess() || result.getOut().isEmpty()) {
Toast.makeText(getContext(), "文件不存在: " + path, Toast.LENGTH_SHORT).show();
return;
}
- // Generate unique filename
- String fileName = new File(path).getName();
- String storedPath = ConfigManager.SO_STORAGE_DIR + "/" + System.currentTimeMillis() + "_" + fileName;
+ // Add to global SO files
+ configManager.addGlobalSoFile(path, deleteOriginal);
- // Copy file
- result = Shell.cmd("cp " + path + " " + storedPath).exec();
- if (result.isSuccess()) {
- ConfigManager.SoFile soFile = new ConfigManager.SoFile();
- soFile.name = fileName;
- soFile.storedPath = storedPath;
- soFile.originalPath = path;
- globalSoFiles.add(soFile);
-
- if (deleteOriginal) {
- Shell.cmd("rm " + path).exec();
- }
-
- updateUI();
- Toast.makeText(getContext(), "SO文件已添加", Toast.LENGTH_SHORT).show();
- } else {
- Toast.makeText(getContext(), "复制文件失败", Toast.LENGTH_SHORT).show();
- }
+ // Reload the list
+ loadSoFiles();
+ Toast.makeText(getContext(), "SO文件已添加", Toast.LENGTH_SHORT).show();
}
private void showDeleteConfirmation(ConfigManager.SoFile soFile) {
@@ -223,9 +270,8 @@ public class SoManagerFragment extends Fragment {
}
private void deleteSoFile(ConfigManager.SoFile soFile) {
- Shell.cmd("rm " + soFile.storedPath).exec();
- globalSoFiles.remove(soFile);
- updateUI();
+ configManager.removeGlobalSoFile(soFile);
+ loadSoFiles();
Toast.makeText(getContext(), "SO文件已删除", Toast.LENGTH_SHORT).show();
}
}
\ No newline at end of file
diff --git a/configapp/src/main/res/layout/activity_app_so_config.xml b/configapp/src/main/res/layout/activity_app_so_config.xml
new file mode 100644
index 0000000..9b80163
--- /dev/null
+++ b/configapp/src/main/res/layout/activity_app_so_config.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/configapp/src/main/res/layout/activity_file_browser.xml b/configapp/src/main/res/layout/activity_file_browser.xml
new file mode 100644
index 0000000..a661436
--- /dev/null
+++ b/configapp/src/main/res/layout/activity_file_browser.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/configapp/src/main/res/layout/fragment_so_manager.xml b/configapp/src/main/res/layout/fragment_so_manager.xml
index 2023503..a8153c3 100644
--- a/configapp/src/main/res/layout/fragment_so_manager.xml
+++ b/configapp/src/main/res/layout/fragment_so_manager.xml
@@ -1,62 +1,75 @@
-
-
-
-
-
-
-
+ android:layout_height="match_parent">
+ android:orientation="vertical">
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/configapp/src/main/res/layout/item_file.xml b/configapp/src/main/res/layout/item_file.xml
new file mode 100644
index 0000000..187d4ce
--- /dev/null
+++ b/configapp/src/main/res/layout/item_file.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/configapp/src/main/res/layout/item_so_selection.xml b/configapp/src/main/res/layout/item_so_selection.xml
new file mode 100644
index 0000000..3c4d5fa
--- /dev/null
+++ b/configapp/src/main/res/layout/item_so_selection.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file