[app] Simplify code (#844)
This commit is contained in:
parent
a8e094a612
commit
8e156c7958
|
|
@ -1,8 +1,6 @@
|
|||
-keep class org.lsposed.manager.Constants {
|
||||
public static void showErrorToast(int);
|
||||
}
|
||||
-keepclasseswithmembers class org.lsposed.manager.receivers.LSPManagerServiceClient {
|
||||
private static android.os.IBinder binder;
|
||||
public static void setBinder(android.os.IBinder);
|
||||
}
|
||||
|
||||
-keepclassmembers class * implements android.os.Parcelable {
|
||||
|
|
@ -19,7 +17,7 @@
|
|||
}
|
||||
|
||||
-repackageclasses
|
||||
# temporarily disable it: https://issuetracker.google.com/issues/155606069
|
||||
# temporarily disable it: https://issuetracker.google.com/issues/155606069
|
||||
# -allowaccessmodification
|
||||
-overloadaggressively
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import org.lsposed.lspd.models.Application;
|
|||
import org.lsposed.lspd.models.UserInfo;
|
||||
import org.lsposed.lspd.utils.ParceledListSlice;
|
||||
import org.lsposed.manager.adapters.ScopeAdapter;
|
||||
import org.lsposed.manager.receivers.LSPManagerServiceClient;
|
||||
import org.lsposed.manager.receivers.LSPManagerServiceHolder;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -41,10 +41,14 @@ import java.util.List;
|
|||
|
||||
public class ConfigManager {
|
||||
|
||||
public static boolean isBinderAlive() {
|
||||
return LSPManagerServiceHolder.getService() != null;
|
||||
}
|
||||
|
||||
public static int getXposedApiVersion() {
|
||||
try {
|
||||
return LSPManagerServiceClient.getXposedApiVersion();
|
||||
} catch (RemoteException | NullPointerException e) {
|
||||
return LSPManagerServiceHolder.getService().getXposedApiVersion();
|
||||
} catch (RemoteException e) {
|
||||
Log.e(App.TAG, Log.getStackTraceString(e));
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -52,8 +56,8 @@ public class ConfigManager {
|
|||
|
||||
public static String getXposedVersionName() {
|
||||
try {
|
||||
return LSPManagerServiceClient.getXposedVersionName();
|
||||
} catch (RemoteException | NullPointerException e) {
|
||||
return LSPManagerServiceHolder.getService().getXposedVersionName();
|
||||
} catch (RemoteException e) {
|
||||
Log.e(App.TAG, Log.getStackTraceString(e));
|
||||
return null;
|
||||
}
|
||||
|
|
@ -61,8 +65,8 @@ public class ConfigManager {
|
|||
|
||||
public static int getXposedVersionCode() {
|
||||
try {
|
||||
return LSPManagerServiceClient.getXposedVersionCode();
|
||||
} catch (RemoteException | NullPointerException e) {
|
||||
return LSPManagerServiceHolder.getService().getXposedVersionCode();
|
||||
} catch (RemoteException e) {
|
||||
Log.e(App.TAG, Log.getStackTraceString(e));
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -71,8 +75,8 @@ public class ConfigManager {
|
|||
public static List<PackageInfo> getInstalledPackagesFromAllUsers(int flags, boolean filterNoProcess) {
|
||||
List<PackageInfo> list = new ArrayList<>();
|
||||
try {
|
||||
list.addAll(LSPManagerServiceClient.getInstalledPackagesFromAllUsers(flags, filterNoProcess));
|
||||
} catch (RemoteException | NullPointerException e) {
|
||||
list.addAll(LSPManagerServiceHolder.getService().getInstalledPackagesFromAllUsers(flags, filterNoProcess).getList());
|
||||
} catch (RemoteException e) {
|
||||
Log.e(App.TAG, Log.getStackTraceString(e));
|
||||
}
|
||||
return list;
|
||||
|
|
@ -80,8 +84,8 @@ public class ConfigManager {
|
|||
|
||||
public static String[] getEnabledModules() {
|
||||
try {
|
||||
return LSPManagerServiceClient.enabledModules();
|
||||
} catch (RemoteException | NullPointerException e) {
|
||||
return LSPManagerServiceHolder.getService().enabledModules();
|
||||
} catch (RemoteException e) {
|
||||
Log.e(App.TAG, Log.getStackTraceString(e));
|
||||
return new String[0];
|
||||
}
|
||||
|
|
@ -89,8 +93,8 @@ public class ConfigManager {
|
|||
|
||||
public static boolean setModuleEnabled(String packageName, boolean enable) {
|
||||
try {
|
||||
return enable ? LSPManagerServiceClient.enableModule(packageName) : LSPManagerServiceClient.disableModule(packageName);
|
||||
} catch (RemoteException | NullPointerException e) {
|
||||
return enable ? LSPManagerServiceHolder.getService().enableModule(packageName) : LSPManagerServiceHolder.getService().disableModule(packageName);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(App.TAG, Log.getStackTraceString(e));
|
||||
return false;
|
||||
}
|
||||
|
|
@ -105,8 +109,8 @@ public class ConfigManager {
|
|||
app.packageName = application.packageName;
|
||||
list.add(app);
|
||||
});
|
||||
return LSPManagerServiceClient.setModuleScope(packageName, new ParceledListSlice<>(list));
|
||||
} catch (RemoteException | NullPointerException e) {
|
||||
return LSPManagerServiceHolder.getService().setModuleScope(packageName, new ParceledListSlice<>(list));
|
||||
} catch (RemoteException e) {
|
||||
Log.e(App.TAG, Log.getStackTraceString(e));
|
||||
return false;
|
||||
}
|
||||
|
|
@ -115,7 +119,7 @@ public class ConfigManager {
|
|||
public static List<ScopeAdapter.ApplicationWithEquals> getModuleScope(String packageName) {
|
||||
List<ScopeAdapter.ApplicationWithEquals> list = new ArrayList<>();
|
||||
try {
|
||||
List<Application> applications = LSPManagerServiceClient.getModuleScope(packageName).getList();
|
||||
List<Application> applications = LSPManagerServiceHolder.getService().getModuleScope(packageName).getList();
|
||||
if (applications == null) {
|
||||
return list;
|
||||
}
|
||||
|
|
@ -124,7 +128,7 @@ public class ConfigManager {
|
|||
list.add(new ScopeAdapter.ApplicationWithEquals(application));
|
||||
}
|
||||
});
|
||||
} catch (RemoteException | NullPointerException e) {
|
||||
} catch (RemoteException e) {
|
||||
Log.e(App.TAG, Log.getStackTraceString(e));
|
||||
}
|
||||
return list;
|
||||
|
|
@ -132,8 +136,8 @@ public class ConfigManager {
|
|||
|
||||
public static boolean isResourceHookEnabled() {
|
||||
try {
|
||||
return LSPManagerServiceClient.isResourceHook();
|
||||
} catch (RemoteException | NullPointerException e) {
|
||||
return LSPManagerServiceHolder.getService().isResourceHook();
|
||||
} catch (RemoteException e) {
|
||||
Log.e(App.TAG, Log.getStackTraceString(e));
|
||||
return false;
|
||||
}
|
||||
|
|
@ -141,9 +145,9 @@ public class ConfigManager {
|
|||
|
||||
public static boolean setResourceHookEnabled(boolean enabled) {
|
||||
try {
|
||||
LSPManagerServiceClient.setResourceHook(enabled);
|
||||
LSPManagerServiceHolder.getService().setResourceHook(enabled);
|
||||
return true;
|
||||
} catch (RemoteException | NullPointerException e) {
|
||||
} catch (RemoteException e) {
|
||||
Log.e(App.TAG, Log.getStackTraceString(e));
|
||||
return false;
|
||||
}
|
||||
|
|
@ -151,8 +155,8 @@ public class ConfigManager {
|
|||
|
||||
public static boolean isVerboseLogEnabled() {
|
||||
try {
|
||||
return LSPManagerServiceClient.isVerboseLog();
|
||||
} catch (RemoteException | NullPointerException e) {
|
||||
return LSPManagerServiceHolder.getService().isVerboseLog();
|
||||
} catch (RemoteException e) {
|
||||
Log.e(App.TAG, Log.getStackTraceString(e));
|
||||
return false;
|
||||
}
|
||||
|
|
@ -160,9 +164,9 @@ public class ConfigManager {
|
|||
|
||||
public static boolean setVerboseLogEnabled(boolean enabled) {
|
||||
try {
|
||||
LSPManagerServiceClient.setVerboseLog(enabled);
|
||||
LSPManagerServiceHolder.getService().setVerboseLog(enabled);
|
||||
return true;
|
||||
} catch (RemoteException | NullPointerException e) {
|
||||
} catch (RemoteException e) {
|
||||
Log.e(App.TAG, Log.getStackTraceString(e));
|
||||
return false;
|
||||
}
|
||||
|
|
@ -170,8 +174,8 @@ public class ConfigManager {
|
|||
|
||||
public static ParcelFileDescriptor getLogs(boolean verbose) {
|
||||
try {
|
||||
return verbose ? LSPManagerServiceClient.getVerboseLog() : LSPManagerServiceClient.getModulesLog();
|
||||
} catch (RemoteException | NullPointerException e) {
|
||||
return verbose ? LSPManagerServiceHolder.getService().getVerboseLog() : LSPManagerServiceHolder.getService().getModulesLog();
|
||||
} catch (RemoteException e) {
|
||||
Log.e(App.TAG, Log.getStackTraceString(e));
|
||||
return null;
|
||||
}
|
||||
|
|
@ -179,8 +183,8 @@ public class ConfigManager {
|
|||
|
||||
public static boolean clearLogs(boolean verbose) {
|
||||
try {
|
||||
return LSPManagerServiceClient.clearLogs(verbose);
|
||||
} catch (RemoteException | NullPointerException e) {
|
||||
return LSPManagerServiceHolder.getService().clearLogs(verbose);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(App.TAG, Log.getStackTraceString(e));
|
||||
return false;
|
||||
}
|
||||
|
|
@ -188,8 +192,8 @@ public class ConfigManager {
|
|||
|
||||
public static PackageInfo getPackageInfo(String packageName, int flags, int userId) throws PackageManager.NameNotFoundException {
|
||||
try {
|
||||
return LSPManagerServiceClient.getPackageInfo(packageName, flags, userId);
|
||||
} catch (RemoteException | NullPointerException e) {
|
||||
return LSPManagerServiceHolder.getService().getPackageInfo(packageName, flags, userId);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(App.TAG, Log.getStackTraceString(e));
|
||||
throw new PackageManager.NameNotFoundException();
|
||||
}
|
||||
|
|
@ -197,9 +201,9 @@ public class ConfigManager {
|
|||
|
||||
public static boolean forceStopPackage(String packageName, int userId) {
|
||||
try {
|
||||
LSPManagerServiceClient.forceStopPackage(packageName, userId);
|
||||
LSPManagerServiceHolder.getService().forceStopPackage(packageName, userId);
|
||||
return true;
|
||||
} catch (RemoteException | NullPointerException e) {
|
||||
} catch (RemoteException e) {
|
||||
Log.e(App.TAG, Log.getStackTraceString(e));
|
||||
return false;
|
||||
}
|
||||
|
|
@ -207,9 +211,9 @@ public class ConfigManager {
|
|||
|
||||
public static boolean reboot(boolean confirm, String reason, boolean wait) {
|
||||
try {
|
||||
LSPManagerServiceClient.reboot(confirm, reason, wait);
|
||||
LSPManagerServiceHolder.getService().reboot(confirm, reason, wait);
|
||||
return true;
|
||||
} catch (RemoteException | NullPointerException e) {
|
||||
} catch (RemoteException e) {
|
||||
Log.e(App.TAG, Log.getStackTraceString(e));
|
||||
return false;
|
||||
}
|
||||
|
|
@ -217,8 +221,8 @@ public class ConfigManager {
|
|||
|
||||
public static boolean uninstallPackage(String packageName, int userId) {
|
||||
try {
|
||||
return LSPManagerServiceClient.uninstallPackage(packageName, userId);
|
||||
} catch (RemoteException | NullPointerException e) {
|
||||
return LSPManagerServiceHolder.getService().uninstallPackage(packageName, userId);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(App.TAG, Log.getStackTraceString(e));
|
||||
return false;
|
||||
}
|
||||
|
|
@ -226,8 +230,8 @@ public class ConfigManager {
|
|||
|
||||
public static boolean isSepolicyLoaded() {
|
||||
try {
|
||||
return LSPManagerServiceClient.isSepolicyLoaded();
|
||||
} catch (RemoteException | NullPointerException e) {
|
||||
return LSPManagerServiceHolder.getService().isSepolicyLoaded();
|
||||
} catch (RemoteException e) {
|
||||
Log.e(App.TAG, Log.getStackTraceString(e));
|
||||
return false;
|
||||
}
|
||||
|
|
@ -235,8 +239,8 @@ public class ConfigManager {
|
|||
|
||||
public static List<UserInfo> getUsers() {
|
||||
try {
|
||||
return LSPManagerServiceClient.getUsers();
|
||||
} catch (RemoteException | NullPointerException e) {
|
||||
return LSPManagerServiceHolder.getService().getUsers();
|
||||
} catch (RemoteException e) {
|
||||
Log.e(App.TAG, Log.getStackTraceString(e));
|
||||
return null;
|
||||
}
|
||||
|
|
@ -245,9 +249,9 @@ public class ConfigManager {
|
|||
public static boolean installExistingPackageAsUser(String packageName, int userId) {
|
||||
final int INSTALL_SUCCEEDED = 1;
|
||||
try {
|
||||
var ret = LSPManagerServiceClient.installExistingPackageAsUser(packageName, userId);
|
||||
var ret = LSPManagerServiceHolder.getService().installExistingPackageAsUser(packageName, userId);
|
||||
return ret == INSTALL_SUCCEEDED;
|
||||
} catch (RemoteException | NullPointerException e) {
|
||||
} catch (RemoteException e) {
|
||||
Log.e(App.TAG, Log.getStackTraceString(e));
|
||||
return false;
|
||||
}
|
||||
|
|
@ -260,24 +264,24 @@ public class ConfigManager {
|
|||
|
||||
public static boolean systemServerRequested() {
|
||||
try {
|
||||
return LSPManagerServiceClient.systemServerRequested();
|
||||
} catch (Throwable e) {
|
||||
return LSPManagerServiceHolder.getService().systemServerRequested();
|
||||
} catch (RemoteException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean dex2oatFlagsLoaded() {
|
||||
try {
|
||||
return LSPManagerServiceClient.dex2oatFlagsLoaded();
|
||||
} catch (Throwable e) {
|
||||
return LSPManagerServiceHolder.getService().dex2oatFlagsLoaded();
|
||||
} catch (RemoteException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static int startActivityAsUserWithFeature(Intent intent, int userId) {
|
||||
try {
|
||||
return LSPManagerServiceClient.startActivityAsUserWithFeature(intent, userId);
|
||||
} catch (Throwable e) {
|
||||
return LSPManagerServiceHolder.getService().startActivityAsUserWithFeature(intent, userId);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(App.TAG, Log.getStackTraceString(e));
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -286,8 +290,8 @@ public class ConfigManager {
|
|||
public static List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, int flags, int userId) {
|
||||
List<ResolveInfo> list = new ArrayList<>();
|
||||
try {
|
||||
list.addAll(LSPManagerServiceClient.queryIntentActivitiesAsUser(intent, flags, userId).getList());
|
||||
} catch (Throwable e) {
|
||||
list.addAll(LSPManagerServiceHolder.getService().queryIntentActivitiesAsUser(intent, flags, userId).getList());
|
||||
} catch (RemoteException e) {
|
||||
Log.e(App.TAG, Log.getStackTraceString(e));
|
||||
}
|
||||
return list;
|
||||
|
|
|
|||
|
|
@ -20,11 +20,18 @@
|
|||
|
||||
package org.lsposed.manager;
|
||||
|
||||
import android.os.IBinder;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.lsposed.manager.receivers.LSPManagerServiceHolder;
|
||||
|
||||
public class Constants {
|
||||
|
||||
public static void showErrorToast(int type) {
|
||||
Toast.makeText(App.getInstance(), R.string.app_destroyed, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
public static void setBinder(IBinder binder) {
|
||||
LSPManagerServiceHolder.init(binder);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,8 +77,8 @@ import org.lsposed.manager.ui.fragment.CompileDialogFragment;
|
|||
import org.lsposed.manager.util.GlideApp;
|
||||
import org.lsposed.manager.util.ModuleUtil;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
|
|
@ -259,13 +259,9 @@ public class ScopeAdapter extends RecyclerView.Adapter<ScopeAdapter.ViewHolder>
|
|||
}
|
||||
return true;
|
||||
} else if (itemId == R.id.backup) {
|
||||
Calendar now = Calendar.getInstance();
|
||||
fragment.backupLauncher.launch(String.format(Locale.US,
|
||||
"%s_%04d%02d%02d_%02d%02d%02d.lsp",
|
||||
module.getAppName(),
|
||||
now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1,
|
||||
now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY),
|
||||
now.get(Calendar.MINUTE), now.get(Calendar.SECOND)));
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
fragment.backupLauncher.launch(String.format(Locale.ROOT,
|
||||
"%s_%s.lsp", module.getAppName(), now.toString()));
|
||||
return true;
|
||||
} else if (itemId == R.id.restore) {
|
||||
fragment.restoreLauncher.launch(new String[]{"*/*"});
|
||||
|
|
|
|||
|
|
@ -1,189 +0,0 @@
|
|||
/*
|
||||
* This file is part of LSPosed.
|
||||
*
|
||||
* LSPosed is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LSPosed is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LSPosed. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Copyright (C) 2021 LSPosed Contributors
|
||||
*/
|
||||
|
||||
package org.lsposed.manager.receivers;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.os.IBinder;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import org.lsposed.lspd.ILSPManagerService;
|
||||
import org.lsposed.lspd.models.Application;
|
||||
import org.lsposed.lspd.models.UserInfo;
|
||||
import org.lsposed.lspd.utils.ParceledListSlice;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class LSPManagerServiceClient {
|
||||
|
||||
@SuppressWarnings("FieldMayBeFinal")
|
||||
private static IBinder binder = null;
|
||||
private static ILSPManagerService service = null;
|
||||
|
||||
private static void ensureService() throws NullPointerException {
|
||||
if (service == null) {
|
||||
if (binder != null) {
|
||||
service = ILSPManagerService.Stub.asInterface(binder);
|
||||
} else {
|
||||
throw new NullPointerException("binder is null");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int getXposedApiVersion() throws RemoteException, NullPointerException {
|
||||
ensureService();
|
||||
return service.getXposedApiVersion();
|
||||
}
|
||||
|
||||
public static String getXposedVersionName() throws RemoteException, NullPointerException {
|
||||
ensureService();
|
||||
return service.getXposedVersionName();
|
||||
}
|
||||
|
||||
public static int getXposedVersionCode() throws RemoteException, NullPointerException {
|
||||
ensureService();
|
||||
return service.getXposedVersionCode();
|
||||
}
|
||||
|
||||
|
||||
public static List<PackageInfo> getInstalledPackagesFromAllUsers(int flags, boolean filterNoProcess) throws RemoteException, NullPointerException {
|
||||
ensureService();
|
||||
ParceledListSlice<PackageInfo> parceledListSlice = service.getInstalledPackagesFromAllUsers(flags, filterNoProcess);
|
||||
//
|
||||
return parceledListSlice.getList();
|
||||
}
|
||||
|
||||
public static String[] enabledModules() throws RemoteException, NullPointerException {
|
||||
ensureService();
|
||||
return service.enabledModules();
|
||||
}
|
||||
|
||||
public static boolean enableModule(String packageName) throws RemoteException, NullPointerException {
|
||||
ensureService();
|
||||
return service.enableModule(packageName);
|
||||
}
|
||||
|
||||
public static boolean disableModule(String packageName) throws RemoteException, NullPointerException {
|
||||
ensureService();
|
||||
return service.disableModule(packageName);
|
||||
}
|
||||
|
||||
public static boolean setModuleScope(String packageName, ParceledListSlice<Application> list) throws RemoteException, NullPointerException {
|
||||
ensureService();
|
||||
return service.setModuleScope(packageName, list);
|
||||
}
|
||||
|
||||
public static ParceledListSlice<Application> getModuleScope(String packageName) throws RemoteException, NullPointerException {
|
||||
ensureService();
|
||||
return service.getModuleScope(packageName);
|
||||
}
|
||||
|
||||
public static boolean isResourceHook() throws RemoteException, NullPointerException {
|
||||
ensureService();
|
||||
return service.isResourceHook();
|
||||
}
|
||||
|
||||
public static void setResourceHook(boolean enabled) throws RemoteException, NullPointerException {
|
||||
ensureService();
|
||||
service.setResourceHook(enabled);
|
||||
}
|
||||
|
||||
public static boolean isVerboseLog() throws RemoteException, NullPointerException {
|
||||
ensureService();
|
||||
return service.isVerboseLog();
|
||||
}
|
||||
|
||||
public static void setVerboseLog(boolean enabled) throws RemoteException, NullPointerException {
|
||||
ensureService();
|
||||
service.setVerboseLog(enabled);
|
||||
}
|
||||
|
||||
public static ParcelFileDescriptor getVerboseLog() throws RemoteException, NullPointerException {
|
||||
ensureService();
|
||||
return service.getVerboseLog();
|
||||
}
|
||||
|
||||
public static ParcelFileDescriptor getModulesLog() throws RemoteException, NullPointerException {
|
||||
ensureService();
|
||||
return service.getModulesLog();
|
||||
}
|
||||
|
||||
public static boolean clearLogs(boolean verbose) throws RemoteException, NullPointerException {
|
||||
ensureService();
|
||||
return service.clearLogs(verbose);
|
||||
}
|
||||
|
||||
public static PackageInfo getPackageInfo(String packageName, int flags, int uid) throws RemoteException, NullPointerException {
|
||||
ensureService();
|
||||
return service.getPackageInfo(packageName, flags, uid);
|
||||
}
|
||||
|
||||
public static void forceStopPackage(String packageName, int userId) throws RemoteException, NullPointerException {
|
||||
ensureService();
|
||||
service.forceStopPackage(packageName, userId);
|
||||
}
|
||||
|
||||
public static void reboot(boolean confirm, String reason, boolean wait) throws RemoteException, NullPointerException {
|
||||
ensureService();
|
||||
service.reboot(confirm, reason, wait);
|
||||
}
|
||||
|
||||
public static boolean uninstallPackage(String packageName, int userId) throws RemoteException, NullPointerException {
|
||||
ensureService();
|
||||
return service.uninstallPackage(packageName, userId);
|
||||
}
|
||||
|
||||
public static boolean isSepolicyLoaded() throws RemoteException, NullPointerException {
|
||||
ensureService();
|
||||
return service.isSepolicyLoaded();
|
||||
}
|
||||
|
||||
public static List<UserInfo> getUsers() throws RemoteException, NullPointerException {
|
||||
ensureService();
|
||||
return service.getUsers();
|
||||
}
|
||||
|
||||
public static int installExistingPackageAsUser(String packageName, int userId) throws RemoteException, NullPointerException {
|
||||
ensureService();
|
||||
return service.installExistingPackageAsUser(packageName, userId);
|
||||
}
|
||||
|
||||
public static boolean systemServerRequested() throws RemoteException, NullPointerException {
|
||||
ensureService();
|
||||
return service.systemServerRequested();
|
||||
}
|
||||
|
||||
public static int startActivityAsUserWithFeature(Intent intent, int userId) throws RemoteException, NullPointerException {
|
||||
ensureService();
|
||||
return service.startActivityAsUserWithFeature(intent, userId);
|
||||
}
|
||||
|
||||
public static ParceledListSlice<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, int flags, int userId) throws RemoteException, NullPointerException {
|
||||
ensureService();
|
||||
return service.queryIntentActivitiesAsUser(intent, flags, userId);
|
||||
}
|
||||
|
||||
public static boolean dex2oatFlagsLoaded() throws RemoteException, NullPointerException {
|
||||
ensureService();
|
||||
return service.dex2oatFlagsLoaded();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* This file is part of LSPosed.
|
||||
*
|
||||
* LSPosed is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LSPosed is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LSPosed. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Copyright (C) 2021 LSPosed Contributors
|
||||
*/
|
||||
|
||||
package org.lsposed.manager.receivers;
|
||||
|
||||
import android.os.IBinder;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.system.Os;
|
||||
|
||||
import org.lsposed.lspd.ILSPManagerService;
|
||||
|
||||
public class LSPManagerServiceHolder implements IBinder.DeathRecipient {
|
||||
private static LSPManagerServiceHolder holder = null;
|
||||
private static ILSPManagerService service = null;
|
||||
|
||||
public static void init(IBinder binder) {
|
||||
if (holder == null) {
|
||||
holder = new LSPManagerServiceHolder(binder);
|
||||
}
|
||||
}
|
||||
|
||||
public static ILSPManagerService getService() {
|
||||
return service;
|
||||
}
|
||||
|
||||
private LSPManagerServiceHolder(IBinder binder) {
|
||||
linkToDeath(binder);
|
||||
service = ILSPManagerService.Stub.asInterface(binder);
|
||||
}
|
||||
|
||||
private void linkToDeath(IBinder binder) {
|
||||
try {
|
||||
binder.linkToDeath(this, 0);
|
||||
} catch (RemoteException e) {
|
||||
binderDied();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void binderDied() {
|
||||
System.exit(0);
|
||||
Process.killProcess(Os.getpid());
|
||||
}
|
||||
}
|
||||
|
|
@ -33,6 +33,7 @@ import androidx.navigation.NavController;
|
|||
import androidx.navigation.Navigation;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
|
||||
import org.lsposed.manager.ConfigManager;
|
||||
import org.lsposed.manager.NavGraphDirections;
|
||||
import org.lsposed.manager.R;
|
||||
import org.lsposed.manager.databinding.ActivityMainBinding;
|
||||
|
|
@ -101,12 +102,15 @@ public class MainActivity extends BaseActivity {
|
|||
} else if (!TextUtils.isEmpty(intent.getDataString())) {
|
||||
switch (intent.getDataString()) {
|
||||
case "modules":
|
||||
if (!ConfigManager.isBinderAlive()) break;
|
||||
navController.navigate(R.id.action_modules_fragment);
|
||||
break;
|
||||
case "logs":
|
||||
if (!ConfigManager.isBinderAlive()) break;
|
||||
navController.navigate(R.id.action_logs_fragment);
|
||||
break;
|
||||
case "repo":
|
||||
if (!ConfigManager.isBinderAlive() && !ConfigManager.isMagiskInstalled()) break;
|
||||
navController.navigate(R.id.action_repo_fragment);
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,23 +52,18 @@ public class BaseActivity extends MaterialActivity {
|
|||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (!BuildConfig.DEBUG) {
|
||||
// make sure the versions are consistent
|
||||
String coreVersionStr = ConfigManager.getXposedVersionName();
|
||||
if (coreVersionStr != null) {
|
||||
if (!BuildConfig.VERSION_NAME.equals(coreVersionStr)) {
|
||||
new AlertDialog.Builder(this)
|
||||
.setMessage(R.string.outdated_manager)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, id) -> {
|
||||
NavUtil.startURL(this, getString(R.string.about_source));
|
||||
finish();
|
||||
})
|
||||
.setCancelable(false)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
// make sure the versions are consistent
|
||||
if (BuildConfig.DEBUG) return;
|
||||
if (!ConfigManager.isBinderAlive()) return;
|
||||
if (BuildConfig.VERSION_NAME.equals(ConfigManager.getXposedVersionName())) return;
|
||||
new AlertDialog.Builder(this)
|
||||
.setMessage(R.string.outdated_manager)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, id) -> {
|
||||
NavUtil.startURL(this, getString(R.string.about_source));
|
||||
finish();
|
||||
})
|
||||
.setCancelable(false)
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -81,10 +81,8 @@ public class HomeFragment extends BaseFragment {
|
|||
|
||||
BaseActivity activity = (BaseActivity) requireActivity();
|
||||
binding.status.setOnClickListener(v -> {
|
||||
if (ConfigManager.getXposedApiVersion() != -1) {
|
||||
new InfoDialogBuilder(activity)
|
||||
.setTitle(R.string.info)
|
||||
.show();
|
||||
if (ConfigManager.isBinderAlive()) {
|
||||
new InfoDialogBuilder(activity).setTitle(R.string.info).show();
|
||||
} else {
|
||||
NavUtil.startURL(activity, getString(R.string.about_source));
|
||||
}
|
||||
|
|
@ -112,9 +110,8 @@ public class HomeFragment extends BaseFragment {
|
|||
Glide.with(binding.appIcon)
|
||||
.load(wrap(activity.getApplicationInfo(), getResources().getConfiguration().hashCode()))
|
||||
.into(binding.appIcon);
|
||||
String installXposedVersion = ConfigManager.getXposedVersionName();
|
||||
int cardBackgroundColor;
|
||||
if (installXposedVersion != null) {
|
||||
if (ConfigManager.isBinderAlive()) {
|
||||
if (!ConfigManager.isSepolicyLoaded()) {
|
||||
binding.statusTitle.setText(R.string.partial_activated);
|
||||
cardBackgroundColor = ResourcesKt.resolveColor(activity.getTheme(), R.attr.colorWarning);
|
||||
|
|
@ -139,7 +136,8 @@ public class HomeFragment extends BaseFragment {
|
|||
binding.statusTitle.setText(R.string.activated);
|
||||
cardBackgroundColor = ResourcesKt.resolveColor(activity.getTheme(), R.attr.colorNormal);
|
||||
binding.statusIcon.setImageResource(R.drawable.ic_check_circle);
|
||||
binding.statusSummary.setText(String.format(Locale.US, "%s (%d)", installXposedVersion, ConfigManager.getXposedVersionCode()));
|
||||
binding.statusSummary.setText(String.format(Locale.ROOT, "%s (%d)",
|
||||
ConfigManager.getXposedVersionName(), ConfigManager.getXposedVersionCode()));
|
||||
}
|
||||
} else {
|
||||
cardBackgroundColor = ResourcesKt.resolveColor(activity.getTheme(), R.attr.colorInstall);
|
||||
|
|
@ -171,7 +169,7 @@ public class HomeFragment extends BaseFragment {
|
|||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (requireInstalled && ConfigManager.getXposedVersionName() == null) {
|
||||
if (requireInstalled && !ConfigManager.isBinderAlive()) {
|
||||
Snackbar.make(snackbar, R.string.lsposed_not_active, Snackbar.LENGTH_LONG).show();
|
||||
} else {
|
||||
getNavController().navigate(fragment);
|
||||
|
|
@ -182,7 +180,12 @@ public class HomeFragment extends BaseFragment {
|
|||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
int moduleCount = ModuleUtil.getInstance().getEnabledModulesCount();
|
||||
int moduleCount;
|
||||
if (ConfigManager.isBinderAlive()) {
|
||||
moduleCount = ModuleUtil.getInstance().getEnabledModulesCount();
|
||||
} else {
|
||||
moduleCount = 0;
|
||||
}
|
||||
binding.modulesSummary.setText(getResources().getQuantityString(R.plurals.modules_enabled_count, moduleCount, moduleCount));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ import android.view.View;
|
|||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
|
@ -62,9 +61,9 @@ import java.io.FileOutputStream;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
|
|
@ -80,30 +79,6 @@ public class LogsFragment extends BaseFragment {
|
|||
private final Handler handler = new Handler(Looper.getMainLooper());
|
||||
private FragmentLogsBinding binding;
|
||||
private LinearLayoutManagerFix layoutManager;
|
||||
ActivityResultLauncher<String> saveLogsLauncher = registerForActivityResult(new ActivityResultContracts.CreateDocument(),
|
||||
uri -> {
|
||||
if (uri != null) {
|
||||
try {
|
||||
// grantUriPermission might throw RemoteException on MIUI
|
||||
requireContext().grantUriPermission(BuildConfig.APPLICATION_ID, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
||||
try (var os = requireContext().getContentResolver().openOutputStream(uri)) {
|
||||
ParcelFileDescriptor parcelFileDescriptor = ConfigManager.getLogs(verbose);
|
||||
if (parcelFileDescriptor == null) {
|
||||
return;
|
||||
}
|
||||
try (var is = new FileInputStream(parcelFileDescriptor.getFileDescriptor())) {
|
||||
FileUtils.copy(is, os);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Snackbar.make(binding.snackbar, getResources().getString(R.string.logs_save_failed) + "\n" + e.getMessage(), Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
|
|
@ -242,13 +217,11 @@ public class LogsFragment extends BaseFragment {
|
|||
if (parcelFileDescriptor == null) {
|
||||
return;
|
||||
}
|
||||
Calendar now = Calendar.getInstance();
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String filename = String.format(Locale.US,
|
||||
"LSPosed_%s_%04d%02d%02d_%02d%02d%02d.log",
|
||||
"LSPosed_%s_%s.log",
|
||||
verbose ? "Verbose" : "Modules",
|
||||
now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1,
|
||||
now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY),
|
||||
now.get(Calendar.MINUTE), now.get(Calendar.SECOND));
|
||||
now.toString());
|
||||
File cacheFile = new File(requireActivity().getCacheDir(), filename);
|
||||
try (var os = new FileOutputStream(cacheFile); var is = new FileInputStream(parcelFileDescriptor.getFileDescriptor())) {
|
||||
FileUtils.copy(is, os);
|
||||
|
|
@ -267,13 +240,37 @@ public class LogsFragment extends BaseFragment {
|
|||
}
|
||||
|
||||
private void save() {
|
||||
Calendar now = Calendar.getInstance();
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String filename = String.format(Locale.US,
|
||||
"LSPosed_%s_%04d%02d%02d_%02d%02d%02d.log",
|
||||
"LSPosed_%s_%s.log",
|
||||
verbose ? "Verbose" : "Modules",
|
||||
now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1,
|
||||
now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY),
|
||||
now.get(Calendar.MINUTE), now.get(Calendar.SECOND));
|
||||
now.toString());
|
||||
var contract = new ActivityResultContracts.CreateDocument();
|
||||
var saveLogsLauncher = registerForActivityResult(contract,
|
||||
uri -> {
|
||||
if (uri == null) return;
|
||||
try {
|
||||
// grantUriPermission might throw RemoteException on MIUI
|
||||
requireContext().grantUriPermission(BuildConfig.APPLICATION_ID, uri,
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
||||
try (var os = requireContext().getContentResolver().openOutputStream(uri)) {
|
||||
ParcelFileDescriptor parcelFileDescriptor = ConfigManager.getLogs(verbose);
|
||||
if (parcelFileDescriptor == null) {
|
||||
return;
|
||||
}
|
||||
try (var is = new FileInputStream(parcelFileDescriptor.getFileDescriptor())) {
|
||||
FileUtils.copy(is, os);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Snackbar.make(binding.snackbar, getResources().getString(R.string.logs_save_failed) + "\n" +
|
||||
e.getMessage(), Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
});
|
||||
saveLogsLauncher.launch(filename);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ import android.view.ViewGroup;
|
|||
import android.widget.Filter;
|
||||
import android.widget.Filterable;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
|
@ -42,7 +41,6 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import org.lsposed.manager.App;
|
||||
import org.lsposed.manager.ConfigManager;
|
||||
import org.lsposed.manager.R;
|
||||
import org.lsposed.manager.databinding.FragmentRepoBinding;
|
||||
import org.lsposed.manager.databinding.ItemOnlinemoduleBinding;
|
||||
|
|
@ -106,15 +104,6 @@ public class RepoFragment extends BaseFragment implements RepoLoader.Listener {
|
|||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
if (ConfigManager.getXposedVersionName() == null && !ConfigManager.isMagiskInstalled()) {
|
||||
Toast.makeText(requireActivity(), R.string.lsposed_not_active, Toast.LENGTH_LONG).show();
|
||||
getNavController().navigateUp();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrepareOptionsMenu(Menu menu) {
|
||||
searchView = (SearchView) menu.findItem(R.id.menu_search).getActionView();
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ import org.lsposed.manager.ui.activity.MainActivity;
|
|||
import org.lsposed.manager.util.BackupUtils;
|
||||
import org.lsposed.manager.util.theme.ThemeUtil;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Locale;
|
||||
|
||||
import rikka.core.util.ResourceUtils;
|
||||
|
|
@ -77,14 +77,6 @@ public class SettingsFragment extends BaseFragment {
|
|||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
if (ConfigManager.getXposedVersionName() == null) {
|
||||
Snackbar.make(binding.snackbar, R.string.lsposed_not_active, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
|
|
@ -152,14 +144,14 @@ public class SettingsFragment extends BaseFragment {
|
|||
public void onCreatePreferencesFix(Bundle savedInstanceState, String rootKey) {
|
||||
addPreferencesFromResource(R.xml.prefs);
|
||||
|
||||
boolean installed = ConfigManager.getXposedVersionName() != null;
|
||||
boolean installed = ConfigManager.isBinderAlive();
|
||||
SwitchPreference prefVerboseLogs = findPreference("disable_verbose_log");
|
||||
if (prefVerboseLogs != null) {
|
||||
if (requireActivity().getApplicationInfo().uid / 100000 != 0) {
|
||||
prefVerboseLogs.setVisible(false);
|
||||
} else {
|
||||
prefVerboseLogs.setEnabled(installed);
|
||||
prefVerboseLogs.setChecked(!ConfigManager.isVerboseLogEnabled());
|
||||
prefVerboseLogs.setChecked(!installed || !ConfigManager.isVerboseLogEnabled());
|
||||
prefVerboseLogs.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
boolean result = ConfigManager.setVerboseLogEnabled(!(boolean) newValue);
|
||||
SettingsFragment fragment = (SettingsFragment) getParentFragment();
|
||||
|
|
@ -176,7 +168,7 @@ public class SettingsFragment extends BaseFragment {
|
|||
SwitchPreference prefEnableResources = findPreference("enable_resources");
|
||||
if (prefEnableResources != null) {
|
||||
prefEnableResources.setEnabled(installed);
|
||||
prefEnableResources.setChecked(ConfigManager.isResourceHookEnabled());
|
||||
prefEnableResources.setChecked(installed && ConfigManager.isResourceHookEnabled());
|
||||
prefEnableResources.setOnPreferenceChangeListener((preference, newValue) -> ConfigManager.setResourceHookEnabled((boolean) newValue));
|
||||
}
|
||||
|
||||
|
|
@ -184,12 +176,9 @@ public class SettingsFragment extends BaseFragment {
|
|||
if (backup != null) {
|
||||
backup.setEnabled(installed);
|
||||
backup.setOnPreferenceClickListener(preference -> {
|
||||
Calendar now = Calendar.getInstance();
|
||||
backupLauncher.launch(String.format(Locale.US,
|
||||
"LSPosed_%04d%02d%02d_%02d%02d%02d.lsp",
|
||||
now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1,
|
||||
now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY),
|
||||
now.get(Calendar.MINUTE), now.get(Calendar.SECOND)));
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
backupLauncher.launch(String.format(Locale.ROOT,
|
||||
"LSPosed_%s.lsp", now.toString()));
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,11 +58,11 @@ dependencies {
|
|||
implementation("dev.rikka.ndk:riru:${moduleMinRiruVersionName}")
|
||||
implementation("dev.rikka.ndk.thirdparty:cxx:1.1.0")
|
||||
implementation("io.github.vvb2060.ndk:dobby:1.2")
|
||||
implementation("com.android.tools.build:apksig:7.0.0-beta05")
|
||||
implementation("com.android.tools.build:apksig:7.0.0")
|
||||
implementation("org.apache.commons:commons-lang3:3.12.0")
|
||||
implementation("de.upb.cs.swt:axml:2.1.1")
|
||||
compileOnly(project(":hiddenapi-stubs"))
|
||||
compileOnly("androidx.annotation:annotation:1.2.0")
|
||||
compileOnly(project(":hiddenapi-stubs"))
|
||||
implementation(project(":interface"))
|
||||
implementation(project(":hiddenapi-bridge"))
|
||||
implementation(project(":manager-service"))
|
||||
|
|
|
|||
|
|
@ -83,9 +83,8 @@ public class InstallerVerifier {
|
|||
public static void hookXposedInstaller(final ClassLoader classLoader, IBinder binder) {
|
||||
Utils.logI("Found LSPosed Manager, hooking it");
|
||||
try {
|
||||
Class<?> serviceClass = XposedHelpers.findClass("org.lsposed.manager.receivers.LSPManagerServiceClient", classLoader);
|
||||
XposedHelpers.setStaticObjectField(serviceClass, "binder", binder);
|
||||
|
||||
var clazz = XposedHelpers.findClass("org.lsposed.manager.Constants", classLoader);
|
||||
XposedHelpers.callStaticMethod(clazz, "setBinder", IBinder.class, binder);
|
||||
Utils.logI("Hooked LSPosed Manager");
|
||||
} catch (Throwable t) {
|
||||
Utils.logW("Could not hook LSPosed Manager", t);
|
||||
|
|
|
|||
Loading…
Reference in New Issue