diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 2a1107f8..d85eac45 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -21,6 +21,7 @@ import com.android.build.api.variant.impl.ApplicationVariantImpl
import com.android.build.api.component.analytics.AnalyticsEnabledApplicationVariant
import com.android.build.gradle.internal.dsl.BuildType
import java.nio.file.Paths
+import java.time.Instant
plugins {
id("org.gradle.idea")
@@ -88,6 +89,7 @@ android {
"pt",
"es",
)
+ buildConfigField("long", "BUILD_TIME", Instant.now().epochSecond.toString())
}
compileOptions {
diff --git a/app/src/main/java/org/lsposed/manager/App.java b/app/src/main/java/org/lsposed/manager/App.java
index 78e59603..91e09c2f 100644
--- a/app/src/main/java/org/lsposed/manager/App.java
+++ b/app/src/main/java/org/lsposed/manager/App.java
@@ -30,6 +30,8 @@ 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;
@@ -37,13 +39,20 @@ import org.lsposed.manager.util.DoHDNS;
import org.lsposed.manager.util.theme.ThemeUtil;
import java.io.File;
+import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
import java.util.Locale;
import okhttp3.Cache;
-import okhttp3.MediaType;
+import okhttp3.Call;
+import okhttp3.Callback;
import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
import okhttp3.logging.HttpLoggingInterceptor;
import rikka.material.app.DayNightDelegate;
@@ -63,9 +72,6 @@ public class App extends Application {
private static Cache okHttpCache;
private SharedPreferences pref;
- public static final MediaType JSON
- = MediaType.get("application/json; charset=utf-8");
-
public static App getInstance() {
return instance;
@@ -117,6 +123,7 @@ public class App extends Application {
DayNightDelegate.setApplicationContext(this);
DayNightDelegate.setDefaultNightMode(ThemeUtil.getDarkTheme());
RepoLoader.getInstance().loadRemoteData();
+ loadRemoteVersion();
}
@NonNull
@@ -144,4 +151,51 @@ 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 published = info.get("published_at").getAsString();
+ var time = LocalDateTime.parse(published).toInstant(ZoneOffset.UTC).getEpochSecond();
+ var now = Instant.now().getEpochSecond();
+ pref.edit().putLong("latest_release", time).putLong("latest_check", now).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, e.getMessage(), e);
+ }
+ };
+ getOkHttpClient().newCall(request).enqueue(callback);
+ }
+
+ public static boolean needUpdate() {
+ var now = Instant.now();
+ var buildTime = Instant.ofEpochSecond(BuildConfig.BUILD_TIME);
+ var check = getPreferences().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 release = getPreferences().getLong("latest_release", 0);
+ if (release > 0) {
+ var releaseTime = Instant.ofEpochSecond(release);
+ return releaseTime.atOffset(ZoneOffset.UTC).minusDays(1).toInstant().isAfter(buildTime);
+ }
+ }
+ return buildTime.atOffset(ZoneOffset.UTC).plusDays(30).toInstant().isBefore(now);
+ }
}
diff --git a/app/src/main/java/org/lsposed/manager/repo/RepoLoader.java b/app/src/main/java/org/lsposed/manager/repo/RepoLoader.java
index 52bbc760..5fa2d857 100644
--- a/app/src/main/java/org/lsposed/manager/repo/RepoLoader.java
+++ b/app/src/main/java/org/lsposed/manager/repo/RepoLoader.java
@@ -178,9 +178,11 @@ public class RepoLoader {
}
public interface Listener {
- void repoLoaded();
+ default void repoLoaded() {
+ }
- void moduleReleasesLoaded(OnlineModule module);
+ default void moduleReleasesLoaded(OnlineModule module) {
+ }
void onThrowable(Throwable t);
}
diff --git a/app/src/main/java/org/lsposed/manager/ui/activity/CrashReportActivity.java b/app/src/main/java/org/lsposed/manager/ui/activity/CrashReportActivity.java
index 87be5fea..cb6ac237 100644
--- a/app/src/main/java/org/lsposed/manager/ui/activity/CrashReportActivity.java
+++ b/app/src/main/java/org/lsposed/manager/ui/activity/CrashReportActivity.java
@@ -23,7 +23,6 @@ package org.lsposed.manager.ui.activity;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Intent;
-import android.content.pm.PackageInfo;
import android.os.Build;
import android.os.Bundle;
@@ -51,7 +50,7 @@ public class CrashReportActivity extends AppCompatActivity {
binding = ActivityCrashReportBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
binding.copyLogs.setOnClickListener(v -> {
- ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
+ var clipboard = getSystemService(ClipboardManager.class);
//Are there any devices without clipboard...?
if (clipboard != null) {
ClipData clip = ClipData.newPlainText("LSPManagerCrashInfo", getAllErrorDetailsFromIntent(getIntent()));
@@ -66,26 +65,29 @@ public class CrashReportActivity extends AppCompatActivity {
Date currentDate = new Date();
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US);
- String versionName = getVersionName();
+ String versionName = String.format("%s (%s)", BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE);
String errorDetails = "";
errorDetails += "Build version: " + versionName + " \n";
errorDetails += "Current date: " + dateFormat.format(currentDate) + " \n";
- errorDetails += "Device: " + getDeviceModelName() + " \n \n";
+ errorDetails += "Device: " + getDeviceModelName() + " \n";
+ errorDetails += "Fingerprint: " + getFingerprint() + " \n \n";
errorDetails += "SDK: " + Build.VERSION.SDK_INT + " \n \n";
errorDetails += "Stack trace: \n";
errorDetails += getStackTraceFromIntent(intent);
return errorDetails;
}
- private String getVersionName() {
- try {
- PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
- return String.format("%s (%s)", packageInfo.versionName, packageInfo.versionCode);
- } catch (Exception e) {
- return "Unknown";
- }
+ private String getFingerprint() {
+ return Build.BRAND + '/' +
+ Build.PRODUCT + '/' +
+ Build.DEVICE + ':' +
+ Build.VERSION.RELEASE + '/' +
+ Build.ID + '/' +
+ Build.VERSION.INCREMENTAL + ':' +
+ Build.TYPE + '/' +
+ Build.TAGS;
}
private String getDeviceModelName() {
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 7e344b53..b40aa11d 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
@@ -32,6 +32,7 @@ import androidx.core.text.HtmlCompat;
import com.bumptech.glide.Glide;
import com.google.android.material.snackbar.Snackbar;
+import org.lsposed.manager.App;
import org.lsposed.manager.BuildConfig;
import org.lsposed.manager.ConfigManager;
import org.lsposed.manager.R;
@@ -118,6 +119,11 @@ public class HomeFragment extends BaseFragment {
cardBackgroundColor = ResourcesKt.resolveColor(activity.getTheme(), R.attr.colorWarning);
binding.statusIcon.setImageResource(R.drawable.ic_warning);
binding.statusSummary.setText(R.string.system_prop_incorrect_summary);
+ } else if (App.needUpdate()) {
+ binding.statusTitle.setText(R.string.need_update);
+ cardBackgroundColor = ResourcesKt.resolveColor(activity.getTheme(), R.attr.colorWarning);
+ binding.statusIcon.setImageResource(R.drawable.ic_warning);
+ binding.statusSummary.setText(R.string.please_update_summary);
} else {
binding.statusTitle.setText(R.string.activated);
cardBackgroundColor = ResourcesKt.resolveColor(activity.getTheme(), R.attr.colorNormal);
diff --git a/app/src/main/java/org/lsposed/manager/ui/fragment/RepoFragment.java b/app/src/main/java/org/lsposed/manager/ui/fragment/RepoFragment.java
index 06bf7af3..433dd3cc 100644
--- a/app/src/main/java/org/lsposed/manager/ui/fragment/RepoFragment.java
+++ b/app/src/main/java/org/lsposed/manager/ui/fragment/RepoFragment.java
@@ -147,11 +147,6 @@ public class RepoFragment extends BaseFragment implements RepoLoader.Listener {
});
}
- @Override
- public void moduleReleasesLoaded(OnlineModule module) {
-
- }
-
@Override
public void onThrowable(Throwable t) {
if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
@@ -202,9 +197,7 @@ public class RepoFragment extends BaseFragment implements RepoLoader.Listener {
sb.append(summary);
}
holder.appDescription.setText(sb);
- holder.itemView.setOnClickListener(v -> {
- getNavController().navigate(RepoFragmentDirections.actionRepoFragmentToRepoItemFragment(module.getName(), module.getDescription()));
- });
+ holder.itemView.setOnClickListener(v -> getNavController().navigate(RepoFragmentDirections.actionRepoFragmentToRepoItemFragment(module.getName(), module.getDescription())));
}
@Override
diff --git a/app/src/main/java/org/lsposed/manager/ui/fragment/RepoItemFragment.java b/app/src/main/java/org/lsposed/manager/ui/fragment/RepoItemFragment.java
index d1e1e22b..348704c4 100644
--- a/app/src/main/java/org/lsposed/manager/ui/fragment/RepoItemFragment.java
+++ b/app/src/main/java/org/lsposed/manager/ui/fragment/RepoItemFragment.java
@@ -31,9 +31,7 @@ import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
-import android.webkit.RenderProcessGoneDetail;
import android.webkit.WebResourceRequest;
-import android.webkit.WebResourceResponse;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
@@ -195,11 +193,6 @@ public class RepoItemFragment extends BaseFragment implements RepoLoader.Listene
return super.onOptionsItemSelected(item);
}
- @Override
- public void repoLoaded() {
-
- }
-
@Override
public void moduleReleasesLoaded(OnlineModule module) {
this.module = module;
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 09c492e9..b8ab3e29 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -39,12 +39,14 @@
已激活
未安装
部分激活
+ 需要更新
SEPolicy 未被正确加载
警告:SEPolicy 未被正确加载,作用于系统框架的模块将不起作用。
请将此报告给 Magisk 开发者。]]>
系统框架注入失败
警告:系统注入失败。
这是极罕见的情况,可能是由 Magisk 或低质 Magisk 模块导致。
请尝试禁用除 Riru 和 LSPosed 外的 Magisk 模块,或向开发者提供完整日志。]]>
系统属性异常
警告:系统属性异常。
一些必须的系统属性被删除或被修改。
模块可能会随机失效。]]>
+ 请安装新版 LSPosed
API 版本
框架版本
管理器版本
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 09510aa5..c76256e6 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -39,12 +39,14 @@
Activated
Not installed
Partially activated
+ Need to update
SEPolicy is not loaded properly
WARNING: SEPolicy is not loaded properly, modules that hook System Framework will not work.
Please report this to Magisk developer.]]>
System Framework injection failed
WARNING: System Framework inject failed.
This is rare and may be caused by Magisk or some low-quality Magisk modules.
Please try to disable Magisk modules other than Riru and LSPosed or submit full log to developers.]]>
System prop incorrect
WARNING: System prop incorrect.
Some necessary system properties deleted or modified.
Modules may invalidate occasionally.]]>
+ Please install the latest version of LSPosed
API version
Framework version
Manager version