diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index f15f6c58..df3862cc 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -161,7 +161,6 @@ tasks.whenTaskAdded {
dependencies {
val glideVersion = "4.12.0"
- val okhttpVersion = "4.9.1"
val navVersion: String by rootProject.extra
annotationProcessor("com.github.bumptech.glide:compiler:$glideVersion")
implementation("androidx.activity:activity:1.3.1")
@@ -178,9 +177,10 @@ dependencies {
implementation("com.google.android.material:material:1.5.0-alpha04")
implementation("com.google.code.gson:gson:2.8.8")
implementation("com.takisoft.preferencex:preferencex:1.1.0")
- implementation("com.squareup.okhttp3:okhttp:$okhttpVersion")
- implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:$okhttpVersion")
- implementation("com.squareup.okhttp3:logging-interceptor:$okhttpVersion")
+ implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.2"))
+ implementation("com.squareup.okhttp3:okhttp")
+ implementation("com.squareup.okhttp3:okhttp-dnsoverhttps")
+ implementation("com.squareup.okhttp3:logging-interceptor")
implementation("dev.rikka.rikkax.appcompat:appcompat:1.2.0-rc01")
implementation("dev.rikka.rikkax.core:core:1.3.2")
implementation("dev.rikka.rikkax.insets:insets:1.1.0")
diff --git a/app/src/main/java/org/lsposed/manager/App.java b/app/src/main/java/org/lsposed/manager/App.java
index 289c9aae..99e8c1b9 100644
--- a/app/src/main/java/org/lsposed/manager/App.java
+++ b/app/src/main/java/org/lsposed/manager/App.java
@@ -35,14 +35,13 @@ import android.util.Log;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceManager;
-import com.google.gson.JsonParser;
-
import org.lsposed.hiddenapibypass.HiddenApiBypass;
import org.lsposed.manager.repo.RepoLoader;
import org.lsposed.manager.ui.activity.CrashReportActivity;
import org.lsposed.manager.util.DoHDNS;
import org.lsposed.manager.util.ModuleUtil;
import org.lsposed.manager.util.ThemeUtil;
+import org.lsposed.manager.util.UpdateUtil;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -50,19 +49,13 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
-import java.time.Instant;
-import java.time.ZoneOffset;
import java.util.Locale;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import okhttp3.Cache;
-import okhttp3.Call;
-import okhttp3.Callback;
import okhttp3.OkHttpClient;
-import okhttp3.Request;
-import okhttp3.Response;
import okhttp3.logging.HttpLoggingInterceptor;
import rikka.material.app.DayNightDelegate;
import rikka.material.app.LocaleDelegate;
@@ -183,7 +176,7 @@ public class App extends Application {
}
}, new IntentFilter(Intent.ACTION_PACKAGE_CHANGED));
- loadRemoteVersion();
+ UpdateUtil.loadRemoteVersion();
RepoLoader.getInstance().loadRemoteData();
executorService.submit(HTML_TEMPLATE);
@@ -215,58 +208,6 @@ public class App extends Application {
return okHttpCache;
}
- private void loadRemoteVersion() {
- var request = new Request.Builder()
- .url("https://api.github.com/repos/LSPosed/LSPosed/releases/latest")
- .addHeader("Accept", "application/vnd.github.v3+json")
- .build();
- var callback = new Callback() {
- @Override
- public void onResponse(@NonNull Call call, @NonNull Response response) {
- if (!response.isSuccessful()) return;
- var body = response.body();
- if (body == null) return;
- try {
- var info = JsonParser.parseReader(body.charStream()).getAsJsonObject();
- var name = info.getAsJsonArray("assets").get(0).getAsJsonObject().get("name").getAsString();
- var code = Integer.parseInt(name.split("-", 4)[2]);
- var now = Instant.now().getEpochSecond();
- pref.edit()
- .putInt("latest_version", code)
- .putLong("latest_check", now)
- .putBoolean("checked", true)
- .apply();
- } catch (Throwable t) {
- Log.e(App.TAG, t.getMessage(), t);
- }
- }
-
- @Override
- public void onFailure(@NonNull Call call, @NonNull IOException e) {
- Log.e(App.TAG, "loadRemoteVersion: " + e.getMessage());
- if (pref.getBoolean("checked", false)) return;
- pref.edit().putBoolean("checked", true).apply();
- }
- };
- getOkHttpClient().newCall(request).enqueue(callback);
- }
-
- public static boolean needUpdate() {
- var pref = getPreferences();
- if (!pref.getBoolean("checked", false)) return false;
- var now = Instant.now();
- var buildTime = Instant.ofEpochSecond(BuildConfig.BUILD_TIME);
- var check = pref.getLong("latest_check", 0);
- if (check > 0) {
- var checkTime = Instant.ofEpochSecond(check);
- if (checkTime.atOffset(ZoneOffset.UTC).plusDays(30).toInstant().isBefore(now))
- return true;
- var code = pref.getInt("latest_version", 0);
- return code > BuildConfig.VERSION_CODE;
- }
- return buildTime.atOffset(ZoneOffset.UTC).plusDays(30).toInstant().isBefore(now);
- }
-
public static Locale getLocale() {
String tag = getPreferences().getString("language", null);
if (TextUtils.isEmpty(tag) || "SYSTEM".equals(tag)) {
diff --git a/app/src/main/java/org/lsposed/manager/ConfigManager.java b/app/src/main/java/org/lsposed/manager/ConfigManager.java
index 99ac291b..8822e092 100644
--- a/app/src/main/java/org/lsposed/manager/ConfigManager.java
+++ b/app/src/main/java/org/lsposed/manager/ConfigManager.java
@@ -341,4 +341,12 @@ public class ConfigManager {
return new HashMap<>();
}
}
+
+ public static void flashZip(String zipPath, ParcelFileDescriptor outputStream) {
+ try {
+ LSPManagerServiceHolder.getService().flashZip(zipPath, outputStream);
+ } catch (RemoteException e) {
+ Log.e(App.TAG, Log.getStackTraceString(e));
+ }
+ }
}
diff --git a/app/src/main/java/org/lsposed/manager/ui/dialog/FlashDialogBuilder.java b/app/src/main/java/org/lsposed/manager/ui/dialog/FlashDialogBuilder.java
new file mode 100644
index 00000000..9811a7b8
--- /dev/null
+++ b/app/src/main/java/org/lsposed/manager/ui/dialog/FlashDialogBuilder.java
@@ -0,0 +1,108 @@
+package org.lsposed.manager.ui.dialog;
+
+import static org.lsposed.manager.App.TAG;
+
+import android.content.Context;
+import android.graphics.Typeface;
+import android.os.ParcelFileDescriptor;
+import android.text.method.LinkMovementMethod;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
+
+import com.google.android.material.textview.MaterialTextView;
+
+import org.lsposed.manager.App;
+import org.lsposed.manager.ConfigManager;
+import org.lsposed.manager.R;
+import org.lsposed.manager.databinding.DialogWarningBinding;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+import rikka.widget.borderview.BorderNestedScrollView;
+
+public class FlashDialogBuilder extends BlurBehindDialogBuilder {
+ private final String zipPath;
+ private final TextView textView;
+ private final BorderNestedScrollView rootView;
+
+ public FlashDialogBuilder(@NonNull Context context, @NonNull String zipPath) {
+ super(context);
+ this.zipPath = zipPath;
+ setTitle(R.string.update_lsposed);
+
+ textView = new MaterialTextView(context);
+ textView.setText(R.string.update_lsposed_msg);
+ textView.setMovementMethod(LinkMovementMethod.getInstance());
+
+ LayoutInflater inflater = LayoutInflater.from(context);
+ DialogWarningBinding binding = DialogWarningBinding.inflate(inflater, null, false);
+ binding.container.addView(textView);
+ rootView = binding.getRoot();
+ setView(rootView);
+
+ setNegativeButton(android.R.string.cancel, null);
+ setPositiveButton(R.string.install, null);
+ setCancelable(false);
+ }
+
+ @Override
+ public AlertDialog show() {
+ var dialog = super.show();
+ var button = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
+ button.setOnClickListener((v) -> setFlashView(v, dialog));
+ return dialog;
+ }
+
+ private void setFlashView(View view, AlertDialog dialog) {
+ var positiveButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
+ var negativeButton = dialog.getButton(AlertDialog.BUTTON_NEGATIVE);
+ positiveButton.setEnabled(false);
+ positiveButton.setText(android.R.string.ok);
+ positiveButton.setOnClickListener((v) -> dialog.dismiss());
+ negativeButton.setVisibility(View.GONE);
+
+ textView.setText("");
+ textView.setTypeface(Typeface.MONOSPACE);
+ App.getExecutorService().submit(() -> flash(view, positiveButton));
+ }
+
+ private void flash(View view, Button button) {
+ try {
+ var pipe = ParcelFileDescriptor.createReliablePipe();
+ var readSide = pipe[0];
+ var writeSide = pipe[1];
+
+ ConfigManager.flashZip(zipPath, writeSide);
+ writeSide.close();
+
+ var inputStream = new ParcelFileDescriptor.AutoCloseInputStream(readSide);
+ var reader = new BufferedReader(new InputStreamReader(inputStream));
+
+ for (var line = ""; line != null; line = reader.readLine()) {
+ if (line.length() > 0) {
+ var showLine = line + "\n";
+ view.post(() -> {
+ textView.append(showLine);
+ rootView.fullScroll(View.FOCUS_DOWN);
+ });
+ }
+ }
+
+ reader.close();
+ } catch (IOException e) {
+ Log.e(TAG, "flash", e);
+ view.post(() -> textView.append("\n\n" + e.getMessage()));
+ rootView.fullScroll(View.FOCUS_DOWN);
+ }
+
+ view.post(() -> button.setEnabled(true));
+ }
+}
diff --git a/app/src/main/java/org/lsposed/manager/ui/dialog/InfoDialogBuilder.java b/app/src/main/java/org/lsposed/manager/ui/dialog/InfoDialogBuilder.java
index 85f4ddf9..db11334b 100644
--- a/app/src/main/java/org/lsposed/manager/ui/dialog/InfoDialogBuilder.java
+++ b/app/src/main/java/org/lsposed/manager/ui/dialog/InfoDialogBuilder.java
@@ -43,17 +43,17 @@ public class InfoDialogBuilder extends BlurBehindDialogBuilder {
if (ConfigManager.isBinderAlive()) {
binding.apiVersion.setText(String.valueOf(ConfigManager.getXposedApiVersion()));
- binding.frameworkVersion.setText(String.format(Locale.US, "%s (%s)", ConfigManager.getXposedVersionName(), ConfigManager.getXposedVersionCode()));
+ binding.frameworkVersion.setText(String.format(Locale.ROOT, "%s (%s)", ConfigManager.getXposedVersionName(), ConfigManager.getXposedVersionCode()));
} else {
binding.apiVersion.setText(R.string.not_installed);
binding.frameworkVersion.setText(R.string.not_installed);
}
- binding.managerVersion.setText(String.format(Locale.US, "%s (%s)", BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE));
+ binding.managerVersion.setText(String.format(Locale.ROOT, "%s (%s)", BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE));
if (Build.VERSION.PREVIEW_SDK_INT != 0) {
- binding.systemVersion.setText(String.format(Locale.US, "%1$s Preview (API %2$d)", Build.VERSION.CODENAME, Build.VERSION.SDK_INT));
+ binding.systemVersion.setText(String.format(Locale.ROOT, "%1$s Preview (API %2$d)", Build.VERSION.CODENAME, Build.VERSION.SDK_INT));
} else {
- binding.systemVersion.setText(String.format(Locale.US, "%1$s (API %2$d)", Build.VERSION.RELEASE, Build.VERSION.SDK_INT));
+ binding.systemVersion.setText(String.format(Locale.ROOT, "%1$s (API %2$d)", Build.VERSION.RELEASE, Build.VERSION.SDK_INT));
}
binding.device.setText(getDevice());
diff --git a/app/src/main/java/org/lsposed/manager/ui/fragment/AppListFragment.java b/app/src/main/java/org/lsposed/manager/ui/fragment/AppListFragment.java
index 6e4b1d2e..ec4f0b66 100644
--- a/app/src/main/java/org/lsposed/manager/ui/fragment/AppListFragment.java
+++ b/app/src/main/java/org/lsposed/manager/ui/fragment/AppListFragment.java
@@ -70,7 +70,7 @@ public class AppListFragment extends BaseFragment {
binding.appBar.setLifted(true);
String title;
if (module.userId != 0) {
- title = String.format(Locale.US, "%s (%d)", module.getAppName(), module.userId);
+ title = String.format(Locale.ROOT, "%s (%d)", module.getAppName(), module.userId);
} else {
title = module.getAppName();
}
diff --git a/app/src/main/java/org/lsposed/manager/ui/fragment/HomeFragment.java b/app/src/main/java/org/lsposed/manager/ui/fragment/HomeFragment.java
index a87671c3..981769fb 100644
--- a/app/src/main/java/org/lsposed/manager/ui/fragment/HomeFragment.java
+++ b/app/src/main/java/org/lsposed/manager/ui/fragment/HomeFragment.java
@@ -43,10 +43,12 @@ import org.lsposed.manager.databinding.DialogAboutBinding;
import org.lsposed.manager.databinding.FragmentHomeBinding;
import org.lsposed.manager.receivers.LSPManagerServiceHolder;
import org.lsposed.manager.ui.dialog.BlurBehindDialogBuilder;
+import org.lsposed.manager.ui.dialog.FlashDialogBuilder;
import org.lsposed.manager.ui.dialog.InfoDialogBuilder;
import org.lsposed.manager.ui.dialog.WarningDialogBuilder;
import org.lsposed.manager.util.ModuleUtil;
import org.lsposed.manager.util.NavUtil;
+import org.lsposed.manager.util.UpdateUtil;
import org.lsposed.manager.util.chrome.LinkTransformationMethod;
import java.util.Locale;
@@ -92,13 +94,20 @@ public class HomeFragment extends BaseFragment {
Activity activity = requireActivity();
binding.status.setOnClickListener(v -> {
- if (ConfigManager.isBinderAlive() && !App.needUpdate()) {
+ if (ConfigManager.isBinderAlive() && !UpdateUtil.needUpdate()) {
if (!ConfigManager.isSepolicyLoaded() || !ConfigManager.systemServerRequested() || !ConfigManager.dex2oatFlagsLoaded()) {
new WarningDialogBuilder(activity).show();
} else {
- new InfoDialogBuilder(activity).setTitle(R.string.info).show();
+ new InfoDialogBuilder(activity).show();
}
} else {
+ if (UpdateUtil.canUpdate()) {
+ var zip = App.getPreferences().getString("zip_file", null);
+ if (zip != null) {
+ new FlashDialogBuilder(activity, zip).show();
+ return;
+ }
+ }
NavUtil.startURL(activity, getString(R.string.about_source));
}
});
@@ -108,7 +117,7 @@ public class HomeFragment extends BaseFragment {
binding.settings.setOnClickListener(new StartFragmentListener(R.id.action_settings_fragment, false));
binding.issue.setOnClickListener(view -> NavUtil.startURL(activity, "https://github.com/LSPosed/LSPosed/issues"));
- updateStates(requireActivity(), ConfigManager.isBinderAlive(), App.needUpdate());
+ updateStates(requireActivity(), ConfigManager.isBinderAlive(), UpdateUtil.needUpdate());
return binding.getRoot();
}
@@ -116,7 +125,7 @@ public class HomeFragment extends BaseFragment {
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
int itemId = item.getItemId();
if (itemId == R.id.menu_refresh) {
- updateStates(requireActivity(), ConfigManager.isBinderAlive(), App.needUpdate());
+ updateStates(requireActivity(), ConfigManager.isBinderAlive(), UpdateUtil.needUpdate());
} else if (itemId == R.id.menu_info) {
new InfoDialogBuilder(requireActivity()).setTitle(R.string.info).show();
} else if (itemId == R.id.menu_about) {
@@ -129,7 +138,7 @@ public class HomeFragment extends BaseFragment {
R.string.about_view_source_code,
"GitHub",
"Telegram"), HtmlCompat.FROM_HTML_MODE_LEGACY));
- binding.designAboutVersion.setText(String.format(Locale.US, "%s (%s)", BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE));
+ binding.designAboutVersion.setText(String.format(Locale.ROOT, "%s (%s)", BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE));
new BlurBehindDialogBuilder(activity)
.setView(binding.getRoot())
.show();
@@ -140,7 +149,7 @@ public class HomeFragment extends BaseFragment {
private void updateStates(Activity activity, boolean binderAlive, boolean needUpdate) {
int cardBackgroundColor;
if (binderAlive) {
- StringBuilder sb = new StringBuilder(String.format(Locale.US, "%s (%d)",
+ StringBuilder sb = new StringBuilder(String.format(Locale.ROOT, "%s (%d)",
ConfigManager.getXposedVersionName(), ConfigManager.getXposedVersionCode()));
if (needUpdate) {
cardBackgroundColor = ResourceUtils.resolveColor(activity.getTheme(), R.attr.colorInstall);
diff --git a/app/src/main/java/org/lsposed/manager/util/UpdateUtil.java b/app/src/main/java/org/lsposed/manager/util/UpdateUtil.java
new file mode 100644
index 00000000..32c5c993
--- /dev/null
+++ b/app/src/main/java/org/lsposed/manager/util/UpdateUtil.java
@@ -0,0 +1,115 @@
+package org.lsposed.manager.util;
+
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.google.gson.JsonParser;
+
+import org.lsposed.manager.App;
+import org.lsposed.manager.BuildConfig;
+
+import java.io.File;
+import java.io.IOException;
+import java.time.Instant;
+import java.time.ZoneOffset;
+
+import okhttp3.Call;
+import okhttp3.Callback;
+import okhttp3.Request;
+import okhttp3.Response;
+import okio.Okio;
+
+public class UpdateUtil {
+ public static void loadRemoteVersion() {
+ var pref = App.getPreferences();
+ var request = new Request.Builder()
+ .url("https://api.github.com/repos/LSPosed/LSPosed/releases/latest")
+ .addHeader("Accept", "application/vnd.github.v3+json")
+ .build();
+ var callback = new Callback() {
+ @Override
+ public void onResponse(@NonNull Call call, @NonNull Response response) {
+ if (!response.isSuccessful()) return;
+ var body = response.body();
+ if (body == null) return;
+ try {
+ var info = JsonParser.parseReader(body.charStream()).getAsJsonObject();
+ var assets = info.getAsJsonArray("assets").get(0).getAsJsonObject();
+ var name = assets.get("name").getAsString();
+ var code = Integer.parseInt(name.split("-", 4)[2]);
+ var now = Instant.now().getEpochSecond();
+ pref.edit()
+ .putInt("latest_version", code)
+ .putLong("latest_check", now)
+ .putBoolean("checked", true)
+ .apply();
+ var updatedAt = Instant.parse(assets.get("updated_at").getAsString());
+ var downloadUrl = assets.get("browser_download_url").getAsString();
+ var nowZipTime = pref.getLong("zip_time", BuildConfig.BUILD_TIME);
+ if (updatedAt.isAfter(Instant.ofEpochSecond(nowZipTime))) {
+ var zip = downloadNewZipSync(downloadUrl, name);
+ var size = assets.get("size").getAsLong();
+ if (zip != null && zip.length() == size) {
+ pref.edit()
+ .putLong("zip_time", updatedAt.getEpochSecond())
+ .putString("zip_file", zip.getAbsolutePath())
+ .apply();
+ }
+ }
+ } catch (Throwable t) {
+ Log.e(App.TAG, t.getMessage(), t);
+ }
+ }
+
+ @Override
+ public void onFailure(@NonNull Call call, @NonNull IOException e) {
+ Log.e(App.TAG, "loadRemoteVersion: " + e.getMessage());
+ if (pref.getBoolean("checked", false)) return;
+ pref.edit().putBoolean("checked", true).apply();
+ }
+ };
+ App.getOkHttpClient().newCall(request).enqueue(callback);
+ }
+
+ public static boolean needUpdate() {
+ var pref = App.getPreferences();
+ if (!pref.getBoolean("checked", false)) return false;
+ var now = Instant.now();
+ var buildTime = Instant.ofEpochSecond(BuildConfig.BUILD_TIME);
+ var check = pref.getLong("latest_check", 0);
+ if (check > 0) {
+ var checkTime = Instant.ofEpochSecond(check);
+ if (checkTime.atOffset(ZoneOffset.UTC).plusDays(30).toInstant().isBefore(now))
+ return true;
+ var code = pref.getInt("latest_version", 0);
+ return code > BuildConfig.VERSION_CODE;
+ }
+ return buildTime.atOffset(ZoneOffset.UTC).plusDays(30).toInstant().isBefore(now);
+ }
+
+ @Nullable
+ private static File downloadNewZipSync(String url, String name) {
+ var request = new Request.Builder().url(url).build();
+ var zip = new File(App.getInstance().getCacheDir(), name);
+ try (Response response = App.getOkHttpClient().newCall(request).execute()) {
+ var body = response.body();
+ if (!response.isSuccessful() || body == null) return null;
+ try (var source = body.source();
+ var sink = Okio.buffer(Okio.sink(zip))) {
+ sink.writeAll(source);
+ }
+ } catch (IOException e) {
+ Log.e(App.TAG, "downloadNewZipSync: " + e.getMessage());
+ return null;
+ }
+ return zip;
+ }
+
+ public static boolean canUpdate() {
+ var pref = App.getPreferences();
+ var zipTime = pref.getLong("zip_time", BuildConfig.BUILD_TIME);
+ return zipTime > BuildConfig.BUILD_TIME;
+ }
+}
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 024ad190..f7f9eb11 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -59,6 +59,8 @@
创建快捷方式
不再显示
创建快捷方式失败:%1$s
+ 更新 LSPosed
+ 是否安装新版 LSPosed?完成后设备将自动重启
保存
LSPosed 日志
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index b41d6dc1..c0de4156 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -61,6 +61,8 @@
Create shortcut
Never show
Failed to create shortcut: %1$s
+ Update LSPosed
+ Want to install a new version of LSPosed? The device will reboot after completion
Save
diff --git a/core/build.gradle.kts b/core/build.gradle.kts
index 92c44716..2a2b3e2d 100644
--- a/core/build.gradle.kts
+++ b/core/build.gradle.kts
@@ -309,13 +309,32 @@ val pushLspdNative = task("pushLspdNative", Exec::class) {
}
commandLine(adb, "push", "libdaemon.so", "/data/local/tmp/libdaemon.so")
}
-task("reRunLspd", Exec::class) {
+val reRunLspd = task("reRunLspd", Exec::class) {
dependsOn(pushLspd)
dependsOn(pushLspdNative)
dependsOn(killLspd)
commandLine(adb, "shell", "su", "-c", "sh /data/adb/modules/riru_lsposed/service.sh&")
isIgnoreExitValue = true
}
+val tmpApk = "/data/local/tmp/lsp.apk"
+val pushApk = task("pushApk", Exec::class) {
+ dependsOn(":app:assembleDebug")
+ workingDir("${project(":app").buildDir}/outputs/apk/debug")
+ commandLine(adb, "push", "LSPosedManager-v$verName-$verCode-debug.apk", tmpApk)
+}
+val openApp = task("openApp", Exec::class) {
+ commandLine(
+ adb, "shell", "am start -a android.intent.action.MAIN " +
+ "-c org.lsposed.manager.LAUNCH_MANAGER " +
+ "com.android.shell/.BugreportWarningActivity"
+ )
+}
+task("reRunApp", Exec::class) {
+ dependsOn(pushApk)
+ commandLine(adb, "shell", "su", "-c", "mv -f $tmpApk /data/adb/lspd/manager.apk")
+ isIgnoreExitValue = true
+ finalizedBy(reRunLspd)
+}
val generateVersion = task("generateVersion", Copy::class) {
inputs.property("VERSION_CODE", verCode)
diff --git a/core/src/main/java/org/lsposed/lspd/service/LSPManagerService.java b/core/src/main/java/org/lsposed/lspd/service/LSPManagerService.java
index de05e06c..be4911b5 100644
--- a/core/src/main/java/org/lsposed/lspd/service/LSPManagerService.java
+++ b/core/src/main/java/org/lsposed/lspd/service/LSPManagerService.java
@@ -22,6 +22,7 @@ package org.lsposed.lspd.service;
import static android.content.Context.BIND_AUTO_CREATE;
import static org.lsposed.lspd.service.ServiceManager.TAG;
+import android.annotation.SuppressLint;
import android.app.INotificationManager;
import android.app.IServiceConnection;
import android.app.Notification;
@@ -65,12 +66,15 @@ import org.lsposed.lspd.util.FakeContext;
import org.lsposed.lspd.util.Utils;
import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
+import java.util.concurrent.TimeUnit;
import de.robv.android.xposed.XposedBridge;
import hidden.HiddenApiBridge;
@@ -254,6 +258,7 @@ public class LSPManagerService extends ILSPManagerService.Stub {
}
}
+ @SuppressLint("WrongConstant")
public static void broadcastIntent(String modulePackageName, int moduleUserId, boolean packageFullyRemoved) {
Intent intent = new Intent(Intent.ACTION_PACKAGE_CHANGED);
intent.addFlags(0x01000000); //Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
@@ -713,4 +718,30 @@ public class LSPManagerService extends ILSPManagerService.Stub {
createOrUpdateShortcut(true);
setAddShortcut(true);
}
+
+ @Override
+ public void flashZip(String zipPath, ParcelFileDescriptor outputStream) {
+ var processBuilder = new ProcessBuilder("magisk", "--install-module", zipPath);
+ var fd = new File("/proc/self/fd/" + outputStream.getFd());
+ processBuilder.redirectOutput(ProcessBuilder.Redirect.appendTo(fd));
+ try (outputStream; var fdw = new FileOutputStream(fd, true)) {
+ var proc = processBuilder.start();
+ if (proc.waitFor(10, TimeUnit.SECONDS)) {
+ var exit = proc.exitValue();
+ if (exit == 0) {
+ fdw.write("- Reboot after 5s\n".getBytes());
+ Thread.sleep(5000);
+ reboot(false);
+ } else {
+ var s = "! Flash failed, exit with " + exit + "\n";
+ fdw.write(s.getBytes());
+ }
+ } else {
+ proc.destroy();
+ fdw.write("! Timeout, abort\n".getBytes());
+ }
+ } catch (IOException | InterruptedException e) {
+ Log.e(TAG, "flashZip: ", e);
+ }
+ }
}
diff --git a/manager-service/src/main/aidl/org/lsposed/lspd/ILSPManagerService.aidl b/manager-service/src/main/aidl/org/lsposed/lspd/ILSPManagerService.aidl
index c24d5c41..65669d29 100644
--- a/manager-service/src/main/aidl/org/lsposed/lspd/ILSPManagerService.aidl
+++ b/manager-service/src/main/aidl/org/lsposed/lspd/ILSPManagerService.aidl
@@ -71,4 +71,6 @@ interface ILSPManagerService {
boolean isAddShortcut() = 37;
void setAddShortcut(boolean enabled) = 38;
+
+ oneway void flashZip(String zipPath, in ParcelFileDescriptor outputStream) = 39;
}