From 4c4427ca523c0a7d47874658e0304c7a60571ce7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=97=E5=AE=AB=E9=9B=AA=E7=8F=8A?= Date: Tue, 12 Oct 2021 15:49:17 +0800 Subject: [PATCH] In-app update (#1244) --- app/build.gradle.kts | 8 +- .../main/java/org/lsposed/manager/App.java | 63 +--------- .../org/lsposed/manager/ConfigManager.java | 8 ++ .../manager/ui/dialog/FlashDialogBuilder.java | 108 ++++++++++++++++ .../manager/ui/dialog/InfoDialogBuilder.java | 8 +- .../manager/ui/fragment/AppListFragment.java | 2 +- .../manager/ui/fragment/HomeFragment.java | 21 +++- .../org/lsposed/manager/util/UpdateUtil.java | 115 ++++++++++++++++++ app/src/main/res/values-zh-rCN/strings.xml | 2 + app/src/main/res/values/strings.xml | 2 + core/build.gradle.kts | 21 +++- .../lspd/service/LSPManagerService.java | 31 +++++ .../org/lsposed/lspd/ILSPManagerService.aidl | 2 + 13 files changed, 314 insertions(+), 77 deletions(-) create mode 100644 app/src/main/java/org/lsposed/manager/ui/dialog/FlashDialogBuilder.java create mode 100644 app/src/main/java/org/lsposed/manager/util/UpdateUtil.java 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; }