In-app update (#1244)
This commit is contained in:
parent
7d1a317120
commit
4c4427ca52
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
"<b><a href=\"https://github.com/LSPosed/LSPosed\">GitHub</a></b>",
|
||||
"<b><a href=\"https://t.me/LSPosed\">Telegram</a></b>"), 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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -59,6 +59,8 @@
|
|||
<string name="create_shortcut">创建快捷方式</string>
|
||||
<string name="never_show">不再显示</string>
|
||||
<string name="failed_to_create_shortcut">创建快捷方式失败:%1$s</string>
|
||||
<string name="update_lsposed">更新 LSPosed</string>
|
||||
<string name="update_lsposed_msg">是否安装新版 LSPosed?完成后设备将自动重启</string>
|
||||
<!-- LogsActivity -->
|
||||
<string name="menuSaveToSd">保存</string>
|
||||
<string name="nav_item_logs_lsp">LSPosed 日志</string>
|
||||
|
|
|
|||
|
|
@ -61,6 +61,8 @@
|
|||
<string name="create_shortcut">Create shortcut</string>
|
||||
<string name="never_show">Never show</string>
|
||||
<string name="failed_to_create_shortcut">Failed to create shortcut: %1$s</string>
|
||||
<string name="update_lsposed">Update LSPosed</string>
|
||||
<string name="update_lsposed_msg">Want to install a new version of LSPosed? The device will reboot after completion</string>
|
||||
|
||||
<!-- LogsActivity -->
|
||||
<string name="menuSaveToSd">Save</string>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,4 +71,6 @@ interface ILSPManagerService {
|
|||
boolean isAddShortcut() = 37;
|
||||
|
||||
void setAddShortcut(boolean enabled) = 38;
|
||||
|
||||
oneway void flashZip(String zipPath, in ParcelFileDescriptor outputStream) = 39;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue