[app] Async improvements

This commit is contained in:
tehcneko 2021-08-25 14:37:24 +08:00 committed by vvb2060
parent eb61f84c67
commit 8d3e8e8ed2
10 changed files with 192 additions and 166 deletions

View File

@ -20,7 +20,6 @@
package org.lsposed.manager;
import android.annotation.SuppressLint;
import android.app.Application;
import android.content.Intent;
import android.content.SharedPreferences;
@ -30,6 +29,7 @@ import android.system.Os;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.preference.PreferenceManager;
import com.google.gson.JsonParser;
@ -47,6 +47,8 @@ import java.io.StringWriter;
import java.time.Instant;
import java.time.ZoneOffset;
import java.util.Locale;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import okhttp3.Cache;
import okhttp3.Call;
@ -67,12 +69,11 @@ public class App extends Application {
}
public static final String TAG = "LSPosedManager";
@SuppressLint("StaticFieldLeak")
private static App instance = null;
private static OkHttpClient okHttpClient;
private static Cache okHttpCache;
private SharedPreferences pref;
private ExecutorService executorService;
public static App getInstance() {
return instance;
@ -82,6 +83,10 @@ public class App extends Application {
return instance.pref;
}
public static ExecutorService getExecutorService() {
return instance.executorService;
}
private void setCrashReport() {
Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
@ -116,6 +121,8 @@ public class App extends Application {
instance = this;
executorService = Executors.newCachedThreadPool();
pref = PreferenceManager.getDefaultSharedPreferences(this);
if ("CN".equals(Locale.getDefault().getCountry())) {
if (!pref.contains("doh")) {

View File

@ -255,7 +255,7 @@ public class ScopeAdapter extends RecyclerView.Adapter<ScopeAdapter.ViewHolder>
if (launchIntent != null) {
ConfigManager.startActivityAsUserWithFeature(launchIntent, module.userId);
} else {
fragment.makeSnackBar(R.string.module_no_ui, Snackbar.LENGTH_LONG);
Snackbar.make(fragment.binding.snackbar, R.string.module_no_ui, Snackbar.LENGTH_LONG).show();
}
return true;
} else if (itemId == R.id.backup) {
@ -469,7 +469,7 @@ public class ScopeAdapter extends RecyclerView.Adapter<ScopeAdapter.ViewHolder>
checkedList.remove(appInfo.application);
}
if (!ConfigManager.setModuleScope(module.packageName, checkedList)) {
fragment.makeSnackBar(R.string.failed_to_save_scope_list, Snackbar.LENGTH_SHORT);
Snackbar.make(fragment.binding.snackbar, R.string.failed_to_save_scope_list, Snackbar.LENGTH_SHORT).show();
if (!isChecked) {
checkedList.add(appInfo.application);
} else {

View File

@ -19,28 +19,25 @@
package org.lsposed.manager.ui.fragment;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.SearchView;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.google.android.material.snackbar.Snackbar;
import org.lsposed.manager.BuildConfig;
import org.lsposed.manager.App;
import org.lsposed.manager.R;
import org.lsposed.manager.adapters.ScopeAdapter;
import org.lsposed.manager.databinding.FragmentAppListBinding;
@ -111,56 +108,35 @@ public class AppListFragment extends BaseFragment {
backupLauncher = registerForActivityResult(new ActivityResultContracts.CreateDocument(),
uri -> {
if (uri != null) {
if (uri == null) return;
runAsync(() -> {
try {
// grantUriPermission might throw RemoteException on MIUI
requireActivity().grantUriPermission(BuildConfig.APPLICATION_ID, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
BackupUtils.backup(uri, modulePackageName);
} catch (Exception e) {
e.printStackTrace();
}
AlertDialog alertDialog = new AlertDialog.Builder(requireActivity())
.setCancelable(false)
.setMessage(R.string.settings_backuping)
.show();
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
boolean success = BackupUtils.backup(requireActivity(), uri, modulePackageName);
try {
requireActivity().runOnUiThread(() -> {
alertDialog.dismiss();
makeSnackBar(success ? R.string.settings_backup_success : R.string.settings_backup_failed, Snackbar.LENGTH_SHORT);
});
} catch (Exception e) {
e.printStackTrace();
var text = App.getInstance().getString(R.string.settings_backup_failed2, e.getMessage());
if (binding != null && isResumed()) {
Snackbar.make(binding.snackbar, text, Snackbar.LENGTH_LONG).show();
} else {
Toast.makeText(App.getInstance(), text, Toast.LENGTH_LONG).show();
}
});
}
}
});
});
restoreLauncher = registerForActivityResult(new ActivityResultContracts.OpenDocument(),
uri -> {
if (uri != null) {
if (uri == null) return;
runAsync(() -> {
try {
// grantUriPermission might throw RemoteException on MIUI
requireActivity().grantUriPermission(BuildConfig.APPLICATION_ID, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
BackupUtils.restore(uri, modulePackageName);
} catch (Exception e) {
e.printStackTrace();
}
AlertDialog alertDialog = new AlertDialog.Builder(requireActivity())
.setCancelable(false)
.setMessage(R.string.settings_restoring)
.show();
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
boolean success = BackupUtils.restore(requireActivity(), uri, modulePackageName);
try {
requireActivity().runOnUiThread(() -> {
alertDialog.dismiss();
makeSnackBar(success ? R.string.settings_restore_success : R.string.settings_restore_failed, Snackbar.LENGTH_SHORT);
scopeAdapter.refresh(false);
});
} catch (Exception e) {
e.printStackTrace();
var text = App.getInstance().getString(R.string.settings_restore_failed2, e.getMessage());
if (binding != null && isResumed()) {
Snackbar.make(binding.snackbar, text, Snackbar.LENGTH_LONG).show();
} else {
Toast.makeText(App.getInstance(), text, Toast.LENGTH_LONG).show();
}
});
}
}
});
});
requireActivity().getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
@ -207,12 +183,4 @@ public class AppListFragment extends BaseFragment {
}
return super.onContextItemSelected(item);
}
public void makeSnackBar(String text, @Snackbar.Duration int duration) {
Snackbar.make(binding.snackbar, text, duration).show();
}
public void makeSnackBar(@StringRes int text, @Snackbar.Duration int duration) {
Snackbar.make(binding.snackbar, text, duration).show();
}
}

View File

@ -26,8 +26,11 @@ import androidx.fragment.app.Fragment;
import androidx.navigation.NavController;
import androidx.navigation.fragment.NavHostFragment;
import org.lsposed.manager.App;
import org.lsposed.manager.R;
import java.util.concurrent.Future;
public class BaseFragment extends Fragment {
public void navigateUp() {
getNavController().navigateUp();
@ -59,4 +62,8 @@ public class BaseFragment extends Fragment {
onPrepareOptionsMenu(toolbar.getMenu());
}
}
public Future<?> runAsync(Runnable runnable) {
return App.getExecutorService().submit(runnable);
}
}

View File

@ -34,7 +34,6 @@ import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatDialogFragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.Lifecycle;
import com.google.android.material.snackbar.Snackbar;
@ -93,7 +92,7 @@ public class CompileDialogFragment extends AppCompatDialogFragment {
appInfo = arguments.getParcelable(KEY_APP_INFO);
String[] command = COMPILE_RESET_COMMAND;
command[6] = appInfo.packageName;
new CompileTask(this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, command);
new CompileTask(this).executeOnExecutor(App.getExecutorService(), command);
} else {
dismissAllowingStateLoss();
}
@ -156,8 +155,8 @@ public class CompileDialogFragment extends AppCompatDialogFragment {
if (fragment != null) {
fragment.dismissAllowingStateLoss();
AppListFragment appListFragment = (AppListFragment) fragment.getParentFragment();
if (appListFragment != null && appListFragment.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
appListFragment.makeSnackBar(text, Snackbar.LENGTH_LONG);
if (appListFragment != null && appListFragment.binding != null && appListFragment.isResumed()) {
Snackbar.make(appListFragment.binding.snackbar, text, Snackbar.LENGTH_LONG).show();
return;
}
}

View File

@ -34,6 +34,7 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
@ -46,6 +47,7 @@ import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.tabs.TabLayout;
import org.lsposed.manager.App;
import org.lsposed.manager.ConfigManager;
import org.lsposed.manager.R;
import org.lsposed.manager.databinding.FragmentLogsBinding;
@ -80,15 +82,18 @@ public class LogsFragment extends BaseFragment {
new ActivityResultContracts.CreateDocument(),
uri -> {
if (uri == null) return;
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
runAsync(() -> {
try (var os = new ZipOutputStream(requireContext().getContentResolver().openOutputStream(uri))) {
os.setLevel(Deflater.BEST_COMPRESSION);
zipLogs(os);
os.finish();
} catch (IOException e) {
var str = getResources().getString(R.string.logs_save_failed);
Snackbar.make(binding.snackbar, str + "\n" + e.getMessage(),
Snackbar.LENGTH_LONG).show();
var text = App.getInstance().getString(R.string.logs_save_failed2, e.getMessage());
if (binding != null && isResumed()) {
Snackbar.make(binding.snackbar, text, Snackbar.LENGTH_LONG).show();
} else {
Toast.makeText(App.getInstance(), text, Toast.LENGTH_LONG).show();
}
}
});
});

View File

@ -19,21 +19,20 @@
package org.lsposed.manager.ui.fragment;
import android.content.Intent;
import android.os.AsyncTask;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
import androidx.preference.SwitchPreference;
import androidx.recyclerview.widget.RecyclerView;
@ -42,7 +41,6 @@ import com.google.android.material.snackbar.Snackbar;
import com.takisoft.preferencex.PreferenceFragmentCompat;
import org.lsposed.manager.App;
import org.lsposed.manager.BuildConfig;
import org.lsposed.manager.ConfigManager;
import org.lsposed.manager.R;
import org.lsposed.manager.databinding.FragmentSettingsBinding;
@ -82,61 +80,55 @@ public class SettingsFragment extends BaseFragment {
}
public static class PreferenceFragment extends PreferenceFragmentCompat {
private SettingsFragment parentFragment;
ActivityResultLauncher<String> backupLauncher = registerForActivityResult(new ActivityResultContracts.CreateDocument(),
uri -> {
if (uri != null) {
if (uri == null || parentFragment == null) return;
parentFragment.runAsync(() -> {
try {
// grantUriPermission might throw RemoteException on MIUI
requireActivity().grantUriPermission(BuildConfig.APPLICATION_ID, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
BackupUtils.backup(uri);
} catch (Exception e) {
e.printStackTrace();
}
AlertDialog alertDialog = new AlertDialog.Builder(requireActivity())
.setCancelable(false)
.setMessage(R.string.settings_backuping)
.show();
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
boolean success = BackupUtils.backup(requireContext(), uri);
try {
SettingsFragment fragment = (SettingsFragment) getParentFragment();
requireActivity().runOnUiThread(() -> {
alertDialog.dismiss();
fragment.makeSnackBar(success ? R.string.settings_backup_success : R.string.settings_backup_failed, Snackbar.LENGTH_SHORT);
});
} catch (Exception e) {
e.printStackTrace();
var text = App.getInstance().getString(R.string.settings_backup_failed2, e.getMessage());
if (parentFragment != null && parentFragment.binding != null && isResumed()) {
Snackbar.make(parentFragment.binding.snackbar, text, Snackbar.LENGTH_LONG).show();
} else {
Toast.makeText(App.getInstance(), text, Toast.LENGTH_LONG).show();
}
});
}
}
});
});
ActivityResultLauncher<String[]> restoreLauncher = registerForActivityResult(new ActivityResultContracts.OpenDocument(),
uri -> {
if (uri != null) {
if (uri == null || parentFragment == null) return;
parentFragment.runAsync(() -> {
try {
// grantUriPermission might throw RemoteException on MIUI
requireActivity().grantUriPermission(BuildConfig.APPLICATION_ID, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
BackupUtils.restore(uri);
} catch (Exception e) {
e.printStackTrace();
}
AlertDialog alertDialog = new AlertDialog.Builder(requireActivity())
.setCancelable(false)
.setMessage(R.string.settings_restoring)
.show();
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
boolean success = BackupUtils.restore(requireContext(), uri);
try {
SettingsFragment fragment = (SettingsFragment) getParentFragment();
requireActivity().runOnUiThread(() -> {
alertDialog.dismiss();
fragment.makeSnackBar(success ? R.string.settings_restore_success : R.string.settings_restore_failed, Snackbar.LENGTH_SHORT);
});
} catch (Exception e) {
e.printStackTrace();
var text = App.getInstance().getString(R.string.settings_restore_failed2, e.getMessage());
if (parentFragment != null && parentFragment.binding != null && isResumed()) {
Snackbar.make(parentFragment.binding.snackbar, text, Snackbar.LENGTH_LONG).show();
} else {
Toast.makeText(App.getInstance(), text, Toast.LENGTH_LONG).show();
}
});
}
}
});
});
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
parentFragment = (SettingsFragment) requireParentFragment();
}
@Override
public void onDetach() {
super.onDetach();
parentFragment = null;
}
@Override
public void onCreatePreferencesFix(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.prefs);

View File

@ -20,16 +20,16 @@
package org.lsposed.manager.util;
import android.content.Context;
import android.net.Uri;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.lsposed.manager.App;
import org.lsposed.manager.ConfigManager;
import org.lsposed.manager.adapters.ScopeAdapter;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.zip.GZIPInputStream;
@ -38,55 +38,45 @@ import java.util.zip.GZIPOutputStream;
public class BackupUtils {
private static final int VERSION = 2;
public static boolean backup(Context context, Uri uri) {
return backup(context, uri, null);
public static void backup(Uri uri) throws JSONException, IOException {
backup(uri, null);
}
public static boolean backup(Context context, Uri uri, String packageName) {
try {
JSONObject rootObject = new JSONObject();
rootObject.put("version", VERSION);
JSONArray modulesArray = new JSONArray();
var modules = ModuleUtil.getInstance().getModules();
for (ModuleUtil.InstalledModule module : modules.values()) {
if (packageName != null && !module.packageName.equals(packageName)) {
continue;
}
JSONObject moduleObject = new JSONObject();
moduleObject.put("enable", ModuleUtil.getInstance().isModuleEnabled(module.packageName));
moduleObject.put("package", module.packageName);
List<ScopeAdapter.ApplicationWithEquals> scope = ConfigManager.getModuleScope(module.packageName);
JSONArray scopeArray = new JSONArray();
for (ScopeAdapter.ApplicationWithEquals s : scope) {
JSONObject app = new JSONObject();
app.put("package", s.packageName);
app.put("userId", s.userId);
scopeArray.put(app);
}
moduleObject.put("scope", scopeArray);
modulesArray.put(moduleObject);
public static void backup(Uri uri, String packageName) throws IOException, JSONException {
JSONObject rootObject = new JSONObject();
rootObject.put("version", VERSION);
JSONArray modulesArray = new JSONArray();
var modules = ModuleUtil.getInstance().getModules();
for (ModuleUtil.InstalledModule module : modules.values()) {
if (packageName != null && !module.packageName.equals(packageName)) {
continue;
}
rootObject.put("modules", modulesArray);
OutputStream outputStream = context.getContentResolver().openOutputStream(uri);
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream);
gzipOutputStream.write(rootObject.toString().getBytes());
gzipOutputStream.close();
outputStream.close();
return true;
} catch (Exception e) {
e.printStackTrace();
JSONObject moduleObject = new JSONObject();
moduleObject.put("enable", ModuleUtil.getInstance().isModuleEnabled(module.packageName));
moduleObject.put("package", module.packageName);
List<ScopeAdapter.ApplicationWithEquals> scope = ConfigManager.getModuleScope(module.packageName);
JSONArray scopeArray = new JSONArray();
for (ScopeAdapter.ApplicationWithEquals s : scope) {
JSONObject app = new JSONObject();
app.put("package", s.packageName);
app.put("userId", s.userId);
scopeArray.put(app);
}
moduleObject.put("scope", scopeArray);
modulesArray.put(moduleObject);
}
rootObject.put("modules", modulesArray);
try (GZIPOutputStream gzipOutputStream = new GZIPOutputStream(App.getInstance().getContentResolver().openOutputStream(uri))) {
gzipOutputStream.write(rootObject.toString().getBytes());
}
return false;
}
public static boolean restore(Context context, Uri uri) {
return restore(context, uri, null);
public static void restore(Uri uri) throws JSONException, IOException {
restore(uri, null);
}
public static boolean restore(Context context, Uri uri, String packageName) {
try {
InputStream inputStream = context.getContentResolver().openInputStream(uri);
GZIPInputStream gzipInputStream = new GZIPInputStream(inputStream, 32);
public static void restore(Uri uri, String packageName) throws IOException, JSONException {
try (GZIPInputStream gzipInputStream = new GZIPInputStream(App.getInstance().getContentResolver().openInputStream(uri), 32)) {
StringBuilder string = new StringBuilder();
byte[] data = new byte[32];
int bytesRead;
@ -94,7 +84,6 @@ public class BackupUtils {
string.append(new String(data, 0, bytesRead));
}
gzipInputStream.close();
inputStream.close();
JSONObject rootObject = new JSONObject(string.toString());
int version = rootObject.getInt("version");
if (version == VERSION || version == 1) {
@ -122,12 +111,8 @@ public class BackupUtils {
}
}
} else {
return false;
throw new IllegalArgumentException("Unknown backup file version");
}
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}

View File

@ -0,0 +1,61 @@
package org.lsposed.manager.util;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeProvider;
public class EmptyAccessibilityDelegate extends View.AccessibilityDelegate {
@Override
public void sendAccessibilityEvent(View host, int eventType) {
}
@Override
public boolean performAccessibilityAction(View host, int action, Bundle args) {
return true;
}
@Override
public void sendAccessibilityEventUnchecked(View host, AccessibilityEvent event) {
}
@Override
public boolean dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
return true;
}
@Override
public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
}
@Override
public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
}
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
}
@Override
public void addExtraDataToAccessibilityNodeInfo(View host, AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) {
}
@Override
public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child, AccessibilityEvent event) {
return true;
}
@Override
public AccessibilityNodeProvider getAccessibilityNodeProvider(View host) {
return null;
}
}

View File

@ -62,7 +62,7 @@
<string name="menuSaveToSd">Save</string>
<string name="nav_item_logs_lsp">LSPosed Logs</string>
<string name="nav_item_logs_module">Modules Logs</string>
<string name="logs_save_failed">Failed to save:</string>
<string name="logs_save_failed2">Failed to save:\n%s</string>
<string name="menuClearLog">Clear log now</string>
<string name="logs_cleared">Log successfully cleared.</string>
<string name="scroll_top">Scroll to top</string>
@ -72,6 +72,8 @@
<string name="menuReload">Reload</string>
<string name="logs_clear_failed_2">Failed to clear the log</string>
<string name="verbose_log_not_avaliable">Verbose log is not available, if you just enabled it without reboot, try again after reboot.</string>
<string name="logs_saving">Saving logs…</string>
<string name="logs_saved">Logs saved!</string>
<!-- Notification -->
<string name="module_is_not_activated_yet">Xposed module is not activated yet</string>
@ -155,11 +157,11 @@
<string name="settings_backup">Backup</string>
<string name="settings_backuping">Backing up…</string>
<string name="settings_backup_success">Backup finished!</string>
<string name="settings_backup_failed">Failed to backup</string>
<string name="settings_backup_failed2">Failed to backup:\n%s</string>
<string name="settings_restore">Restore</string>
<string name="settings_restoring">Restoring…</string>
<string name="settings_restore_success">Restore finished!</string>
<string name="settings_restore_failed">Failed to restore</string>
<string name="settings_restore_failed2">Failed to restore:\n%s</string>
<string name="group_network">Network</string>
<string name="dns_over_http">DNS over HTTPS</string>
<string name="dns_over_http_summary">Workaround DNS poisoning in some nations</string>