You've already forked Magisk
mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-09-06 06:36:58 +00:00
Compare commits
195 Commits
manager-v5
...
manager-v5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15ed3e52f2 | ||
|
|
8990919dab | ||
|
|
e5638e4b15 | ||
|
|
404c6fac9a | ||
|
|
267395bfa2 | ||
|
|
920fc5ae99 | ||
|
|
92ed0ae51b | ||
|
|
6764a98409 | ||
|
|
fd7b5f393a | ||
|
|
2ca528f93f | ||
|
|
ce2e6b7d35 | ||
|
|
684c5d225a | ||
|
|
b75018b03b | ||
|
|
41499d4b3c | ||
|
|
383c97c303 | ||
|
|
74b54ef371 | ||
|
|
bbf7b4db79 | ||
|
|
c61f0acab5 | ||
|
|
398af123b2 | ||
|
|
315fa9d7d3 | ||
|
|
fb5e8ef40c | ||
|
|
e79d764148 | ||
|
|
ebbee0dc43 | ||
|
|
ed0c16e201 | ||
|
|
209fdf349a | ||
|
|
f49f2afacd | ||
|
|
8c6330a3c4 | ||
|
|
337b777125 | ||
|
|
1b756e8d96 | ||
|
|
52d478df1a | ||
|
|
0c782edf21 | ||
|
|
e3948d295e | ||
|
|
5f2c742a5c | ||
|
|
b30c77aab9 | ||
|
|
a5916b9c49 | ||
|
|
453180e30b | ||
|
|
8bd432d391 | ||
|
|
c9d3e20aef | ||
|
|
d5408d1f09 | ||
|
|
f334532aba | ||
|
|
be77c09f3d | ||
|
|
7de6a92753 | ||
|
|
36f76f5a14 | ||
|
|
b84523d557 | ||
|
|
2c78c415e9 | ||
|
|
79ccb30dd2 | ||
|
|
3c566becf6 | ||
|
|
151ca593af | ||
|
|
4132eacba0 | ||
|
|
06e6151816 | ||
|
|
70277d4edd | ||
|
|
d21d2f1a9c | ||
|
|
74a7be996f | ||
|
|
3f38579529 | ||
|
|
4d5a9f6e15 | ||
|
|
41f47acd76 | ||
|
|
821dcaa7c7 | ||
|
|
7135d26419 | ||
|
|
f7fd354dce | ||
|
|
0c69a65bc4 | ||
|
|
2f2ca5eab4 | ||
|
|
df9c40c035 | ||
|
|
25b67017e4 | ||
|
|
bc9c3346f3 | ||
|
|
1db7e19fe8 | ||
|
|
102c03ce2b | ||
|
|
ec19eb4455 | ||
|
|
6d9924d50e | ||
|
|
16c4d74274 | ||
|
|
e4af5fd36a | ||
|
|
702775493a | ||
|
|
b2ae826066 | ||
|
|
cc3e9990fa | ||
|
|
271cbddd5e | ||
|
|
c1423ca9ad | ||
|
|
74379150a1 | ||
|
|
c840a30c30 | ||
|
|
ae5277a898 | ||
|
|
bffa837825 | ||
|
|
b9e7d0faea | ||
|
|
860b08d9ed | ||
|
|
691dc1d49e | ||
|
|
9d6886d367 | ||
|
|
9589b68f5a | ||
|
|
28d88af1af | ||
|
|
8b5acd1849 | ||
|
|
33dc63a7fd | ||
|
|
d0a86385b7 | ||
|
|
50a49e2c8c | ||
|
|
c60adb113e | ||
|
|
aee015e8f6 | ||
|
|
bf6af29205 | ||
|
|
329905d472 | ||
|
|
00d450d262 | ||
|
|
2365d1bd20 | ||
|
|
5b385c18e5 | ||
|
|
98c0434ec0 | ||
|
|
f318d0a3bc | ||
|
|
27f5b410c0 | ||
|
|
3f55be9676 | ||
|
|
b05d2d3a2d | ||
|
|
19af5f9e0b | ||
|
|
f37f330670 | ||
|
|
40082d4571 | ||
|
|
00d655f346 | ||
|
|
821726e7c0 | ||
|
|
759e905c3c | ||
|
|
8bf7e42913 | ||
|
|
0dcd073554 | ||
|
|
2fe35d578d | ||
|
|
8d139e156e | ||
|
|
7c2849356a | ||
|
|
0025ffd1c0 | ||
|
|
2ef7146642 | ||
|
|
1b27e69e40 | ||
|
|
8e7b757efd | ||
|
|
1ab543cea1 | ||
|
|
a3f86903e4 | ||
|
|
c239c305ab | ||
|
|
2e02af994e | ||
|
|
836d9afe17 | ||
|
|
007a352742 | ||
|
|
e526e5659e | ||
|
|
4a5227c7bf | ||
|
|
c2c151ec4c | ||
|
|
452096e7e4 | ||
|
|
50c2a9859e | ||
|
|
677b667307 | ||
|
|
1adf331268 | ||
|
|
349b3e961b | ||
|
|
96650c06f0 | ||
|
|
26038a0a07 | ||
|
|
6a148b5dd9 | ||
|
|
0e109ef979 | ||
|
|
de2285d5e9 | ||
|
|
b2483ba437 | ||
|
|
a82a5e5a49 | ||
|
|
d161a02e71 | ||
|
|
d2b6a700b1 | ||
|
|
af203cef24 | ||
|
|
673e917e76 | ||
|
|
a3bd41db54 | ||
|
|
0d9527921a | ||
|
|
f0e4aec0af | ||
|
|
b0d65b5edd | ||
|
|
75532ef591 | ||
|
|
9a6d1bd700 | ||
|
|
a7ed6c15d3 | ||
|
|
5ee49ba065 | ||
|
|
d34bd47bea | ||
|
|
f17792380b | ||
|
|
c11920110e | ||
|
|
ec5a993fea | ||
|
|
d250c2cc89 | ||
|
|
767e73f40c | ||
|
|
3f699c9d2f | ||
|
|
50dbd9befd | ||
|
|
760e01bf92 | ||
|
|
543f435b1e | ||
|
|
91337218b3 | ||
|
|
afff3c0a49 | ||
|
|
a1871e4bc3 | ||
|
|
3aa0294cd4 | ||
|
|
310b266251 | ||
|
|
21b1b5098e | ||
|
|
a3a4a5d8a5 | ||
|
|
270536f33c | ||
|
|
66bb433cc6 | ||
|
|
bd4ef1a03a | ||
|
|
aa2d9a3bf1 | ||
|
|
fd6cbb138c | ||
|
|
aa75c8e5e4 | ||
|
|
c461fc6daa | ||
|
|
96eaa833f5 | ||
|
|
863b13a694 | ||
|
|
e6fea4e6dd | ||
|
|
83bfc13056 | ||
|
|
bc4f09209b | ||
|
|
967ca17238 | ||
|
|
595c72147c | ||
|
|
f3c3b5a649 | ||
|
|
1cd2c5e653 | ||
|
|
b2873dd44b | ||
|
|
bb80ab4026 | ||
|
|
80cabb338b | ||
|
|
2c69e2c151 | ||
|
|
c1dd23f5e0 | ||
|
|
f93624a41c | ||
|
|
9f4559a059 | ||
|
|
fd05cad303 | ||
|
|
d58b06e493 | ||
|
|
2f0b549027 | ||
|
|
87dbd7e541 | ||
|
|
96e5da36be | ||
|
|
43745edac0 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -5,8 +5,8 @@
|
||||
/build
|
||||
app/release
|
||||
*.hprof
|
||||
app/.externalNativeBuild/
|
||||
*.sh
|
||||
.externalNativeBuild/
|
||||
src/main/assets
|
||||
public.certificate.x509.pem
|
||||
private.key.pk8
|
||||
*.apk
|
||||
|
||||
@@ -1,7 +1,2 @@
|
||||
# Magisk Manager
|
||||
This is one of the submodules used in Magisk. The project is licensed under GPL v3 (or newer).
|
||||
More info are written in the [Magisk Main Repo](https://github.com/topjohnwu/Magisk)
|
||||
|
||||
## Building Notes
|
||||
You need to install CMake and NDK to build the zipadjust library.
|
||||
There are several files required to let Magisk Manager work properly, and they can be copied by using the build script in the [Magisk Main Repo](https://github.com/topjohnwu/Magisk). These files are: `magisk_uninstaller.sh`, `util_functions.sh`, `public.certificate.x509.pem`, and `private.key.pk8` under the `app/src/main/assets` folder.
|
||||
This repo is no longer an independent component. It is a submodule of the [Magisk Project](https://github.com/topjohnwu/Magisk).
|
||||
|
||||
1
app/.gitignore
vendored
1
app/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
/build
|
||||
@@ -1,66 +0,0 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 27
|
||||
buildToolsVersion "27.0.1"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.topjohnwu.magisk"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 27
|
||||
versionCode 64
|
||||
versionName "5.4.3"
|
||||
ndk {
|
||||
moduleName 'zipadjust'
|
||||
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
|
||||
}
|
||||
javaCompileOptions {
|
||||
annotationProcessorOptions {
|
||||
argument('butterknife.debuggable', 'false')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
dexOptions {
|
||||
preDexLibraries true
|
||||
javaMaxHeapSize "2g"
|
||||
}
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path 'src/main/jni/CMakeLists.txt'
|
||||
}
|
||||
}
|
||||
lintOptions {
|
||||
disable 'MissingTranslation'
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
google()
|
||||
maven { url "https://jitpack.io" }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation project(':crypto')
|
||||
implementation 'com.android.support:recyclerview-v7:27.0.1'
|
||||
implementation 'com.android.support:cardview-v7:27.0.1'
|
||||
implementation 'com.android.support:design:27.0.1'
|
||||
implementation 'com.android.support:support-v4:27.0.1'
|
||||
implementation 'com.jakewharton:butterknife:8.8.1'
|
||||
implementation 'com.atlassian.commonmark:commonmark:0.10.0'
|
||||
implementation 'org.kamranzafar:jtar:2.3'
|
||||
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.text.Html;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.components.AboutCardRow;
|
||||
import com.topjohnwu.magisk.components.Activity;
|
||||
import com.topjohnwu.magisk.components.AlertDialogBuilder;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Locale;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class AboutActivity extends Activity {
|
||||
|
||||
@BindView(R.id.toolbar) Toolbar toolbar;
|
||||
@BindView(R.id.app_version_info) AboutCardRow appVersionInfo;
|
||||
@BindView(R.id.app_changelog) AboutCardRow appChangelog;
|
||||
@BindView(R.id.app_developers) AboutCardRow appDevelopers;
|
||||
@BindView(R.id.app_translators) AboutCardRow appTranslators;
|
||||
@BindView(R.id.app_source_code) AboutCardRow appSourceCode;
|
||||
@BindView(R.id.support_thread) AboutCardRow supportThread;
|
||||
@BindView(R.id.donation) AboutCardRow donation;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (getMagiskManager().isDarkTheme) {
|
||||
setTheme(R.style.AppTheme_Transparent_Dark);
|
||||
}
|
||||
setContentView(R.layout.activity_about);
|
||||
ButterKnife.bind(this);
|
||||
|
||||
setSupportActionBar(toolbar);
|
||||
toolbar.setNavigationOnClickListener(view -> finish());
|
||||
|
||||
ActionBar ab = getSupportActionBar();
|
||||
if (ab != null) {
|
||||
ab.setTitle(R.string.about);
|
||||
ab.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
appVersionInfo.setSummary(String.format(Locale.US, "%s (%d)", BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE));
|
||||
|
||||
String changes = null;
|
||||
try (InputStream is = getAssets().open("changelog.html")) {
|
||||
int size = is.available();
|
||||
|
||||
byte[] buffer = new byte[size];
|
||||
is.read(buffer);
|
||||
|
||||
changes = new String(buffer);
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
|
||||
appChangelog.removeSummary();
|
||||
if (changes == null) {
|
||||
appChangelog.setVisibility(View.GONE);
|
||||
} else {
|
||||
Spanned result;
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
|
||||
result = Html.fromHtml(changes, Html.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE);
|
||||
} else {
|
||||
result = Html.fromHtml(changes);
|
||||
}
|
||||
appChangelog.setOnClickListener(v -> {
|
||||
AlertDialog d = new AlertDialogBuilder(this)
|
||||
.setTitle(R.string.app_changelog)
|
||||
.setMessage(result)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
|
||||
//noinspection ConstantConditions
|
||||
((TextView) d.findViewById(android.R.id.message)).setMovementMethod(LinkMovementMethod.getInstance());
|
||||
});
|
||||
}
|
||||
|
||||
appDevelopers.removeSummary();
|
||||
appDevelopers.setOnClickListener(view -> {
|
||||
Spanned result;
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
|
||||
result = Html.fromHtml(getString(R.string.app_developers_), Html.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE);
|
||||
} else {
|
||||
result = Html.fromHtml(getString(R.string.app_developers_));
|
||||
}
|
||||
AlertDialog d = new AlertDialogBuilder(this)
|
||||
.setTitle(R.string.app_developers)
|
||||
.setMessage(result)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.create();
|
||||
|
||||
d.show();
|
||||
//noinspection ConstantConditions
|
||||
((TextView) d.findViewById(android.R.id.message)).setMovementMethod(LinkMovementMethod.getInstance());
|
||||
});
|
||||
|
||||
String translators = getString(R.string.translators);
|
||||
if (TextUtils.isEmpty(translators)) {
|
||||
appTranslators.setVisibility(View.GONE);
|
||||
} else {
|
||||
appTranslators.setSummary(translators);
|
||||
}
|
||||
|
||||
appSourceCode.removeSummary();
|
||||
appSourceCode.setOnClickListener(view -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(Const.Url.SOURCE_CODE_URL))));
|
||||
|
||||
supportThread.removeSummary();
|
||||
supportThread.setOnClickListener(view -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(Const.Url.XDA_THREAD))));
|
||||
|
||||
donation.removeSummary();
|
||||
donation.setOnClickListener(view -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(Const.Url.DONATION_URL))));
|
||||
|
||||
setFloating();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,200 +0,0 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.container.Module;
|
||||
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
|
||||
import com.topjohnwu.magisk.database.SuDatabaseHelper;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
public class MagiskManager extends Application {
|
||||
|
||||
// Global weak reference to self
|
||||
private static WeakReference<MagiskManager> weakSelf;
|
||||
|
||||
// Topics
|
||||
public final Topic magiskHideDone = new Topic();
|
||||
public final Topic reloadActivity = new Topic();
|
||||
public final Topic moduleLoadDone = new Topic();
|
||||
public final Topic repoLoadDone = new Topic();
|
||||
public final Topic updateCheckDone = new Topic();
|
||||
public final Topic safetyNetDone = new Topic();
|
||||
public final Topic localeDone = new Topic();
|
||||
|
||||
// Info
|
||||
public boolean hasInit = false;
|
||||
public int userId;
|
||||
public String magiskVersionString;
|
||||
public int magiskVersionCode = -1;
|
||||
public String remoteMagiskVersionString;
|
||||
public int remoteMagiskVersionCode = -1;
|
||||
public String magiskLink;
|
||||
public String releaseNoteLink;
|
||||
public String remoteManagerVersionString;
|
||||
public int remoteManagerVersionCode = -1;
|
||||
public String managerLink;
|
||||
public String bootBlock = null;
|
||||
public int snet_version;
|
||||
public int updateServiceVersion;
|
||||
|
||||
// Data
|
||||
public Map<String, Module> moduleMap;
|
||||
public List<Locale> locales;
|
||||
|
||||
// Configurations
|
||||
public static Locale locale;
|
||||
public static Locale defaultLocale;
|
||||
|
||||
public boolean magiskHide;
|
||||
public boolean isDarkTheme;
|
||||
public boolean updateNotification;
|
||||
public boolean suReauth;
|
||||
public boolean coreOnly;
|
||||
public int suRequestTimeout;
|
||||
public int suLogTimeout = 14;
|
||||
public int suAccessState;
|
||||
public int multiuserMode;
|
||||
public int suResponseType;
|
||||
public int suNotificationType;
|
||||
public int suNamespaceMode;
|
||||
public String localeConfig;
|
||||
public int updateChannel;
|
||||
public String bootFormat;
|
||||
public String customChannelUrl;
|
||||
|
||||
// Global resources
|
||||
public SharedPreferences prefs;
|
||||
public SuDatabaseHelper suDB;
|
||||
public RepoDatabaseHelper repoDB;
|
||||
public Shell shell;
|
||||
public Runnable permissionGrantCallback = null;
|
||||
|
||||
private static Handler mHandler = new Handler();
|
||||
|
||||
public MagiskManager() {
|
||||
weakSelf = new WeakReference<>(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
userId = getApplicationInfo().uid / 100000;
|
||||
|
||||
if (Utils.getDatabasePath(this, SuDatabaseHelper.DB_NAME).exists()) {
|
||||
// Don't migrate yet, wait and check Magisk version
|
||||
suDB = new SuDatabaseHelper(this);
|
||||
} else {
|
||||
suDB = new SuDatabaseHelper();
|
||||
}
|
||||
|
||||
// If detect original package, self destruct!
|
||||
if (!getPackageName().equals(Const.ORIG_PKG_NAME)) {
|
||||
try {
|
||||
getPackageManager().getApplicationInfo(Const.ORIG_PKG_NAME, 0);
|
||||
Shell.su(String.format(Locale.US, "pm uninstall --user %d %s", userId, getPackageName()));
|
||||
return;
|
||||
} catch (PackageManager.NameNotFoundException ignored) { /* Expected*/ }
|
||||
}
|
||||
|
||||
repoDB = new RepoDatabaseHelper(this);
|
||||
defaultLocale = Locale.getDefault();
|
||||
setLocale();
|
||||
loadConfig();
|
||||
}
|
||||
|
||||
public static MagiskManager get() {
|
||||
return weakSelf.get();
|
||||
}
|
||||
|
||||
public void setLocale() {
|
||||
localeConfig = prefs.getString(Const.Key.LOCALE, "");
|
||||
if (localeConfig.isEmpty()) {
|
||||
locale = defaultLocale;
|
||||
} else {
|
||||
locale = Locale.forLanguageTag(localeConfig);
|
||||
}
|
||||
Resources res = getBaseContext().getResources();
|
||||
Configuration config = new Configuration(res.getConfiguration());
|
||||
config.setLocale(locale);
|
||||
res.updateConfiguration(config, res.getDisplayMetrics());
|
||||
}
|
||||
|
||||
public void loadConfig() {
|
||||
isDarkTheme = prefs.getBoolean(Const.Key.DARK_THEME, false);
|
||||
|
||||
// su
|
||||
suRequestTimeout = Utils.getPrefsInt(prefs, Const.Key.SU_REQUEST_TIMEOUT, Const.Value.timeoutList[2]);
|
||||
suResponseType = Utils.getPrefsInt(prefs, Const.Key.SU_AUTO_RESPONSE, Const.Value.SU_PROMPT);
|
||||
suNotificationType = Utils.getPrefsInt(prefs, Const.Key.SU_NOTIFICATION, Const.Value.NOTIFICATION_TOAST);
|
||||
suReauth = prefs.getBoolean(Const.Key.SU_REAUTH, false);
|
||||
suAccessState = suDB.getSettings(Const.Key.ROOT_ACCESS, Const.Value.ROOT_ACCESS_APPS_AND_ADB);
|
||||
multiuserMode = suDB.getSettings(Const.Key.SU_MULTIUSER_MODE, Const.Value.MULTIUSER_MODE_OWNER_ONLY);
|
||||
suNamespaceMode = suDB.getSettings(Const.Key.SU_MNT_NS, Const.Value.NAMESPACE_MODE_REQUESTER);
|
||||
|
||||
coreOnly = prefs.getBoolean(Const.Key.DISABLE, false);
|
||||
updateNotification = prefs.getBoolean(Const.Key.UPDATE_NOTIFICATION, true);
|
||||
updateChannel = Utils.getPrefsInt(prefs, Const.Key.UPDATE_CHANNEL, Const.Value.STABLE_CHANNEL);
|
||||
bootFormat = prefs.getString(Const.Key.BOOT_FORMAT, ".img");
|
||||
snet_version = prefs.getInt(Const.Key.SNET_VER, -1);
|
||||
updateServiceVersion = prefs.getInt(Const.Key.UPDATE_SERVICE_VER, -1);
|
||||
customChannelUrl = prefs.getString(Const.Key.CUSTOM_CHANNEL, "");
|
||||
}
|
||||
|
||||
public static void toast(String msg, int duration) {
|
||||
mHandler.post(() -> Toast.makeText(weakSelf.get(), msg, duration).show());
|
||||
}
|
||||
|
||||
public static void toast(int resId, int duration) {
|
||||
mHandler.post(() -> Toast.makeText(weakSelf.get(), resId, duration).show());
|
||||
}
|
||||
|
||||
public void loadMagiskInfo() {
|
||||
List<String> ret;
|
||||
ret = Shell.sh("magisk -v");
|
||||
if (!Utils.isValidShellResponse(ret)) {
|
||||
ret = Shell.sh("getprop magisk.version");
|
||||
if (Utils.isValidShellResponse(ret)) {
|
||||
try {
|
||||
magiskVersionString = ret.get(0);
|
||||
magiskVersionCode = (int) Double.parseDouble(ret.get(0)) * 10;
|
||||
} catch (NumberFormatException ignored) {}
|
||||
}
|
||||
} else {
|
||||
magiskVersionString = ret.get(0).split(":")[0];
|
||||
ret = Shell.sh("magisk -V");
|
||||
try {
|
||||
magiskVersionCode = Integer.parseInt(ret.get(0));
|
||||
} catch (NumberFormatException ignored) {}
|
||||
}
|
||||
if (magiskVersionCode > 1435) {
|
||||
ret = Shell.su("resetprop -p " + Const.MAGISKHIDE_PROP);
|
||||
} else {
|
||||
ret = Shell.sh("getprop " + Const.MAGISKHIDE_PROP);
|
||||
}
|
||||
try {
|
||||
magiskHide = !Utils.isValidShellResponse(ret) || Integer.parseInt(ret.get(0)) != 0;
|
||||
} catch (NumberFormatException e) {
|
||||
magiskHide = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void setPermissionGrantCallback(Runnable callback) {
|
||||
permissionGrantCallback = callback;
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.job.JobInfo;
|
||||
import android.app.job.JobScheduler;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.topjohnwu.magisk.asyncs.CheckUpdates;
|
||||
import com.topjohnwu.magisk.asyncs.LoadModules;
|
||||
import com.topjohnwu.magisk.asyncs.ParallelTask;
|
||||
import com.topjohnwu.magisk.asyncs.UpdateRepos;
|
||||
import com.topjohnwu.magisk.components.Activity;
|
||||
import com.topjohnwu.magisk.database.SuDatabaseHelper;
|
||||
import com.topjohnwu.magisk.services.UpdateCheckService;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class SplashActivity extends Activity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
MagiskManager mm = getMagiskManager();
|
||||
|
||||
// Dynamic detect all locales
|
||||
new LoadLocale().exec();
|
||||
|
||||
// Create notification channel on Android O
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationChannel channel = new NotificationChannel(Const.ID.NOTIFICATION_CHANNEL,
|
||||
getString(R.string.magisk_updates), NotificationManager.IMPORTANCE_DEFAULT);
|
||||
getSystemService(NotificationManager.class).createNotificationChannel(channel);
|
||||
}
|
||||
|
||||
mm.loadMagiskInfo();
|
||||
LoadModules loadModuleTask = new LoadModules();
|
||||
|
||||
if (Utils.checkNetworkStatus()) {
|
||||
|
||||
// Fire update check
|
||||
new CheckUpdates().exec();
|
||||
|
||||
// Add repo update check
|
||||
loadModuleTask.setCallBack(() -> new UpdateRepos(false).exec());
|
||||
}
|
||||
|
||||
// Magisk working as expected
|
||||
if (Shell.rootAccess() && mm.magiskVersionCode > 0) {
|
||||
|
||||
List<String> ret = Shell.su("echo \"$BOOTIMAGE\"");
|
||||
if (Utils.isValidShellResponse(ret)) {
|
||||
mm.bootBlock = ret.get(0);
|
||||
}
|
||||
|
||||
// Setup suDB
|
||||
SuDatabaseHelper.setupSuDB();
|
||||
|
||||
// Check alternative Magisk Manager
|
||||
String pkg;
|
||||
if (getPackageName().equals(Const.ORIG_PKG_NAME) &&
|
||||
(pkg = mm.suDB.getStrings(Const.Key.SU_REQUESTER, null)) != null) {
|
||||
Shell.su_raw("pm uninstall " + pkg);
|
||||
mm.suDB.setStrings(Const.Key.SU_REQUESTER, null);
|
||||
}
|
||||
|
||||
// Add update checking service
|
||||
if (Const.Value.UPDATE_SERVICE_VER > mm.updateServiceVersion) {
|
||||
ComponentName service = new ComponentName(this, UpdateCheckService.class);
|
||||
JobInfo info = new JobInfo.Builder(Const.ID.UPDATE_SERVICE_ID, service)
|
||||
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
|
||||
.setPersisted(true)
|
||||
.setPeriodic(8 * 60 * 60 * 1000)
|
||||
.build();
|
||||
((JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE)).schedule(info);
|
||||
mm.updateServiceVersion = Const.Value.UPDATE_SERVICE_VER;
|
||||
}
|
||||
|
||||
// Fire asynctasks
|
||||
loadModuleTask.exec();
|
||||
|
||||
// Check dtbo status
|
||||
Utils.patchDTBO();
|
||||
}
|
||||
|
||||
// Write back default values
|
||||
mm.prefs.edit()
|
||||
.putBoolean(Const.Key.DARK_THEME, mm.isDarkTheme)
|
||||
.putBoolean(Const.Key.MAGISKHIDE, mm.magiskHide)
|
||||
.putBoolean(Const.Key.UPDATE_NOTIFICATION, mm.updateNotification)
|
||||
.putBoolean(Const.Key.HOSTS, Utils.itemExist(Const.MAGISK_HOST_FILE()))
|
||||
.putBoolean(Const.Key.DISABLE, Utils.itemExist(Const.MAGISK_DISABLE_FILE))
|
||||
.putBoolean(Const.Key.SU_REAUTH, mm.suReauth)
|
||||
.putString(Const.Key.SU_REQUEST_TIMEOUT, String.valueOf(mm.suRequestTimeout))
|
||||
.putString(Const.Key.SU_AUTO_RESPONSE, String.valueOf(mm.suResponseType))
|
||||
.putString(Const.Key.SU_NOTIFICATION, String.valueOf(mm.suNotificationType))
|
||||
.putString(Const.Key.ROOT_ACCESS, String.valueOf(mm.suAccessState))
|
||||
.putString(Const.Key.SU_MULTIUSER_MODE, String.valueOf(mm.multiuserMode))
|
||||
.putString(Const.Key.SU_MNT_NS, String.valueOf(mm.suNamespaceMode))
|
||||
.putString(Const.Key.UPDATE_CHANNEL, String.valueOf(mm.updateChannel))
|
||||
.putString(Const.Key.LOCALE, mm.localeConfig)
|
||||
.putString(Const.Key.BOOT_FORMAT, mm.bootFormat)
|
||||
.putInt(Const.Key.UPDATE_SERVICE_VER, mm.updateServiceVersion)
|
||||
.apply();
|
||||
|
||||
mm.hasInit = true;
|
||||
|
||||
Intent intent = new Intent(this, MainActivity.class);
|
||||
intent.putExtra(Const.Key.OPEN_SECTION, getIntent().getStringExtra(Const.Key.OPEN_SECTION));
|
||||
intent.putExtra(Const.Key.INTENT_PERM, getIntent().getStringExtra(Const.Key.INTENT_PERM));
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
static class LoadLocale extends ParallelTask<Void, Void, Void> {
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
MagiskManager.get().locales = Utils.getAvailableLocale();
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid) {
|
||||
MagiskManager.get().localeDone.publish();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.webkit.WebView;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
|
||||
import org.commonmark.node.Node;
|
||||
import org.commonmark.parser.Parser;
|
||||
import org.commonmark.renderer.html.HtmlRenderer;
|
||||
|
||||
public class MarkDownWindow extends ParallelTask<Void, Void, String> {
|
||||
|
||||
private String mTitle, mUrl;
|
||||
|
||||
public MarkDownWindow(Activity context, String title, String url) {
|
||||
super(context);
|
||||
mTitle = title;
|
||||
mUrl = url;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String doInBackground(Void... voids) {
|
||||
String md = WebService.getString(mUrl);
|
||||
Parser parser = Parser.builder().build();
|
||||
HtmlRenderer renderer = HtmlRenderer.builder().build();
|
||||
Node doc = parser.parse(md);
|
||||
return String.format(
|
||||
"<link rel='stylesheet' type='text/css' href='file:///android_asset/%s.css'/> %s",
|
||||
MagiskManager.get().isDarkTheme ? "dark" : "light", renderer.render(doc));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(String html) {
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(getActivity());
|
||||
alert.setTitle(mTitle);
|
||||
|
||||
WebView wv = new WebView(getActivity());
|
||||
wv.loadDataWithBaseURL("fake://", html, "text/html", "UTF-8", null);
|
||||
|
||||
alert.setView(wv);
|
||||
alert.setNegativeButton(R.string.close, (dialog, id) -> dialog.dismiss());
|
||||
alert.show();
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class RestoreStockBoot extends ParallelTask<Void, Void, Boolean> {
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... voids) {
|
||||
String sha1;
|
||||
List<String> ret = Utils.readFile("/.backup/.sha1");
|
||||
if (Utils.isValidShellResponse(ret)) {
|
||||
sha1 = ret.get(0);
|
||||
} else {
|
||||
ret = Shell.su("cat /init.magisk.rc | grep STOCKSHA1");
|
||||
if (!Utils.isValidShellResponse(ret))
|
||||
return false;
|
||||
sha1 = ret.get(0).substring(ret.get(0).indexOf('=') + 1);
|
||||
}
|
||||
|
||||
ret = Shell.su("restore_imgs " + sha1 + " && echo true || echo false");
|
||||
|
||||
return Utils.isValidShellResponse(ret) && Boolean.parseBoolean(ret.get(ret.size() - 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
if (result) {
|
||||
MagiskManager.toast(R.string.restore_done, Toast.LENGTH_SHORT);
|
||||
} else {
|
||||
MagiskManager.toast(R.string.restore_fail, Toast.LENGTH_LONG);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package com.topjohnwu.magisk.container;
|
||||
|
||||
import android.os.Handler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public abstract class CallbackList<E> extends ArrayList<E> {
|
||||
|
||||
private Handler handler;
|
||||
|
||||
protected CallbackList() {
|
||||
handler = new Handler();
|
||||
}
|
||||
|
||||
public abstract void onAddElement(E e);
|
||||
|
||||
public synchronized boolean add(E e) {
|
||||
boolean ret = super.add(e);
|
||||
handler.post(() -> onAddElement(e));
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package com.topjohnwu.magisk.container;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class InputStreamWrapper extends InputStream {
|
||||
private InputStream in;
|
||||
|
||||
public InputStreamWrapper(InputStream in) {
|
||||
this.in = in;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
return in.available();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
in.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void mark(int readlimit) {
|
||||
in.mark(readlimit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean markSupported() {
|
||||
return in.markSupported();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int read() throws IOException {
|
||||
return in.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(@NonNull byte[] b) throws IOException {
|
||||
return in.read(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int read(@NonNull byte[] b, int off, int len) throws IOException {
|
||||
return in.read(b, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void reset() throws IOException {
|
||||
in.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long skip(long n) throws IOException {
|
||||
return in.skip(n);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return in.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return in.equals(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return in.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,365 +0,0 @@
|
||||
package com.topjohnwu.magisk.database;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.database.DatabaseUtils;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.container.Policy;
|
||||
import com.topjohnwu.magisk.container.SuLogEntry;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.DateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class SuDatabaseHelper extends SQLiteOpenHelper {
|
||||
|
||||
public static final String DB_NAME = "su.db";
|
||||
|
||||
private static final int DATABASE_VER = 5;
|
||||
private static final String POLICY_TABLE = "policies";
|
||||
private static final String LOG_TABLE = "logs";
|
||||
private static final String SETTINGS_TABLE = "settings";
|
||||
private static final String STRINGS_TABLE = "strings";
|
||||
|
||||
private static String GLOBAL_DB;
|
||||
|
||||
private Context mContext;
|
||||
private PackageManager pm;
|
||||
private SQLiteDatabase mDb;
|
||||
|
||||
private static Context preProcess() {
|
||||
Context context;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
Context ce = MagiskManager.get();
|
||||
context = ce.createDeviceProtectedStorageContext();
|
||||
File oldDB = Utils.getDatabasePath(ce, DB_NAME);
|
||||
if (oldDB.exists()) {
|
||||
// Migrate DB path
|
||||
context.moveDatabaseFrom(ce, DB_NAME);
|
||||
}
|
||||
} else {
|
||||
context = MagiskManager.get();
|
||||
}
|
||||
GLOBAL_DB = context.getFilesDir().getParentFile().getParent() + "/magisk.db";
|
||||
File db = Utils.getDatabasePath(context, DB_NAME);
|
||||
if (!db.exists() && Utils.itemExist(GLOBAL_DB)) {
|
||||
// Migrate global DB to ours
|
||||
db.getParentFile().mkdirs();
|
||||
Shell.su(
|
||||
"magisk --clone-attr " + context.getFilesDir() + " " + GLOBAL_DB,
|
||||
"chmod 660 " + GLOBAL_DB,
|
||||
"ln " + GLOBAL_DB + " " + db
|
||||
);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
public static void setupSuDB() {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
// Check if we need to migrate suDB
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && mm.magiskVersionCode >= 1410 &&
|
||||
Utils.getDatabasePath(mm, SuDatabaseHelper.DB_NAME).exists()) {
|
||||
mm.suDB.close();
|
||||
mm.suDB = new SuDatabaseHelper();
|
||||
}
|
||||
|
||||
File suDbFile = mm.suDB.getDbFile();
|
||||
|
||||
if (!Utils.itemExist(GLOBAL_DB)) {
|
||||
// Hard link our DB globally
|
||||
Shell.su_raw("ln " + suDbFile + " " + GLOBAL_DB);
|
||||
}
|
||||
|
||||
// Check if we are linked globally
|
||||
List<String> ret = Shell.sh("ls -l " + suDbFile);
|
||||
if (Utils.isValidShellResponse(ret)) {
|
||||
try {
|
||||
int links = Integer.parseInt(ret.get(0).trim().split("\\s+")[1]);
|
||||
if (links < 2) {
|
||||
mm.suDB.close();
|
||||
suDbFile.delete();
|
||||
new File(suDbFile + "-journal").delete();
|
||||
mm.suDB = new SuDatabaseHelper();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public SuDatabaseHelper() {
|
||||
this(preProcess());
|
||||
}
|
||||
|
||||
public SuDatabaseHelper(Context context) {
|
||||
super(context, DB_NAME, null, DATABASE_VER);
|
||||
mContext = context;
|
||||
pm = context.getPackageManager();
|
||||
mDb = getWritableDatabase();
|
||||
cleanup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
onUpgrade(db, 0, DATABASE_VER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
try {
|
||||
if (oldVersion == 0) {
|
||||
createTables(db);
|
||||
oldVersion = 3;
|
||||
}
|
||||
if (oldVersion == 1) {
|
||||
// We're dropping column app_name, rename and re-construct table
|
||||
db.execSQL("ALTER TABLE " + POLICY_TABLE + " RENAME TO " + POLICY_TABLE + "_old");
|
||||
|
||||
// Create the new tables
|
||||
createTables(db);
|
||||
|
||||
// Migrate old data to new tables
|
||||
db.execSQL(
|
||||
"INSERT INTO " + POLICY_TABLE + " SELECT " +
|
||||
"uid, package_name, policy, until, logging, notification " +
|
||||
"FROM " + POLICY_TABLE + "_old");
|
||||
db.execSQL("DROP TABLE " + POLICY_TABLE + "_old");
|
||||
|
||||
File oldDB = Utils.getDatabasePath(MagiskManager.get(), "sulog.db");
|
||||
if (oldDB.exists()) {
|
||||
migrateLegacyLogList(oldDB, db);
|
||||
MagiskManager.get().deleteDatabase("sulog.db");
|
||||
}
|
||||
++oldVersion;
|
||||
}
|
||||
if (oldVersion == 2) {
|
||||
db.execSQL("UPDATE " + LOG_TABLE + " SET time=time*1000");
|
||||
++oldVersion;
|
||||
}
|
||||
if (oldVersion == 3) {
|
||||
db.execSQL(
|
||||
"CREATE TABLE IF NOT EXISTS " + STRINGS_TABLE + " " +
|
||||
"(key TEXT, value TEXT, PRIMARY KEY(key))");
|
||||
++oldVersion;
|
||||
}
|
||||
if (oldVersion == 4) {
|
||||
db.execSQL("UPDATE " + POLICY_TABLE + " SET uid=uid%100000");
|
||||
++oldVersion;
|
||||
}
|
||||
|
||||
if (!Utils.itemExist(GLOBAL_DB)) {
|
||||
// Hard link our DB globally
|
||||
Shell.su_raw("ln " + getDbFile() + " " + GLOBAL_DB);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
onDowngrade(db, DATABASE_VER, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
MagiskManager.toast(R.string.su_db_corrupt, Toast.LENGTH_LONG);
|
||||
// Remove everything, we do not support downgrade
|
||||
db.execSQL("DROP TABLE IF EXISTS " + POLICY_TABLE);
|
||||
db.execSQL("DROP TABLE IF EXISTS " + LOG_TABLE);
|
||||
db.execSQL("DROP TABLE IF EXISTS " + SETTINGS_TABLE);
|
||||
db.execSQL("DROP TABLE IF EXISTS " + STRINGS_TABLE);
|
||||
onUpgrade(db, 0, DATABASE_VER);
|
||||
}
|
||||
|
||||
public File getDbFile() {
|
||||
return mContext.getDatabasePath(DB_NAME);
|
||||
}
|
||||
|
||||
private void createTables(SQLiteDatabase db) {
|
||||
// Policies
|
||||
db.execSQL(
|
||||
"CREATE TABLE IF NOT EXISTS " + POLICY_TABLE + " " +
|
||||
"(uid INT, package_name TEXT, policy INT, " +
|
||||
"until INT, logging INT, notification INT, " +
|
||||
"PRIMARY KEY(uid))");
|
||||
|
||||
// Logs
|
||||
db.execSQL(
|
||||
"CREATE TABLE IF NOT EXISTS " + LOG_TABLE + " " +
|
||||
"(from_uid INT, package_name TEXT, app_name TEXT, from_pid INT, " +
|
||||
"to_uid INT, action INT, time INT, command TEXT)");
|
||||
|
||||
// Settings
|
||||
db.execSQL(
|
||||
"CREATE TABLE IF NOT EXISTS " + SETTINGS_TABLE + " " +
|
||||
"(key TEXT, value INT, PRIMARY KEY(key))");
|
||||
}
|
||||
|
||||
private void cleanup() {
|
||||
// Clear outdated policies
|
||||
mDb.delete(POLICY_TABLE, "until > 0 AND until < ?",
|
||||
new String[] { String.valueOf(System.currentTimeMillis() / 1000) });
|
||||
// Clear outdated logs
|
||||
mDb.delete(LOG_TABLE, "time < ?", new String[] { String.valueOf(
|
||||
System.currentTimeMillis() - MagiskManager.get().suLogTimeout * 86400000) });
|
||||
}
|
||||
|
||||
public void deletePolicy(Policy policy) {
|
||||
deletePolicy(policy.packageName);
|
||||
}
|
||||
|
||||
public void deletePolicy(String pkg) {
|
||||
mDb.delete(POLICY_TABLE, "package_name=?", new String[] { pkg });
|
||||
}
|
||||
|
||||
public void deletePolicy(int uid) {
|
||||
mDb.delete(POLICY_TABLE, "uid=?", new String[]{String.valueOf(uid)});
|
||||
}
|
||||
|
||||
public Policy getPolicy(int uid) {
|
||||
Policy policy = null;
|
||||
try (Cursor c = mDb.query(POLICY_TABLE, null, "uid=?", new String[] { String.valueOf(uid % 100000) }, null, null, null)) {
|
||||
if (c.moveToNext()) {
|
||||
policy = new Policy(c, pm);
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
deletePolicy(uid);
|
||||
return null;
|
||||
}
|
||||
return policy;
|
||||
}
|
||||
|
||||
public Policy getPolicy(String pkg) {
|
||||
Policy policy = null;
|
||||
try (Cursor c = mDb.query(POLICY_TABLE, null, "package_name=?", new String[] { pkg }, null, null, null)) {
|
||||
if (c.moveToNext()) {
|
||||
policy = new Policy(c, pm);
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
deletePolicy(pkg);
|
||||
return null;
|
||||
}
|
||||
return policy;
|
||||
}
|
||||
|
||||
public void addPolicy(Policy policy) {
|
||||
mDb.replace(POLICY_TABLE, null, policy.getContentValues());
|
||||
}
|
||||
|
||||
public void updatePolicy(Policy policy) {
|
||||
mDb.update(POLICY_TABLE, policy.getContentValues(), "package_name=?",
|
||||
new String[] { policy.packageName });
|
||||
}
|
||||
|
||||
public List<Policy> getPolicyList(PackageManager pm) {
|
||||
try (Cursor c = mDb.query(POLICY_TABLE, null, null, null, null, null, null)) {
|
||||
List<Policy> ret = new ArrayList<>(c.getCount());
|
||||
while (c.moveToNext()) {
|
||||
try {
|
||||
Policy policy = new Policy(c, pm);
|
||||
ret.add(policy);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
// The app no longer exist, remove from DB
|
||||
deletePolicy(c.getInt(c.getColumnIndex("uid")));
|
||||
}
|
||||
}
|
||||
Collections.sort(ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
public List<List<Integer>> getLogStructure() {
|
||||
try (Cursor c = mDb.query(LOG_TABLE, new String[] { "time" }, null, null, null, null, "time DESC")) {
|
||||
List<List<Integer>> ret = new ArrayList<>();
|
||||
List<Integer> list = null;
|
||||
String dateString = null, newString;
|
||||
while (c.moveToNext()) {
|
||||
Date date = new Date(c.getLong(c.getColumnIndex("time")));
|
||||
newString = DateFormat.getDateInstance(DateFormat.MEDIUM, MagiskManager.locale).format(date);
|
||||
if (!TextUtils.equals(dateString, newString)) {
|
||||
dateString = newString;
|
||||
list = new ArrayList<>();
|
||||
ret.add(list);
|
||||
}
|
||||
list.add(c.getPosition());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
public Cursor getLogCursor() {
|
||||
return getLogCursor(mDb);
|
||||
}
|
||||
|
||||
public Cursor getLogCursor(SQLiteDatabase db) {
|
||||
return db.query(LOG_TABLE, null, null, null, null, null, "time DESC");
|
||||
}
|
||||
|
||||
private void migrateLegacyLogList(File oldDB, SQLiteDatabase newDB) {
|
||||
try (SQLiteDatabase oldDb = SQLiteDatabase.openDatabase(oldDB.getPath(), null, SQLiteDatabase.OPEN_READWRITE);
|
||||
Cursor c = getLogCursor(oldDb)) {
|
||||
while (c.moveToNext()) {
|
||||
ContentValues values = new ContentValues();
|
||||
DatabaseUtils.cursorRowToContentValues(c, values);
|
||||
newDB.insert(LOG_TABLE, null, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addLog(SuLogEntry log) {
|
||||
mDb.insert(LOG_TABLE, null, log.getContentValues());
|
||||
}
|
||||
|
||||
public void clearLogs() {
|
||||
mDb.delete(LOG_TABLE, null, null);
|
||||
}
|
||||
|
||||
public void setSettings(String key, int value) {
|
||||
ContentValues data = new ContentValues();
|
||||
data.put("key", key);
|
||||
data.put("value", value);
|
||||
mDb.replace(SETTINGS_TABLE, null, data);
|
||||
}
|
||||
|
||||
public int getSettings(String key, int defaultValue) {
|
||||
int value = defaultValue;
|
||||
try (Cursor c = mDb.query(SETTINGS_TABLE, null, "key=?",new String[] { key }, null, null, null)) {
|
||||
if (c.moveToNext()) {
|
||||
value = c.getInt(c.getColumnIndex("value"));
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setStrings(String key, String value) {
|
||||
if (value == null) {
|
||||
mDb.delete(STRINGS_TABLE, "key=?", new String[] { key });
|
||||
} else {
|
||||
ContentValues data = new ContentValues();
|
||||
data.put("key", key);
|
||||
data.put("value", value);
|
||||
mDb.replace(STRINGS_TABLE, null, data);
|
||||
}
|
||||
}
|
||||
|
||||
public String getStrings(String key, String defaultValue) {
|
||||
String value = defaultValue;
|
||||
try (Cursor c = mDb.query(STRINGS_TABLE, null, "key=?",new String[] { key }, null, null, null)) {
|
||||
if (c.moveToNext()) {
|
||||
value = c.getString(c.getColumnIndex("value"));
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.topjohnwu.magisk.superuser;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.topjohnwu.magisk.components.Activity;
|
||||
|
||||
public class RequestActivity extends Activity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Intent intent = getIntent();
|
||||
if (intent == null) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK).setClass(this, SuRequestActivity.class);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
@@ -1,209 +0,0 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Modified by topjohnwu, based on Chainfire's libsuperuser
|
||||
*/
|
||||
|
||||
public class Shell {
|
||||
|
||||
// -2 = not initialized; -1 = no shell; 0 = non root shell; 1 = root shell
|
||||
public static int status = -2;
|
||||
|
||||
private final Process process;
|
||||
private final OutputStream STDIN;
|
||||
private final InputStream STDOUT;
|
||||
private final InputStream STDERR;
|
||||
|
||||
private static void testRootShell(Shell shell) throws IOException {
|
||||
shell.STDIN.write(("id\n").getBytes("UTF-8"));
|
||||
shell.STDIN.flush();
|
||||
String s = new BufferedReader(new InputStreamReader(shell.STDOUT)).readLine();
|
||||
if (TextUtils.isEmpty(s) || !s.contains("uid=0")) {
|
||||
shell.STDIN.close();
|
||||
shell.STDIN.close();
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
|
||||
public Shell(String command) throws IOException {
|
||||
process = Runtime.getRuntime().exec(command);
|
||||
STDIN = process.getOutputStream();
|
||||
STDOUT = process.getInputStream();
|
||||
STDERR = process.getErrorStream();
|
||||
}
|
||||
|
||||
public static Shell getShell() {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
boolean needNewShell = mm.shell == null;
|
||||
|
||||
if (!needNewShell) {
|
||||
try {
|
||||
mm.shell.process.exitValue();
|
||||
// The process is dead
|
||||
needNewShell = true;
|
||||
} catch (IllegalThreadStateException ignored) {
|
||||
// This should be the expected result
|
||||
}
|
||||
}
|
||||
|
||||
if (needNewShell) {
|
||||
status = 1;
|
||||
try {
|
||||
mm.shell = new Shell("su --mount-master");
|
||||
testRootShell(mm.shell);
|
||||
} catch (IOException e) {
|
||||
// Mount master not implemented
|
||||
try {
|
||||
mm.shell = new Shell("su");
|
||||
testRootShell(mm.shell);
|
||||
} catch (IOException e1) {
|
||||
// No root exists
|
||||
status = 0;
|
||||
try {
|
||||
mm.shell = new Shell("sh");
|
||||
} catch (IOException e2) {
|
||||
status = -1;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rootAccess()) {
|
||||
// Load utility shell scripts
|
||||
try (InputStream in = mm.getAssets().open(Const.UTIL_FUNCTIONS)) {
|
||||
mm.shell.loadInputStream(in);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// Root shell initialization
|
||||
String bbpath = Const.BUSYBOX_PATH();
|
||||
mm.shell.run_raw(false, false,
|
||||
"export PATH=" + bbpath + ":$PATH",
|
||||
"mount_partitions",
|
||||
"find_boot_image",
|
||||
"migrate_boot_backup");
|
||||
}
|
||||
}
|
||||
|
||||
return mm.shell;
|
||||
}
|
||||
|
||||
public static boolean rootAccess() {
|
||||
if (status == -2) getShell();
|
||||
return status > 0;
|
||||
}
|
||||
|
||||
public void run(Collection<String> output, Collection<String> error, String... commands) {
|
||||
StreamGobbler out, err;
|
||||
synchronized (process) {
|
||||
try {
|
||||
out = new StreamGobbler(STDOUT, output);
|
||||
err = new StreamGobbler(STDERR, error);
|
||||
out.start();
|
||||
err.start();
|
||||
run_raw(output != null, error != null, commands);
|
||||
STDIN.write("echo \'-shell-done-\'\necho \'-shell-done-\' >&2\n".getBytes("UTF-8"));
|
||||
STDIN.flush();
|
||||
try {
|
||||
out.join();
|
||||
err.join();
|
||||
} catch (InterruptedException ignored) {}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
process.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void run_raw(boolean stdout, boolean stderr, String... commands) {
|
||||
String suffix = "\n";
|
||||
if (!stderr) suffix = " 2>/dev/null" + suffix;
|
||||
if (!stdout) suffix = " >/dev/null" + suffix;
|
||||
synchronized (process) {
|
||||
try {
|
||||
for (String command : commands) {
|
||||
Logger.shell(true, command);
|
||||
STDIN.write((command + suffix).getBytes("UTF-8"));
|
||||
STDIN.flush();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
process.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void loadInputStream(InputStream in) {
|
||||
synchronized (process) {
|
||||
try {
|
||||
Utils.inToOut(in, STDIN);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static List<String> sh(String... commands) {
|
||||
List<String> res = new ArrayList<>();
|
||||
sh(res, commands);
|
||||
return res;
|
||||
}
|
||||
|
||||
public static void sh(Collection<String> output, String... commands) {
|
||||
Shell shell = getShell();
|
||||
if (shell == null)
|
||||
return;
|
||||
shell.run(output, null, commands);
|
||||
}
|
||||
|
||||
public static void sh_raw(String... commands) {
|
||||
Shell shell = getShell();
|
||||
if (shell == null)
|
||||
return;
|
||||
shell.run_raw(false, false, commands);
|
||||
}
|
||||
|
||||
public static List<String> su(String... commands) {
|
||||
if (!rootAccess()) return sh();
|
||||
return sh(commands);
|
||||
}
|
||||
|
||||
public static void su(Collection<String> output, String... commands) {
|
||||
if (!rootAccess()) return;
|
||||
sh(output, commands);
|
||||
}
|
||||
|
||||
public static void su_raw(String... commands) {
|
||||
if (!rootAccess()) return;
|
||||
sh_raw(commands);
|
||||
}
|
||||
|
||||
public static abstract class AbstractList<E> extends java.util.AbstractList<E> {
|
||||
|
||||
@Override
|
||||
public abstract boolean add(E e);
|
||||
|
||||
@Override
|
||||
public E get(int i) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Modified by topjohnwu, based on Chainfire's libsuperuser
|
||||
*/
|
||||
|
||||
public class StreamGobbler extends Thread {
|
||||
|
||||
private BufferedReader reader;
|
||||
private Collection<String> writer;
|
||||
|
||||
/**
|
||||
* <p>StreamGobbler constructor</p>
|
||||
*
|
||||
* <p>We use this class because sh STDOUT and STDERR should be read as quickly as
|
||||
* possible to prevent a deadlock from occurring, or Process.waitFor() never
|
||||
* returning (as the buffer is full, pausing the native process)</p>
|
||||
*
|
||||
* @param in InputStream to read from
|
||||
* @param out {@literal List<String>} to write to, or null
|
||||
*/
|
||||
public StreamGobbler(InputStream in, Collection<String> out) {
|
||||
try {
|
||||
while (in.available() != 0) {
|
||||
in.skip(in.available());
|
||||
}
|
||||
} catch (IOException ignored) {}
|
||||
reader = new BufferedReader(new InputStreamReader(in));
|
||||
writer = out == null ? null : Collections.synchronizedCollection(out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// keep reading the InputStream until it ends (or an error occurs)
|
||||
try {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (TextUtils.equals(line, "-shell-done-"))
|
||||
return;
|
||||
if (writer != null) writer.add(line);
|
||||
Logger.shell(false, line);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// reader probably closed, expected exit condition
|
||||
}
|
||||
|
||||
// make sure our stream is closed and resources will be freed
|
||||
try {
|
||||
reader.close();
|
||||
} catch (IOException e) {
|
||||
// read already closed
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.6)
|
||||
add_library(zipadjust SHARED
|
||||
jni_glue.c
|
||||
zipadjust.c)
|
||||
find_library(libz z)
|
||||
find_library(liblog log)
|
||||
target_link_libraries(zipadjust ${libz} ${liblog})
|
||||
@@ -1,19 +0,0 @@
|
||||
//
|
||||
// Java entry point
|
||||
//
|
||||
|
||||
#include <jni.h>
|
||||
#include "zipadjust.h"
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_topjohnwu_magisk_utils_ZipUtils_zipAdjust(JNIEnv *env, jclass type, jstring filenameIn_,
|
||||
jstring filenameOut_) {
|
||||
const char *filenameIn = (*env)->GetStringUTFChars(env, filenameIn_, 0);
|
||||
const char *filenameOut = (*env)->GetStringUTFChars(env, filenameOut_, 0);
|
||||
|
||||
// TODO
|
||||
zipadjust(filenameIn, filenameOut, 0);
|
||||
|
||||
(*env)->ReleaseStringUTFChars(env, filenameIn_, filenameIn);
|
||||
(*env)->ReleaseStringUTFChars(env, filenameOut_, filenameOut);
|
||||
}
|
||||
@@ -1,297 +0,0 @@
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <zlib.h>
|
||||
#include <unistd.h>
|
||||
#include "zipadjust.h"
|
||||
|
||||
#ifndef O_BINARY
|
||||
#define O_BINARY 0
|
||||
#define O_TEXT 0
|
||||
#endif
|
||||
|
||||
#pragma pack(1)
|
||||
struct local_header_struct {
|
||||
uint32_t signature;
|
||||
uint16_t extract_version;
|
||||
uint16_t flags;
|
||||
uint16_t compression_method;
|
||||
uint16_t last_modified_time;
|
||||
uint16_t last_modified_date;
|
||||
uint32_t crc32;
|
||||
uint32_t size_compressed;
|
||||
uint32_t size_uncompressed;
|
||||
uint16_t length_filename;
|
||||
uint16_t length_extra;
|
||||
// filename
|
||||
// extra
|
||||
};
|
||||
typedef struct local_header_struct local_header_t;
|
||||
|
||||
#pragma pack(1)
|
||||
struct data_descriptor_struct {
|
||||
uint32_t signature;
|
||||
uint32_t crc32;
|
||||
uint32_t size_compressed;
|
||||
uint32_t size_uncompressed;
|
||||
};
|
||||
typedef struct data_descriptor_struct data_descriptor_t;
|
||||
|
||||
#pragma pack(1)
|
||||
struct central_header_struct {
|
||||
uint32_t signature;
|
||||
uint16_t version_made;
|
||||
uint16_t version_needed;
|
||||
uint16_t flags;
|
||||
uint16_t compression_method;
|
||||
uint16_t last_modified_time;
|
||||
uint16_t last_modified_date;
|
||||
uint32_t crc32;
|
||||
uint32_t size_compressed;
|
||||
uint32_t size_uncompressed;
|
||||
uint16_t length_filename;
|
||||
uint16_t length_extra;
|
||||
uint16_t length_comment;
|
||||
uint16_t disk_start;
|
||||
uint16_t attr_internal;
|
||||
uint32_t attr_external;
|
||||
uint32_t offset;
|
||||
// filename
|
||||
// extra
|
||||
// comment
|
||||
};
|
||||
typedef struct central_header_struct central_header_t;
|
||||
|
||||
#pragma pack(1)
|
||||
struct central_footer_struct {
|
||||
uint32_t signature;
|
||||
uint16_t disk_number;
|
||||
uint16_t disk_number_central_directory;
|
||||
uint16_t central_directory_entries_this_disk;
|
||||
uint16_t central_directory_entries_total;
|
||||
uint32_t central_directory_size;
|
||||
uint32_t central_directory_offset;
|
||||
uint16_t length_comment;
|
||||
// comment
|
||||
};
|
||||
typedef struct central_footer_struct central_footer_t;
|
||||
|
||||
#define MAGIC_LOCAL_HEADER 0x04034b50
|
||||
#define MAGIC_DATA_DESCRIPTOR 0x08074b50
|
||||
#define MAGIC_CENTRAL_HEADER 0x02014b50
|
||||
#define MAGIC_CENTRAL_FOOTER 0x06054b50
|
||||
|
||||
static int xerror(char* message) {
|
||||
LOGE("%s\n", message);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int xseekread(int fd, off_t offset, void* buf, size_t bytes) {
|
||||
if (lseek(fd, offset, SEEK_SET) == (off_t)-1) return xerror("Seek failed");
|
||||
if (read(fd, buf, bytes) != bytes) return xerror("Read failed");
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int xseekwrite(int fd, off_t offset, void* buf, size_t bytes) {
|
||||
if (lseek(fd, offset, SEEK_SET) == (off_t)-1) return xerror("Seek failed");
|
||||
if (write(fd, buf, bytes) != bytes) return xerror("Write failed");
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int xfilecopy(int fdIn, int fdOut, off_t offsetIn, off_t offsetOut, size_t bytes) {
|
||||
if ((offsetIn != (off_t)-1) && (lseek(fdIn, offsetIn, SEEK_SET) == (off_t)-1)) return xerror("Seek failed");
|
||||
if ((offsetOut != (off_t)-1) && (lseek(fdOut, offsetOut, SEEK_SET) == (off_t)-1)) return xerror("Seek failed");
|
||||
|
||||
int CHUNK = 256 * 1024;
|
||||
void* buf = malloc(CHUNK);
|
||||
if (buf == NULL) return xerror("malloc failed");
|
||||
size_t left = bytes;
|
||||
while (left > 0) {
|
||||
size_t wanted = (left < CHUNK) ? left : CHUNK;
|
||||
size_t r = read(fdIn, buf, wanted);
|
||||
if (r <= 0) return xerror("Read failed");
|
||||
if (write(fdOut, buf, r) != r) return xerror("Write failed");
|
||||
left -= r;
|
||||
}
|
||||
free(buf);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int xdecompress(int fdIn, int fdOut, off_t offsetIn, off_t offsetOut, size_t bytes) {
|
||||
if ((offsetIn != (off_t)-1) && (lseek(fdIn, offsetIn, SEEK_SET) == (off_t)-1)) return xerror("Seek failed");
|
||||
if ((offsetOut != (off_t)-1) && (lseek(fdOut, offsetOut, SEEK_SET) == (off_t)-1)) return xerror("Seek failed");
|
||||
|
||||
int CHUNK = 256 * 1024;
|
||||
|
||||
int ret;
|
||||
unsigned have;
|
||||
z_stream strm;
|
||||
unsigned char in[CHUNK];
|
||||
unsigned char out[CHUNK];
|
||||
|
||||
strm.zalloc = Z_NULL;
|
||||
strm.zfree = Z_NULL;
|
||||
strm.opaque = Z_NULL;
|
||||
strm.avail_in = 0;
|
||||
strm.next_in = Z_NULL;
|
||||
ret = inflateInit2(&strm, -15);
|
||||
if (ret != Z_OK) return xerror("ret != Z_OK");
|
||||
|
||||
do {
|
||||
strm.avail_in = read(fdIn, in, CHUNK);
|
||||
if (strm.avail_in == 0) break;
|
||||
strm.next_in = in;
|
||||
|
||||
do {
|
||||
strm.avail_out = CHUNK;
|
||||
strm.next_out = out;
|
||||
|
||||
ret = inflate(&strm, Z_NO_FLUSH);
|
||||
if (ret == Z_STREAM_ERROR) return xerror("Stream error");
|
||||
switch (ret) {
|
||||
case Z_NEED_DICT:
|
||||
ret = Z_DATA_ERROR;
|
||||
case Z_DATA_ERROR:
|
||||
case Z_MEM_ERROR:
|
||||
(void)inflateEnd(&strm);
|
||||
return xerror("DICT/DATA/MEM error");
|
||||
}
|
||||
|
||||
have = CHUNK - strm.avail_out;
|
||||
if (write(fdOut, out, have) != have) {
|
||||
(void)inflateEnd(&strm);
|
||||
return xerror("Write failed");
|
||||
}
|
||||
} while (strm.avail_out == 0);
|
||||
} while (ret != Z_STREAM_END);
|
||||
(void)inflateEnd(&strm);
|
||||
|
||||
return ret == Z_STREAM_END ? 1 : 0;
|
||||
}
|
||||
|
||||
int zipadjust(const char* filenameIn, const char* filenameOut, int decompress) {
|
||||
int ok = 0;
|
||||
|
||||
int fin = open(filenameIn, O_RDONLY | O_BINARY);
|
||||
if (fin > 0) {
|
||||
unsigned int size = lseek(fin, 0, SEEK_END);
|
||||
lseek(fin, 0, SEEK_SET);
|
||||
LOGD("%d bytes\n", size);
|
||||
|
||||
char filename[1024];
|
||||
|
||||
central_footer_t central_footer;
|
||||
uint32_t central_directory_in_position = 0;
|
||||
uint32_t central_directory_in_size = 0;
|
||||
uint32_t central_directory_out_size = 0;
|
||||
|
||||
int i;
|
||||
for (i = size - 4; i >= 0; i--) {
|
||||
uint32_t magic = 0;
|
||||
if (!xseekread(fin, i, &magic, sizeof(uint32_t))) return 0;
|
||||
if (magic == MAGIC_CENTRAL_FOOTER) {
|
||||
LOGD("central footer @ %08X\n", i);
|
||||
if (!xseekread(fin, i, ¢ral_footer, sizeof(central_footer_t))) return 0;
|
||||
|
||||
central_header_t central_header;
|
||||
if (!xseekread(fin, central_footer.central_directory_offset, ¢ral_header, sizeof(central_header_t))) return 0;
|
||||
if ( central_header.signature == MAGIC_CENTRAL_HEADER ) {
|
||||
central_directory_in_position = central_footer.central_directory_offset;
|
||||
central_directory_in_size = size - central_footer.central_directory_offset;
|
||||
LOGD("central header @ %08X (%d)\n", central_footer.central_directory_offset, central_footer.central_directory_size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (central_directory_in_position == 0) return 0;
|
||||
|
||||
unsigned char* central_directory_in = (unsigned char*)malloc(central_directory_in_size);
|
||||
unsigned char* central_directory_out = (unsigned char*)malloc(central_directory_in_size);
|
||||
if (!xseekread(fin, central_directory_in_position, central_directory_in, central_directory_in_size)) return 0;
|
||||
memset(central_directory_out, 0, central_directory_in_size);
|
||||
|
||||
unlink(filenameOut);
|
||||
int fout = open(filenameOut, O_CREAT | O_WRONLY | O_BINARY, 0644);
|
||||
if (fout > 0) {
|
||||
|
||||
uintptr_t central_directory_in_index = 0;
|
||||
uintptr_t central_directory_out_index = 0;
|
||||
|
||||
central_header_t* central_header = NULL;
|
||||
|
||||
uint32_t out_index = 0;
|
||||
|
||||
while (1) {
|
||||
central_header = (central_header_t*)¢ral_directory_in[central_directory_in_index];
|
||||
if (central_header->signature != MAGIC_CENTRAL_HEADER) break;
|
||||
|
||||
filename[central_header->length_filename] = (char)0;
|
||||
memcpy(filename, ¢ral_directory_in[central_directory_in_index + sizeof(central_header_t)], central_header->length_filename);
|
||||
LOGD("%s (%d --> %d) [%08X] (%d)\n", filename, central_header->size_uncompressed, central_header->size_compressed, central_header->crc32, central_header->length_extra + central_header->length_comment);
|
||||
|
||||
local_header_t local_header;
|
||||
if (!xseekread(fin, central_header->offset, &local_header, sizeof(local_header_t))) return 0;
|
||||
|
||||
// save and update to next index before we clobber the data
|
||||
uint16_t compression_method_old = central_header->compression_method;
|
||||
uint32_t size_compressed_old = central_header->size_compressed;
|
||||
uint32_t offset_old = central_header->offset;
|
||||
uint32_t length_extra_old = central_header->length_extra;
|
||||
central_directory_in_index += sizeof(central_header_t) + central_header->length_filename + central_header->length_extra + central_header->length_comment;
|
||||
|
||||
// copying, rewriting, and correcting local and central headers so all the information matches, and no data descriptors are necessary
|
||||
central_header->offset = out_index;
|
||||
central_header->flags = central_header->flags & !8;
|
||||
if (decompress && (compression_method_old == 8)) {
|
||||
central_header->compression_method = 0;
|
||||
central_header->size_compressed = central_header->size_uncompressed;
|
||||
}
|
||||
central_header->length_extra = 0;
|
||||
central_header->length_comment = 0;
|
||||
local_header.compression_method = central_header->compression_method;
|
||||
local_header.flags = central_header->flags;
|
||||
local_header.crc32 = central_header->crc32;
|
||||
local_header.size_uncompressed = central_header->size_uncompressed;
|
||||
local_header.size_compressed = central_header->size_compressed;
|
||||
local_header.length_extra = 0;
|
||||
|
||||
if (!xseekwrite(fout, out_index, &local_header, sizeof(local_header_t))) return 0;
|
||||
out_index += sizeof(local_header_t);
|
||||
if (!xseekwrite(fout, out_index, &filename[0], central_header->length_filename)) return 0;
|
||||
out_index += central_header->length_filename;
|
||||
|
||||
if (decompress && (compression_method_old == 8)) {
|
||||
if (!xdecompress(fin, fout, offset_old + sizeof(local_header_t) + central_header->length_filename + length_extra_old, out_index, size_compressed_old)) return 0;
|
||||
} else {
|
||||
if (!xfilecopy(fin, fout, offset_old + sizeof(local_header_t) + central_header->length_filename + length_extra_old, out_index, size_compressed_old)) return 0;
|
||||
}
|
||||
out_index += local_header.size_compressed;
|
||||
|
||||
memcpy(¢ral_directory_out[central_directory_out_index], central_header, sizeof(central_header_t) + central_header->length_filename);
|
||||
central_directory_out_index += sizeof(central_header_t) + central_header->length_filename;
|
||||
}
|
||||
|
||||
central_directory_out_size = central_directory_out_index;
|
||||
central_footer.central_directory_size = central_directory_out_size;
|
||||
central_footer.central_directory_offset = out_index;
|
||||
central_footer.length_comment = 0;
|
||||
if (!xseekwrite(fout, out_index, central_directory_out, central_directory_out_size)) return 0;
|
||||
out_index += central_directory_out_size;
|
||||
if (!xseekwrite(fout, out_index, ¢ral_footer, sizeof(central_footer_t))) return 0;
|
||||
|
||||
LOGD("central header @ %08X (%d)\n", central_footer.central_directory_offset, central_footer.central_directory_size);
|
||||
LOGD("central footer @ %08X\n", out_index);
|
||||
|
||||
close(fout);
|
||||
ok = 1;
|
||||
}
|
||||
|
||||
free(central_directory_in);
|
||||
free(central_directory_out);
|
||||
close(fin);
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
#ifndef MAGISKMANAGER_ZIPADJUST_H_H
|
||||
#define MAGISKMANAGER_ZIPADJUST_H_H
|
||||
|
||||
#include <android/log.h>
|
||||
|
||||
int zipadjust(const char* filenameIn, const char* filenameOut, int decompress);
|
||||
|
||||
#define LOG_TAG "zipadjust"
|
||||
|
||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||
|
||||
#endif //MAGISKMANAGER_ZIPADJUST_H_H
|
||||
@@ -1,4 +0,0 @@
|
||||
<vector android:height="24dp" android:viewportHeight="400.0"
|
||||
android:viewportWidth="400.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillAlpha="1.00" android:fillColor="#000000" android:pathData="M200.6,24.2C231.8,24.2 263,33 289.6,49.5C304.7,58.8 318.2,70.7 329.8,84.1C351.5,109.6 365.2,141.7 368.8,175C373.6,217.4 361.5,261.5 335.5,295.5C334.2,297.1 332.8,298.7 331.9,300.7C341.7,310.1 351,320 360.9,329.3C322.7,339.3 284.5,349.6 246.3,359.9C256.4,323.5 266,287 275.7,250.5C262.4,250.5 249.1,250.5 235.8,250.5C228.5,269.8 221.2,289.1 214.1,308.4C205.9,308.6 197.8,308.5 189.6,308.5C188.4,306.7 187.1,304.9 185.9,303C192.4,285.5 199.1,268 205.6,250.5C189.5,250.6 173.4,250.3 157.4,250.6C150.4,270 142.9,289.2 135.8,308.5C129.8,308.5 123.9,308.4 117.9,308.6C130.4,317.8 144,325.6 158.9,330.3C171.9,334.6 185.6,336.6 199.4,336.7C199.4,349.4 199.3,362.1 199.4,374.8C165.8,374.9 132.2,364.5 104.4,345.6C91.7,337 80.3,326.5 70.2,314.9C48.5,289.4 34.8,257.3 31.2,224C26.3,180.4 39.2,134.9 66.8,100.7C67.2,99.9 67.7,99.2 68.1,98.4C58.3,88.9 49,79 39.1,69.7C77.3,59.7 115.6,49.4 153.7,39.1C143.6,75.7 133.8,112.5 124.1,149.3C137.9,149.3 151.7,149.2 165.5,149.3C172.9,130 180.2,110.6 187.3,91.2C195.5,90.8 203.7,91 211.8,91C213,92.8 214.3,94.6 215.5,96.3C209.1,114 202.3,131.6 195.8,149.2C211.8,149.3 227.8,149.2 243.9,149.3C251.2,129.9 258.3,110.3 265.8,90.9C271.5,90.8 277.3,91.6 282.9,90.4C280.2,89.5 278.1,87.7 275.9,86.1C254,70.7 227.4,62.2 200.6,62.3C200.6,49.6 200.7,36.9 200.6,24.2M292.5,100C286.4,116.4 280.2,132.8 274,149.3C280.9,149.2 287.8,149.3 294.7,149.2C296,150.8 297.3,152.4 298.6,153.9C297.1,162.1 295.7,170.3 294.3,178.5C283.9,178.5 273.4,178.5 262.9,178.5C257.5,192.7 252.1,206.9 246.9,221.2C258.7,221.3 270.5,221.1 282.3,221.3C283.5,222.9 284.8,224.4 286.1,225.9C284.9,233.7 283.4,241.4 282.1,249.1C281.6,251 283.6,252.1 284.6,253.3C291.5,259.8 297.7,266.9 304.8,273.1C312.9,262.1 319.6,250.1 324.2,237.3C334.3,208.7 334.2,176.7 323.7,148.3C317,130.1 306.4,113.4 292.5,100M95.2,125.9C86.2,138 79,151.4 74.5,165.8C65.3,194.4 66.4,226.3 77.6,254.2C84.6,271.5 95.1,287.4 108.7,300.1C114.9,283.6 121.3,267.1 127.2,250.5C121.5,250.5 115.8,250.5 110,250.5C108.6,250.4 106.7,251 105.8,249.5C104.5,247.9 102.3,246.3 102.9,244.1C104.2,236.5 105.5,228.9 106.8,221.2C117.4,221.2 128.1,221.4 138.7,221.1C143.9,206.9 149.3,192.7 154.6,178.5C142.9,178.4 131.1,178.6 119.3,178.4C117.6,177.3 116.4,175.3 115,173.8C116.3,165.8 117.9,157.9 119,150C118.3,148.2 116.6,147.1 115.3,145.7C108.6,139.2 102.3,132.1 95.2,125.9M168.8,221.2C184.8,221.2 200.8,221.3 216.8,221.2C222.2,207 227.5,192.8 232.8,178.5C216.8,178.5 200.7,178.5 184.6,178.5C179.4,192.8 174,207 168.8,221.2Z"/>
|
||||
</vector>
|
||||
@@ -1,7 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path android:fillColor="#000" android:pathData="M5.41,21L6.12,17H2.12L2.47,15H6.47L7.53,9H3.53L3.88,7H7.88L8.59,3H10.59L9.88,7H15.88L16.59,3H18.59L17.88,7H21.88L21.53,9H17.53L16.47,15H20.47L20.12,17H16.12L15.41,21H13.41L14.12,17H8.12L7.41,21H5.41M9.53,9L8.47,15H14.47L15.53,9H9.53Z" />
|
||||
</vector>
|
||||
@@ -1,86 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<android.support.v7.widget.CardView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:card_view="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginBottom="@dimen/card_vertical_margin"
|
||||
android:layout_marginEnd="@dimen/card_horizontal_margin"
|
||||
android:layout_marginStart="@dimen/card_horizontal_margin"
|
||||
android:layout_marginTop="@dimen/card_vertical_margin"
|
||||
style="?attr/cardStyle"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
card_view:cardCornerRadius="@dimen/card_corner_radius"
|
||||
card_view:cardElevation="@dimen/card_elevation">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:padding="@dimen/card_layout_padding">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/app_icon"
|
||||
android:layout_width="@dimen/card_appicon_size"
|
||||
android:layout_height="@dimen/card_appicon_size"
|
||||
android:layout_centerVertical="true"
|
||||
android:scaleType="centerCrop"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_alignBottom="@+id/app_icon"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical"
|
||||
android:paddingEnd="@dimen/card_appicon_size"
|
||||
android:paddingStart="65dp"
|
||||
android:weightSum="1">
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/app_name"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:maxLines="1"
|
||||
android:paddingEnd="3dp"
|
||||
android:paddingStart="3dp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/app_package"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="25dp"
|
||||
android:ellipsize="marquee"
|
||||
android:gravity="center_vertical"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
android:paddingEnd="3dp"
|
||||
android:paddingStart="3dp"
|
||||
android:singleLine="true"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/checkbox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:focusable="false"
|
||||
android:gravity="center"
|
||||
android:src="@drawable/ic_menu_overflow_material"
|
||||
android:checked="false" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
|
||||
</android.support.v7.widget.CardView>
|
||||
|
||||
|
||||
@@ -1,195 +0,0 @@
|
||||
<resources>
|
||||
<!--Welcome Activity-->
|
||||
<string name="modules">Modules</string>
|
||||
<string name="downloads">Téléchargements</string>
|
||||
<string name="superuser">Superuser</string>
|
||||
<string name="log">Journal</string>
|
||||
<string name="settings">Paramètres</string>
|
||||
<string name="install">Installer</string>
|
||||
<!--Status Fragment-->
|
||||
<string name="magisk_version_error">Magisk non installé</string>
|
||||
<string name="checking_for_updates">Vérification de mises à jour…</string>
|
||||
<string name="magisk_update_available">Magisk v%1$s disponible !</string>
|
||||
<string name="invalid_update_channel">Canal de mise à jour invalide</string>
|
||||
<string name="safetyNet_check_text">Appuyer pour lancer le contrôle SafetyNet</string>
|
||||
<string name="checking_safetyNet_status">Vérification de l\'état de SafetyNet…</string>
|
||||
<string name="safetyNet_check_success">Contrôle SafetyNet passé avec succès</string>
|
||||
<string name="safetyNet_api_error">Erreur d\'API SafetyNet</string>
|
||||
<string name="safetyNet_network_loss">Connexion réseau indisponible</string>
|
||||
<string name="safetyNet_service_disconnected">Le service a été tué</string>
|
||||
<string name="safetyNet_res_invalid">La réponse est invalide</string>
|
||||
<!--Install Fragment-->
|
||||
<string name="advanced_settings_title">Paramètres avancés</string>
|
||||
<string name="keep_force_encryption">Garder le chiffrement forcé</string>
|
||||
<string name="keep_dm_verity">Garder dm-verity</string>
|
||||
<string name="current_magisk_title">Version installée : %1$s</string>
|
||||
<string name="install_magisk_title">Dernière Magisk : %1$s</string>
|
||||
<string name="uninstall">Désinstaller</string>
|
||||
<string name="uninstall_magisk_title">Désinstaller Magisk</string>
|
||||
<string name="uninstall_magisk_msg">Tous les modules seront désactivés/effacés. Le root sera enlevé et vos données seront potentiellement chiffrées si elles ne le sont pas actuellement</string>
|
||||
<string name="update">Mise à jour %1$s</string>
|
||||
<!--Module Fragment-->
|
||||
<string name="no_info_provided">(Aucune information transmise)</string>
|
||||
<string name="no_modules_found">Aucun module trouvé</string>
|
||||
<string name="update_file_created">Le module va être mis à jour au prochain redémarrage</string>
|
||||
<string name="remove_file_created">Le module va être supprimé au prochain redémarrage</string>
|
||||
<string name="remove_file_deleted">Le module ne va pas être supprimé au prochain redémarrage</string>
|
||||
<string name="disable_file_created">Le module va être désactivé au prochain redémarrage</string>
|
||||
<string name="disable_file_removed">Le module va être activé au prochain redémarrage</string>
|
||||
<string name="author">Créé par %1$s</string>
|
||||
<!--Repo Fragment-->
|
||||
<string name="update_available">Mise à jour disponible</string>
|
||||
<string name="installed">Installé</string>
|
||||
<string name="not_installed">Non installé</string>
|
||||
<!--Log Fragment-->
|
||||
<string name="menuSaveLog">Enregistrer journal</string>
|
||||
<string name="menuReload">Actualiser</string>
|
||||
<string name="menuClearLog">Effacer le journal maintenant</string>
|
||||
<string name="logs_cleared">Journal effacé avec succès</string>
|
||||
<string name="log_is_empty">Journal vide</string>
|
||||
<string name="logs_save_failed">Impossible d\'écrire le journal sur la carte SD:</string>
|
||||
<!--About Activity-->
|
||||
<string name="about">À propos</string>
|
||||
<string name="app_developers">Principaux développeurs</string>
|
||||
<string name="app_developers_"><![CDATA[Appli créée par <a href="https://github.com/topjohnwu">topjohnwu</a> en collaboration avec <a href="https://github.com/d8ahazard">Digitalhigh</a> et <a href="https://github.com/dvdandroid">Dvdandroid</a>.]]></string>
|
||||
<string name="app_changelog">Modifications de l\'application</string>
|
||||
<string name="translators">Primokorn, Naboleo, tonymanou</string>
|
||||
<string name="app_version">Version de l\'application</string>
|
||||
<string name="app_source_code">Code source</string>
|
||||
<string name="donation">Dons</string>
|
||||
<string name="app_translators">Traducteurs de l\'application</string>
|
||||
<string name="support_thread">Fil de discussion pour support</string>
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="permissionNotGranted">Cette fonctionnalité ne marchera pas sans la permission d\'écriture sur le stockage externe.</string>
|
||||
<string name="no_thanks">Non merci</string>
|
||||
<string name="yes">Oui</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="close">Fermer</string>
|
||||
<string name="repo_install_title">Installer %1$s</string>
|
||||
<string name="repo_install_msg">Voulez-vous installer %1$s maintenant ?</string>
|
||||
<string name="download">Télécharger</string>
|
||||
<string name="download_file_error">Erreur de téléchargement du fichier</string>
|
||||
<string name="reboot">Redémarrer</string>
|
||||
<string name="zip_process_msg">Exécution du fichier zip…</string>
|
||||
<string name="downloading_toast">Téléchargement %1$s</string>
|
||||
<string name="magisk_update_title">Nouvelle mise à jour de Magisk disponible !</string>
|
||||
<string name="settings_reboot_toast">Redémarrer pour appliquer les changements</string>
|
||||
<string name="release_notes">Notes de version</string>
|
||||
<string name="repo_cache_cleared">Cache du dépôt effacé</string>
|
||||
<string name="safetyNet_hide_notice">Cette application utilise SafetyNet\nDéjà pris en charge par MagiskHide par défaut</string>
|
||||
<string name="process_error">Erreur de traitement</string>
|
||||
<string name="internal_storage">Le zip est stocké dans :\n[Stockage interne]%1$s</string>
|
||||
<string name="zip_process_title">Traitement</string>
|
||||
<string name="zip_download_title">Téléchargement</string>
|
||||
<string name="zip_download_msg">Téléchargement du fichier zip (%1$d%%)…</string>
|
||||
<string name="manager_update_title">Nouvelle mise à jour de Magisk Manager disponible !</string>
|
||||
<string name="manager_download_install">Appuyez pour télécharger et installer</string>
|
||||
<string name="dtbo_patched_title">DTBO a été patché !</string>
|
||||
<string name="dtbo_patched_reboot">Magisk Manager a patché dtbo.img, veuillez redémarrer</string>
|
||||
<string name="magisk_updates">Mises à jour Magisk</string>
|
||||
<string name="flashing">Flashage en cours</string>
|
||||
<string name="hide_manager_toast">Masquage de Magisk Manager…</string>
|
||||
<string name="hide_manager_toast2">Cela peut prendre du temps…</string>
|
||||
<string name="hide_manager_fail_toast">Échec du masquage de Magisk Manager…</string>
|
||||
<string name="download_zip_only">Télécharger uniquement le zip</string>
|
||||
<string name="patch_boot_file">Patcher le fichier image Boot</string>
|
||||
<string name="direct_install">Installation directe (recommandée)</string>
|
||||
<string name="install_second_slot">Intaller sur le second slot (après une OTA)</string>
|
||||
<string name="select_method">Choisir une méthode</string>
|
||||
<string name="no_boot_file_patch_support">La version cible de Magisk ne supporte pas la modification du fichier image Boot</string>
|
||||
<string name="boot_file_patch_msg">Sélectionnez l\'image Boot stock au format .img ou .img.tar</string>
|
||||
<string name="complete_uninstall">Désinstaller complètement</string>
|
||||
<string name="restore_stock_boot">Restaurer le Boot stock</string>
|
||||
<string name="restore_done">Restauration effectuée !</string>
|
||||
<string name="restore_fail">Pas de sauvegarde stock disponible !</string>
|
||||
<string name="uninstall_toast">Désinstallation de Magisk Manager dans 5 secondes, n\'oubliez pas de redémarrer par la suite</string>
|
||||
<string name="proprietary_title">Télécharger le code propriétaire</string>
|
||||
<string name="proprietary_notice">Magisk Manager est un logiciel FOSS et ne contient donc pas le code propriétaire de l\'API SafetyNet de Google. Autorisez-vous Magisk Manager à télécharger une extension (qui contient GoogleApiClient) afin de tester SafetyNet ?</string>
|
||||
<string name="su_db_corrupt">La base de données SU est corrompue, création d\'une nouvelle base de données prévue</string>
|
||||
<!--Settings Activity -->
|
||||
<string name="settings_general_category">Général</string>
|
||||
<string name="settings_dark_theme_title">Thème sombre</string>
|
||||
<string name="settings_dark_theme_summary">Activer le thème sombre</string>
|
||||
<string name="settings_notification_title">Notification de mise à jour</string>
|
||||
<string name="settings_notification_summary">Afficher des notifications de mises à jour quand une nouvelle version est disponible</string>
|
||||
<string name="settings_clear_cache_title">Effacer le cache du dépôt</string>
|
||||
<string name="settings_clear_cache_summary">Effacer les informations en cache des dépôts distants, pour forcer une actualisation de l\'application</string>
|
||||
<string name="settings_core_only_title">Mode Magisk Core uniquement</string>
|
||||
<string name="settings_core_only_summary">Activer uniquement les fonctionnalités de base, tous les modules ne seront pas chargés. MagiskSU, MagiskHide et les hosts systemless restent activés</string>
|
||||
<string name="settings_magiskhide_summary">Masquer Magisk de diverses détections</string>
|
||||
<string name="settings_hosts_title">Hosts systemless</string>
|
||||
<string name="settings_hosts_summary">Support hosts systemless pour les applications type Adblock</string>
|
||||
<string name="settings_su_app_adb">Applications et ADB</string>
|
||||
<string name="settings_su_app">Applications uniquement</string>
|
||||
<string name="settings_su_adb">ADB uniquement</string>
|
||||
<string name="settings_su_disable">Désactivé</string>
|
||||
<string name="settings_su_request_10">10 secondes</string>
|
||||
<string name="settings_su_request_20">20 secondes</string>
|
||||
<string name="settings_su_request_30">30 secondes</string>
|
||||
<string name="settings_su_request_60">60 secondes</string>
|
||||
<string name="superuser_access">Accès Superuser</string>
|
||||
<string name="auto_response">Réponse automatique</string>
|
||||
<string name="request_timeout">Délai de requête</string>
|
||||
<string name="superuser_notification">Notification Superuser</string>
|
||||
<string name="request_timeout_summary">%1$s secondes</string>
|
||||
<string name="settings_hide_manager_title">Masquer Magisk Manager</string>
|
||||
<string name="settings_hide_manager_summary">Reconstruire Magisk Manager avec un nom de paquet aléatoire</string>
|
||||
<string name="language">Langue</string>
|
||||
<string name="system_default">(Selon système)</string>
|
||||
<string name="settings_update">Paramètres de mises à jour</string>
|
||||
<string name="settings_update_channel_title">Canal de mise à jour</string>
|
||||
<string name="settings_update_stable">Stable</string>
|
||||
<string name="settings_update_beta">Beta</string>
|
||||
<string name="settings_update_custom">Personnaliser</string>
|
||||
<string name="settings_update_custom_msg">Insérer une URL personnalisée</string>
|
||||
<string name="settings_boot_format_title">Format de sortie du boot patché</string>
|
||||
<string name="settings_boot_format_summary">Sélectionner le format de l\'image boot patchée finale.\nChoisir .img pour flasher via fastboot/mode download ; choisir .img.tar pour flasher via ODIN.</string>
|
||||
<string name="settings_su_reauth_title">Ré-authentifier après mise à jour</string>
|
||||
<string name="settings_su_reauth_summary">Ré-authentifier les permissions superuser après les mises à jour d\'une application</string>
|
||||
<string name="multiuser_mode">Mode multi-utilisateurs</string>
|
||||
<string name="settings_owner_only">Appareil du propriétaire uniquement</string>
|
||||
<string name="settings_owner_manage">Appareil du propriétaire géré</string>
|
||||
<string name="settings_user_independent">Utilisateur indépendant</string>
|
||||
<string name="owner_only_summary">Seul le propriétaire a l\'accès root</string>
|
||||
<string name="owner_manage_summary">Seul le propriétaire peut gérer les accès root et recevoir des demandes de permissions</string>
|
||||
<string name="user_indepenent_summary">Chaque utilisateur dispose de ses propres règles root</string>
|
||||
<string name="multiuser_hint_owner_request">Une requête a été envoyée au propriétaire de l\'appareil. Veuillez basculer sur le profil du propriétaire et accorder la permission</string>
|
||||
<string name="mount_namespace_mode">Monter le mode espace de noms</string>
|
||||
<string name="settings_ns_global">Espace de noms global</string>
|
||||
<string name="settings_ns_requester">Hériter de l\'espace de noms</string>
|
||||
<string name="settings_ns_isolate">Espace de noms isolé</string>
|
||||
<string name="global_summary">Toutes les sessions root utilisent l\'espace de noms global monté</string>
|
||||
<string name="requester_summary">Les sessions root hériteront des espaces de noms du demandeur</string>
|
||||
<string name="isolate_summary">Chaque session root aura son propre espace de noms isolé</string>
|
||||
<!--Superuser-->
|
||||
<string name="su_request_title">Requête Superuser</string>
|
||||
<string name="deny_with_str">Refuser%1$s</string>
|
||||
<string name="deny">Refuser</string>
|
||||
<string name="prompt">Demander</string>
|
||||
<string name="grant">Accepter</string>
|
||||
<string name="su_warning">Accepter un accès complet à votre appareil.\nRefuser si vous n\'êtes pas sûr!</string>
|
||||
<string name="forever">Toujours</string>
|
||||
<string name="once">Une fois</string>
|
||||
<string name="tenmin">10 min</string>
|
||||
<string name="twentymin">20 min</string>
|
||||
<string name="thirtymin">30 min</string>
|
||||
<string name="sixtymin">60 min</string>
|
||||
<string name="su_allow_toast">%1$s a obtenu les droits Superuser</string>
|
||||
<string name="su_deny_toast">%1$s n\'a pas obtenu les droits Superuser</string>
|
||||
<string name="no_apps_found">Aucun application trouvée</string>
|
||||
<string name="su_snack_grant">Les droits Superuser de %1$s sont accordés</string>
|
||||
<string name="su_snack_deny">Les droits Superuser de %1$s sont refusés</string>
|
||||
<string name="su_snack_notif_on">Les notifications pour %1$s sont activées</string>
|
||||
<string name="su_snack_notif_off">Les notifications pour %1$s sont désactivées</string>
|
||||
<string name="su_snack_log_on">La journalisation pour %1$s est activée</string>
|
||||
<string name="su_snack_log_off">La journalisation pour %1$s est désactivée</string>
|
||||
<string name="su_snack_revoke">Les droits de %1$s sont annulés</string>
|
||||
<string name="su_revoke_title">Annuler ?</string>
|
||||
<string name="su_revoke_msg">Vous confirmez l\'annulation des droits pour %1$s ?</string>
|
||||
<string name="toast">Toast</string>
|
||||
<string name="none">Aucun</string>
|
||||
<!--Superuser logs-->
|
||||
<string name="pid">PID :\u0020</string>
|
||||
<string name="target_uid">UID cible :\\u0020</string>
|
||||
<string name="command">Commande :\u0020</string>
|
||||
</resources>
|
||||
@@ -1,212 +0,0 @@
|
||||
<resources>
|
||||
<!--Universal-->
|
||||
|
||||
<!--Welcome Activity-->
|
||||
<string name="modules">Moduli</string>
|
||||
<string name="downloads">Download</string>
|
||||
<string name="superuser">Superuser</string>
|
||||
<string name="log">Registro eventi</string>
|
||||
<string name="settings">Impostazioni</string>
|
||||
<string name="install">Installa</string>
|
||||
|
||||
<!--Status Fragment-->
|
||||
<string name="magisk_version_error">Magisk non installato</string>
|
||||
|
||||
<string name="checking_for_updates">Controllo aggiornamenti…</string>
|
||||
<string name="magisk_update_available">È disponibile Magisk v%1$s!</string>
|
||||
<string name="invalid_update_channel">Canale di aggiornamento non valido</string>
|
||||
<string name="safetyNet_check_text">Tocca per controllare SafetyNet</string>
|
||||
<string name="checking_safetyNet_status">Controllo stato di SafetyNet</string>
|
||||
<string name="safetyNet_check_success">Controllo di SafetyNet OK</string>
|
||||
<string name="safetyNet_api_error">Errore API di SafetyNet</string>
|
||||
<string name="safetyNet_network_loss">Connessione di rete persa</string>
|
||||
<string name="safetyNet_service_disconnected">Il servizio è stato terminato</string>
|
||||
<string name="safetyNet_res_invalid">La risposta non è valida</string>
|
||||
|
||||
<!--Install Fragment-->
|
||||
<string name="advanced_settings_title">Impostazioni avanzate</string>
|
||||
<string name="keep_force_encryption">Mantieni crittografia forzata</string>
|
||||
<string name="keep_dm_verity">Mantieni dm-verity</string>
|
||||
<string name="current_magisk_title">Versione installata: %1$s</string>
|
||||
<string name="install_magisk_title">Ultima versione disponibile: %1$s</string>
|
||||
<string name="uninstall">Disinstalla</string>
|
||||
<string name="uninstall_magisk_title">Disinstalla Magisk</string>
|
||||
<string name="uninstall_magisk_msg">Tutti i moduli verranno disabilitati/rimossi. Il root verrà rimosso e se il dispositivo non è crittografato è possibile che vengano crittografati tutti i dati</string>
|
||||
<string name="update">Aggiorna %1$s</string>
|
||||
|
||||
<!--Module Fragment-->
|
||||
<string name="no_info_provided">(Nessuna informazione)</string>
|
||||
<string name="no_modules_found">Nessun modulo trovato</string>
|
||||
<string name="update_file_created">Il modulo sarà aggiornato al prossimo riavvio</string>
|
||||
<string name="remove_file_created">Il modulo sarà rimosso al prossimo riavvio</string>
|
||||
<string name="remove_file_deleted">Il modulo non sarà rimosso al prossimo riavvio</string>
|
||||
<string name="disable_file_created">Il modulo sarà disabilitato al prossimo riavvio</string>
|
||||
<string name="disable_file_removed">Il modulo sarà abilitato al prossimo riavvio</string>
|
||||
<string name="author">Creato da: %1$s</string>
|
||||
|
||||
<!--Repo Fragment-->
|
||||
<string name="update_available">Aggiornamento disponibile</string>
|
||||
<string name="installed">Installato</string>
|
||||
<string name="not_installed">Non installato</string>
|
||||
|
||||
<!--Log Fragment-->
|
||||
<string name="menuSaveLog">Salva registro eventi</string>
|
||||
<string name="menuReload">Ricarica</string>
|
||||
<string name="menuClearLog">Pulisci registro eventi</string>
|
||||
<string name="logs_cleared">Registro eventi creato correttamente</string>
|
||||
<string name="log_is_empty">Il registro eventi è vuoto</string>
|
||||
<string name="logs_save_failed">Impossibile scrivere registro eventi nella SD</string>
|
||||
|
||||
<!--About Activity-->
|
||||
<string name="about">Informazioni</string>
|
||||
<string name="app_developers">Sviluppatori</string>
|
||||
<string name="app_developers_"><![CDATA[App creata da <a href="https://github.com/topjohnwu">topjohnwu</a> in collaborazione con <a href="https://github.com/d8ahazard">Digitalhigh</a> e <a href="https://github.com/dvdandroid">Dvdandroid</a>.]]></string>
|
||||
<string name="app_changelog">Novità</string>
|
||||
<string name="translators">Auanasgheps | Fabb2303 | bovirus</string>
|
||||
<string name="app_version">Versione app</string>
|
||||
<string name="app_source_code">Codice sorgente</string>
|
||||
<string name="donation">Dona</string>
|
||||
<string name="app_translators">Traduttori app</string>
|
||||
<string name="support_thread">Supporto app</string>
|
||||
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="permissionNotGranted">Questa funzione non sarà operativa senza il permesso di scrittura nella memoria di archiviazione esterna.</string>
|
||||
<string name="no_thanks">No, grazie</string>
|
||||
<string name="yes">Sì</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="close">Chiudi</string>
|
||||
<string name="repo_install_title">Installazione %1$s</string>
|
||||
<string name="repo_install_msg">Vuoi installare %1$s ?</string>
|
||||
<string name="download">Download</string>
|
||||
<string name="download_file_error">Errore nel download del file</string>
|
||||
<string name="reboot">Riavvia</string>
|
||||
<string name="downloading_toast">Download di %1$s</string>
|
||||
<string name="magisk_update_title">Disponibile un nuovo aggiornamento di Magisk!</string>
|
||||
<string name="settings_reboot_toast">Riavvia per applicare</string>
|
||||
<string name="release_notes">Note di rilascio</string>
|
||||
<string name="repo_cache_cleared">La cache delle repository è stata pulita</string>
|
||||
<string name="safetyNet_hide_notice">Quest\'app usa SafetyNet\ned è già gestita da MagiskHide</string>
|
||||
<string name="process_error">Errore di elaborazione</string>
|
||||
<string name="internal_storage">Il file zip si trova in:\n[memoria interna]%1$s</string>
|
||||
<string name="zip_download_title">Download in corso</string>
|
||||
<string name="zip_download_msg">Download del file zip (%1$d%%) …</string>
|
||||
<string name="zip_process_title">Elaborazione</string>
|
||||
<string name="zip_process_msg">Elaborazione del file zip…</string>
|
||||
<string name="manager_update_title">Nuovo aggiornamento di Magisk Manager disponibile!</string>
|
||||
<string name="manager_download_install">Premere per scaricare e installare</string>
|
||||
<string name="dtbo_patched_title">DTBO è stato aggiornato!</string>
|
||||
<string name="dtbo_patched_reboot">Magisk Manager ha aggiornato dtbo.img, riavvia per completare</string>
|
||||
<string name="magisk_updates">Aggiornamento Magisk</string>
|
||||
<string name="flashing">Flash in corso…</string>
|
||||
<string name="hide_manager_toast">Nascondendo Magisk Manager…</string>
|
||||
<string name="hide_manager_toast2">Potrebbe volerci un po\'…</string>
|
||||
<string name="hide_manager_fail_toast">Non è stato possibile nascondere Magisk Manager</string>
|
||||
<string name="download_zip_only">Scarica solo il file zip</string>
|
||||
<string name="patch_boot_file">Aggiorna l\'immagine di boot</string>
|
||||
<string name="direct_install">Installazione diretta (raccomandata)</string>
|
||||
<string name="install_second_slot">Installa nel secondo slot (dopo OTA)</string>
|
||||
<string name="select_method">Seleziona un metodo</string>
|
||||
<string name="no_boot_file_patch_support">La versione Magisk di destinazione non supporta l\'aggiornamento dell\'immagine di boot</string>
|
||||
<string name="boot_file_patch_msg">Seleziona l\'immagine originale di boot in formato .img o img.tar</string>
|
||||
<string name="complete_uninstall">Completa disinstallazione</string>
|
||||
<string name="restore_stock_boot">Ripristina l\'immagine originale di boot</string>
|
||||
<string name="restore_done">Ripristino completato!</string>
|
||||
<string name="restore_fail">Non esiste un\'immagine originale di boot!</string>
|
||||
<string name="uninstall_toast">Disinstallazione di Magisk Manager in 5 secondi, riavvia manualmente per completare</string>
|
||||
<string name="proprietary_title">Scarica codice proprietario</string>
|
||||
<string name="proprietary_notice">Magisk Manager è FOSS, quindi non contiene il codice proprietario delle API Google SafetyNet.\n\nVuoi permettere il download di un\'estensione (che contiene GoogleApiClient) per controllare lo stato di SafetyNet?</string>
|
||||
<string name="su_db_corrupt">Il database SU è corrotto, un nuovo DB verrà ricreato</string>
|
||||
|
||||
<!--Settings Activity -->
|
||||
<string name="settings_general_category">Generale</string>
|
||||
<string name="settings_dark_theme_title">Tema scuro</string>
|
||||
<string name="settings_dark_theme_summary">Abilita il tema scuro</string>
|
||||
<string name="settings_notification_title">Notifica aggiornamenti</string>
|
||||
<string name="settings_notification_summary">Mostra una notifica quando sono disponibili aggiornamenti</string>
|
||||
<string name="settings_clear_cache_title">Pulisci cache repository</string>
|
||||
<string name="settings_clear_cache_summary">Pulisci la cache delle repository e forza l\'aggiornamento online dell\'app</string>
|
||||
<string name="settings_hide_manager_title">Nascondi Magisk Manager</string>
|
||||
<string name="settings_hide_manager_summary">Reinstalla Magisk Manager con un nome del pacchetto casuale</string>
|
||||
<string name="language">Lingua</string>
|
||||
<string name="system_default">(Predefinito)</string>
|
||||
<string name="settings_update">Impostazioni di aggiornamento</string>
|
||||
<string name="settings_update_channel_title">Canale di aggiornamento</string>
|
||||
<string name="settings_update_stable">Stabile</string>
|
||||
<string name="settings_update_beta">Beta</string>
|
||||
<string name="settings_update_custom">Personalizzato</string>
|
||||
+ <string name="settings_update_custom_msg">Inserisci un URL personalizzato</string>
|
||||
<string name="settings_boot_format_title">Formato dell\'immagine di boot aggiornata</string>
|
||||
<string name="settings_boot_format_summary">Seleziona il formato nel quale l\'immagine di boot verrà salvata.\nSeleziona .img per il flash in fastboot/download mode; Seleziona .img.tar per il flash con Odin.</string>
|
||||
<string name="settings_core_only_title">Modalità Magisk Core</string>
|
||||
<string name="settings_core_only_summary">Abilita solo le funzioni principali. Nessun modulo verrà caricato. MagiskSU, MagiskHide e host systemless rimarranno abilitati</string>
|
||||
<string name="settings_magiskhide_summary">Nasconde Magisk da numerose rilevazioni</string>
|
||||
<string name="settings_hosts_title">Host systemless</string>
|
||||
<string name="settings_hosts_summary">Supporto a host systemless per le app che bloccano le pubblicità</string>
|
||||
|
||||
<string name="settings_su_app_adb">App e ADB</string>
|
||||
<string name="settings_su_app">Solo app</string>
|
||||
<string name="settings_su_adb">Solo ADB</string>
|
||||
<string name="settings_su_disable">Disabilitato</string>
|
||||
<string name="settings_su_request_10">10 secondi</string>
|
||||
<string name="settings_su_request_20">20 secondi</string>
|
||||
<string name="settings_su_request_30">30 secondi</string>
|
||||
<string name="settings_su_request_60">60 secondi</string>
|
||||
<string name="superuser_access">Accesso Superuser</string>
|
||||
<string name="auto_response">Accesso predefinito</string>
|
||||
<string name="request_timeout">Timeout richiesta</string>
|
||||
<string name="superuser_notification">Notifica Superuser</string>
|
||||
<string name="request_timeout_summary">%1$s secondi</string>
|
||||
<string name="settings_su_reauth_title">Ri-autentica dopo un aggiornamento</string>
|
||||
<string name="settings_su_reauth_summary">Ri-autentica i permessi Superuser dopo un aggiornamento dell\'app</string>
|
||||
|
||||
<string name="multiuser_mode">Modalità multiutente</string>
|
||||
<string name="settings_owner_only">Solo proprietario del dispositivo</string>
|
||||
<string name="settings_owner_manage">Gestito dal proprietario utente</string>
|
||||
<string name="settings_user_independent">Utente indipendente</string>
|
||||
<string name="owner_only_summary">Solo il proprietario ha i permessi di root</string>
|
||||
<string name="owner_manage_summary">Solo il proprietario può gestire accesso root e ricevere richieste</string>
|
||||
<string name="user_indepenent_summary">Ogni utente ha le sue regole di root separate</string>
|
||||
<string name="multiuser_hint_owner_request">Una richiesta è stata inviata al proprietario del dispositivo. Accedi come proprietario dispositivo e concedi i permessi.</string>
|
||||
|
||||
<string name="mount_namespace_mode">Modalità mount Namespace</string>
|
||||
<string name="settings_ns_global">Namespace globale</string>
|
||||
<string name="settings_ns_requester">Namespace ereditato</string>
|
||||
<string name="settings_ns_isolate">Namespace isolato</string>
|
||||
<string name="global_summary">Tutte le sessioni di root erediteranno il Namespace globale</string>
|
||||
<string name="requester_summary">Le sessioni di root erediteranno il Namespace del loro richiedente</string>
|
||||
<string name="isolate_summary">Ogni sessione di root avrà il suo Namespace isolato</string>
|
||||
|
||||
<!--Superuser-->
|
||||
<string name="su_request_title">Richiesta Superuser</string>
|
||||
<string name="deny_with_str">Nega %1$s</string>
|
||||
<string name="deny">Nega</string>
|
||||
<string name="prompt">Chiedi</string>
|
||||
<string name="grant">Concedi</string>
|
||||
<string name="su_warning">Concede il pieno accesso al dispositivo.\nNega se non sei sicuro</string>
|
||||
<string name="forever">Sempre</string>
|
||||
<string name="once">Una volta</string>
|
||||
<string name="tenmin">10 minuti</string>
|
||||
<string name="twentymin">20 minuti</string>
|
||||
<string name="thirtymin">30 minuti</string>
|
||||
<string name="sixtymin">60 minuti</string>
|
||||
<string name="su_allow_toast">%1$s ha ottenuto i permessi Superuser</string>
|
||||
<string name="su_deny_toast">%1$s non ha ottenuto i permessi Superuser</string>
|
||||
<string name="no_apps_found">Nessuna app trovata</string>
|
||||
<string name="su_snack_grant"> %1$s ha ottenuto i permessi Superuser</string>
|
||||
<string name="su_snack_deny"> %1$s non ha ottenuto i permessi Superuser</string>
|
||||
<string name="su_snack_notif_on">Notifiche per %1$s abilitate</string>
|
||||
<string name="su_snack_notif_off">Notifiche per %1$s disabilitate</string>
|
||||
<string name="su_snack_log_on">Registro eventi abilitato per %1$s</string>
|
||||
<string name="su_snack_log_off">Registro eventi non abilitato per %1$s</string>
|
||||
<string name="su_snack_revoke">I diritti di %1$s sono stati revocati</string>
|
||||
<string name="su_revoke_title">Revocare?</string>
|
||||
<string name="su_revoke_msg">Confermi la revoca dei diritti di %1$s?</string>
|
||||
<string name="toast">Toast</string>
|
||||
<string name="none">Nessuno</string>
|
||||
|
||||
<!--Superuser logs-->
|
||||
<string name="pid">PID:\u0020</string>
|
||||
<string name="target_uid">UID destinazione:\u0020</string>
|
||||
<string name="command">Comando:\u0020</string>
|
||||
|
||||
</resources>
|
||||
@@ -1,148 +0,0 @@
|
||||
<resources>
|
||||
<!--Universal-->
|
||||
|
||||
<!--Welcome Activity-->
|
||||
<string name="modules">モジュール</string>
|
||||
<string name="downloads">ダウンロード</string>
|
||||
<string name="superuser">スーパーユーザー</string>
|
||||
<string name="log">ログ</string>
|
||||
<string name="settings">設定</string>
|
||||
<string name="install">インストール</string>
|
||||
|
||||
<!--Status Fragment-->
|
||||
<string name="magisk_version_error">Magisk がインストールされていません</string>
|
||||
|
||||
<string name="checking_for_updates">更新を確認中...</string>
|
||||
<string name="magisk_update_available">Magisk v%1$s が利用可能です!</string>
|
||||
<string name="safetyNet_check_text">タップしてSafetyNetチェックを開始</string>
|
||||
<string name="checking_safetyNet_status">SafetyNet Statusをチェック中…</string>
|
||||
|
||||
<!--Install Fragment-->
|
||||
<string name="advanced_settings_title">高度な設定</string>
|
||||
<string name="keep_force_encryption">強制的な暗号化を維持する</string>
|
||||
<string name="keep_dm_verity">dm-verityを維持する</string>
|
||||
<string name="current_magisk_title">インストール済: %1$s</string>
|
||||
<string name="install_magisk_title">最新: %1$s</string>
|
||||
<string name="uninstall">アンインストール</string>
|
||||
<string name="uninstall_magisk_title">Magiskをアンインストールします</string>
|
||||
|
||||
<!--Module Fragment-->
|
||||
<string name="no_info_provided">(情報がありません)</string>
|
||||
<string name="no_modules_found">モジュールが見つかりません</string>
|
||||
<string name="update_file_created">次の再起動時にモジュールを更新する</string>
|
||||
<string name="remove_file_created">次の再起動時にモジュールを削除する</string>
|
||||
<string name="remove_file_deleted">次の再起動時にモジュールを削除しない</string>
|
||||
<string name="disable_file_created">次の再起動時にモジュールを無効にする</string>
|
||||
<string name="disable_file_removed">次の再起動時にモジュールを有効にする</string>
|
||||
<string name="author">作者: %1$s</string>
|
||||
|
||||
<!--Repo Fragment-->
|
||||
<string name="update_available">利用可能な更新</string>
|
||||
<string name="installed">インストール済</string>
|
||||
<string name="not_installed">未インストール</string>
|
||||
|
||||
<!--Log Fragment-->
|
||||
<string name="menuSaveLog">ログ保存</string>
|
||||
<string name="menuReload">リロード</string>
|
||||
<string name="menuClearLog">ログを消去する</string>
|
||||
<string name="logs_cleared">ログは正常にクリアされました</string>
|
||||
<string name="log_is_empty">ログは空です</string>
|
||||
<string name="logs_save_failed">SDカードにログを書き込むことができません:</string>
|
||||
|
||||
<!--About Activity-->
|
||||
<string name="about">このアプリについて</string>
|
||||
<string name="app_developers">主な開発者</string>
|
||||
<string name="app_developers_"><![CDATA[このアプリは<a href="https://github.com/topjohnwu">topjohnwu</a>と<a href="https://github.com/d8ahazard">Digitalhigh</a>と<a href="https://github.com/dvdandroid">Dvdandroid</a>によって作られました。]]></string>
|
||||
<string name="app_changelog">アプリの更新履歴</string>
|
||||
<string name="translators">神楽坂桜(Sakura_Sa233#Twitter)/ hota (@lindwurm)</string>
|
||||
<string name="app_version">アプリのバージョン</string>
|
||||
<string name="app_source_code">ソースコード</string>
|
||||
<string name="donation">寄付</string>
|
||||
<string name="app_translators">アプリの翻訳者</string>
|
||||
<string name="support_thread">サポートスレッド</string>
|
||||
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="permissionNotGranted">この機能は外部ストレージへの書き込み権限がないと作動しません</string>
|
||||
<string name="no_thanks">いいえ</string>
|
||||
<string name="yes">はい</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="close">閉じる</string>
|
||||
<string name="repo_install_title">%1$s をインストール</string>
|
||||
<string name="repo_install_msg">%1$s をインストールしますか?</string>
|
||||
<string name="download">ダウンロード</string>
|
||||
<string name="download_file_error">ダウンロード中にエラーが発生しました</string>
|
||||
<string name="reboot">再起動</string>
|
||||
<string name="zip_process_msg">zipファイルの処理中…</string>
|
||||
<string name="downloading_toast">%1$s をダウンロード中</string>
|
||||
<string name="magisk_update_title">新しいMagiskの更新が利用可能です!</string>
|
||||
<string name="settings_reboot_toast">再起動して設定を適用する</string>
|
||||
<string name="release_notes">リリースノート</string>
|
||||
<string name="repo_cache_cleared">リポジトリキャッシュを消去しました</string>
|
||||
<string name="safetyNet_hide_notice">このアプリはSafetyNetを使用しています。\n既定ではMagiskHideで既に処理されています</string>
|
||||
<string name="process_error">プロセスエラー</string>
|
||||
<string name="internal_storage">zipは:\n[Internal Storage]%1$sに保存されます</string>
|
||||
<string name="zip_process_title">処理</string>
|
||||
|
||||
<!--Settings Activity -->
|
||||
<string name="settings_general_category">一般</string>
|
||||
<string name="settings_dark_theme_title">ダークテーマ</string>
|
||||
<string name="settings_dark_theme_summary">ダークテーマを有効にする</string>
|
||||
<string name="settings_notification_title">更新通知</string>
|
||||
<string name="settings_notification_summary">新しいバージョンが利用可能になったときに通知する</string>
|
||||
<string name="settings_clear_cache_title">キャッシュを消去</string>
|
||||
<string name="settings_clear_cache_summary">オンラインリポジトリのキャッシュされた情報をクリアし、アプリをオンラインで更新する</string>
|
||||
|
||||
<string name="settings_core_only_title">Magisk コアモード</string>
|
||||
<string name="settings_core_only_summary">コア機能のみを有効にすると、すべてのモジュールがロードされなくなります。 MagiskSU、MagiskHide、systemless hostsは引き続き有効になります。</string>
|
||||
<string name="settings_magiskhide_summary">さまざまな検出からMagiskを隠す</string>
|
||||
<string name="settings_hosts_title">Systemless hosts</string>
|
||||
<string name="settings_hosts_summary">AdblockのためのSystemless hostsサポート</string>
|
||||
|
||||
<string name="settings_su_app_adb">アプリとADB</string>
|
||||
<string name="settings_su_app">アプリのみ</string>
|
||||
<string name="settings_su_adb">ADBのみ</string>
|
||||
<string name="settings_su_disable">無効</string>
|
||||
<string name="settings_su_request_10">10秒</string>
|
||||
<string name="settings_su_request_20">20秒</string>
|
||||
<string name="settings_su_request_30">30秒</string>
|
||||
<string name="settings_su_request_60">60秒</string>
|
||||
<string name="superuser_access">スーパーユーザーアクセス</string>
|
||||
<string name="auto_response">自動反応</string>
|
||||
<string name="request_timeout">リクエストタイムアウト</string>
|
||||
<string name="superuser_notification">スーパーユーザー通知</string>
|
||||
<string name="request_timeout_summary">%1$s秒</string>
|
||||
|
||||
<!--Superuser-->
|
||||
<string name="su_request_title">スーパーユーザーリクエスト</string>
|
||||
<string name="deny_with_str">拒否 %1$s</string>
|
||||
<string name="deny">拒否</string>
|
||||
<string name="prompt">プロンプト</string>
|
||||
<string name="grant">許可</string>
|
||||
<string name="su_warning">端末への完全なアクセスを許可します。\nもし確信が持てなければ拒否してください!</string>
|
||||
<string name="forever">今後も</string>
|
||||
<string name="once">一度だけ</string>
|
||||
<string name="tenmin">10分</string>
|
||||
<string name="twentymin">20分</string>
|
||||
<string name="thirtymin">30分</string>
|
||||
<string name="sixtymin">60分</string>
|
||||
<string name="su_allow_toast">%1$s はスーパーユーザー権限を許可されました</string>
|
||||
<string name="su_deny_toast">%1$s はスーパーユーザー権限を拒否されました</string>
|
||||
<string name="no_apps_found">アプリが見つかりません</string>
|
||||
<string name="su_snack_grant">%1$s のスーパーユーザー権限が許可されました</string>
|
||||
<string name="su_snack_deny">%1$s のスーパーユーザー権限は拒否されました</string>
|
||||
<string name="su_snack_notif_on">%1$s の通知は有効です</string>
|
||||
<string name="su_snack_notif_off">%1$s の通知は無効です</string>
|
||||
<string name="su_snack_log_on">%1$s のログは有効です</string>
|
||||
<string name="su_snack_log_off">%1$s のログは無効です</string>
|
||||
<string name="su_snack_revoke">%1$s の権限は取り消されました</string>
|
||||
<string name="su_revoke_title">確認</string>
|
||||
<string name="su_revoke_msg">%1$s の権限を取り消すことを承認しますか?</string>
|
||||
<string name="toast">トースト通知</string>
|
||||
<string name="none">なし</string>
|
||||
|
||||
<!--Superuser logs-->
|
||||
<string name="pid">PID:\u0020</string>
|
||||
<string name="target_uid">ターゲット UID:\u0020</string>
|
||||
<string name="command">コマンド:\u0020</string>
|
||||
|
||||
</resources>
|
||||
@@ -1,209 +0,0 @@
|
||||
<resources>
|
||||
|
||||
|
||||
|
||||
|
||||
<!--Welcome Activity-->
|
||||
|
||||
<string name="modules">Módulos</string>
|
||||
<string name="downloads">Baixar</string>
|
||||
<string name="superuser">Superusuário</string>
|
||||
<string name="log">Registro</string>
|
||||
<string name="settings">Configurações</string>
|
||||
<string name="install">Instalar</string>
|
||||
|
||||
<!--Status Fragment-->
|
||||
<string name="magisk_version_error">Magisk não instalado</string>
|
||||
|
||||
<string name="checking_for_updates">Checando por atualizações…</string>
|
||||
<string name="magisk_update_available">Magisk v%1$s disponível!</string>
|
||||
<string name="safetyNet_check_text">Pressione para checar o SafetyNet</string>
|
||||
<string name="checking_safetyNet_status">Verificando status do SafetyNet…</string>
|
||||
<string name="safetyNet_check_success">SafetyNet verificado</string>
|
||||
<string name="safetyNet_api_error">SafetyNet erro na API</string>
|
||||
<string name="safetyNet_network_loss">Conexão com a rede perdida</string>
|
||||
<string name="safetyNet_service_disconnected">O serviço foi morto</string>
|
||||
<string name="safetyNet_res_invalid">A resposta é inválida</string>
|
||||
|
||||
<!--Install Fragment-->
|
||||
<string name="advanced_settings_title">Configurações avançadas</string>
|
||||
<string name="keep_force_encryption">Keep force encryption</string>
|
||||
<string name="keep_dm_verity">Keep dm-verity</string>
|
||||
<string name="current_magisk_title">Versão instalada: %1$s</string>
|
||||
<string name="install_magisk_title">Última versão: %1$s</string>
|
||||
<string name="uninstall">Desinstalar</string>
|
||||
<string name="uninstall_magisk_title">Desinstalar Magisk</string>
|
||||
<string name="uninstall_magisk_msg">Todos os modulos magisk será desativado/removido. O root será removido, e possivelmente a encriptação de seus dados se seus dados não foram encriptado.</string>
|
||||
<string name="update">Atualizar %1$s</string>
|
||||
|
||||
<!--Module Fragment-->
|
||||
<string name="no_info_provided">(Nenhuma informação fornecida)</string>
|
||||
<string name="no_modules_found">Nenhum módulo encontrado</string>
|
||||
<string name="update_file_created">Módulo será atualizado na próxima reinicialização</string>
|
||||
<string name="remove_file_created">Módulo será removido na próxima reinicialização</string>
|
||||
<string name="remove_file_deleted">Módulo não será removido na próxima reinicialização</string>
|
||||
<string name="disable_file_created">Módulo será desativado na próxima reinicialização</string>
|
||||
<string name="disable_file_removed">Módulo será ativado na próxima reinicialização</string>
|
||||
<string name="author">Criado por %1$s</string>
|
||||
|
||||
<!--Repo Fragment-->
|
||||
<string name="update_available">Atualização disponível</string>
|
||||
<string name="installed">Instalado</string>
|
||||
<string name="not_installed">Não Instalado</string>
|
||||
|
||||
<!--Log Fragment-->
|
||||
<string name="menuSaveLog">Salvar registro</string>
|
||||
<string name="menuReload">Recarregar</string>
|
||||
<string name="menuClearLog">Limpar registro agora</string>
|
||||
<string name="logs_cleared">Registro limpado com sucesso</string>
|
||||
<string name="log_is_empty">Registro está vazio</string>
|
||||
<string name="logs_save_failed">Não foi possível gravar o registro para o cartão SD:</string>
|
||||
|
||||
<!--About Activity-->
|
||||
<string name="about">Sobre</string>
|
||||
<string name="app_developers">Principais desenvolvedores</string>
|
||||
<string name="app_developers_"><![CDATA[App criado por <a href="https://github.com/topjohnwu">topjohnwu</a> em colaboração com <a href="https://github.com/d8ahazard">Digitalhigh</a> e <a href="https://github.com/dvdandroid">Dvdandroid</a>.]]></string>
|
||||
<string name="app_changelog">Registro de Mudanças do App</string>
|
||||
<string name="translators">Killer7Mod</string>
|
||||
<string name="app_version">Versão do App</string>
|
||||
<string name="app_source_code">Código fonte</string>
|
||||
<string name="donation">Doação</string>
|
||||
<string name="app_translators">Tradutores do App</string>
|
||||
<string name="support_thread">Suporte</string>
|
||||
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="permissionNotGranted">Este recurso não funcionará sem permissão de escrita do armazenamento externo.</string>
|
||||
<string name="no_thanks">Não, Obrigado</string>
|
||||
<string name="yes">Sim</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="close">Fechar</string>
|
||||
<string name="repo_install_title">Instalar %1$s</string>
|
||||
<string name="repo_install_msg">Você deseja instalar%1$s ?</string>
|
||||
<string name="download">Baixar</string>
|
||||
<string name="download_file_error">Erro ao baixar o arquivo</string>
|
||||
<string name="reboot">Reiniciar</string>
|
||||
<string name="downloading_toast">Baixando %1$s</string>
|
||||
<string name="magisk_update_title">Nova atualização do Magisk disponível!</string>
|
||||
<string name="settings_reboot_toast">Reinicie para aplicar configurações</string>
|
||||
<string name="release_notes">Notas da atualização</string>
|
||||
<string name="repo_cache_cleared">Cache do Repo. limpado</string>
|
||||
<string name="safetyNet_hide_notice">Este aplicativo usa SafetyNet\nJá manipulado pelo MagiskHide por padrão</string>
|
||||
<string name="process_error">Erro no processo</string>
|
||||
<string name="internal_storage">O zip foi salvo em:\n[Armazenamento interno]%1$s</string>
|
||||
<string name="zip_download_title">Baixando</string>
|
||||
<string name="zip_download_msg">Baixando o arquivo zip (%1$d%%) …</string>
|
||||
<string name="zip_process_title">Processando</string>
|
||||
<string name="zip_process_msg">Processando arquivo zip …</string>
|
||||
<string name="manager_update_title">Nova atualização do Magisk Manager disponível!</string>
|
||||
<string name="manager_download_install">Pressione para baixar e instalar</string>
|
||||
<string name="magisk_updates">Atualizações do Magisk</string>
|
||||
<string name="flashing">Flasheando</string>
|
||||
<string name="hide_manager_toast">Ocultando Magisk Manager…</string>
|
||||
<string name="hide_manager_fail_toast">Falha ao ocultar o Magisk Manager…</string>
|
||||
<string name="download_zip_only">Baixar somente o zip</string>
|
||||
<string name="patch_boot_file">Arquivo Patch Boot Image</string>
|
||||
<string name="direct_install">Instalação Direta (Recomendado)</string>
|
||||
<string name="install_second_slot">Instalação no segundo Slot (Depois do OTA)</string>
|
||||
<string name="select_method">Selecionar Método</string>
|
||||
<string name="no_boot_file_patch_support">Versão do Magisk escolhida não suporta arquivo de patch boot image</string>
|
||||
<string name="boot_file_patch_msg">Selecione a stock boot image despejada(dump) no formato .img ou img.tar</string>
|
||||
<string name="complete_uninstall">Desinstalação Completa</string>
|
||||
<string name="restore_stock_boot">Restaurar Stock Boot</string>
|
||||
<string name="restore_done">Restauração Completa!</string>
|
||||
<string name="restore_fail">backup da Stock não existe!</string>
|
||||
<string name="uninstall_toast">Desinstalando o Magisk Manager em 5 segundos, por favor reinicie manualmente depois</string>
|
||||
<string name="proprietary_title">Baixar Código do Proprietário</string>
|
||||
<string name="proprietary_notice">Magisk Manager é FOSS então não contém o código proprietário da API SafetyNet do Google.\n\nVocê permite que o Magisk Manager baixe uma extensão (contém o GoogleApiClient) para verificações SafetyNet?</string>
|
||||
|
||||
<!--Settings Fragment -->
|
||||
<string name="settings_general_category">Geral</string>
|
||||
<string name="settings_dark_theme_title">Tema escuro</string>
|
||||
<string name="settings_dark_theme_summary">Ativa o tema escuro</string>
|
||||
<string name="settings_notification_title">Notificação de Atualização</string>
|
||||
<string name="settings_notification_summary">Mostrar notificações de atualização quando a nova versão estiver disponível</string>
|
||||
<string name="settings_clear_cache_title">Limpar Repo Cache</string>
|
||||
<string name="settings_clear_cache_summary">Limpe as informações armazenadas em cache para repos. online, forçando o aplicativo a atualizar online</string>
|
||||
<string name="settings_hide_manager_title">Ocultar Magisk Manager</string>
|
||||
<string name="language">Linguagem</string>
|
||||
<string name="system_default">(Padrão do Sistema)</string>
|
||||
<string name="settings_update">Atualizar Configurações</string>
|
||||
<string name="settings_update_channel_title">Canal de atualizações</string>
|
||||
<string name="settings_update_stable">Estável</string>
|
||||
<string name="settings_update_beta">Beta</string>
|
||||
<string name="settings_boot_format_title">Formato de Saida do Boot Patcheado</string>
|
||||
<string name="settings_boot_format_summary">Selecione o formato de saida do Boot Image Patcheado.\nSelecione .img para flashear através do fastboot/modo de download; Selecione .img.tar para flashear com o ODIN.</string>
|
||||
|
||||
<string name="settings_core_only_title">Magisk modo somente Core</string>
|
||||
<string name="settings_core_only_summary">Ativar somente recursos principais, todos os módulos não serão carregados. MagiskSU, MagiskHide, e systemless hosts ainda estará ativado</string>
|
||||
<string name="settings_magiskhide_summary">Ocultar Magisk de várias detecções</string>
|
||||
<string name="settings_hosts_title">Ativar systemless hosts</string>
|
||||
<string name="settings_hosts_summary">Suporte do systemless para Adblock apps</string>
|
||||
|
||||
<string name="settings_su_app_adb">Aplicativos e ADB</string>
|
||||
<string name="settings_su_app">Somente Aplicativos</string>
|
||||
<string name="settings_su_adb">Somente ADB</string>
|
||||
<string name="settings_su_disable">Desativado</string>
|
||||
<string name="settings_su_request_10">10 segundos</string>
|
||||
<string name="settings_su_request_20">20 segundos</string>
|
||||
<string name="settings_su_request_30">30 segundos</string>
|
||||
<string name="settings_su_request_60">60 segundos</string>
|
||||
<string name="superuser_access">Acesso de superusuário</string>
|
||||
<string name="auto_response">Resposta Automática</string>
|
||||
<string name="request_timeout">Tempo limite de solicitação</string>
|
||||
<string name="superuser_notification">Notificação do superusuário</string>
|
||||
<string name="request_timeout_summary">%1$s segundos</string>
|
||||
<string name="settings_su_reauth_title">Re-autenticar após a atualização</string>
|
||||
<string name="settings_su_reauth_summary">Re-autenticar permissões de superusuário após as atualizações de um aplicativo</string>
|
||||
|
||||
<string name="multiuser_mode">Modo Multiusuário</string>
|
||||
<string name="settings_owner_only">Apenas Proprietário do Dispositivo</string>
|
||||
<string name="settings_owner_manage">Proprietário do dispositivo gerenciado</string>
|
||||
<string name="settings_user_independent">Usuário Independente</string>
|
||||
<string name="owner_only_summary">Somente o proprietário possui acesso de root</string>
|
||||
<string name="owner_manage_summary">Somente o proprietário pode gerenciar o acesso root e receber paineis de solicitações</string>
|
||||
<string name="user_indepenent_summary">Cada usuário tem suas próprias regras raiz separadas</string>
|
||||
<string name="multiuser_hint_owner_request">Um pedido foi enviado ao proprietário do dispositivo. Mude para o proprietário e conceda a permissão</string>
|
||||
|
||||
<string name="mount_namespace_mode">Modo namespace de montagem</string>
|
||||
<string name="settings_ns_global">Global namespace</string>
|
||||
<string name="settings_ns_requester">Herdar namespace</string>
|
||||
<string name="settings_ns_isolate">Isolar namespace</string>
|
||||
<string name="global_summary">Todas as sessões raiz usam o namespace de montagem global</string>
|
||||
<string name="requester_summary">As sessões de raiz herdarão o namespace do seu solicitante</string>
|
||||
<string name="isolate_summary">Cada sessão raiz terá seu próprio namespace isolado</string>
|
||||
|
||||
<!--Superuser-->
|
||||
<string name="su_request_title">Solicitação de superusuário</string>
|
||||
<string name="deny_with_str">Negar%1$s</string>
|
||||
<string name="deny">Negar</string>
|
||||
<string name="prompt">Perguntar</string>
|
||||
<string name="grant">Permitir</string>
|
||||
<string name="su_warning">Concede acesso total ao seu dispositivo.\nNegue se você não tiver certeza!</string>
|
||||
<string name="forever">Sempre</string>
|
||||
<string name="once">Uma vez</string>
|
||||
<string name="tenmin">10 minutos</string>
|
||||
<string name="twentymin">20 minutos</string>
|
||||
<string name="thirtymin">30 minutos</string>
|
||||
<string name="sixtymin">60 minutos</string>
|
||||
<string name="su_allow_toast">%1$s foi permitido o acesso de superusuário</string>
|
||||
<string name="su_deny_toast">%1$s foi negado o acesso de superusuário</string>
|
||||
<string name="no_apps_found">Não foram encontrados apps</string>
|
||||
<string name="su_snack_grant">Acesso de superusuário do %1$s está permitido</string>
|
||||
<string name="su_snack_deny">Acesso de superusuário do %1$s está negado</string>
|
||||
<string name="su_snack_notif_on">Notificações do %1$s está ativado</string>
|
||||
<string name="su_snack_notif_off">Notificações do %1$s está desativado</string>
|
||||
<string name="su_snack_log_on">Registro do %1$s está ativado</string>
|
||||
<string name="su_snack_log_off">Registro do %1$s está desativado</string>
|
||||
<string name="su_snack_revoke">%1$s Direitos foi revogados</string>
|
||||
<string name="su_revoke_title">Revogar?</string>
|
||||
<string name="su_revoke_msg">Revogar os diretos do %1$s, Confirmar?</string>
|
||||
<string name="toast">Notificação(Toast)</string>
|
||||
<string name="none">Nenhum</string>
|
||||
|
||||
<!--Superuser logs-->
|
||||
<string name="pid">PID:\u0020</string>
|
||||
<string name="target_uid">Alvo UID:\u0020</string>
|
||||
<string name="command">Comando:\u0020</string>
|
||||
|
||||
|
||||
</resources>
|
||||
@@ -1,207 +0,0 @@
|
||||
<resources>
|
||||
<!--Universal-->
|
||||
|
||||
<!--Welcome Activity-->
|
||||
<string name="modules">Модули</string>
|
||||
<string name="downloads">Загрузки</string>
|
||||
<string name="superuser">Суперпользователь</string>
|
||||
<string name="log">История</string>
|
||||
<string name="settings">Настройки</string>
|
||||
<string name="install">Установка</string>
|
||||
|
||||
<!--Status Fragment-->
|
||||
<string name="magisk_version_error">Magisk не установлен</string>
|
||||
|
||||
<string name="checking_for_updates">Проверка обновлений…</string>
|
||||
<string name="magisk_update_available">Доступен Magisk v%1$s!</string>
|
||||
<string name="safetyNet_check_text">Проверить статус SafetyNet</string>
|
||||
<string name="checking_safetyNet_status">Проверка статуса SafetyNet…</string>
|
||||
<string name="safetyNet_check_success">Проверка SafetyNet пройдена</string>
|
||||
<string name="safetyNet_api_error">Ошибка в API SafetyNet</string>
|
||||
<string name="safetyNet_network_loss">Подключение к интернету прервано</string>
|
||||
<string name="safetyNet_service_disconnected">Служба была остановлена</string>
|
||||
<string name="safetyNet_res_invalid">Некорректный ответ</string>
|
||||
|
||||
<!--Install Fragment-->
|
||||
<string name="advanced_settings_title">Расширенные настройки</string>
|
||||
<string name="keep_force_encryption">Оставить принуд. шифрование</string>
|
||||
<string name="keep_dm_verity">Оставить dm-verity</string>
|
||||
<string name="current_magisk_title">Текущая версия: %1$s</string>
|
||||
<string name="install_magisk_title">Последняя версия: %1$s</string>
|
||||
<string name="uninstall">Удалить</string>
|
||||
<string name="uninstall_magisk_title">Удалить Magisk</string>
|
||||
<string name="uninstall_magisk_msg">Все модули были отключены либо удалены. Root-доступ будет удален, данные зашифруются, если до сих пор не были зашифрованы</string>
|
||||
<string name="update">Обновить %1$s</string>
|
||||
|
||||
<!--Module Fragment-->
|
||||
<string name="no_info_provided">(Нет предоставленной информации)</string>
|
||||
<string name="no_modules_found">Модули не обнаружены</string>
|
||||
<string name="update_file_created">Модуль будет обновлен при перезагрузке</string>
|
||||
<string name="remove_file_created">Модуль будет удалён при перезагрузке</string>
|
||||
<string name="remove_file_deleted">Модуль не будет удалён при перезагрузке</string>
|
||||
<string name="disable_file_created">Модуль будет отключен при перезагрузке</string>
|
||||
<string name="disable_file_removed">Модуль будет включен при перезагрузке</string>
|
||||
<string name="author">Автор: %1$s</string>
|
||||
|
||||
<!--Repo Fragment-->
|
||||
<string name="update_available">Доступно обновление</string>
|
||||
<string name="installed">Установлен</string>
|
||||
<string name="not_installed">Не установлен</string>
|
||||
|
||||
<!--Log Fragment-->
|
||||
<string name="menuSaveLog">"Сохранить историю "</string>
|
||||
<string name="menuReload">Обновить</string>
|
||||
<string name="menuClearLog">Очистить историю сейчас</string>
|
||||
<string name="logs_cleared">История успешно очищена</string>
|
||||
<string name="log_is_empty">История пуста</string>
|
||||
<string name="logs_save_failed">Не удалось записать файл истории на карту памяти:</string>
|
||||
|
||||
<!--About Activity-->
|
||||
<string name="about">О приложении</string>
|
||||
<string name="app_developers">Главные разработчики</string>
|
||||
<string name="app_developers_"><![CDATA[Приложение создано <a href="https://github.com/topjohnwu">topjohnwu</a> совместно с <a href="https://github.com/d8ahazard">Digitalhigh</a> и <a href="https://github.com/dvdandroid">Dvdandroid</a>.]]></string>
|
||||
<string name="app_changelog">Список изменений</string>
|
||||
<string name="translators" />
|
||||
<string name="app_version">Версия</string>
|
||||
<string name="app_source_code">Исходный код</string>
|
||||
<string name="donation">Поддержать проект</string>
|
||||
<string name="app_translators">Переводчики</string>
|
||||
<string name="support_thread">Страница поддержки</string>
|
||||
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="permissionNotGranted">Данная функция не будет работать без разрешения на запись во внешнее хранилище</string>
|
||||
<string name="no_thanks">Нет, спасибо</string>
|
||||
<string name="yes">Да</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="close">Закрыть</string>
|
||||
<string name="repo_install_title">Установить %1$s</string>
|
||||
<string name="repo_install_msg">Желаете установить %1$s ?</string>
|
||||
<string name="download">Скачать</string>
|
||||
<string name="download_file_error">Ошибка скачивания файла</string>
|
||||
<string name="reboot">Перезагрузка</string>
|
||||
<string name="downloading_toast">Скачивание %1$s</string>
|
||||
<string name="magisk_update_title">Доступно новое обновление Magisk!</string>
|
||||
<string name="settings_reboot_toast">Для применения настроек выполните перезагрузку</string>
|
||||
<string name="release_notes">Особенности версии</string>
|
||||
<string name="repo_cache_cleared">Кеш репозитория очищен</string>
|
||||
<string name="safetyNet_hide_notice">Данное приложение использует SafetyNet.\nУже обработано MagiskHide по умолчанию</string>
|
||||
<string name="process_error">Ошибка обработки</string>
|
||||
<string name="internal_storage">Архив расположен:\n[Внутреннее Хранилище]%1$s</string>
|
||||
<string name="zip_download_title">Загрузка</string>
|
||||
<string name="zip_download_msg">Загрузка архива (%1$d%%) …</string>
|
||||
<string name="zip_process_title">Обработка</string>
|
||||
<string name="zip_process_msg">Обработка архива…</string>
|
||||
<string name="manager_update_title">Доступно новое обновление менеджера Magisk!</string>
|
||||
<string name="manager_download_install">Нажмите, чтобы скачать и установить</string>
|
||||
<string name="magisk_updates">Обновления Magisk</string>
|
||||
<string name="flashing">Прошивка…</string>
|
||||
<string name="hide_manager_toast">Скрытие Менеджера Magisk…</string>
|
||||
<string name="hide_manager_toast2">Может занять некоторое время…</string>
|
||||
<string name="hide_manager_fail_toast">Скрытие Менеджера Magisk неудачно…</string>
|
||||
<string name="download_zip_only">Загрузка только архива</string>
|
||||
<string name="patch_boot_file">Пропатчить boot-образ</string>
|
||||
<string name="direct_install">Непосредственная установка (рекомендуется)</string>
|
||||
<string name="install_second_slot">Установить во Второй Слот (после OTA)</string>
|
||||
<string name="select_method">Выбрать способ</string>
|
||||
<string name="no_boot_file_patch_support">Целевая версия Magisk не поддерживает патчинг boot-образа</string>
|
||||
<string name="boot_file_patch_msg">Выберите оригинальный дамп boot-образа, .img либо .img.tar формата</string>
|
||||
<string name="complete_uninstall">Удаление завершено</string>
|
||||
<string name="restore_stock_boot">Восстановить оригинальный boot-образ</string>
|
||||
<string name="restore_done">Восстановление завершено!</string>
|
||||
<string name="restore_fail">Резервная копия отсутствует!</string>
|
||||
<string name="uninstall_toast">Удаление менеджера Magisk в течении 5 секунд, затем, пожалуйста, вручную выполните перезагрузку</string>
|
||||
<string name="proprietary_title">Загрузка собственного кода</string>
|
||||
<string name="proprietary_notice">Менеджер Magisk является свободно распространяемым приложением, поэтому он не содержит собственный код API SafetyNet от Google.\n\nРазрешите ли Вы менеджеру Magisk загрузить расширение (содержит GoogleApiClient) для проверки SafetyNet?</string>
|
||||
|
||||
<!--Settings Activity -->
|
||||
<string name="settings_general_category">Основные</string>
|
||||
<string name="settings_dark_theme_title">Тёмная тема</string>
|
||||
<string name="settings_dark_theme_summary">Включить тёмное оформление</string>
|
||||
<string name="settings_notification_title">Уведомление об обновлении</string>
|
||||
<string name="settings_notification_summary">Показывать уведомления об обновлении, когда доступна новая версия</string>
|
||||
<string name="settings_clear_cache_title">Очистка кеша</string>
|
||||
<string name="settings_clear_cache_summary">Очистить сохранённую информацию о сетевых репозиториях, заставляя приложение принудительно обновляться через Интернет</string>
|
||||
<string name="settings_hide_manager_title">Скрыть Менеджер Magisk</string>
|
||||
<string name="settings_hide_manager_summary">Пересобрать менеджер Magisk с случайным именем пакета</string>
|
||||
<string name="language">Язык</string>
|
||||
<string name="system_default">По умолчанию (системный)</string>
|
||||
<string name="settings_update">Настройки обновления</string>
|
||||
<string name="settings_update_channel_title">Источник обновления</string>
|
||||
<string name="settings_update_stable">Стабильный релиз</string>
|
||||
<string name="settings_update_beta">Релиз beta</string>
|
||||
<string name="settings_boot_format_title">Формат boot-образа</string>
|
||||
<string name="settings_boot_format_summary">Выберите тип выходного формата патченого boot-образа.\n.img - для прошивки с помощью fastboot либо режима download\n.img.tar - для прошивки с помощью ODIN</string>
|
||||
|
||||
<string name="settings_core_only_title">Режим Magisk Core</string>
|
||||
<string name="settings_core_only_summary">Включить возможности только уровня Core, все модули не будут загружены. MagiskSU, MagiskHide и внесистемные хосты останутся включенными</string>
|
||||
<string name="settings_magiskhide_summary">Скрыть Magisk от различных проверок</string>
|
||||
<string name="settings_hosts_title">Внесистемные хосты</string>
|
||||
<string name="settings_hosts_summary">Поддержка внесистемных хостов для приложений блокировки рекламы</string>
|
||||
|
||||
<string name="settings_su_app_adb">Приложения и ADB</string>
|
||||
<string name="settings_su_app">Приложения</string>
|
||||
<string name="settings_su_adb">ADB</string>
|
||||
<string name="settings_su_disable">Отключен</string>
|
||||
<string name="settings_su_request_10">10 секунд</string>
|
||||
<string name="settings_su_request_20">20 секунд</string>
|
||||
<string name="settings_su_request_30">30 секунд</string>
|
||||
<string name="settings_su_request_60">60 секунд</string>
|
||||
<string name="superuser_access">Доступ суперпользователя</string>
|
||||
<string name="auto_response">Автоответ</string>
|
||||
<string name="request_timeout">Период запроса</string>
|
||||
<string name="superuser_notification">Уведомление суперпользователя</string>
|
||||
<string name="request_timeout_summary">%1$s сек.</string>
|
||||
<string name="settings_su_reauth_title">Реаутентификация после обновления</string>
|
||||
<string name="settings_su_reauth_summary">Перевыдача прав суперпользователя после обновлений приложения</string>
|
||||
|
||||
<string name="multiuser_mode">Многопользовательский режим</string>
|
||||
<string name="settings_owner_only">Только владелец</string>
|
||||
<string name="settings_owner_manage">Регулировка владельцем</string>
|
||||
<string name="settings_user_independent">Независимый пользователь</string>
|
||||
<string name="owner_only_summary">Только владелец имеет root-доступ</string>
|
||||
<string name="owner_manage_summary">Только владелец может управлять root-доступом и обрабатывать запросы на предоставление</string>
|
||||
<string name="user_indepenent_summary">Каждый пользователь имеет свои собственные правила root-доступа</string>
|
||||
<string name="multiuser_hint_owner_request">Запрос был отправлен владельцу устройства. Пожалуйста, переключитесь на профиль владельца и предоставьте разрешение</string>
|
||||
|
||||
<string name="mount_namespace_mode">Режим монтирования пространства имён</string>
|
||||
<string name="settings_ns_global">Глобальное пространство имён</string>
|
||||
<string name="settings_ns_requester">Наследуемое пространство имён</string>
|
||||
<string name="settings_ns_isolate">Изолированное пространство имён</string>
|
||||
<string name="global_summary">Все сеансы Суперпользователя используют глобальное пространство имён</string>
|
||||
<string name="requester_summary">Сессии Суперпользователя наследуют пространство имен запрашивающего</string>
|
||||
<string name="isolate_summary">Каждая сессия Суперпользователя будет иметь собственное изолированное пространство имен</string>
|
||||
|
||||
<!--Superuser-->
|
||||
<string name="su_request_title">Запрос прав Суперпользователя</string>
|
||||
<string name="deny_with_str">Отказать %1$s</string>
|
||||
<string name="deny">Отказать</string>
|
||||
<string name="prompt">Запрос</string>
|
||||
<string name="grant">Предоставить</string>
|
||||
<string name="su_warning">Предоставить полный доступ к устройству.\nЕсли не уверены, что желаете продолжить, отклоните данное действие!</string>
|
||||
<string name="forever">Навсегда</string>
|
||||
<string name="once">Единожды</string>
|
||||
<string name="tenmin">10 мин.</string>
|
||||
<string name="twentymin">20 мин.</string>
|
||||
<string name="thirtymin">30 мин.</string>
|
||||
<string name="sixtymin">60 мин.</string>
|
||||
<string name="su_allow_toast">%1$s предоставлены права Суперпользователя</string>
|
||||
<string name="su_deny_toast">%1$s отказано в правах Суперпользователя</string>
|
||||
<string name="no_apps_found">Приложения не обнаружены</string>
|
||||
<string name="su_snack_grant">%1$s предоставлены права Суперпользователя</string>
|
||||
<string name="su_snack_deny">%1$s отказано в правах Суперпользователя</string>
|
||||
<string name="su_snack_notif_on">Уведомления для %1$s включены</string>
|
||||
<string name="su_snack_notif_off">Уведомления для %1$s отключены</string>
|
||||
<string name="su_snack_log_on">История событий для %1$s включена</string>
|
||||
<string name="su_snack_log_off">История событий для %1$s отключена</string>
|
||||
<string name="su_snack_revoke">Права для %1$s отозваны</string>
|
||||
<string name="su_revoke_title">Отозвать?</string>
|
||||
<string name="su_revoke_msg">Подтвердить отзыв прав для %1$s?</string>
|
||||
<string name="toast">Всплывающее уведомление</string>
|
||||
<string name="none">Ничего</string>
|
||||
|
||||
<!--Superuser logs-->
|
||||
<string name="pid">PID:\u0020</string>
|
||||
<string name="target_uid">Целевой UID:\u0020</string>
|
||||
<string name="command">Команда:\u0020</string>
|
||||
|
||||
</resources>
|
||||
56
build.gradle
56
build.gradle
@@ -1,18 +1,52 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
google()
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.topjohnwu.magisk"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion rootProject.ext.compileSdkVersion
|
||||
versionCode 115
|
||||
versionName "5.7.0"
|
||||
javaCompileOptions {
|
||||
annotationProcessorOptions {
|
||||
argument('butterknife.debuggable', 'false')
|
||||
}
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.0.1'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
dexOptions {
|
||||
preDexLibraries true
|
||||
javaMaxHeapSize "2g"
|
||||
}
|
||||
lintOptions {
|
||||
disable 'MissingTranslation'
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation project(':utils')
|
||||
implementation 'com.github.topjohnwu:libsu:1.1.1'
|
||||
implementation "com.android.support:recyclerview-v7:${rootProject.ext.supportLibVersion}"
|
||||
implementation "com.android.support:cardview-v7:${rootProject.ext.supportLibVersion}"
|
||||
implementation "com.android.support:design:${rootProject.ext.supportLibVersion}"
|
||||
implementation "com.android.support:support-v4:${rootProject.ext.supportLibVersion}"
|
||||
implementation 'com.jakewharton:butterknife:8.8.1'
|
||||
implementation 'com.atlassian.commonmark:commonmark:0.10.0'
|
||||
implementation 'org.kamranzafar:jtar:2.3'
|
||||
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
|
||||
}
|
||||
|
||||
1
crypto/.gitignore
vendored
1
crypto/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
/build
|
||||
@@ -1,38 +0,0 @@
|
||||
apply plugin: 'java-library'
|
||||
|
||||
apply plugin: 'com.github.johnrengelman.shadow'
|
||||
apply plugin: 'java'
|
||||
|
||||
sourceCompatibility = "1.8"
|
||||
targetCompatibility = "1.8"
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes 'Main-Class': 'com.topjohnwu.crypto.ZipSigner'
|
||||
}
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
baseName = 'zipsigner'
|
||||
classifier = null
|
||||
version = 1.0
|
||||
}
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.1'
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation 'org.bouncycastle:bcprov-jdk15on:1.58'
|
||||
implementation 'org.bouncycastle:bcpkix-jdk15on:1.58'
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package com.topjohnwu.crypto;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class ByteArrayStream extends ByteArrayOutputStream {
|
||||
public byte[] getBuf() {
|
||||
return buf;
|
||||
}
|
||||
public synchronized void readFrom(InputStream is) {
|
||||
readFrom(is, Integer.MAX_VALUE);
|
||||
}
|
||||
public synchronized void readFrom(InputStream is, int len) {
|
||||
int read;
|
||||
byte buffer[] = new byte[4096];
|
||||
try {
|
||||
while ((read = is.read(buffer, 0, len < buffer.length ? len : buffer.length)) > 0) {
|
||||
write(buffer, 0, read);
|
||||
len -= read;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
public synchronized void writeTo(OutputStream out, int off, int len) throws IOException {
|
||||
out.write(buf, off, len);
|
||||
}
|
||||
public ByteArrayInputStream getInputStream() {
|
||||
return new ByteArrayInputStream(buf, 0, count);
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
package com.topjohnwu.crypto;
|
||||
|
||||
import org.bouncycastle.asn1.ASN1InputStream;
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
|
||||
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
|
||||
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
|
||||
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.Key;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.spec.ECPrivateKeySpec;
|
||||
import java.security.spec.ECPublicKeySpec;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
class CryptoUtils {
|
||||
|
||||
private static final Map<String, String> ID_TO_ALG;
|
||||
private static final Map<String, String> ALG_TO_ID;
|
||||
|
||||
static {
|
||||
ID_TO_ALG = new HashMap<>();
|
||||
ALG_TO_ID = new HashMap<>();
|
||||
ID_TO_ALG.put(X9ObjectIdentifiers.ecdsa_with_SHA256.getId(), "SHA256withECDSA");
|
||||
ID_TO_ALG.put(X9ObjectIdentifiers.ecdsa_with_SHA384.getId(), "SHA384withECDSA");
|
||||
ID_TO_ALG.put(X9ObjectIdentifiers.ecdsa_with_SHA512.getId(), "SHA512withECDSA");
|
||||
ID_TO_ALG.put(PKCSObjectIdentifiers.sha1WithRSAEncryption.getId(), "SHA1withRSA");
|
||||
ID_TO_ALG.put(PKCSObjectIdentifiers.sha256WithRSAEncryption.getId(), "SHA256withRSA");
|
||||
ID_TO_ALG.put(PKCSObjectIdentifiers.sha512WithRSAEncryption.getId(), "SHA512withRSA");
|
||||
ALG_TO_ID.put("SHA256withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA256.getId());
|
||||
ALG_TO_ID.put("SHA384withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA384.getId());
|
||||
ALG_TO_ID.put("SHA512withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA512.getId());
|
||||
ALG_TO_ID.put("SHA1withRSA", PKCSObjectIdentifiers.sha1WithRSAEncryption.getId());
|
||||
ALG_TO_ID.put("SHA256withRSA", PKCSObjectIdentifiers.sha256WithRSAEncryption.getId());
|
||||
ALG_TO_ID.put("SHA512withRSA", PKCSObjectIdentifiers.sha512WithRSAEncryption.getId());
|
||||
}
|
||||
|
||||
private static String getSignatureAlgorithm(Key key) throws Exception {
|
||||
if ("EC".equals(key.getAlgorithm())) {
|
||||
int curveSize;
|
||||
KeyFactory factory = KeyFactory.getInstance("EC");
|
||||
if (key instanceof PublicKey) {
|
||||
ECPublicKeySpec spec = factory.getKeySpec(key, ECPublicKeySpec.class);
|
||||
curveSize = spec.getParams().getCurve().getField().getFieldSize();
|
||||
} else if (key instanceof PrivateKey) {
|
||||
ECPrivateKeySpec spec = factory.getKeySpec(key, ECPrivateKeySpec.class);
|
||||
curveSize = spec.getParams().getCurve().getField().getFieldSize();
|
||||
} else {
|
||||
throw new InvalidKeySpecException();
|
||||
}
|
||||
if (curveSize <= 256) {
|
||||
return "SHA256withECDSA";
|
||||
} else if (curveSize <= 384) {
|
||||
return "SHA384withECDSA";
|
||||
} else {
|
||||
return "SHA512withECDSA";
|
||||
}
|
||||
} else if ("RSA".equals(key.getAlgorithm())) {
|
||||
return "SHA256withRSA";
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported key type " + key.getAlgorithm());
|
||||
}
|
||||
}
|
||||
|
||||
static AlgorithmIdentifier getSignatureAlgorithmIdentifier(Key key) throws Exception {
|
||||
String id = ALG_TO_ID.get(getSignatureAlgorithm(key));
|
||||
if (id == null) {
|
||||
throw new IllegalArgumentException("Unsupported key type " + key.getAlgorithm());
|
||||
}
|
||||
return new AlgorithmIdentifier(new ASN1ObjectIdentifier(id));
|
||||
}
|
||||
|
||||
static boolean verify(PublicKey key, byte[] input, byte[] signature,
|
||||
AlgorithmIdentifier algId) throws Exception {
|
||||
String algName = ID_TO_ALG.get(algId.getAlgorithm().getId());
|
||||
if (algName == null) {
|
||||
throw new IllegalArgumentException("Unsupported algorithm " + algId.getAlgorithm());
|
||||
}
|
||||
Signature verifier = Signature.getInstance(algName);
|
||||
verifier.initVerify(key);
|
||||
verifier.update(input);
|
||||
return verifier.verify(signature);
|
||||
}
|
||||
|
||||
static byte[] sign(PrivateKey privateKey, byte[] input) throws Exception {
|
||||
Signature signer = Signature.getInstance(getSignatureAlgorithm(privateKey));
|
||||
signer.initSign(privateKey);
|
||||
signer.update(input);
|
||||
return signer.sign();
|
||||
}
|
||||
|
||||
static X509Certificate readPublicKey(InputStream input)
|
||||
throws IOException, GeneralSecurityException {
|
||||
try {
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
return (X509Certificate) cf.generateCertificate(input);
|
||||
} finally {
|
||||
input.close();
|
||||
}
|
||||
}
|
||||
|
||||
/** Read a PKCS#8 format private key. */
|
||||
static PrivateKey readPrivateKey(InputStream input)
|
||||
throws IOException, GeneralSecurityException {
|
||||
try {
|
||||
byte[] buffer = new byte[4096];
|
||||
int size = input.read(buffer);
|
||||
byte[] bytes = Arrays.copyOf(buffer, size);
|
||||
/* Check to see if this is in an EncryptedPrivateKeyInfo structure. */
|
||||
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
|
||||
/*
|
||||
* Now it's in a PKCS#8 PrivateKeyInfo structure. Read its Algorithm
|
||||
* OID and use that to construct a KeyFactory.
|
||||
*/
|
||||
ASN1InputStream bIn = new ASN1InputStream(new ByteArrayInputStream(spec.getEncoded()));
|
||||
PrivateKeyInfo pki = PrivateKeyInfo.getInstance(bIn.readObject());
|
||||
String algOid = pki.getPrivateKeyAlgorithm().getAlgorithm().getId();
|
||||
return KeyFactory.getInstance(algOid).generatePrivate(spec);
|
||||
} finally {
|
||||
input.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
package com.topjohnwu.crypto;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.JarInputStream;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
/*
|
||||
* A universal random access interface for both JarFile and JarInputStream
|
||||
*
|
||||
* In the case when JarInputStream is provided to constructor, the whole stream
|
||||
* will be loaded into memory for random access purposes.
|
||||
* On the other hand, when a JarFile is provided, it simply works as a wrapper.
|
||||
* */
|
||||
|
||||
public class JarMap implements Closeable, AutoCloseable {
|
||||
private JarFile jarFile;
|
||||
private JarInputStream jis;
|
||||
private InputStream is;
|
||||
private File file;
|
||||
private boolean isInputStream = false, hasLoaded = false, verify;
|
||||
private LinkedHashMap<String, JarEntry> bufMap = new LinkedHashMap<>();
|
||||
|
||||
public JarMap(File file) throws IOException {
|
||||
this(file, true);
|
||||
}
|
||||
|
||||
public JarMap(File file, boolean verify) throws IOException {
|
||||
this(file, verify, ZipFile.OPEN_READ);
|
||||
}
|
||||
|
||||
public JarMap(File file, boolean verify, int mode) throws IOException {
|
||||
this.file = file;
|
||||
jarFile = new JarFile(file, verify, mode);
|
||||
}
|
||||
|
||||
public JarMap(String name) throws IOException {
|
||||
this(new File(name));
|
||||
}
|
||||
|
||||
public JarMap(String name, boolean verify) throws IOException {
|
||||
this(new File(name), verify);
|
||||
}
|
||||
|
||||
public JarMap(InputStream is) throws IOException {
|
||||
this(is, true);
|
||||
}
|
||||
|
||||
public JarMap(InputStream is, boolean verify) throws IOException {
|
||||
isInputStream = true;
|
||||
this.is = is;
|
||||
this.verify = verify;
|
||||
}
|
||||
|
||||
private void loadJarInputStream() {
|
||||
if (!isInputStream || hasLoaded) return;
|
||||
hasLoaded = true;
|
||||
JarEntry entry;
|
||||
try {
|
||||
jis = new JarInputStream(is, verify);
|
||||
while ((entry = jis.getNextJarEntry()) != null) {
|
||||
bufMap.put(entry.getName(), new JarMapEntry(entry, jis));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public InputStream getInputStream() {
|
||||
try {
|
||||
return isInputStream ? is : new FileInputStream(file);
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Manifest getManifest() throws IOException {
|
||||
loadJarInputStream();
|
||||
return isInputStream ? jis.getManifest() : jarFile.getManifest();
|
||||
}
|
||||
|
||||
public InputStream getInputStream(ZipEntry ze) throws IOException {
|
||||
loadJarInputStream();
|
||||
return isInputStream ? ((JarMapEntry) bufMap.get(ze.getName())).getInputStream() :
|
||||
jarFile.getInputStream(ze);
|
||||
}
|
||||
|
||||
public OutputStream getOutputStream(ZipEntry ze) {
|
||||
if (!isInputStream) // Only support inputstream mode
|
||||
return null;
|
||||
loadJarInputStream();
|
||||
ByteArrayStream bs = ((JarMapEntry) bufMap.get(ze.getName())).data;
|
||||
bs.reset();
|
||||
return bs;
|
||||
}
|
||||
|
||||
public byte[] getRawData(ZipEntry ze) throws IOException {
|
||||
if (isInputStream) {
|
||||
loadJarInputStream();
|
||||
return ((JarMapEntry) bufMap.get(ze.getName())).data.toByteArray();
|
||||
} else {
|
||||
ByteArrayStream bytes = new ByteArrayStream();
|
||||
bytes.readFrom(jarFile.getInputStream(ze));
|
||||
return bytes.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
public Enumeration<JarEntry> entries() {
|
||||
loadJarInputStream();
|
||||
return isInputStream ? Collections.enumeration(bufMap.values()) : jarFile.entries();
|
||||
}
|
||||
|
||||
public ZipEntry getEntry(String name) {
|
||||
return getJarEntry(name);
|
||||
}
|
||||
|
||||
public JarEntry getJarEntry(String name) {
|
||||
loadJarInputStream();
|
||||
return isInputStream ? bufMap.get(name) : jarFile.getJarEntry(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (isInputStream)
|
||||
is.close();
|
||||
else
|
||||
jarFile.close();
|
||||
}
|
||||
|
||||
private static class JarMapEntry extends JarEntry {
|
||||
ByteArrayStream data;
|
||||
JarMapEntry(JarEntry je, InputStream is) {
|
||||
super(je);
|
||||
data = new ByteArrayStream();
|
||||
data.readFrom(is);
|
||||
}
|
||||
InputStream getInputStream() {
|
||||
return data.getInputStream();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,494 +0,0 @@
|
||||
package com.topjohnwu.crypto;
|
||||
|
||||
import org.bouncycastle.asn1.ASN1InputStream;
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||
import org.bouncycastle.asn1.DEROutputStream;
|
||||
import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
|
||||
import org.bouncycastle.cert.jcajce.JcaCertStore;
|
||||
import org.bouncycastle.cms.CMSException;
|
||||
import org.bouncycastle.cms.CMSProcessableByteArray;
|
||||
import org.bouncycastle.cms.CMSSignedData;
|
||||
import org.bouncycastle.cms.CMSSignedDataGenerator;
|
||||
import org.bouncycastle.cms.CMSTypedData;
|
||||
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.operator.ContentSigner;
|
||||
import org.bouncycastle.operator.OperatorCreationException;
|
||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
|
||||
import org.bouncycastle.util.encoders.Base64;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.security.DigestOutputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.Provider;
|
||||
import java.security.Security;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/*
|
||||
* Modified from from AOSP(Marshmallow) SignAPK.java
|
||||
* */
|
||||
|
||||
public class SignAPK {
|
||||
|
||||
private static final String CERT_SF_NAME = "META-INF/CERT.SF";
|
||||
private static final String CERT_SIG_NAME = "META-INF/CERT.%s";
|
||||
|
||||
public static Provider sBouncyCastleProvider;
|
||||
// bitmasks for which hash algorithms we need the manifest to include.
|
||||
private static final int USE_SHA1 = 1;
|
||||
private static final int USE_SHA256 = 2;
|
||||
|
||||
static {
|
||||
sBouncyCastleProvider = new BouncyCastleProvider();
|
||||
Security.insertProviderAt(sBouncyCastleProvider, 1);
|
||||
}
|
||||
|
||||
public static void signZip(InputStream publicIn, InputStream privateIn,
|
||||
JarMap input, File output, boolean minSign) throws Exception {
|
||||
int alignment = 4;
|
||||
BufferedOutputStream outputFile;
|
||||
int hashes = 0;
|
||||
X509Certificate publicKey = CryptoUtils.readPublicKey(publicIn);
|
||||
hashes |= getDigestAlgorithm(publicKey);
|
||||
|
||||
// Set the ZIP file timestamp to the starting valid time
|
||||
// of the 0th certificate plus one hour (to match what
|
||||
// we've historically done).
|
||||
long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000;
|
||||
PrivateKey privateKey = CryptoUtils.readPrivateKey(privateIn);
|
||||
|
||||
outputFile = new BufferedOutputStream(new FileOutputStream(output));
|
||||
if (minSign) {
|
||||
signWholeFile(input.getInputStream(), publicKey, privateKey, outputFile);
|
||||
} else {
|
||||
JarOutputStream outputJar = new JarOutputStream(outputFile);
|
||||
// For signing .apks, use the maximum compression to make
|
||||
// them as small as possible (since they live forever on
|
||||
// the system partition). For OTA packages, use the
|
||||
// default compression level, which is much much faster
|
||||
// and produces output that is only a tiny bit larger
|
||||
// (~0.1% on full OTA packages I tested).
|
||||
outputJar.setLevel(9);
|
||||
Manifest manifest = addDigestsToManifest(input, hashes);
|
||||
copyFiles(manifest, input, outputJar, timestamp, alignment);
|
||||
signFile(manifest, input, publicKey, privateKey, outputJar);
|
||||
outputJar.close();
|
||||
}
|
||||
input.close();
|
||||
outputFile.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return one of USE_SHA1 or USE_SHA256 according to the signature
|
||||
* algorithm specified in the cert.
|
||||
*/
|
||||
private static int getDigestAlgorithm(X509Certificate cert) {
|
||||
String sigAlg = cert.getSigAlgName().toUpperCase(Locale.US);
|
||||
if ("SHA1WITHRSA".equals(sigAlg) ||
|
||||
"MD5WITHRSA".equals(sigAlg)) { // see "HISTORICAL NOTE" above.
|
||||
return USE_SHA1;
|
||||
} else if (sigAlg.startsWith("SHA256WITH")) {
|
||||
return USE_SHA256;
|
||||
} else {
|
||||
throw new IllegalArgumentException("unsupported signature algorithm \"" + sigAlg +
|
||||
"\" in cert [" + cert.getSubjectDN());
|
||||
}
|
||||
}
|
||||
/** Returns the expected signature algorithm for this key type. */
|
||||
private static String getSignatureAlgorithm(X509Certificate cert) {
|
||||
String sigAlg = cert.getSigAlgName().toUpperCase(Locale.US);
|
||||
String keyType = cert.getPublicKey().getAlgorithm().toUpperCase(Locale.US);
|
||||
if ("RSA".equalsIgnoreCase(keyType)) {
|
||||
if (getDigestAlgorithm(cert) == USE_SHA256) {
|
||||
return "SHA256withRSA";
|
||||
} else {
|
||||
return "SHA1withRSA";
|
||||
}
|
||||
} else if ("EC".equalsIgnoreCase(keyType)) {
|
||||
return "SHA256withECDSA";
|
||||
} else {
|
||||
throw new IllegalArgumentException("unsupported key type: " + keyType);
|
||||
}
|
||||
}
|
||||
// Files matching this pattern are not copied to the output.
|
||||
private static Pattern stripPattern =
|
||||
Pattern.compile("^(META-INF/((.*)[.](SF|RSA|DSA|EC)|com/android/otacert))|(" +
|
||||
Pattern.quote(JarFile.MANIFEST_NAME) + ")$");
|
||||
|
||||
/**
|
||||
* Add the hash(es) of every file to the manifest, creating it if
|
||||
* necessary.
|
||||
*/
|
||||
private static Manifest addDigestsToManifest(JarMap jar, int hashes)
|
||||
throws IOException, GeneralSecurityException {
|
||||
Manifest input = jar.getManifest();
|
||||
Manifest output = new Manifest();
|
||||
Attributes main = output.getMainAttributes();
|
||||
if (input != null) {
|
||||
main.putAll(input.getMainAttributes());
|
||||
} else {
|
||||
main.putValue("Manifest-Version", "1.0");
|
||||
main.putValue("Created-By", "1.0 (Android SignApk)");
|
||||
}
|
||||
MessageDigest md_sha1 = null;
|
||||
MessageDigest md_sha256 = null;
|
||||
if ((hashes & USE_SHA1) != 0) {
|
||||
md_sha1 = MessageDigest.getInstance("SHA1");
|
||||
}
|
||||
if ((hashes & USE_SHA256) != 0) {
|
||||
md_sha256 = MessageDigest.getInstance("SHA256");
|
||||
}
|
||||
byte[] buffer = new byte[4096];
|
||||
int num;
|
||||
// We sort the input entries by name, and add them to the
|
||||
// output manifest in sorted order. We expect that the output
|
||||
// map will be deterministic.
|
||||
TreeMap<String, JarEntry> byName = new TreeMap<String, JarEntry>();
|
||||
for (Enumeration<JarEntry> e = jar.entries(); e.hasMoreElements(); ) {
|
||||
JarEntry entry = e.nextElement();
|
||||
byName.put(entry.getName(), entry);
|
||||
}
|
||||
for (JarEntry entry: byName.values()) {
|
||||
String name = entry.getName();
|
||||
if (!entry.isDirectory() &&
|
||||
(stripPattern == null || !stripPattern.matcher(name).matches())) {
|
||||
InputStream data = jar.getInputStream(entry);
|
||||
while ((num = data.read(buffer)) > 0) {
|
||||
if (md_sha1 != null) md_sha1.update(buffer, 0, num);
|
||||
if (md_sha256 != null) md_sha256.update(buffer, 0, num);
|
||||
}
|
||||
Attributes attr = null;
|
||||
if (input != null) attr = input.getAttributes(name);
|
||||
attr = attr != null ? new Attributes(attr) : new Attributes();
|
||||
if (md_sha1 != null) {
|
||||
attr.putValue("SHA1-Digest",
|
||||
new String(Base64.encode(md_sha1.digest()), "ASCII"));
|
||||
}
|
||||
if (md_sha256 != null) {
|
||||
attr.putValue("SHA-256-Digest",
|
||||
new String(Base64.encode(md_sha256.digest()), "ASCII"));
|
||||
}
|
||||
output.getEntries().put(name, attr);
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/** Write to another stream and track how many bytes have been
|
||||
* written.
|
||||
*/
|
||||
private static class CountOutputStream extends FilterOutputStream {
|
||||
private int mCount;
|
||||
public CountOutputStream(OutputStream out) {
|
||||
super(out);
|
||||
mCount = 0;
|
||||
}
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
super.write(b);
|
||||
mCount++;
|
||||
}
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
super.write(b, off, len);
|
||||
mCount += len;
|
||||
}
|
||||
public int size() {
|
||||
return mCount;
|
||||
}
|
||||
}
|
||||
/** Write a .SF file with a digest of the specified manifest. */
|
||||
private static void writeSignatureFile(Manifest manifest, OutputStream out,
|
||||
int hash)
|
||||
throws IOException, GeneralSecurityException {
|
||||
Manifest sf = new Manifest();
|
||||
Attributes main = sf.getMainAttributes();
|
||||
main.putValue("Signature-Version", "1.0");
|
||||
main.putValue("Created-By", "1.0 (Android SignApk)");
|
||||
MessageDigest md = MessageDigest.getInstance(
|
||||
hash == USE_SHA256 ? "SHA256" : "SHA1");
|
||||
PrintStream print = new PrintStream(
|
||||
new DigestOutputStream(new ByteArrayOutputStream(), md),
|
||||
true, "UTF-8");
|
||||
// Digest of the entire manifest
|
||||
manifest.write(print);
|
||||
print.flush();
|
||||
main.putValue(hash == USE_SHA256 ? "SHA-256-Digest-Manifest" : "SHA1-Digest-Manifest",
|
||||
new String(Base64.encode(md.digest()), "ASCII"));
|
||||
Map<String, Attributes> entries = manifest.getEntries();
|
||||
for (Map.Entry<String, Attributes> entry : entries.entrySet()) {
|
||||
// Digest of the manifest stanza for this entry.
|
||||
print.print("Name: " + entry.getKey() + "\r\n");
|
||||
for (Map.Entry<Object, Object> att : entry.getValue().entrySet()) {
|
||||
print.print(att.getKey() + ": " + att.getValue() + "\r\n");
|
||||
}
|
||||
print.print("\r\n");
|
||||
print.flush();
|
||||
Attributes sfAttr = new Attributes();
|
||||
sfAttr.putValue(hash == USE_SHA256 ? "SHA-256-Digest" : "SHA1-Digest-Manifest",
|
||||
new String(Base64.encode(md.digest()), "ASCII"));
|
||||
sf.getEntries().put(entry.getKey(), sfAttr);
|
||||
}
|
||||
CountOutputStream cout = new CountOutputStream(out);
|
||||
sf.write(cout);
|
||||
// A bug in the java.util.jar implementation of Android platforms
|
||||
// up to version 1.6 will cause a spurious IOException to be thrown
|
||||
// if the length of the signature file is a multiple of 1024 bytes.
|
||||
// As a workaround, add an extra CRLF in this case.
|
||||
if ((cout.size() % 1024) == 0) {
|
||||
cout.write('\r');
|
||||
cout.write('\n');
|
||||
}
|
||||
}
|
||||
/** Sign data and write the digital signature to 'out'. */
|
||||
private static void writeSignatureBlock(
|
||||
CMSTypedData data, X509Certificate publicKey, PrivateKey privateKey,
|
||||
OutputStream out)
|
||||
throws IOException,
|
||||
CertificateEncodingException,
|
||||
OperatorCreationException,
|
||||
CMSException {
|
||||
ArrayList<X509Certificate> certList = new ArrayList<>(1);
|
||||
certList.add(publicKey);
|
||||
JcaCertStore certs = new JcaCertStore(certList);
|
||||
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
|
||||
ContentSigner signer = new JcaContentSignerBuilder(getSignatureAlgorithm(publicKey))
|
||||
.setProvider(sBouncyCastleProvider)
|
||||
.build(privateKey);
|
||||
gen.addSignerInfoGenerator(
|
||||
new JcaSignerInfoGeneratorBuilder(
|
||||
new JcaDigestCalculatorProviderBuilder()
|
||||
.setProvider(sBouncyCastleProvider)
|
||||
.build())
|
||||
.setDirectSignature(true)
|
||||
.build(signer, publicKey));
|
||||
gen.addCertificates(certs);
|
||||
CMSSignedData sigData = gen.generate(data, false);
|
||||
ASN1InputStream asn1 = new ASN1InputStream(sigData.getEncoded());
|
||||
DEROutputStream dos = new DEROutputStream(out);
|
||||
dos.writeObject(asn1.readObject());
|
||||
}
|
||||
/**
|
||||
* Copy all the files in a manifest from input to output. We set
|
||||
* the modification times in the output to a fixed time, so as to
|
||||
* reduce variation in the output file and make incremental OTAs
|
||||
* more efficient.
|
||||
*/
|
||||
private static void copyFiles(Manifest manifest, JarMap in, JarOutputStream out,
|
||||
long timestamp, int alignment) throws IOException {
|
||||
byte[] buffer = new byte[4096];
|
||||
int num;
|
||||
Map<String, Attributes> entries = manifest.getEntries();
|
||||
ArrayList<String> names = new ArrayList<>(entries.keySet());
|
||||
Collections.sort(names);
|
||||
boolean firstEntry = true;
|
||||
long offset = 0L;
|
||||
// We do the copy in two passes -- first copying all the
|
||||
// entries that are STORED, then copying all the entries that
|
||||
// have any other compression flag (which in practice means
|
||||
// DEFLATED). This groups all the stored entries together at
|
||||
// the start of the file and makes it easier to do alignment
|
||||
// on them (since only stored entries are aligned).
|
||||
for (String name : names) {
|
||||
JarEntry inEntry = in.getJarEntry(name);
|
||||
JarEntry outEntry = null;
|
||||
if (inEntry.getMethod() != JarEntry.STORED) continue;
|
||||
// Preserve the STORED method of the input entry.
|
||||
outEntry = new JarEntry(inEntry);
|
||||
outEntry.setTime(timestamp);
|
||||
// 'offset' is the offset into the file at which we expect
|
||||
// the file data to begin. This is the value we need to
|
||||
// make a multiple of 'alignement'.
|
||||
offset += JarFile.LOCHDR + outEntry.getName().length();
|
||||
if (firstEntry) {
|
||||
// The first entry in a jar file has an extra field of
|
||||
// four bytes that you can't get rid of; any extra
|
||||
// data you specify in the JarEntry is appended to
|
||||
// these forced four bytes. This is JAR_MAGIC in
|
||||
// JarOutputStream; the bytes are 0xfeca0000.
|
||||
offset += 4;
|
||||
firstEntry = false;
|
||||
}
|
||||
if (alignment > 0 && (offset % alignment != 0)) {
|
||||
// Set the "extra data" of the entry to between 1 and
|
||||
// alignment-1 bytes, to make the file data begin at
|
||||
// an aligned offset.
|
||||
int needed = alignment - (int)(offset % alignment);
|
||||
outEntry.setExtra(new byte[needed]);
|
||||
offset += needed;
|
||||
}
|
||||
out.putNextEntry(outEntry);
|
||||
InputStream data = in.getInputStream(inEntry);
|
||||
while ((num = data.read(buffer)) > 0) {
|
||||
out.write(buffer, 0, num);
|
||||
offset += num;
|
||||
}
|
||||
out.flush();
|
||||
}
|
||||
// Copy all the non-STORED entries. We don't attempt to
|
||||
// maintain the 'offset' variable past this point; we don't do
|
||||
// alignment on these entries.
|
||||
for (String name : names) {
|
||||
JarEntry inEntry = in.getJarEntry(name);
|
||||
JarEntry outEntry = null;
|
||||
if (inEntry.getMethod() == JarEntry.STORED) continue;
|
||||
// Create a new entry so that the compressed len is recomputed.
|
||||
outEntry = new JarEntry(name);
|
||||
outEntry.setTime(timestamp);
|
||||
out.putNextEntry(outEntry);
|
||||
InputStream data = in.getInputStream(inEntry);
|
||||
while ((num = data.read(buffer)) > 0) {
|
||||
out.write(buffer, 0, num);
|
||||
}
|
||||
out.flush();
|
||||
}
|
||||
}
|
||||
|
||||
// This class is to provide a file's content, but trimming out the last two bytes
|
||||
// Used for signWholeFile
|
||||
private static class CMSProcessableFile implements CMSTypedData {
|
||||
|
||||
private InputStream is;
|
||||
private ASN1ObjectIdentifier type;
|
||||
ByteArrayStream bos;
|
||||
|
||||
CMSProcessableFile(InputStream is) {
|
||||
this.is = is;
|
||||
type = new ASN1ObjectIdentifier(CMSObjectIdentifiers.data.getId());
|
||||
bos = new ByteArrayStream();
|
||||
bos.readFrom(is);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ASN1ObjectIdentifier getContentType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutputStream out) throws IOException, CMSException {
|
||||
bos.writeTo(out, 0, bos.size() - 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getContent() {
|
||||
return is;
|
||||
}
|
||||
|
||||
byte[] getTail() {
|
||||
return Arrays.copyOfRange(bos.getBuf(), bos.size() - 22, bos.size());
|
||||
}
|
||||
}
|
||||
|
||||
private static void signWholeFile(InputStream input, X509Certificate publicKey,
|
||||
PrivateKey privateKey, OutputStream outputStream)
|
||||
throws Exception {
|
||||
ByteArrayOutputStream temp = new ByteArrayOutputStream();
|
||||
// put a readable message and a null char at the start of the
|
||||
// archive comment, so that tools that display the comment
|
||||
// (hopefully) show something sensible.
|
||||
// TODO: anything more useful we can put in this message?
|
||||
byte[] message = "signed by SignApk".getBytes("UTF-8");
|
||||
temp.write(message);
|
||||
temp.write(0);
|
||||
|
||||
CMSProcessableFile cmsFile = new CMSProcessableFile(input);
|
||||
writeSignatureBlock(cmsFile, publicKey, privateKey, temp);
|
||||
|
||||
// For a zip with no archive comment, the
|
||||
// end-of-central-directory record will be 22 bytes long, so
|
||||
// we expect to find the EOCD marker 22 bytes from the end.
|
||||
byte[] zipData = cmsFile.getTail();
|
||||
if (zipData[zipData.length-22] != 0x50 ||
|
||||
zipData[zipData.length-21] != 0x4b ||
|
||||
zipData[zipData.length-20] != 0x05 ||
|
||||
zipData[zipData.length-19] != 0x06) {
|
||||
throw new IllegalArgumentException("zip data already has an archive comment");
|
||||
}
|
||||
int total_size = temp.size() + 6;
|
||||
if (total_size > 0xffff) {
|
||||
throw new IllegalArgumentException("signature is too big for ZIP file comment");
|
||||
}
|
||||
// signature starts this many bytes from the end of the file
|
||||
int signature_start = total_size - message.length - 1;
|
||||
temp.write(signature_start & 0xff);
|
||||
temp.write((signature_start >> 8) & 0xff);
|
||||
// Why the 0xff bytes? In a zip file with no archive comment,
|
||||
// bytes [-6:-2] of the file are the little-endian offset from
|
||||
// the start of the file to the central directory. So for the
|
||||
// two high bytes to be 0xff 0xff, the archive would have to
|
||||
// be nearly 4GB in size. So it's unlikely that a real
|
||||
// commentless archive would have 0xffs here, and lets us tell
|
||||
// an old signed archive from a new one.
|
||||
temp.write(0xff);
|
||||
temp.write(0xff);
|
||||
temp.write(total_size & 0xff);
|
||||
temp.write((total_size >> 8) & 0xff);
|
||||
temp.flush();
|
||||
// Signature verification checks that the EOCD header is the
|
||||
// last such sequence in the file (to avoid minzip finding a
|
||||
// fake EOCD appended after the signature in its scan). The
|
||||
// odds of producing this sequence by chance are very low, but
|
||||
// let's catch it here if it does.
|
||||
byte[] b = temp.toByteArray();
|
||||
for (int i = 0; i < b.length-3; ++i) {
|
||||
if (b[i] == 0x50 && b[i+1] == 0x4b && b[i+2] == 0x05 && b[i+3] == 0x06) {
|
||||
throw new IllegalArgumentException("found spurious EOCD header at " + i);
|
||||
}
|
||||
}
|
||||
cmsFile.write(outputStream);
|
||||
outputStream.write(total_size & 0xff);
|
||||
outputStream.write((total_size >> 8) & 0xff);
|
||||
temp.writeTo(outputStream);
|
||||
}
|
||||
private static void signFile(Manifest manifest, JarMap inputJar,
|
||||
X509Certificate publicKey, PrivateKey privateKey,
|
||||
JarOutputStream outputJar)
|
||||
throws Exception {
|
||||
// Assume the certificate is valid for at least an hour.
|
||||
long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000;
|
||||
// MANIFEST.MF
|
||||
JarEntry je = new JarEntry(JarFile.MANIFEST_NAME);
|
||||
je.setTime(timestamp);
|
||||
outputJar.putNextEntry(je);
|
||||
manifest.write(outputJar);
|
||||
je = new JarEntry(CERT_SF_NAME);
|
||||
je.setTime(timestamp);
|
||||
outputJar.putNextEntry(je);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
writeSignatureFile(manifest, baos, getDigestAlgorithm(publicKey));
|
||||
byte[] signedData = baos.toByteArray();
|
||||
outputJar.write(signedData);
|
||||
// CERT.{EC,RSA} / CERT#.{EC,RSA}
|
||||
final String keyType = publicKey.getPublicKey().getAlgorithm();
|
||||
je = new JarEntry(String.format(CERT_SIG_NAME, keyType));
|
||||
je.setTime(timestamp);
|
||||
outputJar.putNextEntry(je);
|
||||
writeSignatureBlock(new CMSProcessableByteArray(signedData),
|
||||
publicKey, privateKey, outputJar);
|
||||
}
|
||||
}
|
||||
@@ -1,231 +0,0 @@
|
||||
package com.topjohnwu.crypto;
|
||||
|
||||
import org.bouncycastle.asn1.ASN1Encodable;
|
||||
import org.bouncycastle.asn1.ASN1EncodableVector;
|
||||
import org.bouncycastle.asn1.ASN1InputStream;
|
||||
import org.bouncycastle.asn1.ASN1Integer;
|
||||
import org.bouncycastle.asn1.ASN1Object;
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||
import org.bouncycastle.asn1.ASN1Primitive;
|
||||
import org.bouncycastle.asn1.ASN1Sequence;
|
||||
import org.bouncycastle.asn1.DEROctetString;
|
||||
import org.bouncycastle.asn1.DERPrintableString;
|
||||
import org.bouncycastle.asn1.DERSequence;
|
||||
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Security;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class SignBoot {
|
||||
|
||||
static {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
|
||||
public static boolean doSignature(String target, InputStream imgIn, OutputStream imgOut,
|
||||
InputStream keyIn, InputStream certIn) {
|
||||
try {
|
||||
ByteArrayStream bas = new ByteArrayStream();
|
||||
bas.readFrom(imgIn);
|
||||
byte[] image = bas.toByteArray();
|
||||
bas.close();
|
||||
int signableSize = getSignableImageSize(image);
|
||||
if (signableSize < image.length) {
|
||||
System.err.println("NOTE: truncating input from " +
|
||||
image.length + " to " + signableSize + " bytes");
|
||||
image = Arrays.copyOf(image, signableSize);
|
||||
} else if (signableSize > image.length) {
|
||||
throw new IllegalArgumentException("Invalid image: too short, expected " +
|
||||
signableSize + " bytes");
|
||||
}
|
||||
BootSignature bootsig = new BootSignature(target, image.length);
|
||||
X509Certificate cert = CryptoUtils.readPublicKey(certIn);
|
||||
bootsig.setCertificate(cert);
|
||||
PrivateKey key = CryptoUtils.readPrivateKey(keyIn);
|
||||
bootsig.setSignature(bootsig.sign(image, key),
|
||||
CryptoUtils.getSignatureAlgorithmIdentifier(key));
|
||||
byte[] encoded_bootsig = bootsig.getEncoded();
|
||||
imgOut.write(image);
|
||||
imgOut.write(encoded_bootsig);
|
||||
imgOut.flush();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(System.err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean verifySignature(InputStream imgIn, InputStream certPath) {
|
||||
try {
|
||||
ByteArrayStream bas = new ByteArrayStream();
|
||||
bas.readFrom(imgIn);
|
||||
byte[] image = bas.toByteArray();
|
||||
bas.close();
|
||||
int signableSize = getSignableImageSize(image);
|
||||
if (signableSize >= image.length) {
|
||||
System.err.println("Invalid image: not signed");
|
||||
return false;
|
||||
}
|
||||
byte[] signature = Arrays.copyOfRange(image, signableSize, image.length);
|
||||
BootSignature bootsig = new BootSignature(signature);
|
||||
if (certPath != null) {
|
||||
bootsig.setCertificate(CryptoUtils.readPublicKey(certPath));
|
||||
}
|
||||
if (bootsig.verify(Arrays.copyOf(image, signableSize))) {
|
||||
System.err.println("Signature is VALID");
|
||||
return true;
|
||||
} else {
|
||||
System.err.println("Signature is INVALID");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(System.err);
|
||||
System.err.println("Invalid image: not signed");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static int getSignableImageSize(byte[] data) throws Exception {
|
||||
if (!Arrays.equals(Arrays.copyOfRange(data, 0, 8),
|
||||
"ANDROID!".getBytes("US-ASCII"))) {
|
||||
throw new IllegalArgumentException("Invalid image header: missing magic");
|
||||
}
|
||||
ByteBuffer image = ByteBuffer.wrap(data);
|
||||
image.order(ByteOrder.LITTLE_ENDIAN);
|
||||
image.getLong(); // magic
|
||||
int kernelSize = image.getInt();
|
||||
image.getInt(); // kernel_addr
|
||||
int ramdskSize = image.getInt();
|
||||
image.getInt(); // ramdisk_addr
|
||||
int secondSize = image.getInt();
|
||||
image.getLong(); // second_addr + tags_addr
|
||||
int pageSize = image.getInt();
|
||||
int length = pageSize // include the page aligned image header
|
||||
+ ((kernelSize + pageSize - 1) / pageSize) * pageSize
|
||||
+ ((ramdskSize + pageSize - 1) / pageSize) * pageSize
|
||||
+ ((secondSize + pageSize - 1) / pageSize) * pageSize;
|
||||
length = ((length + pageSize - 1) / pageSize) * pageSize;
|
||||
if (length <= 0) {
|
||||
throw new IllegalArgumentException("Invalid image header: invalid length");
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
static class BootSignature extends ASN1Object {
|
||||
private ASN1Integer formatVersion;
|
||||
private ASN1Encodable certificate;
|
||||
private AlgorithmIdentifier algorithmIdentifier;
|
||||
private DERPrintableString target;
|
||||
private ASN1Integer length;
|
||||
private DEROctetString signature;
|
||||
private PublicKey publicKey;
|
||||
private static final int FORMAT_VERSION = 1;
|
||||
|
||||
/**
|
||||
* Initializes the object for signing an image file
|
||||
* @param target Target name, included in the signed data
|
||||
* @param length Length of the image, included in the signed data
|
||||
*/
|
||||
public BootSignature(String target, int length) {
|
||||
this.formatVersion = new ASN1Integer(FORMAT_VERSION);
|
||||
this.target = new DERPrintableString(target);
|
||||
this.length = new ASN1Integer(length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the object for verifying a signed image file
|
||||
* @param signature Signature footer
|
||||
*/
|
||||
public BootSignature(byte[] signature)
|
||||
throws Exception {
|
||||
ASN1InputStream stream = new ASN1InputStream(signature);
|
||||
ASN1Sequence sequence = (ASN1Sequence) stream.readObject();
|
||||
formatVersion = (ASN1Integer) sequence.getObjectAt(0);
|
||||
if (formatVersion.getValue().intValue() != FORMAT_VERSION) {
|
||||
throw new IllegalArgumentException("Unsupported format version");
|
||||
}
|
||||
certificate = sequence.getObjectAt(1);
|
||||
byte[] encoded = ((ASN1Object) certificate).getEncoded();
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(encoded);
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
X509Certificate c = (X509Certificate) cf.generateCertificate(bis);
|
||||
publicKey = c.getPublicKey();
|
||||
ASN1Sequence algId = (ASN1Sequence) sequence.getObjectAt(2);
|
||||
algorithmIdentifier = new AlgorithmIdentifier(
|
||||
(ASN1ObjectIdentifier) algId.getObjectAt(0));
|
||||
ASN1Sequence attrs = (ASN1Sequence) sequence.getObjectAt(3);
|
||||
target = (DERPrintableString) attrs.getObjectAt(0);
|
||||
length = (ASN1Integer) attrs.getObjectAt(1);
|
||||
this.signature = (DEROctetString) sequence.getObjectAt(4);
|
||||
}
|
||||
|
||||
public ASN1Object getAuthenticatedAttributes() {
|
||||
ASN1EncodableVector attrs = new ASN1EncodableVector();
|
||||
attrs.add(target);
|
||||
attrs.add(length);
|
||||
return new DERSequence(attrs);
|
||||
}
|
||||
|
||||
public byte[] getEncodedAuthenticatedAttributes() throws IOException {
|
||||
return getAuthenticatedAttributes().getEncoded();
|
||||
}
|
||||
|
||||
public void setSignature(byte[] sig, AlgorithmIdentifier algId) {
|
||||
algorithmIdentifier = algId;
|
||||
signature = new DEROctetString(sig);
|
||||
}
|
||||
|
||||
public void setCertificate(X509Certificate cert)
|
||||
throws Exception, IOException, CertificateEncodingException {
|
||||
ASN1InputStream s = new ASN1InputStream(cert.getEncoded());
|
||||
certificate = s.readObject();
|
||||
publicKey = cert.getPublicKey();
|
||||
}
|
||||
|
||||
public byte[] generateSignableImage(byte[] image) throws IOException {
|
||||
byte[] attrs = getEncodedAuthenticatedAttributes();
|
||||
byte[] signable = Arrays.copyOf(image, image.length + attrs.length);
|
||||
for (int i=0; i < attrs.length; i++) {
|
||||
signable[i+image.length] = attrs[i];
|
||||
}
|
||||
return signable;
|
||||
}
|
||||
|
||||
public byte[] sign(byte[] image, PrivateKey key) throws Exception {
|
||||
byte[] signable = generateSignableImage(image);
|
||||
return CryptoUtils.sign(key, signable);
|
||||
}
|
||||
|
||||
public boolean verify(byte[] image) throws Exception {
|
||||
if (length.getValue().intValue() != image.length) {
|
||||
throw new IllegalArgumentException("Invalid image length");
|
||||
}
|
||||
byte[] signable = generateSignableImage(image);
|
||||
return CryptoUtils.verify(publicKey, signable, signature.getOctets(),
|
||||
algorithmIdentifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ASN1Primitive toASN1Primitive() {
|
||||
ASN1EncodableVector v = new ASN1EncodableVector();
|
||||
v.add(formatVersion);
|
||||
v.add(certificate);
|
||||
v.add(algorithmIdentifier);
|
||||
v.add(getAuthenticatedAttributes());
|
||||
v.add(signature);
|
||||
return new DERSequence(v);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package com.topjohnwu.crypto;
|
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.security.Security;
|
||||
|
||||
public class ZipSigner {
|
||||
public static void main(String[] args) {
|
||||
boolean minSign = false;
|
||||
int argStart = 0;
|
||||
|
||||
if (args.length < 4) {
|
||||
System.err.println("Usage: zipsigner [-m] publickey.x509[.pem] privatekey.pk8 input.jar output.jar");
|
||||
System.exit(2);
|
||||
}
|
||||
|
||||
if (args[0].equals("-m")) {
|
||||
minSign = true;
|
||||
argStart = 1;
|
||||
}
|
||||
|
||||
SignAPK.sBouncyCastleProvider = new BouncyCastleProvider();
|
||||
Security.insertProviderAt(SignAPK.sBouncyCastleProvider, 1);
|
||||
|
||||
File pubKey = new File(args[argStart]);
|
||||
File privKey = new File(args[argStart + 1]);
|
||||
File input = new File(args[argStart + 2]);
|
||||
File output = new File(args[argStart + 3]);
|
||||
|
||||
try (InputStream pub = new FileInputStream(pubKey);
|
||||
InputStream priv = new FileInputStream(privKey);
|
||||
JarMap jar = new JarMap(input, false)) {
|
||||
SignAPK.signZip(pub, priv, jar, output, minSign);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
# Project-wide Gradle settings.
|
||||
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
# Default value: -Xmx10248m -XX:MaxPermSize=256m
|
||||
org.gradle.jvmargs=-Xmx2560m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
org.gradle.parallel=true
|
||||
|
||||
# When set to true the Gradle daemon is used to run the build. For local developer builds this is our favorite property.
|
||||
# The developer environment is optimized for speed and feedback so we nearly always run Gradle jobs with the daemon.
|
||||
org.gradle.daemon=true
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
5
gradle/wrapper/gradle-wrapper.properties
vendored
5
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +0,0 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.2.1-all.zip
|
||||
172
gradlew
vendored
172
gradlew
vendored
@@ -1,172 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
84
gradlew.bat
vendored
84
gradlew.bat
vendored
@@ -1,84 +0,0 @@
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
7
app/proguard-rules.pro → proguard-rules.pro
vendored
7
app/proguard-rules.pro → proguard-rules.pro
vendored
@@ -20,5 +20,10 @@
|
||||
-keepnames class ** { *; }
|
||||
|
||||
# BouncyCastle
|
||||
-keep class org.bouncycastle.jcajce.provider.** { *; }
|
||||
-keep class org.bouncycastle.jcajce.provider.asymmetric.rsa.**SHA1** { *; }
|
||||
-keep class org.bouncycastle.jcajce.provider.asymmetric.RSA** { *; }
|
||||
-keep class org.bouncycastle.jcajce.provider.digest.SHA1** { *; }
|
||||
-dontwarn javax.naming.**
|
||||
|
||||
# Gson
|
||||
-keepattributes Signature
|
||||
@@ -1 +0,0 @@
|
||||
include ':app', ':snet', ':crypto'
|
||||
1
snet/.gitignore
vendored
1
snet/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
/build
|
||||
@@ -1,30 +0,0 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 27
|
||||
buildToolsVersion "27.0.1"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.topjohnwu.snet"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 27
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
google()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'com.google.android.gms:play-services-safetynet:11.6.0'
|
||||
}
|
||||
24
snet/proguard-rules.pro
vendored
24
snet/proguard-rules.pro
vendored
@@ -1,24 +0,0 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
|
||||
-keep class com.topjohnwu.snet.SafetyNet* { *; }
|
||||
-dontwarn java.lang.invoke**
|
||||
@@ -1,7 +0,0 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.topjohnwu.snet">
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.version"
|
||||
android:value="@integer/google_play_services_version" />
|
||||
</manifest>
|
||||
@@ -1,5 +0,0 @@
|
||||
package com.topjohnwu.snet;
|
||||
|
||||
public interface SafetyNetCallback {
|
||||
void onResponse(int responseCode);
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
package com.topjohnwu.snet;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Base64;
|
||||
|
||||
import com.google.android.gms.common.ConnectionResult;
|
||||
import com.google.android.gms.common.GoogleApiAvailability;
|
||||
import com.google.android.gms.common.api.GoogleApiClient;
|
||||
import com.google.android.gms.common.api.ResultCallback;
|
||||
import com.google.android.gms.common.api.Status;
|
||||
import com.google.android.gms.safetynet.SafetyNet;
|
||||
import com.google.android.gms.safetynet.SafetyNetApi;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class SafetyNetHelper
|
||||
implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
|
||||
|
||||
public static final int CAUSE_SERVICE_DISCONNECTED = 0x01;
|
||||
public static final int CAUSE_NETWORK_LOST = 0x02;
|
||||
public static final int RESPONSE_ERR = 0x04;
|
||||
public static final int CONNECTION_FAIL = 0x08;
|
||||
|
||||
public static final int BASIC_PASS = 0x10;
|
||||
public static final int CTS_PASS = 0x20;
|
||||
|
||||
private GoogleApiClient mGoogleApiClient;
|
||||
private Activity mActivity;
|
||||
private int responseCode;
|
||||
private SafetyNetCallback cb;
|
||||
private String dexPath;
|
||||
|
||||
public SafetyNetHelper(Activity activity, String dexPath, SafetyNetCallback cb) {
|
||||
mActivity = activity;
|
||||
this.cb = cb;
|
||||
this.dexPath = dexPath;
|
||||
responseCode = 0;
|
||||
}
|
||||
|
||||
// Entry point to start test
|
||||
public void attest() {
|
||||
// Connect Google Service
|
||||
mGoogleApiClient = new GoogleApiClient.Builder(mActivity)
|
||||
.addApi(SafetyNet.API)
|
||||
.addOnConnectionFailedListener(this)
|
||||
.addConnectionCallbacks(this)
|
||||
.build();
|
||||
mGoogleApiClient.connect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionSuspended(int i) {
|
||||
cb.onResponse(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionFailed(@NonNull ConnectionResult result) {
|
||||
Class<? extends Activity> clazz = mActivity.getClass();
|
||||
try {
|
||||
// Use external resources
|
||||
clazz.getMethod("swapResources", String.class).invoke(mActivity, dexPath);
|
||||
GoogleApiAvailability.getInstance().getErrorDialog(mActivity, result.getErrorCode(), 0).show();
|
||||
clazz.getMethod("restoreResources").invoke(mActivity);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
cb.onResponse(CONNECTION_FAIL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnected(@Nullable Bundle bundle) {
|
||||
// Create nonce
|
||||
byte[] nonce = new byte[24];
|
||||
new SecureRandom().nextBytes(nonce);
|
||||
|
||||
// Call SafetyNet
|
||||
SafetyNet.SafetyNetApi.attest(mGoogleApiClient, nonce)
|
||||
.setResultCallback(new ResultCallback<SafetyNetApi.AttestationResult>() {
|
||||
@Override
|
||||
public void onResult(@NonNull SafetyNetApi.AttestationResult result) {
|
||||
Status status = result.getStatus();
|
||||
try {
|
||||
if (!status.isSuccess()) throw new JSONException("");
|
||||
String json = new String(Base64.decode(
|
||||
result.getJwsResult().split("\\.")[1], Base64.DEFAULT));
|
||||
JSONObject decoded = new JSONObject(json);
|
||||
responseCode |= decoded.getBoolean("ctsProfileMatch") ? CTS_PASS : 0;
|
||||
responseCode |= decoded.getBoolean("basicIntegrity") ? BASIC_PASS : 0;
|
||||
} catch (JSONException e) {
|
||||
responseCode = RESPONSE_ERR;
|
||||
}
|
||||
|
||||
// Disconnect
|
||||
mGoogleApiClient.disconnect();
|
||||
|
||||
// Return results
|
||||
cb.onResponse(responseCode);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -9,10 +9,11 @@
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
|
||||
<application
|
||||
android:name=".MagiskManager"
|
||||
android:allowBackup="true"
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:label="@string/app_name"
|
||||
@@ -52,11 +53,6 @@
|
||||
android:excludeFromRecents="true"
|
||||
android:launchMode="singleTask"
|
||||
android:taskAffinity="internal.superuser"
|
||||
android:theme="@android:style/Theme.NoDisplay" />
|
||||
<activity
|
||||
android:name=".superuser.SuRequestActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:taskAffinity="internal.superuser"
|
||||
android:theme="@style/SuRequest" />
|
||||
|
||||
<receiver android:name=".superuser.SuReceiver" />
|
||||
@@ -75,6 +71,11 @@
|
||||
</receiver>
|
||||
<receiver android:name=".receivers.ManagerUpdate" />
|
||||
<receiver android:name=".receivers.RebootReceiver" />
|
||||
<receiver android:name=".receivers.ShortcutReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.LOCALE_CHANGED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service android:name=".services.OnBootIntentService" />
|
||||
<service
|
||||
@@ -95,7 +96,7 @@
|
||||
<!-- Hardcode GMS version -->
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.version"
|
||||
android:value="11717000" />
|
||||
android:value="7095000" />
|
||||
|
||||
</application>
|
||||
|
||||
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
80
src/main/java/com/topjohnwu/magisk/AboutActivity.java
Normal file
80
src/main/java/com/topjohnwu/magisk/AboutActivity.java
Normal file
@@ -0,0 +1,80 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
|
||||
import com.topjohnwu.magisk.asyncs.MarkDownWindow;
|
||||
import com.topjohnwu.magisk.components.AboutCardRow;
|
||||
import com.topjohnwu.magisk.components.Activity;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class AboutActivity extends Activity {
|
||||
|
||||
@BindView(R.id.toolbar) Toolbar toolbar;
|
||||
@BindView(R.id.app_version_info) AboutCardRow appVersionInfo;
|
||||
@BindView(R.id.app_changelog) AboutCardRow appChangelog;
|
||||
@BindView(R.id.app_translators) AboutCardRow appTranslators;
|
||||
@BindView(R.id.app_source_code) AboutCardRow appSourceCode;
|
||||
@BindView(R.id.support_thread) AboutCardRow supportThread;
|
||||
@BindView(R.id.donation) AboutCardRow donation;
|
||||
|
||||
@Override
|
||||
public int getDarkTheme() {
|
||||
return R.style.AppTheme_Transparent_Dark;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_about);
|
||||
ButterKnife.bind(this);
|
||||
|
||||
setSupportActionBar(toolbar);
|
||||
toolbar.setNavigationOnClickListener(view -> finish());
|
||||
|
||||
ActionBar ab = getSupportActionBar();
|
||||
if (ab != null) {
|
||||
ab.setTitle(R.string.about);
|
||||
ab.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
appVersionInfo.setSummary(String.format(Locale.US, "%s (%d) (%s)",
|
||||
BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE, getPackageName()));
|
||||
|
||||
appChangelog.removeSummary();
|
||||
appChangelog.setOnClickListener(v -> {
|
||||
new MarkDownWindow(this, getString(R.string.app_changelog),
|
||||
getResources().openRawResource(R.raw.changelog)).exec();
|
||||
});
|
||||
|
||||
String translators = getString(R.string.translators);
|
||||
if (TextUtils.isEmpty(translators)) {
|
||||
appTranslators.setVisibility(View.GONE);
|
||||
} else {
|
||||
appTranslators.setSummary(translators);
|
||||
}
|
||||
|
||||
appSourceCode.removeSummary();
|
||||
appSourceCode.setOnClickListener(view -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(Const.Url.SOURCE_CODE_URL))));
|
||||
|
||||
supportThread.removeSummary();
|
||||
supportThread.setOnClickListener(view -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(Const.Url.XDA_THREAD))));
|
||||
|
||||
donation.removeSummary();
|
||||
donation.setOnClickListener(view -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(Const.Url.DONATION_URL))));
|
||||
|
||||
setFloating();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,9 +16,9 @@ import android.widget.Toast;
|
||||
import com.topjohnwu.magisk.asyncs.FlashZip;
|
||||
import com.topjohnwu.magisk.asyncs.InstallMagisk;
|
||||
import com.topjohnwu.magisk.components.Activity;
|
||||
import com.topjohnwu.magisk.container.CallbackList;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.superuser.CallbackList;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
@@ -49,14 +49,14 @@ public class FlashActivity extends Activity {
|
||||
|
||||
@OnClick(R.id.reboot)
|
||||
void reboot() {
|
||||
Shell.su_raw("/system/bin/reboot");
|
||||
Shell.Async.su("/system/bin/reboot");
|
||||
}
|
||||
|
||||
@OnClick(R.id.save_logs)
|
||||
void saveLogs() {
|
||||
Calendar now = Calendar.getInstance();
|
||||
String filename = String.format(Locale.US,
|
||||
"install_log_%04d%02d%02d_%02d:%02d:%02d.log",
|
||||
"install_log_%04d%02d%02d_%02d%02d%02d.log",
|
||||
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));
|
||||
@@ -75,6 +75,11 @@ public class FlashActivity extends Activity {
|
||||
MagiskManager.toast(logFile.getPath(), Toast.LENGTH_LONG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDarkTheme() {
|
||||
return R.style.AppTheme_Transparent_Dark;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -91,10 +96,10 @@ public class FlashActivity extends Activity {
|
||||
reboot.setVisibility(View.GONE);
|
||||
|
||||
logs = new ArrayList<>();
|
||||
List<String> console = new CallbackList<String>() {
|
||||
CallbackList<String> console = new CallbackList<String>(new ArrayList<>()) {
|
||||
@Override
|
||||
public synchronized void onAddElement(String e) {
|
||||
logs.add(e);
|
||||
public void onAddElement(String s) {
|
||||
logs.add(s);
|
||||
flashLogs.setText(TextUtils.join("\n", this));
|
||||
sv.postDelayed(() -> sv.fullScroll(ScrollView.FOCUS_DOWN), 10);
|
||||
}
|
||||
@@ -104,19 +109,16 @@ public class FlashActivity extends Activity {
|
||||
Intent intent = getIntent();
|
||||
Uri uri = intent.getData();
|
||||
|
||||
boolean keepEnc = intent.getBooleanExtra(Const.Key.FLASH_SET_ENC, false);
|
||||
boolean keepVerity = intent.getBooleanExtra(Const.Key.FLASH_SET_VERITY, false);
|
||||
|
||||
switch (intent.getStringExtra(Const.Key.FLASH_ACTION)) {
|
||||
case Const.Value.FLASH_ZIP:
|
||||
new FlashZip(this, uri, console, logs).exec();
|
||||
break;
|
||||
case Const.Value.PATCH_BOOT:
|
||||
new InstallMagisk(this, console, logs, uri, keepEnc, keepVerity, (Uri) intent.getParcelableExtra(Const.Key.FLASH_SET_BOOT))
|
||||
new InstallMagisk(this, console, logs, uri, (Uri) intent.getParcelableExtra(Const.Key.FLASH_SET_BOOT))
|
||||
.exec();
|
||||
break;
|
||||
case Const.Value.FLASH_MAGISK:
|
||||
new InstallMagisk(this, console, logs, uri, keepEnc, keepVerity, intent.getStringExtra(Const.Key.FLASH_SET_BOOT))
|
||||
new InstallMagisk(this, console, logs, uri, intent.getStringExtra(Const.Key.FLASH_SET_BOOT))
|
||||
.exec();
|
||||
break;
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import android.view.ViewGroup;
|
||||
|
||||
import com.topjohnwu.magisk.adapters.TabFragmentAdapter;
|
||||
import com.topjohnwu.magisk.components.Fragment;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
@@ -33,7 +34,9 @@ public class LogFragment extends Fragment {
|
||||
|
||||
TabFragmentAdapter adapter = new TabFragmentAdapter(getChildFragmentManager());
|
||||
|
||||
adapter.addTab(new SuLogFragment(), getString(R.string.superuser));
|
||||
if (!(Const.USER_ID > 0 && getApplication().multiuserMode == Const.Value.MULTIUSER_MODE_OWNER_MANAGED)) {
|
||||
adapter.addTab(new SuLogFragment(), getString(R.string.superuser));
|
||||
}
|
||||
adapter.addTab(new MagiskLogFragment(), getString(R.string.magisk));
|
||||
tab.setupWithViewPager(viewPager);
|
||||
tab.setVisibility(View.VISIBLE);
|
||||
@@ -7,6 +7,7 @@ import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.v4.widget.SwipeRefreshLayout;
|
||||
import android.support.v7.widget.CardView;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -22,10 +23,11 @@ import com.topjohnwu.magisk.asyncs.CheckUpdates;
|
||||
import com.topjohnwu.magisk.components.AlertDialogBuilder;
|
||||
import com.topjohnwu.magisk.components.ExpandableView;
|
||||
import com.topjohnwu.magisk.components.Fragment;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.ShowUI;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import butterknife.BindColor;
|
||||
import butterknife.BindView;
|
||||
@@ -91,7 +93,14 @@ public class MagiskFragment extends Fragment
|
||||
new CheckSafetyNet(getActivity()).exec();
|
||||
collapse();
|
||||
};
|
||||
if (mm.snet_version < 0) {
|
||||
if (!TextUtils.equals(mm.getPackageName(), Const.ORIG_PKG_NAME)) {
|
||||
new AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.cannot_check_sn_title)
|
||||
.setMessage(R.string.cannot_check_sn_notice)
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
} else if (!CheckSafetyNet.dexPath.exists()) {
|
||||
// Show dialog
|
||||
new AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.proprietary_title)
|
||||
@@ -117,8 +126,7 @@ public class MagiskFragment extends Fragment
|
||||
}
|
||||
|
||||
((NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE)).cancelAll();
|
||||
ShowUI.magiskInstallDialog(getActivity(),
|
||||
keepEncChkbox.isChecked(), keepVerityChkbox.isChecked());
|
||||
ShowUI.magiskInstallDialog(getActivity());
|
||||
}
|
||||
|
||||
@OnClick(R.id.uninstall_button)
|
||||
@@ -138,6 +146,11 @@ public class MagiskFragment extends Fragment
|
||||
expandableContainer.expandLayout = expandLayout;
|
||||
setupExpandable();
|
||||
|
||||
keepVerityChkbox.setChecked(mm.keepVerity);
|
||||
keepVerityChkbox.setOnCheckedChangeListener((view, checked) -> mm.keepVerity = checked);
|
||||
keepEncChkbox.setChecked(mm.keepEnc);
|
||||
keepEncChkbox.setOnCheckedChangeListener((view, checked) -> mm.keepEnc = checked);
|
||||
|
||||
mSwipeRefreshLayout.setOnRefreshListener(this);
|
||||
updateUI();
|
||||
|
||||
@@ -155,8 +168,8 @@ public class MagiskFragment extends Fragment
|
||||
|
||||
safetyNetStatusText.setText(R.string.safetyNet_check_text);
|
||||
|
||||
mm.safetyNetDone.hasPublished = false;
|
||||
mm.updateCheckDone.hasPublished = false;
|
||||
mm.safetyNetDone.reset();
|
||||
mm.updateCheckDone.reset();
|
||||
mm.remoteMagiskVersionString = null;
|
||||
mm.remoteMagiskVersionCode = -1;
|
||||
collapse();
|
||||
@@ -172,11 +185,11 @@ public class MagiskFragment extends Fragment
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTopicPublished(Topic topic, Object result) {
|
||||
public void onTopicPublished(Topic topic) {
|
||||
if (topic == mm.updateCheckDone) {
|
||||
updateCheckUI();
|
||||
} else if (topic == mm.safetyNetDone) {
|
||||
updateSafetyNetUI((int) result);
|
||||
updateSafetyNetUI((int) topic.getResults()[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,7 +214,7 @@ public class MagiskFragment extends Fragment
|
||||
|
||||
boolean hasNetwork = Utils.checkNetworkStatus();
|
||||
boolean hasRoot = Shell.rootAccess();
|
||||
boolean isUpToDate = mm.magiskVersionCode > 1300;
|
||||
boolean isUpToDate = mm.magiskVersionCode > Const.MAGISK_VER.UNIFIED;
|
||||
|
||||
magiskUpdate.setVisibility(hasNetwork ? View.VISIBLE : View.GONE);
|
||||
safetyNetCard.setVisibility(hasNetwork ? View.VISIBLE : View.GONE);
|
||||
@@ -84,7 +84,7 @@ public class MagiskHideFragment extends Fragment implements Topic.Subscriber {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTopicPublished(Topic topic, Object result) {
|
||||
public void onTopicPublished(Topic topic) {
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
appAdapter.filter(lastFilter);
|
||||
}
|
||||
@@ -21,8 +21,9 @@ import com.topjohnwu.magisk.asyncs.ParallelTask;
|
||||
import com.topjohnwu.magisk.components.Fragment;
|
||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.CallbackList;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
@@ -113,19 +114,25 @@ public class MagiskLogFragment extends Fragment {
|
||||
mode = (int) params[0];
|
||||
switch (mode) {
|
||||
case 0:
|
||||
StringBuildingList logList = new StringBuildingList();
|
||||
Shell.su(logList, "cat " + Const.MAGISK_LOG + " | tail -n 1000");
|
||||
return logList.getCharSequence();
|
||||
StringBuilder builder = new StringBuilder();
|
||||
CallbackList<String> logs = new CallbackList<String>() {
|
||||
@Override
|
||||
public void onAddElement(String s) {
|
||||
builder.append(s).append('\n');
|
||||
}
|
||||
};
|
||||
Shell.Sync.su(logs, "cat " + Const.MAGISK_LOG + " | tail -n 5000");
|
||||
return builder;
|
||||
|
||||
case 1:
|
||||
Shell.su_raw("echo -n > " + Const.MAGISK_LOG);
|
||||
Shell.Async.su("echo -n > " + Const.MAGISK_LOG);
|
||||
SnackbarMaker.make(txtLog, R.string.logs_cleared, Snackbar.LENGTH_SHORT).show();
|
||||
return "";
|
||||
|
||||
case 2:
|
||||
Calendar now = Calendar.getInstance();
|
||||
String filename = String.format(Locale.US,
|
||||
"magisk_log_%04d%02d%02d_%02d:%02d:%02d.log",
|
||||
"magisk_log_%04d%02d%02d_%02d%02d%02d.log",
|
||||
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));
|
||||
@@ -138,8 +145,16 @@ public class MagiskLogFragment extends Fragment {
|
||||
}
|
||||
|
||||
try (FileWriter out = new FileWriter(targetFile)) {
|
||||
FileWritingList fileWritingList = new FileWritingList(out);
|
||||
Shell.su(fileWritingList, "cat " + Const.MAGISK_LOG);
|
||||
CallbackList<String> list = new CallbackList<String>() {
|
||||
@Override
|
||||
public void onAddElement(String s) {
|
||||
try {
|
||||
out.write(s);
|
||||
out.write("\n");
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
};
|
||||
Shell.Sync.su(list, "cat " + Const.MAGISK_LOG);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
@@ -187,41 +202,4 @@ public class MagiskLogFragment extends Fragment {
|
||||
exec(2);
|
||||
}
|
||||
}
|
||||
|
||||
private static class StringBuildingList extends Shell.AbstractList<String> {
|
||||
|
||||
StringBuilder builder;
|
||||
|
||||
StringBuildingList() {
|
||||
builder = new StringBuilder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(String s) {
|
||||
builder.append(s).append("\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
public CharSequence getCharSequence() {
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
||||
private static class FileWritingList extends Shell.AbstractList<String> {
|
||||
|
||||
private FileWriter writer;
|
||||
|
||||
FileWritingList(FileWriter out) {
|
||||
writer = out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(String s) {
|
||||
try {
|
||||
writer.write(s + "\n");
|
||||
} catch (IOException ignored) {}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
251
src/main/java/com/topjohnwu/magisk/MagiskManager.java
Normal file
251
src/main/java/com/topjohnwu/magisk/MagiskManager.java
Normal file
@@ -0,0 +1,251 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.app.job.JobInfo;
|
||||
import android.app.job.JobScheduler;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.container.Module;
|
||||
import com.topjohnwu.magisk.database.MagiskDatabaseHelper;
|
||||
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
|
||||
import com.topjohnwu.magisk.services.UpdateCheckService;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.BusyBox;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
public class MagiskManager extends Shell.ContainerApp {
|
||||
|
||||
// Global weak reference to self
|
||||
private static WeakReference<MagiskManager> weakSelf;
|
||||
|
||||
// Topics
|
||||
public final Topic magiskHideDone = new Topic();
|
||||
public final Topic reloadActivity = new Topic();
|
||||
public final Topic moduleLoadDone = new Topic();
|
||||
public final Topic repoLoadDone = new Topic();
|
||||
public final Topic updateCheckDone = new Topic();
|
||||
public final Topic safetyNetDone = new Topic();
|
||||
public final Topic localeDone = new Topic();
|
||||
|
||||
// Info
|
||||
public boolean hasInit = false;
|
||||
public String magiskVersionString;
|
||||
public int magiskVersionCode = -1;
|
||||
public String remoteMagiskVersionString;
|
||||
public int remoteMagiskVersionCode = -1;
|
||||
public String magiskLink;
|
||||
public String releaseNoteLink;
|
||||
public String remoteManagerVersionString;
|
||||
public int remoteManagerVersionCode = -1;
|
||||
public String managerLink;
|
||||
public String bootBlock = null;
|
||||
public boolean keepVerity = false;
|
||||
public boolean keepEnc = false;
|
||||
|
||||
// Data
|
||||
public Map<String, Module> moduleMap;
|
||||
public List<Locale> locales;
|
||||
|
||||
// Configurations
|
||||
public static Locale locale;
|
||||
public static Locale defaultLocale;
|
||||
|
||||
public boolean magiskHide;
|
||||
public boolean isDarkTheme;
|
||||
public int suRequestTimeout;
|
||||
public int suLogTimeout = 14;
|
||||
public int suAccessState;
|
||||
public int multiuserMode;
|
||||
public int suResponseType;
|
||||
public int suNotificationType;
|
||||
public int suNamespaceMode;
|
||||
public String localeConfig;
|
||||
public int updateChannel;
|
||||
public String bootFormat;
|
||||
public int repoOrder;
|
||||
|
||||
// Global resources
|
||||
public SharedPreferences prefs;
|
||||
public MagiskDatabaseHelper mDB;
|
||||
public RepoDatabaseHelper repoDB;
|
||||
public Runnable permissionGrantCallback = null;
|
||||
|
||||
private static Handler mHandler = new Handler();
|
||||
|
||||
public MagiskManager() {
|
||||
weakSelf = new WeakReference<>(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
Shell.setFlags(Shell.FLAG_MOUNT_MASTER);
|
||||
Shell.verboseLogging(BuildConfig.DEBUG);
|
||||
BusyBox.BB_PATH = new File(Const.BUSYBOX_PATH);
|
||||
Shell.setInitializer(new Shell.Initializer() {
|
||||
@Override
|
||||
public void onRootShellInit(@NonNull Shell shell) {
|
||||
try (InputStream utils = getAssets().open(Const.UTIL_FUNCTIONS);
|
||||
InputStream magiskDB = getResources().openRawResource(R.raw.magiskdb)) {
|
||||
shell.loadInputStream(null, null, utils);
|
||||
shell.loadInputStream(null, null, magiskDB);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
shell.run(null, null,
|
||||
"mount_partitions",
|
||||
"run_migrations");
|
||||
}
|
||||
});
|
||||
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
|
||||
// Handle duplicate package
|
||||
if (!getPackageName().equals(Const.ORIG_PKG_NAME)) {
|
||||
try {
|
||||
getPackageManager().getApplicationInfo(Const.ORIG_PKG_NAME, 0);
|
||||
Intent intent = getPackageManager().getLaunchIntentForPackage(Const.ORIG_PKG_NAME);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
return;
|
||||
} catch (PackageManager.NameNotFoundException ignored) { /* Expected */ }
|
||||
}
|
||||
|
||||
mDB = MagiskDatabaseHelper.getInstance(this);
|
||||
|
||||
String pkg = mDB.getStrings(Const.Key.SU_REQUESTER, Const.ORIG_PKG_NAME);
|
||||
if (getPackageName().equals(Const.ORIG_PKG_NAME) && !pkg.equals(Const.ORIG_PKG_NAME)) {
|
||||
mDB.setStrings(Const.Key.SU_REQUESTER, null);
|
||||
Utils.uninstallPkg(pkg);
|
||||
mDB = MagiskDatabaseHelper.getInstance(this);
|
||||
}
|
||||
|
||||
defaultLocale = Locale.getDefault();
|
||||
setLocale();
|
||||
loadConfig();
|
||||
}
|
||||
|
||||
public static MagiskManager get() {
|
||||
return weakSelf.get();
|
||||
}
|
||||
|
||||
public void setLocale() {
|
||||
localeConfig = prefs.getString(Const.Key.LOCALE, "");
|
||||
if (localeConfig.isEmpty()) {
|
||||
locale = defaultLocale;
|
||||
} else {
|
||||
locale = Locale.forLanguageTag(localeConfig);
|
||||
}
|
||||
Resources res = getBaseContext().getResources();
|
||||
Configuration config = new Configuration(res.getConfiguration());
|
||||
config.setLocale(locale);
|
||||
res.updateConfiguration(config, res.getDisplayMetrics());
|
||||
}
|
||||
|
||||
public void loadConfig() {
|
||||
// su
|
||||
suRequestTimeout = Utils.getPrefsInt(prefs, Const.Key.SU_REQUEST_TIMEOUT, Const.Value.timeoutList[2]);
|
||||
suResponseType = Utils.getPrefsInt(prefs, Const.Key.SU_AUTO_RESPONSE, Const.Value.SU_PROMPT);
|
||||
suNotificationType = Utils.getPrefsInt(prefs, Const.Key.SU_NOTIFICATION, Const.Value.NOTIFICATION_TOAST);
|
||||
suAccessState = mDB.getSettings(Const.Key.ROOT_ACCESS, Const.Value.ROOT_ACCESS_APPS_AND_ADB);
|
||||
multiuserMode = mDB.getSettings(Const.Key.SU_MULTIUSER_MODE, Const.Value.MULTIUSER_MODE_OWNER_ONLY);
|
||||
suNamespaceMode = mDB.getSettings(Const.Key.SU_MNT_NS, Const.Value.NAMESPACE_MODE_REQUESTER);
|
||||
|
||||
// config
|
||||
isDarkTheme = prefs.getBoolean(Const.Key.DARK_THEME, false);
|
||||
updateChannel = Utils.getPrefsInt(prefs, Const.Key.UPDATE_CHANNEL, Const.Value.STABLE_CHANNEL);
|
||||
bootFormat = prefs.getString(Const.Key.BOOT_FORMAT, ".img");
|
||||
repoOrder = prefs.getInt(Const.Key.REPO_ORDER, Const.Value.ORDER_NAME);
|
||||
}
|
||||
|
||||
public void writeConfig() {
|
||||
prefs.edit()
|
||||
.putBoolean(Const.Key.DARK_THEME, isDarkTheme)
|
||||
.putBoolean(Const.Key.MAGISKHIDE, magiskHide)
|
||||
.putBoolean(Const.Key.HOSTS, Const.MAGISK_HOST_FILE().exists())
|
||||
.putBoolean(Const.Key.COREONLY, Const.MAGISK_DISABLE_FILE.exists())
|
||||
.putString(Const.Key.SU_REQUEST_TIMEOUT, String.valueOf(suRequestTimeout))
|
||||
.putString(Const.Key.SU_AUTO_RESPONSE, String.valueOf(suResponseType))
|
||||
.putString(Const.Key.SU_NOTIFICATION, String.valueOf(suNotificationType))
|
||||
.putString(Const.Key.ROOT_ACCESS, String.valueOf(suAccessState))
|
||||
.putString(Const.Key.SU_MULTIUSER_MODE, String.valueOf(multiuserMode))
|
||||
.putString(Const.Key.SU_MNT_NS, String.valueOf(suNamespaceMode))
|
||||
.putString(Const.Key.UPDATE_CHANNEL, String.valueOf(updateChannel))
|
||||
.putString(Const.Key.LOCALE, localeConfig)
|
||||
.putString(Const.Key.BOOT_FORMAT, bootFormat)
|
||||
.putInt(Const.Key.UPDATE_SERVICE_VER, Const.UPDATE_SERVICE_VER)
|
||||
.putInt(Const.Key.REPO_ORDER, repoOrder)
|
||||
.apply();
|
||||
}
|
||||
|
||||
public static void toast(CharSequence msg, int duration) {
|
||||
mHandler.post(() -> Toast.makeText(weakSelf.get(), msg, duration).show());
|
||||
}
|
||||
|
||||
public static void toast(int resId, int duration) {
|
||||
mHandler.post(() -> Toast.makeText(weakSelf.get(), resId, duration).show());
|
||||
}
|
||||
|
||||
public void loadMagiskInfo() {
|
||||
try {
|
||||
magiskVersionString = Utils.cmd("magisk -v").split(":")[0];
|
||||
magiskVersionCode = Integer.parseInt(Utils.cmd("magisk -V"));
|
||||
String s = Utils.cmd((magiskVersionCode >= Const.MAGISK_VER.RESETPROP_PERSIST ? "resetprop -p " : "getprop ")
|
||||
+ Const.MAGISKHIDE_PROP);
|
||||
magiskHide = s == null || Integer.parseInt(s) != 0;
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
bootBlock = Utils.cmd("echo \"$BOOTIMAGE\"");
|
||||
}
|
||||
|
||||
public void getDefaultInstallFlags() {
|
||||
keepVerity = Boolean.parseBoolean(Utils.cmd("getvar KEEPVERITY; echo $KEEPVERITY")) ||
|
||||
Utils.cmd("echo \"$DTBOIMAGE\"") != null;
|
||||
|
||||
keepEnc = Boolean.parseBoolean(Utils.cmd("getvar KEEPFORCEENCRYPT; echo $KEEPFORCEENCRYPT")) ||
|
||||
TextUtils.equals("encrypted", Utils.cmd("getprop ro.crypto.state"));
|
||||
}
|
||||
|
||||
public void setPermissionGrantCallback(Runnable callback) {
|
||||
permissionGrantCallback = callback;
|
||||
}
|
||||
|
||||
public void setupUpdateCheck() {
|
||||
JobScheduler scheduler = (JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE);
|
||||
|
||||
if (prefs.getBoolean(Const.Key.CHECK_UPDATES, true)) {
|
||||
if (scheduler.getAllPendingJobs().isEmpty() ||
|
||||
Const.UPDATE_SERVICE_VER > prefs.getInt(Const.Key.UPDATE_SERVICE_VER, -1)) {
|
||||
ComponentName service = new ComponentName(this, UpdateCheckService.class);
|
||||
JobInfo info = new JobInfo.Builder(Const.ID.UPDATE_SERVICE_ID, service)
|
||||
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
|
||||
.setPersisted(true)
|
||||
.setPeriodic(8 * 60 * 60 * 1000)
|
||||
.build();
|
||||
scheduler.schedule(info);
|
||||
}
|
||||
} else {
|
||||
scheduler.cancel(Const.UPDATE_SERVICE_VER);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
@@ -16,11 +15,12 @@ import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
import com.topjohnwu.magisk.asyncs.MarkDownWindow;
|
||||
import com.topjohnwu.magisk.components.Activity;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
@@ -29,8 +29,8 @@ public class MainActivity extends Activity
|
||||
implements NavigationView.OnNavigationItemSelectedListener, Topic.Subscriber {
|
||||
|
||||
private final Handler mDrawerHandler = new Handler();
|
||||
private SharedPreferences prefs;
|
||||
private int mDrawerItem;
|
||||
private boolean fromShortcut = true;
|
||||
|
||||
@BindView(R.id.toolbar) Toolbar toolbar;
|
||||
@BindView(R.id.drawer_layout) DrawerLayout drawer;
|
||||
@@ -38,6 +38,11 @@ public class MainActivity extends Activity
|
||||
|
||||
private float toolbarElevation;
|
||||
|
||||
@Override
|
||||
public int getDarkTheme() {
|
||||
return R.style.AppTheme_Dark;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
|
||||
@@ -58,11 +63,6 @@ public class MainActivity extends Activity
|
||||
ActivityCompat.requestPermissions(this, new String[] { perm }, 0);
|
||||
}
|
||||
|
||||
prefs = mm.prefs;
|
||||
|
||||
if (mm.isDarkTheme) {
|
||||
setTheme(R.style.AppTheme_Dark);
|
||||
}
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
ButterKnife.bind(this);
|
||||
@@ -91,6 +91,12 @@ public class MainActivity extends Activity
|
||||
navigate(getIntent().getStringExtra(Const.Key.OPEN_SECTION));
|
||||
|
||||
navigationView.setNavigationItemSelectedListener(this);
|
||||
|
||||
if (mm.prefs.getInt(Const.Key.APP_VER, -1) < BuildConfig.VERSION_CODE) {
|
||||
mm.prefs.edit().putInt(Const.Key.APP_VER, BuildConfig.VERSION_CODE).apply();
|
||||
new MarkDownWindow(this, getString(R.string.app_changelog),
|
||||
getResources().openRawResource(R.raw.changelog)).exec();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -103,7 +109,7 @@ public class MainActivity extends Activity
|
||||
public void onBackPressed() {
|
||||
if (drawer.isDrawerOpen(navigationView)) {
|
||||
drawer.closeDrawer(navigationView);
|
||||
} else if (mDrawerItem != R.id.magisk) {
|
||||
} else if (mDrawerItem != R.id.magisk && !fromShortcut) {
|
||||
navigate(R.id.magisk);
|
||||
} else {
|
||||
finish();
|
||||
@@ -119,7 +125,7 @@ public class MainActivity extends Activity
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTopicPublished(Topic topic, Object result) {
|
||||
public void onTopicPublished(Topic topic) {
|
||||
recreate();
|
||||
}
|
||||
|
||||
@@ -132,24 +138,21 @@ public class MainActivity extends Activity
|
||||
MagiskManager mm = getMagiskManager();
|
||||
Menu menu = navigationView.getMenu();
|
||||
menu.findItem(R.id.magiskhide).setVisible(
|
||||
Shell.rootAccess() && mm.magiskVersionCode >= 1300
|
||||
&& prefs.getBoolean(Const.Key.MAGISKHIDE, false));
|
||||
menu.findItem(R.id.modules).setVisible(
|
||||
Shell.rootAccess() && mm.magiskVersionCode >= Const.MAGISK_VER.UNIFIED
|
||||
&& mm.prefs.getBoolean(Const.Key.MAGISKHIDE, false));
|
||||
menu.findItem(R.id.modules).setVisible(!mm.prefs.getBoolean(Const.Key.COREONLY, false) &&
|
||||
Shell.rootAccess() && mm.magiskVersionCode >= 0);
|
||||
menu.findItem(R.id.downloads).setVisible(Utils.checkNetworkStatus() &&
|
||||
Shell.rootAccess() && mm.magiskVersionCode >= 0);
|
||||
menu.setGroupVisible(R.id.second_group, !mm.coreOnly);
|
||||
menu.findItem(R.id.downloads).setVisible(!mm.prefs.getBoolean(Const.Key.COREONLY, false)
|
||||
&& Utils.checkNetworkStatus() && Shell.rootAccess() && mm.magiskVersionCode >= 0);
|
||||
menu.findItem(R.id.log).setVisible(Shell.rootAccess());
|
||||
menu.findItem(R.id.superuser).setVisible(Shell.rootAccess());
|
||||
menu.findItem(R.id.superuser).setVisible(Shell.rootAccess() &&
|
||||
!(Const.USER_ID > 0 && mm.multiuserMode == Const.Value.MULTIUSER_MODE_OWNER_MANAGED));
|
||||
}
|
||||
|
||||
public void navigate(String item) {
|
||||
int itemId = R.id.magisk;
|
||||
if (item != null) {
|
||||
switch (item) {
|
||||
case "magisk":
|
||||
itemId = R.id.magisk;
|
||||
break;
|
||||
case "superuser":
|
||||
itemId = R.id.superuser;
|
||||
break;
|
||||
@@ -182,22 +185,23 @@ public class MainActivity extends Activity
|
||||
navigationView.setCheckedItem(itemId);
|
||||
switch (itemId) {
|
||||
case R.id.magisk:
|
||||
displayFragment(new MagiskFragment(), "magisk", true);
|
||||
fromShortcut = false;
|
||||
displayFragment(new MagiskFragment(), true);
|
||||
break;
|
||||
case R.id.superuser:
|
||||
displayFragment(new SuperuserFragment(), "superuser", true);
|
||||
displayFragment(new SuperuserFragment(), true);
|
||||
break;
|
||||
case R.id.modules:
|
||||
displayFragment(new ModulesFragment(), "modules", true);
|
||||
displayFragment(new ModulesFragment(), true);
|
||||
break;
|
||||
case R.id.downloads:
|
||||
displayFragment(new ReposFragment(), "downloads", true);
|
||||
displayFragment(new ReposFragment(), true);
|
||||
break;
|
||||
case R.id.magiskhide:
|
||||
displayFragment(new MagiskHideFragment(), Const.Key.MAGISKHIDE, true);
|
||||
displayFragment(new MagiskHideFragment(), true);
|
||||
break;
|
||||
case R.id.log:
|
||||
displayFragment(new LogFragment(), "log", false);
|
||||
displayFragment(new LogFragment(), false);
|
||||
break;
|
||||
case R.id.settings:
|
||||
startActivity(new Intent(this, SettingsActivity.class));
|
||||
@@ -210,12 +214,13 @@ public class MainActivity extends Activity
|
||||
}
|
||||
}
|
||||
|
||||
private void displayFragment(@NonNull Fragment navFragment, String tag, boolean setElevation) {
|
||||
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
||||
private void displayFragment(@NonNull Fragment navFragment, boolean setElevation) {
|
||||
supportInvalidateOptionsMenu();
|
||||
transaction.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out);
|
||||
transaction.replace(R.id.content_frame, navFragment, tag).commitNow();
|
||||
if (setElevation) toolbar.setElevation(toolbarElevation);
|
||||
else toolbar.setElevation(0);
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
|
||||
.replace(R.id.content_frame, navFragment)
|
||||
.commitNow();
|
||||
toolbar.setElevation(setElevation ? toolbarElevation : 0);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,9 @@ import android.support.annotation.Nullable;
|
||||
import android.support.v4.widget.SwipeRefreshLayout;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
@@ -19,6 +22,7 @@ import com.topjohnwu.magisk.container.Module;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -50,6 +54,7 @@ public class ModulesFragment extends Fragment implements Topic.Subscriber {
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_modules, container, false);
|
||||
unbinder = ButterKnife.bind(this, view);
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
mSwipeRefreshLayout.setOnRefreshListener(() -> {
|
||||
recyclerView.setVisibility(View.GONE);
|
||||
@@ -74,7 +79,7 @@ public class ModulesFragment extends Fragment implements Topic.Subscriber {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTopicPublished(Topic topic, Object result) {
|
||||
public void onTopicPublished(Topic topic) {
|
||||
updateUI();
|
||||
}
|
||||
|
||||
@@ -99,6 +104,31 @@ public class ModulesFragment extends Fragment implements Topic.Subscriber {
|
||||
unbinder.unbind();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.menu_reboot, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.reboot:
|
||||
Shell.Async.su("/system/bin/reboot");
|
||||
return true;
|
||||
case R.id.reboot_recovery:
|
||||
Shell.Async.su("/system/bin/reboot recovery");
|
||||
return true;
|
||||
case R.id.reboot_bootloader:
|
||||
Shell.Async.su("/system/bin/reboot bootloader");
|
||||
return true;
|
||||
case R.id.reboot_download:
|
||||
Shell.Async.su("/system/bin/reboot download");
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateUI() {
|
||||
listModules.clear();
|
||||
listModules.addAll(getApplication().moduleMap.values());
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.widget.SwipeRefreshLayout;
|
||||
@@ -7,6 +8,7 @@ import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.SearchView;
|
||||
@@ -15,6 +17,7 @@ import android.widget.TextView;
|
||||
import com.topjohnwu.magisk.adapters.ReposAdapter;
|
||||
import com.topjohnwu.magisk.asyncs.UpdateRepos;
|
||||
import com.topjohnwu.magisk.components.Fragment;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
|
||||
import butterknife.BindView;
|
||||
@@ -24,6 +27,7 @@ import butterknife.Unbinder;
|
||||
public class ReposFragment extends Fragment implements Topic.Subscriber {
|
||||
|
||||
private Unbinder unbinder;
|
||||
private MagiskManager mm;
|
||||
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
||||
@BindView(R.id.empty_rv) TextView emptyRv;
|
||||
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
||||
@@ -41,8 +45,9 @@ public class ReposFragment extends Fragment implements Topic.Subscriber {
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_repos, container, false);
|
||||
unbinder = ButterKnife.bind(this, view);
|
||||
mm = getApplication();
|
||||
|
||||
mSwipeRefreshLayout.setRefreshing(true);
|
||||
mSwipeRefreshLayout.setRefreshing(mm.repoLoadDone.isPending());
|
||||
|
||||
mSwipeRefreshLayout.setOnRefreshListener(() -> {
|
||||
recyclerView.setVisibility(View.VISIBLE);
|
||||
@@ -57,7 +62,7 @@ public class ReposFragment extends Fragment implements Topic.Subscriber {
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
adapter = new ReposAdapter(getApplication().repoDB, getApplication().moduleMap);
|
||||
adapter = new ReposAdapter(mm.repoDB, mm.moduleMap);
|
||||
recyclerView.setAdapter(adapter);
|
||||
super.onResume();
|
||||
}
|
||||
@@ -69,7 +74,7 @@ public class ReposFragment extends Fragment implements Topic.Subscriber {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTopicPublished(Topic topic, Object result) {
|
||||
public void onTopicPublished(Topic topic) {
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
recyclerView.setVisibility(adapter.getItemCount() == 0 ? View.GONE : View.VISIBLE);
|
||||
emptyRv.setVisibility(adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
|
||||
@@ -77,7 +82,7 @@ public class ReposFragment extends Fragment implements Topic.Subscriber {
|
||||
|
||||
@Override
|
||||
public Topic[] getSubscription() {
|
||||
return new Topic[] { getApplication().repoLoadDone };
|
||||
return new Topic[] { mm.repoLoadDone };
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -98,6 +103,21 @@ public class ReposFragment extends Fragment implements Topic.Subscriber {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == R.id.repo_sort) {
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setTitle(R.string.sorting_order)
|
||||
.setSingleChoiceItems(R.array.sorting_orders, mm.repoOrder, (d, which) -> {
|
||||
mm.repoOrder = which;
|
||||
mm.prefs.edit().putInt(Const.Key.REPO_ORDER, mm.repoOrder).apply();
|
||||
adapter.notifyDBChanged();
|
||||
d.dismiss();
|
||||
}).show();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
@@ -11,21 +12,24 @@ import android.preference.PreferenceFragment;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.preference.SwitchPreference;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.text.InputType;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.asyncs.CheckUpdates;
|
||||
import com.topjohnwu.magisk.asyncs.HideManager;
|
||||
import com.topjohnwu.magisk.components.Activity;
|
||||
import com.topjohnwu.magisk.components.AlertDialogBuilder;
|
||||
import com.topjohnwu.magisk.receivers.ManagerUpdate;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.utils.FingerprintHelper;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
||||
import butterknife.BindView;
|
||||
@@ -35,13 +39,14 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
|
||||
|
||||
@BindView(R.id.toolbar) Toolbar toolbar;
|
||||
|
||||
@Override
|
||||
public int getDarkTheme() {
|
||||
return R.style.AppTheme_Transparent_Dark;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (getMagiskManager().isDarkTheme) {
|
||||
setTheme(R.style.AppTheme_Transparent_Dark);
|
||||
}
|
||||
|
||||
setContentView(R.layout.activity_settings);
|
||||
ButterKnife.bind(this);
|
||||
|
||||
@@ -64,7 +69,7 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTopicPublished(Topic topic, Object result) {
|
||||
public void onTopicPublished(Topic topic) {
|
||||
recreate();
|
||||
}
|
||||
|
||||
@@ -96,6 +101,7 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
|
||||
PreferenceCategory magiskCategory = (PreferenceCategory) findPreference("magisk");
|
||||
PreferenceCategory suCategory = (PreferenceCategory) findPreference("superuser");
|
||||
Preference hideManager = findPreference("hide");
|
||||
Preference restoreManager = findPreference("restore");
|
||||
findPreference("clear").setOnPreferenceClickListener((pref) -> {
|
||||
prefs.edit().remove(Const.Key.ETAG_KEY).apply();
|
||||
mm.repoDB.clearRepo();
|
||||
@@ -111,25 +117,20 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
|
||||
multiuserMode = (ListPreference) findPreference(Const.Key.SU_MULTIUSER_MODE);
|
||||
namespaceMode = (ListPreference) findPreference(Const.Key.SU_MNT_NS);
|
||||
SwitchPreference reauth = (SwitchPreference) findPreference(Const.Key.SU_REAUTH);
|
||||
SwitchPreference fingerprint = (SwitchPreference) findPreference(Const.Key.SU_FINGERPRINT);
|
||||
|
||||
updateChannel.setOnPreferenceChangeListener((pref, o) -> {
|
||||
mm.updateChannel = Integer.parseInt((String) o);
|
||||
if (mm.updateChannel == Const.Value.CUSTOM_CHANNEL) {
|
||||
LinearLayout layout = new LinearLayout(getActivity());
|
||||
EditText url = new EditText(getActivity());
|
||||
url.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI);
|
||||
url.setText(mm.customChannelUrl);
|
||||
layout.setOrientation(LinearLayout.VERTICAL);
|
||||
layout.addView(url);
|
||||
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) url.getLayoutParams();
|
||||
params.setMargins(Utils.dpInPx(15), 0, Utils.dpInPx(15), 0);
|
||||
new AlertDialogBuilder(getActivity())
|
||||
View v = LayoutInflater.from(getActivity()).inflate(R.layout.custom_channel_dialog, null);
|
||||
EditText url = v.findViewById(R.id.custom_url);
|
||||
url.setText(mm.prefs.getString(Const.Key.CUSTOM_CHANNEL, ""));
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setTitle(R.string.settings_update_custom)
|
||||
.setMessage(R.string.settings_update_custom_msg)
|
||||
.setView(layout)
|
||||
.setPositiveButton(R.string.ok, (d, i) -> {
|
||||
prefs.edit().putString(Const.Key.CUSTOM_CHANNEL, url.getText().toString()).apply();
|
||||
})
|
||||
.setView(v)
|
||||
.setPositiveButton(R.string.ok, (d, i) ->
|
||||
prefs.edit().putString(Const.Key.CUSTOM_CHANNEL,
|
||||
url.getText().toString()).apply())
|
||||
.setNegativeButton(R.string.close, null)
|
||||
.show();
|
||||
}
|
||||
@@ -138,32 +139,62 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
|
||||
|
||||
setSummary();
|
||||
|
||||
// Disable dangerous settings in user mode if selected owner manage
|
||||
if (mm.userId > 0) {
|
||||
// Disable dangerous settings in secondary user
|
||||
if (Const.USER_ID > 0) {
|
||||
suCategory.removePreference(multiuserMode);
|
||||
}
|
||||
|
||||
// Remove re-authentication option on Android O, it will not work
|
||||
// Disable re-authentication option on Android O, it will not work
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
suCategory.removePreference(reauth);
|
||||
reauth.setEnabled(false);
|
||||
reauth.setSummary(R.string.android_o_not_support);
|
||||
}
|
||||
|
||||
if (mm.getPackageName().equals(Const.ORIG_PKG_NAME) && mm.magiskVersionCode >= 1440) {
|
||||
hideManager.setOnPreferenceClickListener((pref) -> {
|
||||
Utils.runWithPermission(getActivity(),
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
() -> new HideManager().exec());
|
||||
return true;
|
||||
});
|
||||
// Disable fingerprint option if not possible
|
||||
if (!FingerprintHelper.canUseFingerprint()) {
|
||||
fingerprint.setEnabled(false);
|
||||
fingerprint.setSummary(R.string.disable_fingerprint);
|
||||
}
|
||||
|
||||
if (mm.magiskVersionCode >= Const.MAGISK_VER.MANAGER_HIDE) {
|
||||
if (mm.getPackageName().equals(Const.ORIG_PKG_NAME)) {
|
||||
hideManager.setOnPreferenceClickListener((pref) -> {
|
||||
new HideManager(getActivity()).exec();
|
||||
return true;
|
||||
});
|
||||
generalCatagory.removePreference(restoreManager);
|
||||
} else {
|
||||
if (Utils.checkNetworkStatus()) {
|
||||
restoreManager.setOnPreferenceClickListener((pref) -> {
|
||||
Utils.runWithPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE, () -> {
|
||||
Intent intent = new Intent(mm, ManagerUpdate.class);
|
||||
intent.putExtra(Const.Key.INTENT_SET_LINK, mm.managerLink);
|
||||
intent.putExtra(Const.Key.INTENT_SET_FILENAME,
|
||||
Utils.fmt("MagiskManager-v%s(%d).apk",
|
||||
mm.remoteManagerVersionString, mm.remoteManagerVersionCode));
|
||||
mm.sendBroadcast(intent);
|
||||
});
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
generalCatagory.removePreference(restoreManager);
|
||||
}
|
||||
generalCatagory.removePreference(hideManager);
|
||||
}
|
||||
} else {
|
||||
generalCatagory.removePreference(restoreManager);
|
||||
generalCatagory.removePreference(hideManager);
|
||||
}
|
||||
|
||||
if (!Shell.rootAccess() || (Const.USER_ID > 0 &&
|
||||
mm.multiuserMode == Const.Value.MULTIUSER_MODE_OWNER_MANAGED)) {
|
||||
prefScreen.removePreference(suCategory);
|
||||
}
|
||||
|
||||
if (!Shell.rootAccess()) {
|
||||
prefScreen.removePreference(magiskCategory);
|
||||
prefScreen.removePreference(suCategory);
|
||||
generalCatagory.removePreference(hideManager);
|
||||
} else if (mm.magiskVersionCode < 1300) {
|
||||
} else if (mm.magiskVersionCode < Const.MAGISK_VER.UNIFIED) {
|
||||
prefScreen.removePreference(magiskCategory);
|
||||
}
|
||||
}
|
||||
@@ -208,52 +239,44 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
|
||||
boolean enabled;
|
||||
|
||||
switch (key) {
|
||||
case Const.Key.DARK_THEME:
|
||||
enabled = prefs.getBoolean(Const.Key.DARK_THEME, false);
|
||||
if (mm.isDarkTheme != enabled) {
|
||||
mm.reloadActivity.publish(false);
|
||||
}
|
||||
mm.isDarkTheme = prefs.getBoolean(key, false);
|
||||
mm.reloadActivity.publish(false);
|
||||
break;
|
||||
case Const.Key.DISABLE:
|
||||
enabled = prefs.getBoolean(Const.Key.DISABLE, false);
|
||||
if (enabled) {
|
||||
Utils.createFile(Const.MAGISK_DISABLE_FILE);
|
||||
case Const.Key.COREONLY:
|
||||
if (prefs.getBoolean(key, false)) {
|
||||
try {
|
||||
Const.MAGISK_DISABLE_FILE.createNewFile();
|
||||
} catch (IOException ignored) {}
|
||||
} else {
|
||||
Utils.removeItem(Const.MAGISK_DISABLE_FILE);
|
||||
Const.MAGISK_DISABLE_FILE.delete();
|
||||
}
|
||||
Toast.makeText(getActivity(), R.string.settings_reboot_toast, Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
case Const.Key.MAGISKHIDE:
|
||||
enabled = prefs.getBoolean(Const.Key.MAGISKHIDE, false);
|
||||
if (enabled) {
|
||||
Shell.su_raw("magiskhide --enable");
|
||||
if (prefs.getBoolean(key, false)) {
|
||||
Shell.Async.su("magiskhide --enable");
|
||||
} else {
|
||||
Shell.su_raw("magiskhide --disable");
|
||||
Shell.Async.su("magiskhide --disable");
|
||||
}
|
||||
break;
|
||||
case Const.Key.HOSTS:
|
||||
enabled = prefs.getBoolean(Const.Key.HOSTS, false);
|
||||
if (enabled) {
|
||||
Shell.su_raw(
|
||||
if (prefs.getBoolean(key, false)) {
|
||||
Shell.Async.su(
|
||||
"cp -af /system/etc/hosts " + Const.MAGISK_HOST_FILE(),
|
||||
"mount -o bind " + Const.MAGISK_HOST_FILE() + " /system/etc/hosts");
|
||||
} else {
|
||||
Shell.su_raw(
|
||||
Shell.Async.su(
|
||||
"umount -l /system/etc/hosts",
|
||||
"rm -f " + Const.MAGISK_HOST_FILE());
|
||||
}
|
||||
break;
|
||||
case Const.Key.ROOT_ACCESS:
|
||||
mm.suDB.setSettings(Const.Key.ROOT_ACCESS, Utils.getPrefsInt(prefs, Const.Key.ROOT_ACCESS));
|
||||
break;
|
||||
case Const.Key.SU_MULTIUSER_MODE:
|
||||
mm.suDB.setSettings(Const.Key.SU_MULTIUSER_MODE, Utils.getPrefsInt(prefs, Const.Key.SU_MULTIUSER_MODE));
|
||||
break;
|
||||
case Const.Key.SU_MNT_NS:
|
||||
mm.suDB.setSettings(Const.Key.SU_MNT_NS, Utils.getPrefsInt(prefs, Const.Key.SU_MNT_NS));
|
||||
mm.mDB.setSettings(key, Utils.getPrefsInt(prefs, key));
|
||||
break;
|
||||
case Const.Key.LOCALE:
|
||||
mm.setLocale();
|
||||
@@ -262,6 +285,9 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
|
||||
case Const.Key.UPDATE_CHANNEL:
|
||||
new CheckUpdates().exec();
|
||||
break;
|
||||
case Const.Key.CHECK_UPDATES:
|
||||
mm.setupUpdateCheck();
|
||||
break;
|
||||
}
|
||||
mm.loadConfig();
|
||||
setSummary();
|
||||
@@ -285,7 +311,7 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTopicPublished(Topic topic, Object result) {
|
||||
public void onTopicPublished(Topic topic) {
|
||||
setLocalePreference((ListPreference) findPreference(Const.Key.LOCALE));
|
||||
}
|
||||
|
||||
88
src/main/java/com/topjohnwu/magisk/SplashActivity.java
Normal file
88
src/main/java/com/topjohnwu/magisk/SplashActivity.java
Normal file
@@ -0,0 +1,88 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.topjohnwu.magisk.asyncs.CheckUpdates;
|
||||
import com.topjohnwu.magisk.asyncs.LoadModules;
|
||||
import com.topjohnwu.magisk.asyncs.ParallelTask;
|
||||
import com.topjohnwu.magisk.asyncs.UpdateRepos;
|
||||
import com.topjohnwu.magisk.components.Activity;
|
||||
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
|
||||
import com.topjohnwu.magisk.receivers.ShortcutReceiver;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
public class SplashActivity extends Activity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
MagiskManager mm = getMagiskManager();
|
||||
|
||||
mm.repoDB = new RepoDatabaseHelper(this);
|
||||
mm.loadMagiskInfo();
|
||||
mm.getDefaultInstallFlags();
|
||||
Utils.loadPrefs();
|
||||
|
||||
// Dynamic detect all locales
|
||||
new LoadLocale().exec();
|
||||
|
||||
// Create notification channel on Android O
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationChannel channel = new NotificationChannel(Const.ID.NOTIFICATION_CHANNEL,
|
||||
getString(R.string.magisk_updates), NotificationManager.IMPORTANCE_DEFAULT);
|
||||
getSystemService(NotificationManager.class).createNotificationChannel(channel);
|
||||
}
|
||||
|
||||
// Setup shortcuts
|
||||
sendBroadcast(new Intent(this, ShortcutReceiver.class));
|
||||
|
||||
LoadModules loadModuleTask = new LoadModules();
|
||||
|
||||
if (Utils.checkNetworkStatus()) {
|
||||
// Fire update check
|
||||
new CheckUpdates().exec();
|
||||
// Add repo update check
|
||||
loadModuleTask.setCallBack(() -> new UpdateRepos(false).exec());
|
||||
}
|
||||
|
||||
// Magisk working as expected
|
||||
if (Shell.rootAccess() && mm.magiskVersionCode > 0) {
|
||||
// Update check service
|
||||
mm.setupUpdateCheck();
|
||||
// Fire asynctasks
|
||||
loadModuleTask.exec();
|
||||
// Check dtbo status
|
||||
Utils.patchDTBO();
|
||||
}
|
||||
|
||||
// Write back default values
|
||||
mm.writeConfig();
|
||||
|
||||
mm.hasInit = true;
|
||||
|
||||
Intent intent = new Intent(this, MainActivity.class);
|
||||
intent.putExtra(Const.Key.OPEN_SECTION, getIntent().getStringExtra(Const.Key.OPEN_SECTION));
|
||||
intent.putExtra(Const.Key.INTENT_PERM, getIntent().getStringExtra(Const.Key.INTENT_PERM));
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
static class LoadLocale extends ParallelTask<Void, Void, Void> {
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
MagiskManager.get().locales = Utils.getAvailableLocale();
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid) {
|
||||
MagiskManager.get().localeDone.publish();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@ public class SuLogFragment extends Fragment {
|
||||
View v = inflater.inflate(R.layout.fragment_su_log, container, false);
|
||||
unbinder = ButterKnife.bind(this, v);
|
||||
mm = getApplication();
|
||||
adapter = new SuLogAdapter(mm.suDB);
|
||||
adapter = new SuLogAdapter(mm.mDB);
|
||||
recyclerView.setAdapter(adapter);
|
||||
|
||||
updateList();
|
||||
@@ -73,7 +73,7 @@ public class SuLogFragment extends Fragment {
|
||||
updateList();
|
||||
return true;
|
||||
case R.id.menu_clear:
|
||||
mm.suDB.clearLogs();
|
||||
mm.mDB.clearLogs();
|
||||
updateList();
|
||||
return true;
|
||||
default:
|
||||
@@ -34,13 +34,13 @@ public class SuperuserFragment extends Fragment {
|
||||
PackageManager pm = getActivity().getPackageManager();
|
||||
MagiskManager mm = getApplication();
|
||||
|
||||
List<Policy> policyList = mm.suDB.getPolicyList(pm);
|
||||
List<Policy> policyList = mm.mDB.getPolicyList(pm);
|
||||
|
||||
if (policyList.size() == 0) {
|
||||
emptyRv.setVisibility(View.VISIBLE);
|
||||
recyclerView.setVisibility(View.GONE);
|
||||
} else {
|
||||
recyclerView.setAdapter(new PolicyAdapter(policyList, mm.suDB, pm));
|
||||
recyclerView.setAdapter(new PolicyAdapter(policyList, mm.mDB, pm));
|
||||
emptyRv.setVisibility(View.GONE);
|
||||
recyclerView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package com.topjohnwu.magisk.adapters;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -16,11 +15,10 @@ import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.asyncs.ParallelTask;
|
||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -65,30 +63,17 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
||||
holder.appName.setText(info.loadLabel(pm));
|
||||
holder.appPackage.setText(info.packageName);
|
||||
|
||||
// Remove all listeners
|
||||
holder.itemView.setOnClickListener(null);
|
||||
holder.checkBox.setOnCheckedChangeListener(null);
|
||||
|
||||
if (Const.SN_DEFAULTLIST.contains(info.packageName)) {
|
||||
holder.checkBox.setChecked(true);
|
||||
holder.checkBox.setEnabled(false);
|
||||
holder.itemView.setOnClickListener(v ->
|
||||
SnackbarMaker.make(holder.itemView,
|
||||
R.string.safetyNet_hide_notice, Snackbar.LENGTH_LONG).show()
|
||||
);
|
||||
} else {
|
||||
holder.checkBox.setEnabled(true);
|
||||
holder.checkBox.setChecked(mHideList.contains(info.packageName));
|
||||
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> {
|
||||
if (isChecked) {
|
||||
Shell.su_raw("magiskhide --add " + info.packageName);
|
||||
mHideList.add(info.packageName);
|
||||
} else {
|
||||
Shell.su_raw("magiskhide --rm " + info.packageName);
|
||||
mHideList.remove(info.packageName);
|
||||
}
|
||||
});
|
||||
}
|
||||
holder.checkBox.setChecked(mHideList.contains(info.packageName));
|
||||
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> {
|
||||
if (isChecked) {
|
||||
Shell.Async.su("magiskhide --add " + info.packageName);
|
||||
mHideList.add(info.packageName);
|
||||
} else {
|
||||
Shell.Async.su("magiskhide --rm " + info.packageName);
|
||||
mHideList.remove(info.packageName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -108,7 +93,7 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
||||
|
||||
@BindView(R.id.app_icon) ImageView appIcon;
|
||||
@BindView(R.id.app_name) TextView appName;
|
||||
@BindView(R.id.app_package) TextView appPackage;
|
||||
@BindView(R.id.package_name) TextView appPackage;
|
||||
@BindView(R.id.checkbox) CheckBox checkBox;
|
||||
|
||||
ViewHolder(View itemView) {
|
||||
@@ -149,13 +134,13 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
||||
mOriginalList = pm.getInstalledApplications(0);
|
||||
for (Iterator<ApplicationInfo> i = mOriginalList.iterator(); i.hasNext(); ) {
|
||||
ApplicationInfo info = i.next();
|
||||
if (Const.SN_BLACKLIST.contains(info.packageName) || !info.enabled) {
|
||||
if (Const.HIDE_BLACKLIST.contains(info.packageName) || !info.enabled) {
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
Collections.sort(mOriginalList, (a, b) -> a.loadLabel(pm).toString().toLowerCase()
|
||||
.compareTo(b.loadLabel(pm).toString().toLowerCase()));
|
||||
mHideList = Shell.su("magiskhide --ls");
|
||||
mHideList = Shell.Sync.su("magiskhide --ls");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import android.widget.TextView;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.container.Module;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -16,7 +16,7 @@ import com.topjohnwu.magisk.components.AlertDialogBuilder;
|
||||
import com.topjohnwu.magisk.components.ExpandableView;
|
||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.container.Policy;
|
||||
import com.topjohnwu.magisk.database.SuDatabaseHelper;
|
||||
import com.topjohnwu.magisk.database.MagiskDatabaseHelper;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -28,11 +28,11 @@ import butterknife.ButterKnife;
|
||||
public class PolicyAdapter extends RecyclerView.Adapter<PolicyAdapter.ViewHolder> {
|
||||
|
||||
private List<Policy> policyList;
|
||||
private SuDatabaseHelper dbHelper;
|
||||
private MagiskDatabaseHelper dbHelper;
|
||||
private PackageManager pm;
|
||||
private Set<Policy> expandList = new HashSet<>();
|
||||
|
||||
public PolicyAdapter(List<Policy> list, SuDatabaseHelper db, PackageManager pm) {
|
||||
public PolicyAdapter(List<Policy> list, MagiskDatabaseHelper db, PackageManager pm) {
|
||||
policyList = list;
|
||||
dbHelper = db;
|
||||
this.pm = pm;
|
||||
@@ -95,6 +95,7 @@ public class ReposAdapter extends SectionedAdapter<ReposAdapter.SectionHolder, R
|
||||
String author = repo.getAuthor();
|
||||
holder.author.setText(TextUtils.isEmpty(author) ? null : context.getString(R.string.author, author));
|
||||
holder.description.setText(repo.getDescription());
|
||||
holder.updateTime.setText(context.getString(R.string.updated_on, repo.getLastUpdateString()));
|
||||
|
||||
holder.infoLayout.setOnClickListener(v ->
|
||||
new MarkDownWindow((Activity) context, null, repo.getDetailUrl()).exec());
|
||||
@@ -180,6 +181,7 @@ public class ReposAdapter extends SectionedAdapter<ReposAdapter.SectionHolder, R
|
||||
@BindView(R.id.author) TextView author;
|
||||
@BindView(R.id.info_layout) LinearLayout infoLayout;
|
||||
@BindView(R.id.download) ImageView downloadImage;
|
||||
@BindView(R.id.update_time) TextView updateTime;
|
||||
|
||||
RepoHolder(View itemView) {
|
||||
super(itemView);
|
||||
@@ -13,7 +13,7 @@ import android.widget.TextView;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.components.ExpandableView;
|
||||
import com.topjohnwu.magisk.container.SuLogEntry;
|
||||
import com.topjohnwu.magisk.database.SuDatabaseHelper;
|
||||
import com.topjohnwu.magisk.database.MagiskDatabaseHelper;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
@@ -27,10 +27,10 @@ public class SuLogAdapter extends SectionedAdapter<SuLogAdapter.SectionHolder, S
|
||||
|
||||
private List<List<Integer>> logEntryList;
|
||||
private Set<Integer> itemExpanded, sectionExpanded;
|
||||
private SuDatabaseHelper suDB;
|
||||
private MagiskDatabaseHelper suDB;
|
||||
private Cursor suLogCursor = null;
|
||||
|
||||
public SuLogAdapter(SuDatabaseHelper db) {
|
||||
public SuLogAdapter(MagiskDatabaseHelper db) {
|
||||
suDB = db;
|
||||
logEntryList = Collections.emptyList();
|
||||
sectionExpanded = new HashSet<>();
|
||||
@@ -4,14 +4,15 @@ import android.app.Activity;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.reflect.Proxy;
|
||||
@@ -21,42 +22,52 @@ import dalvik.system.DexClassLoader;
|
||||
|
||||
public class CheckSafetyNet extends ParallelTask<Void, Void, Exception> {
|
||||
|
||||
private File dexPath;
|
||||
public static final File dexPath =
|
||||
new File(MagiskManager.get().getFilesDir().getParent() + "/snet", "snet.apk");
|
||||
private DexClassLoader loader;
|
||||
private Class<?> helperClazz, callbackClazz;
|
||||
|
||||
public CheckSafetyNet(Activity activity) {
|
||||
super(activity);
|
||||
dexPath = new File(activity.getCacheDir().getParent() + "/snet", "snet.apk");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
if (mm.snet_version != Const.Value.SNET_VER) {
|
||||
Shell.sh("rm -rf " + dexPath.getParent());
|
||||
private void dlSnet() throws IOException {
|
||||
Shell.Sync.sh("rm -rf " + dexPath.getParent());
|
||||
HttpURLConnection conn = WebService.request(Const.Url.SNET_URL, null);
|
||||
dexPath.getParentFile().mkdir();
|
||||
try (
|
||||
OutputStream out = new BufferedOutputStream(new FileOutputStream(dexPath));
|
||||
InputStream in = new BufferedInputStream(conn.getInputStream())) {
|
||||
ShellUtils.pump(in, out);
|
||||
}
|
||||
conn.disconnect();
|
||||
}
|
||||
|
||||
private void dyload() throws Exception {
|
||||
loader = new DexClassLoader(dexPath.getPath(), dexPath.getParent(),
|
||||
null, ClassLoader.getSystemClassLoader());
|
||||
helperClazz = loader.loadClass(Const.SNET_PKG + ".SafetyNetHelper");
|
||||
callbackClazz = loader.loadClass(Const.SNET_PKG + ".SafetyNetCallback");
|
||||
int snet_ver = (int) helperClazz.getMethod("getVersion").invoke(null);
|
||||
if (snet_ver != Const.SNET_VER) {
|
||||
throw new Exception();
|
||||
}
|
||||
mm.snet_version = Const.Value.SNET_VER;
|
||||
mm.prefs.edit().putInt(Const.Key.SNET_VER, Const.Value.SNET_VER).apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Exception doInBackground(Void... voids) {
|
||||
try {
|
||||
if (!dexPath.exists()) {
|
||||
HttpURLConnection conn = WebService.request(Const.Url.SNET_URL, null);
|
||||
dexPath.getParentFile().mkdir();
|
||||
try (
|
||||
OutputStream out = new BufferedOutputStream(new FileOutputStream(dexPath));
|
||||
InputStream in = new BufferedInputStream(conn.getInputStream())) {
|
||||
Utils.inToOut(in, out);
|
||||
}
|
||||
conn.disconnect();
|
||||
try {
|
||||
dyload();
|
||||
} catch (Exception e) {
|
||||
// If dynamic load failed, try re-downloading and reload
|
||||
dlSnet();
|
||||
dyload();
|
||||
}
|
||||
loader = new DexClassLoader(dexPath.toString(), dexPath.getParent(),
|
||||
null, ClassLoader.getSystemClassLoader());
|
||||
} catch (Exception e) {
|
||||
return e;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -65,8 +76,6 @@ public class CheckSafetyNet extends ParallelTask<Void, Void, Exception> {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
try {
|
||||
if (err != null) throw err;
|
||||
Class<?> helperClazz = loader.loadClass(Const.SNET_PKG + ".SafetyNetHelper");
|
||||
Class<?> callbackClazz = loader.loadClass(Const.SNET_PKG + ".SafetyNetCallback");
|
||||
Object helper = helperClazz.getConstructors()[0].newInstance(
|
||||
getActivity(), dexPath.getPath(), Proxy.newProxyInstance(
|
||||
loader, new Class[] { callbackClazz }, (proxy, method, args) -> {
|
||||
@@ -33,7 +33,7 @@ public class CheckUpdates extends ParallelTask<Void, Void, Void> {
|
||||
jsonStr = WebService.getString(Const.Url.BETA_URL);
|
||||
break;
|
||||
case Const.Value.CUSTOM_CHANNEL:
|
||||
jsonStr = WebService.getString(mm.customChannelUrl);
|
||||
jsonStr = WebService.getString(mm.prefs.getString(Const.Key.CUSTOM_CHANNEL, ""));
|
||||
break;
|
||||
}
|
||||
try {
|
||||
@@ -54,7 +54,7 @@ public class CheckUpdates extends ParallelTask<Void, Void, Void> {
|
||||
@Override
|
||||
protected void onPostExecute(Void v) {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
if (showNotification && mm.updateNotification) {
|
||||
if (showNotification) {
|
||||
if (BuildConfig.VERSION_CODE < mm.remoteManagerVersionCode) {
|
||||
ShowUI.managerUpdateNotification();
|
||||
} else if (mm.magiskVersionCode < mm.remoteMagiskVersionCode) {
|
||||
@@ -8,9 +8,10 @@ import android.view.View;
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.utils.ZipUtils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
@@ -38,8 +39,8 @@ public class FlashZip extends ParallelTask<Void, Void, Integer> {
|
||||
|
||||
private boolean unzipAndCheck() throws Exception {
|
||||
ZipUtils.unzip(mCachedFile, mCachedFile.getParentFile(), "META-INF/com/google/android", true);
|
||||
List<String> ret = Utils.readFile(new File(mCachedFile.getParentFile(), "updater-script").getPath());
|
||||
return Utils.isValidShellResponse(ret) && ret.get(0).contains("#MAGISK");
|
||||
String s = Utils.cmd("head -n 1 " + new File(mCachedFile.getParentFile(), "updater-script"));
|
||||
return s != null && s.contains("#MAGISK");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -55,7 +56,7 @@ public class FlashZip extends ParallelTask<Void, Void, Integer> {
|
||||
) {
|
||||
if (in == null) throw new FileNotFoundException();
|
||||
InputStream buf= new BufferedInputStream(in);
|
||||
Utils.inToOut(buf, out);
|
||||
ShellUtils.pump(buf, out);
|
||||
} catch (FileNotFoundException e) {
|
||||
console.add("! Invalid Uri");
|
||||
throw e;
|
||||
@@ -65,7 +66,7 @@ public class FlashZip extends ParallelTask<Void, Void, Integer> {
|
||||
}
|
||||
if (!unzipAndCheck()) return 0;
|
||||
console.add("- Installing " + Utils.getNameFromUri(mm, mUri));
|
||||
Shell.getShell().run(console, logs,
|
||||
Shell.Sync.su(console, logs,
|
||||
"cd " + mCachedFile.getParent(),
|
||||
"BOOTMODE=true sh update-binary dummy 1 " + mCachedFile + " || echo 'Failed!'"
|
||||
);
|
||||
@@ -85,7 +86,7 @@ public class FlashZip extends ParallelTask<Void, Void, Integer> {
|
||||
@Override
|
||||
protected void onPostExecute(Integer result) {
|
||||
FlashActivity activity = (FlashActivity) getActivity();
|
||||
Shell.su_raw(
|
||||
Shell.Async.su(
|
||||
"rm -rf " + mCachedFile.getParent(),
|
||||
"rm -rf " + Const.TMP_FOLDER_PATH
|
||||
);
|
||||
@@ -1,24 +1,32 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.crypto.JarMap;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.utils.ZipUtils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
import com.topjohnwu.superuser.io.SuFileOutputStream;
|
||||
import com.topjohnwu.utils.JarMap;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.jar.JarEntry;
|
||||
|
||||
public class HideManager extends ParallelTask<Void, Void, Boolean> {
|
||||
|
||||
private ProgressDialog dialog;
|
||||
|
||||
public HideManager(Activity activity) {
|
||||
super(activity);
|
||||
}
|
||||
|
||||
private String genPackageName(String prefix, int length) {
|
||||
StringBuilder builder = new StringBuilder(length);
|
||||
builder.append(prefix);
|
||||
@@ -87,8 +95,9 @@ public class HideManager extends ParallelTask<Void, Void, Boolean> {
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
MagiskManager.toast(R.string.hide_manager_toast, Toast.LENGTH_SHORT);
|
||||
MagiskManager.toast(R.string.hide_manager_toast2, Toast.LENGTH_LONG);
|
||||
dialog = ProgressDialog.show(getActivity(),
|
||||
getActivity().getString(R.string.hide_manager_toast),
|
||||
getActivity().getString(R.string.hide_manager_toast2));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -96,8 +105,7 @@ public class HideManager extends ParallelTask<Void, Void, Boolean> {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
|
||||
// Generate a new unhide app with random package name
|
||||
File repack = new File(Const.EXTERNAL_PATH, "repack.apk");
|
||||
repack.getParentFile().mkdirs();
|
||||
SuFile repack = new SuFile("/data/local/tmp/repack.apk", true);
|
||||
String pkg = genPackageName("com.", Const.ORIG_PKG_NAME.length());
|
||||
|
||||
try {
|
||||
@@ -115,29 +123,28 @@ public class HideManager extends ParallelTask<Void, Void, Boolean> {
|
||||
apk.getOutputStream(je).write(xml);
|
||||
|
||||
// Sign the APK
|
||||
ZipUtils.signZip(apk, repack, false);
|
||||
ZipUtils.signZip(apk, new SuFileOutputStream(repack));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Install the application
|
||||
|
||||
List<String> ret = Shell.su(String.format(Locale.US,
|
||||
"pm install --user %d %s >/dev/null && echo true || echo false",
|
||||
mm.userId, repack));
|
||||
repack.delete();
|
||||
if (!Utils.isValidShellResponse(ret) || !Boolean.parseBoolean(ret.get(0)))
|
||||
if (!ShellUtils.fastCmdResult(Shell.getShell(), "pm install " + repack))
|
||||
return false;
|
||||
|
||||
mm.suDB.setStrings(Const.Key.SU_REQUESTER, pkg);
|
||||
Shell.su_raw(String.format(Locale.US, "pm uninstall --user %d %s", mm.userId, mm.getPackageName()));
|
||||
repack.delete();
|
||||
|
||||
mm.mDB.setStrings(Const.Key.SU_REQUESTER, pkg);
|
||||
Utils.dumpPrefs();
|
||||
Utils.uninstallPkg(Const.ORIG_PKG_NAME);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean b) {
|
||||
dialog.dismiss();
|
||||
if (!b) {
|
||||
MagiskManager.toast(R.string.hide_manager_fail_toast, Toast.LENGTH_LONG);
|
||||
}
|
||||
@@ -1,20 +1,22 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.res.AssetManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
|
||||
import com.topjohnwu.crypto.SignBoot;
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.container.TarEntry;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.utils.ZipUtils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
import com.topjohnwu.superuser.io.SuFileInputStream;
|
||||
import com.topjohnwu.utils.SignBoot;
|
||||
|
||||
import org.kamranzafar.jtar.TarInputStream;
|
||||
import org.kamranzafar.jtar.TarOutputStream;
|
||||
@@ -39,26 +41,24 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
|
||||
private Uri mBootImg, mZip;
|
||||
private List<String> console, logs;
|
||||
private String mBootLocation;
|
||||
private boolean mKeepEnc, mKeepVerity;
|
||||
private int mode;
|
||||
private File install;
|
||||
|
||||
private InstallMagisk(Activity context, List<String> console, List<String> logs, Uri zip, boolean enc, boolean verity) {
|
||||
private InstallMagisk(Activity context, List<String> console, List<String> logs, Uri zip) {
|
||||
super(context);
|
||||
this.console = console;
|
||||
this.logs = logs;
|
||||
mZip = zip;
|
||||
mKeepEnc = enc;
|
||||
mKeepVerity = verity;
|
||||
}
|
||||
|
||||
public InstallMagisk(Activity context, List<String> console, List<String> logs, Uri zip, boolean enc, boolean verity, Uri boot) {
|
||||
this(context, console, logs, zip, enc, verity);
|
||||
public InstallMagisk(Activity context, List<String> console, List<String> logs, Uri zip, Uri boot) {
|
||||
this(context, console, logs, zip);
|
||||
mBootImg = boot;
|
||||
mode = PATCH_MODE;
|
||||
}
|
||||
|
||||
public InstallMagisk(Activity context, List<String> console, List<String> logs, Uri zip, boolean enc, boolean verity, String boot) {
|
||||
this(context, console, logs, zip, enc, verity);
|
||||
public InstallMagisk(Activity context, List<String> console, List<String> logs, Uri zip, String boot) {
|
||||
this(context, console, logs, zip);
|
||||
mBootLocation = boot;
|
||||
mode = DIRECT_MODE;
|
||||
}
|
||||
@@ -67,20 +67,28 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
|
||||
protected Boolean doInBackground(Void... voids) {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
|
||||
File install = new File(
|
||||
install = new File(
|
||||
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ?
|
||||
mm.createDeviceProtectedStorageContext() :
|
||||
mm).getFilesDir().getParent()
|
||||
mm.createDeviceProtectedStorageContext() : mm)
|
||||
.getFilesDir().getParent()
|
||||
, "install");
|
||||
Shell.sh_raw("rm -rf " + install);
|
||||
Shell.Sync.sh("rm -rf " + install);
|
||||
|
||||
List<String> abis = Arrays.asList(Build.SUPPORTED_ABIS);
|
||||
String arch;
|
||||
if (abis.contains("x86_64")) arch = "x64";
|
||||
else if (abis.contains("arm64-v8a")) arch = "arm64";
|
||||
else if (abis.contains("x86")) arch = "x86";
|
||||
else arch = "arm";
|
||||
console.add("- Device platform: " + arch);
|
||||
|
||||
if (mm.remoteMagiskVersionCode >= Const.MAGISK_VER.SEPOL_REFACTOR) {
|
||||
// 32-bit only
|
||||
if (abis.contains("x86")) arch = "x86";
|
||||
else arch = "arm";
|
||||
} else {
|
||||
if (abis.contains("x86_64")) arch = "x64";
|
||||
else if (abis.contains("arm64-v8a")) arch = "arm64";
|
||||
else if (abis.contains("x86")) arch = "x86";
|
||||
else arch = "arm";
|
||||
}
|
||||
|
||||
console.add("- Device platform: " + Build.SUPPORTED_ABIS[0]);
|
||||
|
||||
try {
|
||||
// Unzip files
|
||||
@@ -104,15 +112,15 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
|
||||
console.add("! Cannot unzip zip");
|
||||
throw e;
|
||||
}
|
||||
Shell.Sync.sh("chmod 755 " + install + "/*");
|
||||
|
||||
File boot = new File(install, "boot.img");
|
||||
boolean highCompression = false;
|
||||
switch (mode) {
|
||||
case PATCH_MODE:
|
||||
console.add("- Use boot image: " + boot);
|
||||
// Copy boot image to local
|
||||
try (
|
||||
InputStream in = mm.getContentResolver().openInputStream(mBootImg);
|
||||
OutputStream out = new FileOutputStream(boot)
|
||||
try (InputStream in = mm.getContentResolver().openInputStream(mBootImg);
|
||||
OutputStream out = new FileOutputStream(boot)
|
||||
) {
|
||||
InputStream source;
|
||||
if (in == null) throw new FileNotFoundException();
|
||||
@@ -130,7 +138,7 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
|
||||
// Direct copy raw image
|
||||
source = new BufferedInputStream(in);
|
||||
}
|
||||
Utils.inToOut(source, out);
|
||||
ShellUtils.pump(source, out);
|
||||
} catch (FileNotFoundException e) {
|
||||
console.add("! Invalid Uri");
|
||||
throw e;
|
||||
@@ -140,9 +148,16 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
|
||||
}
|
||||
break;
|
||||
case DIRECT_MODE:
|
||||
console.add("- Use boot image: " + mBootLocation);
|
||||
console.add("- Patch boot/ramdisk image: " + mBootLocation);
|
||||
if (mm.remoteMagiskVersionCode >= 1463) {
|
||||
highCompression = Integer.parseInt(Utils.cmd(Utils.fmt(
|
||||
"%s/magiskboot --parse %s; echo $?",
|
||||
install, mBootLocation))) == 2;
|
||||
if (highCompression)
|
||||
console.add("! Insufficient boot partition size detected");
|
||||
}
|
||||
if (boot.createNewFile()) {
|
||||
Shell.su("cat " + mBootLocation + " > " + boot);
|
||||
Shell.Sync.su("cat " + mBootLocation + " > " + boot);
|
||||
} else {
|
||||
console.add("! Dump boot image failed");
|
||||
return false;
|
||||
@@ -156,50 +171,39 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
|
||||
try (InputStream in = new FileInputStream(boot)) {
|
||||
isSigned = SignBoot.verifySignature(in, null);
|
||||
if (isSigned) {
|
||||
console.add("- Signed boot image detected");
|
||||
console.add("- Boot image is signed with AVB 1.0");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
console.add("! Unable to check signature");
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Force non-root shell
|
||||
Shell shell;
|
||||
if (Shell.rootAccess())
|
||||
shell = new Shell("sh");
|
||||
else
|
||||
shell = Shell.getShell();
|
||||
|
||||
// Patch boot image
|
||||
shell.run(console, logs,
|
||||
Shell.Sync.sh(console, logs,
|
||||
"cd " + install,
|
||||
"KEEPFORCEENCRYPT=" + mKeepEnc + " KEEPVERITY=" + mKeepVerity + " sh " +
|
||||
"update-binary indep boot_patch.sh " + boot + " || echo 'Failed!'");
|
||||
Utils.fmt("KEEPFORCEENCRYPT=%b KEEPVERITY=%b HIGHCOMP=%b " +
|
||||
"sh update-binary indep boot_patch.sh %s || echo 'Failed!'",
|
||||
mm.keepEnc, mm.keepVerity, highCompression, boot));
|
||||
|
||||
if (TextUtils.equals(console.get(console.size() - 1), "Failed!"))
|
||||
return false;
|
||||
|
||||
shell.run(null, null,
|
||||
"mv -f new-boot.img ../",
|
||||
Shell.Sync.sh("mv -f new-boot.img ../",
|
||||
"mv bin/busybox busybox",
|
||||
"rm -rf bin *.img update-binary",
|
||||
"rm -rf magisk.apk bin *.img update-binary",
|
||||
"cd /");
|
||||
|
||||
File patched_boot = new File(install.getParent(), "new-boot.img");
|
||||
SuFile patched_boot = new SuFile(install.getParent(), "new-boot.img");
|
||||
|
||||
if (isSigned) {
|
||||
console.add("- Signing boot image");
|
||||
console.add("- Signing boot image with test keys");
|
||||
File signed = new File(install.getParent(), "signed.img");
|
||||
AssetManager assets = mm.getAssets();
|
||||
try (
|
||||
InputStream in = new FileInputStream(patched_boot);
|
||||
OutputStream out = new BufferedOutputStream(new FileOutputStream(signed));
|
||||
InputStream keyIn = assets.open(Const.PRIVATE_KEY_NAME);
|
||||
InputStream certIn = assets.open(Const.PUBLIC_KEY_NAME)
|
||||
try (InputStream in = new SuFileInputStream(patched_boot);
|
||||
OutputStream out = new BufferedOutputStream(new FileOutputStream(signed))
|
||||
) {
|
||||
SignBoot.doSignature("/boot", in, out, keyIn, certIn);
|
||||
SignBoot.doSignature("/boot", in, out, null, null);
|
||||
}
|
||||
shell.run_raw(false, false, "mv -f " + signed + " " + patched_boot);
|
||||
Shell.Sync.sh("mv -f " + signed + " " + patched_boot);
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
@@ -217,8 +221,8 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
|
||||
out = new BufferedOutputStream(new FileOutputStream(dest));
|
||||
break;
|
||||
}
|
||||
try (InputStream in = new BufferedInputStream(new FileInputStream(patched_boot))) {
|
||||
Utils.inToOut(in, out);
|
||||
try (InputStream in = new SuFileInputStream(patched_boot)) {
|
||||
ShellUtils.pump(in, out);
|
||||
out.close();
|
||||
}
|
||||
console.add("");
|
||||
@@ -228,14 +232,13 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
|
||||
console.add("*********************************");
|
||||
break;
|
||||
case DIRECT_MODE:
|
||||
// Direct flash boot image and patch dtbo if possible
|
||||
Shell.getShell().run(console, logs,
|
||||
"rm -rf /data/magisk/*",
|
||||
"mkdir -p /data/magisk 2>/dev/null",
|
||||
"mv -f " + install + "/* /data/magisk",
|
||||
"rm -rf " + install,
|
||||
"flash_boot_image " + patched_boot + " " + mBootLocation,
|
||||
"patch_dtbo_image");
|
||||
String binPath = mm.remoteMagiskVersionCode >= 1464 ? "/data/adb/magisk" : "/data/magisk";
|
||||
Shell.Sync.su(console, logs,
|
||||
Utils.fmt("rm -rf %s/*; mkdir -p %s; chmod 700 /data/adb", binPath, binPath),
|
||||
Utils.fmt("cp -af %s/* %s; rm -rf %s", install, binPath, install),
|
||||
Utils.fmt("flash_boot_image %s %s", patched_boot, mBootLocation),
|
||||
mm.remoteMagiskVersionCode >= 1464 ? "[ -L /data/magisk.img ] || cp /data/magisk.img /data/adb/magisk.img" : "",
|
||||
mm.keepVerity ? "" : "patch_dtbo_image");
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
@@ -255,6 +258,7 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
|
||||
protected void onPostExecute(Boolean result) {
|
||||
FlashActivity activity = (FlashActivity) getActivity();
|
||||
if (!result) {
|
||||
Shell.Async.sh("rm -rf " + install);
|
||||
console.add("! Installation failed");
|
||||
activity.reboot.setVisibility(View.GONE);
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.container.Module;
|
||||
import com.topjohnwu.magisk.container.ValueSortedMap;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -12,7 +12,7 @@ public class LoadModules extends ParallelTask<Void, Void, Void> {
|
||||
|
||||
private List<String> getModList() {
|
||||
String command = "ls -d " + Const.MAGISK_PATH() + "/* | grep -v lost+found";
|
||||
return Shell.su(command);
|
||||
return Shell.Sync.su(command);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.webkit.WebView;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import org.commonmark.node.Node;
|
||||
import org.commonmark.parser.Parser;
|
||||
import org.commonmark.renderer.html.HtmlRenderer;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class MarkDownWindow extends ParallelTask<Void, Void, String> {
|
||||
|
||||
private String mTitle;
|
||||
private String mUrl;
|
||||
private InputStream is;
|
||||
|
||||
|
||||
public MarkDownWindow(Activity context, String title, String url) {
|
||||
super(context);
|
||||
mTitle = title;
|
||||
mUrl = url;
|
||||
}
|
||||
|
||||
public MarkDownWindow(Activity context, String title, InputStream in) {
|
||||
super(context);
|
||||
mTitle = title;
|
||||
is = in;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String doInBackground(Void... voids) {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
String md;
|
||||
if (mUrl != null) {
|
||||
md = WebService.getString(mUrl);
|
||||
} else {
|
||||
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
||||
ShellUtils.pump(is, out);
|
||||
md = out.toString();
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return "";
|
||||
}
|
||||
}
|
||||
String css;
|
||||
try (
|
||||
InputStream in = mm.getResources().openRawResource(
|
||||
mm.isDarkTheme ? R.raw.dark : R.raw.light);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream()
|
||||
) {
|
||||
ShellUtils.pump(in, out);
|
||||
css = out.toString();
|
||||
in.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return "";
|
||||
}
|
||||
Parser parser = Parser.builder().build();
|
||||
HtmlRenderer renderer = HtmlRenderer.builder().build();
|
||||
Node doc = parser.parse(md);
|
||||
return String.format("<style>%s</style>%s", css, renderer.render(doc));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(String html) {
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(getActivity());
|
||||
alert.setTitle(mTitle);
|
||||
|
||||
WebView wv = new WebView(getActivity());
|
||||
wv.loadDataWithBaseURL("fake://", html, "text/html", "UTF-8", null);
|
||||
|
||||
alert.setView(wv);
|
||||
alert.setNegativeButton(R.string.close, (dialog, id) -> dialog.dismiss());
|
||||
alert.show();
|
||||
}
|
||||
}
|
||||
@@ -12,18 +12,19 @@ import android.widget.Toast;
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.container.InputStreamWrapper;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
import com.topjohnwu.magisk.utils.ZipUtils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
@@ -68,7 +69,7 @@ public class ProcessRepoZip extends ParallelTask<Void, Object, Boolean> {
|
||||
continue;
|
||||
}
|
||||
out.putNextEntry(new JarEntry(path));
|
||||
Utils.inToOut(in, out);
|
||||
ShellUtils.pump(in, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,7 +108,7 @@ public class ProcessRepoZip extends ParallelTask<Void, Object, Boolean> {
|
||||
InputStream in = new BufferedInputStream(new ProgressInputStream(conn.getInputStream()));
|
||||
OutputStream out = new BufferedOutputStream(new FileOutputStream(temp1))
|
||||
) {
|
||||
Utils.inToOut(in, out);
|
||||
ShellUtils.pump(in, out);
|
||||
in.close();
|
||||
}
|
||||
conn.disconnect();
|
||||
@@ -120,14 +121,8 @@ public class ProcessRepoZip extends ParallelTask<Void, Object, Boolean> {
|
||||
// First remove top folder in Github source zip, temp1 -> temp2
|
||||
removeTopFolder(temp1, temp2);
|
||||
|
||||
// Then sign the zip for the first time, temp2 -> temp1
|
||||
ZipUtils.signZip(temp2, temp1, false);
|
||||
|
||||
// Adjust the zip to prevent unzip issues, temp1 -> temp2
|
||||
ZipUtils.zipAdjust(temp1.getPath(), temp2.getPath());
|
||||
|
||||
// Finally, sign the whole zip file again, temp2 -> target
|
||||
ZipUtils.signZip(temp2, mFile, true);
|
||||
// Then sign the zip
|
||||
ZipUtils.signZip(temp2, mFile);
|
||||
|
||||
// Delete temp files
|
||||
temp1.delete();
|
||||
@@ -167,7 +162,7 @@ public class ProcessRepoZip extends ParallelTask<Void, Object, Boolean> {
|
||||
return this;
|
||||
}
|
||||
|
||||
private class ProgressInputStream extends InputStreamWrapper {
|
||||
private class ProgressInputStream extends FilterInputStream {
|
||||
|
||||
ProgressInputStream(InputStream in) {
|
||||
super(in);
|
||||
35
src/main/java/com/topjohnwu/magisk/asyncs/RestoreImages.java
Normal file
35
src/main/java/com/topjohnwu/magisk/asyncs/RestoreImages.java
Normal file
@@ -0,0 +1,35 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
public class RestoreImages extends ParallelTask<Void, Void, Boolean> {
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... voids) {
|
||||
String sha1;
|
||||
sha1 = Utils.cmd("cat /.backup/.sha1");
|
||||
if (sha1 == null) {
|
||||
sha1 = Utils.cmd("cat /init.magisk.rc | grep STOCKSHA1");
|
||||
if (sha1 == null)
|
||||
return false;
|
||||
sha1 = sha1.substring(sha1.indexOf('=') + 1);
|
||||
}
|
||||
|
||||
return ShellUtils.fastCmdResult(Shell.getShell(), "restore_imgs " + sha1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
if (result) {
|
||||
MagiskManager.toast(R.string.restore_done, Toast.LENGTH_SHORT);
|
||||
} else {
|
||||
MagiskManager.toast(R.string.restore_fail, Toast.LENGTH_LONG);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.ReposFragment;
|
||||
import com.topjohnwu.magisk.container.Repo;
|
||||
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Logger;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
@@ -16,6 +14,7 @@ import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -24,55 +23,81 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class UpdateRepos extends ParallelTask<Void, Void, Void> {
|
||||
|
||||
private static final int CHECK_ETAG = 0;
|
||||
private static final int LOAD_NEXT = 1;
|
||||
private static final int LOAD_PREV = 2;
|
||||
private static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
|
||||
|
||||
private MagiskManager mm;
|
||||
private List<String> cached, etags, newEtags = new ArrayList<>();
|
||||
private RepoDatabaseHelper repoDB;
|
||||
private SharedPreferences prefs;
|
||||
private boolean forceUpdate;
|
||||
|
||||
private int tasks = 0;
|
||||
private AtomicInteger taskCount = new AtomicInteger(0);
|
||||
final private Object allDone = new Object();
|
||||
|
||||
public UpdateRepos(boolean force) {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
prefs = mm.prefs;
|
||||
repoDB = mm.repoDB;
|
||||
mm.repoLoadDone.hasPublished = false;
|
||||
mm = MagiskManager.get();
|
||||
mm.repoLoadDone.reset();
|
||||
// Legacy data cleanup
|
||||
File old = new File(mm.getApplicationInfo().dataDir + "/shared_prefs", "RepoMap.xml");
|
||||
if (old.exists() || prefs.getString("repomap", null) != null) {
|
||||
if (old.exists() || mm.prefs.getString("repomap", null) != null) {
|
||||
old.delete();
|
||||
prefs.edit().remove("version").remove("repomap").remove(Const.Key.ETAG_KEY).apply();
|
||||
repoDB.clearRepo();
|
||||
mm.prefs.edit().remove("version").remove("repomap").remove(Const.Key.ETAG_KEY).apply();
|
||||
mm.repoDB.clearRepo();
|
||||
}
|
||||
forceUpdate = force;
|
||||
}
|
||||
|
||||
private void queueTask(Runnable task) {
|
||||
// Thread pool's queue has an upper bound, batch it with 64 tasks
|
||||
while (taskCount.get() >= 64) {
|
||||
waitTasks();
|
||||
}
|
||||
taskCount.incrementAndGet();
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
||||
task.run();
|
||||
if (taskCount.decrementAndGet() == 0) {
|
||||
synchronized (allDone) {
|
||||
allDone.notify();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void waitTasks() {
|
||||
if (taskCount.get() == 0)
|
||||
return;
|
||||
synchronized (allDone) {
|
||||
try {
|
||||
allDone.wait();
|
||||
} catch (InterruptedException e) {
|
||||
// Wait again
|
||||
waitTasks();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadJSON(String jsonString) throws Exception {
|
||||
JSONArray jsonArray = new JSONArray(jsonString);
|
||||
|
||||
// Empty page, throw error
|
||||
if (jsonArray.length() == 0) throw new Exception();
|
||||
if (jsonArray.length() == 0)
|
||||
throw new Exception();
|
||||
|
||||
for (int i = 0; i < jsonArray.length(); i++) {
|
||||
JSONObject jsonobject = jsonArray.getJSONObject(i);
|
||||
String id = jsonobject.getString("description");
|
||||
String name = jsonobject.getString("name");
|
||||
String lastUpdate = jsonobject.getString("pushed_at");
|
||||
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
|
||||
Date updatedDate = format.parse(lastUpdate);
|
||||
++tasks;
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
||||
Repo repo = repoDB.getRepo(id);
|
||||
JSONObject rawRepo = jsonArray.getJSONObject(i);
|
||||
String id = rawRepo.getString("description");
|
||||
String name = rawRepo.getString("name");
|
||||
Date date = dateFormat.parse(rawRepo.getString("pushed_at"));
|
||||
queueTask(() -> {
|
||||
Repo repo = mm.repoDB.getRepo(id);
|
||||
Boolean updated;
|
||||
try {
|
||||
if (repo == null) {
|
||||
repo = new Repo(name, updatedDate);
|
||||
repo = new Repo(name, date);
|
||||
updated = true;
|
||||
} else {
|
||||
// Popout from cached
|
||||
@@ -81,11 +106,11 @@ public class UpdateRepos extends ParallelTask<Void, Void, Void> {
|
||||
repo.update();
|
||||
updated = true;
|
||||
} else {
|
||||
updated = repo.update(updatedDate);
|
||||
updated = repo.update(date);
|
||||
}
|
||||
}
|
||||
if (updated) {
|
||||
repoDB.addRepo(repo);
|
||||
mm.repoDB.addRepo(repo);
|
||||
publishProgress();
|
||||
}
|
||||
if (!id.equals(repo.getId())) {
|
||||
@@ -93,9 +118,8 @@ public class UpdateRepos extends ParallelTask<Void, Void, Void> {
|
||||
}
|
||||
} catch (Repo.IllegalRepoException e) {
|
||||
Logger.error(e.getMessage());
|
||||
repoDB.removeRepo(id);
|
||||
mm.repoDB.removeRepo(id);
|
||||
}
|
||||
--tasks;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -111,7 +135,8 @@ public class UpdateRepos extends ParallelTask<Void, Void, Void> {
|
||||
HttpURLConnection conn = WebService.request(url, header);
|
||||
|
||||
try {
|
||||
if (conn == null) throw new Exception();
|
||||
if (conn == null)
|
||||
throw new Exception();
|
||||
if (conn.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
|
||||
newEtags.add(etag);
|
||||
return page + 1 < etags.size() && loadPage(page + 1, CHECK_ETAG);
|
||||
@@ -143,69 +168,60 @@ public class UpdateRepos extends ParallelTask<Void, Void, Void> {
|
||||
return true;
|
||||
}
|
||||
|
||||
private Void waitTasks() {
|
||||
while (tasks > 0) {
|
||||
try {
|
||||
Thread.sleep(5);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(Void... values) {
|
||||
if (ReposFragment.adapter != null)
|
||||
ReposFragment.adapter.notifyDBChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
mm.repoLoadDone.setPending();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
etags = new ArrayList<>(Arrays.asList(prefs.getString(Const.Key.ETAG_KEY, "").split(",")));
|
||||
cached = repoDB.getRepoIDList();
|
||||
etags = new ArrayList<>(Arrays.asList(mm.prefs.getString(Const.Key.ETAG_KEY, "").split(",")));
|
||||
cached = mm.repoDB.getRepoIDList();
|
||||
|
||||
if (!loadPage(0, CHECK_ETAG)) {
|
||||
// Nothing changed online
|
||||
if (forceUpdate) {
|
||||
for (String id : cached) {
|
||||
if (id == null) continue;
|
||||
++tasks;
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
||||
Repo repo = repoDB.getRepo(id);
|
||||
queueTask(() -> {
|
||||
Repo repo = mm.repoDB.getRepo(id);
|
||||
try {
|
||||
repo.update();
|
||||
repoDB.addRepo(repo);
|
||||
mm.repoDB.addRepo(repo);
|
||||
} catch (Repo.IllegalRepoException e) {
|
||||
Logger.error(e.getMessage());
|
||||
repoDB.removeRepo(repo);
|
||||
mm.repoDB.removeRepo(repo);
|
||||
}
|
||||
--tasks;
|
||||
});
|
||||
}
|
||||
}
|
||||
return waitTasks();
|
||||
waitTasks();
|
||||
} else {
|
||||
waitTasks();
|
||||
|
||||
// The leftover cached means they are removed from online repo
|
||||
mm.repoDB.removeRepo(cached);
|
||||
|
||||
// Update ETag
|
||||
StringBuilder etagBuilder = new StringBuilder();
|
||||
for (int i = 0; i < newEtags.size(); ++i) {
|
||||
if (i != 0) etagBuilder.append(",");
|
||||
etagBuilder.append(newEtags.get(i));
|
||||
}
|
||||
mm.prefs.edit().putString(Const.Key.ETAG_KEY, etagBuilder.toString()).apply();
|
||||
}
|
||||
|
||||
// Wait till all tasks are done
|
||||
waitTasks();
|
||||
|
||||
// The leftover cached means they are removed from online repo
|
||||
repoDB.removeRepo(cached);
|
||||
|
||||
// Update ETag
|
||||
StringBuilder etagBuilder = new StringBuilder();
|
||||
for (int i = 0; i < newEtags.size(); ++i) {
|
||||
if (i != 0) etagBuilder.append(",");
|
||||
etagBuilder.append(newEtags.get(i));
|
||||
}
|
||||
prefs.edit().putString(Const.Key.ETAG_KEY, etagBuilder.toString()).apply();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void v) {
|
||||
MagiskManager.get().repoLoadDone.publish();
|
||||
mm.repoLoadDone.publish();
|
||||
super.onPostExecute(v);
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import android.os.Bundle;
|
||||
import android.support.annotation.Keep;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StyleRes;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.view.WindowManager;
|
||||
|
||||
@@ -17,10 +18,11 @@ import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
public class Activity extends AppCompatActivity {
|
||||
public abstract class Activity extends AppCompatActivity {
|
||||
|
||||
private AssetManager mAssetManager = null;
|
||||
private Resources mResources = null;
|
||||
private AssetManager swappedAssetManager = null;
|
||||
private Resources swappedResources = null;
|
||||
private Resources.Theme swappedTheme = null;
|
||||
private ActivityResultListener activityResultListener;
|
||||
|
||||
public Activity() {
|
||||
@@ -30,12 +32,20 @@ public class Activity extends AppCompatActivity {
|
||||
applyOverrideConfiguration(configuration);
|
||||
}
|
||||
|
||||
@StyleRes
|
||||
public int getDarkTheme() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (this instanceof Topic.Subscriber) {
|
||||
((Topic.Subscriber) this).subscribeTopics();
|
||||
}
|
||||
if (getMagiskManager().isDarkTheme && getDarkTheme() != -1) {
|
||||
setTheme(getDarkTheme());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -57,14 +67,19 @@ public class Activity extends AppCompatActivity {
|
||||
mm.permissionGrantCallback = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resources.Theme getTheme() {
|
||||
return swappedTheme == null ? super.getTheme() : swappedTheme;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AssetManager getAssets() {
|
||||
return mAssetManager == null ? super.getAssets() : mAssetManager;
|
||||
return swappedAssetManager == null ? super.getAssets() : swappedAssetManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resources getResources() {
|
||||
return mResources == null ? super.getResources() : mResources;
|
||||
return swappedResources == null ? super.getResources() : swappedResources;
|
||||
}
|
||||
|
||||
public MagiskManager getMagiskManager() {
|
||||
@@ -92,30 +107,27 @@ public class Activity extends AppCompatActivity {
|
||||
activityResultListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startActivityForResult(Intent intent, int requestCode) {
|
||||
startActivityForResult(intent, requestCode, this::onActivityResult);
|
||||
}
|
||||
|
||||
public void startActivityForResult(Intent intent, int requestCode, ActivityResultListener listener) {
|
||||
activityResultListener = listener;
|
||||
super.startActivityForResult(intent, requestCode);
|
||||
}
|
||||
|
||||
@Keep
|
||||
public void swapResources(String dexPath) {
|
||||
mAssetManager = Utils.getAssets(dexPath);
|
||||
if (mAssetManager == null)
|
||||
public void swapResources(String dexPath, int resId) {
|
||||
swappedAssetManager = Utils.getAssets(dexPath);
|
||||
if (swappedAssetManager == null)
|
||||
return;
|
||||
Resources res = super.getResources();
|
||||
mResources = new Resources(mAssetManager, res.getDisplayMetrics(), res.getConfiguration());
|
||||
mResources.newTheme().setTo(super.getTheme());
|
||||
swappedResources = new Resources(swappedAssetManager, res.getDisplayMetrics(), res.getConfiguration());
|
||||
swappedTheme = swappedResources.newTheme();
|
||||
swappedTheme.applyStyle(resId, true);
|
||||
}
|
||||
|
||||
@Keep
|
||||
public void restoreResources() {
|
||||
mAssetManager = null;
|
||||
mResources = null;
|
||||
swappedAssetManager = null;
|
||||
swappedResources = null;
|
||||
swappedTheme = null;
|
||||
}
|
||||
|
||||
public interface ActivityResultListener {
|
||||
@@ -9,8 +9,6 @@ import android.support.annotation.StyleRes;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewStub;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
@@ -29,7 +27,6 @@ public class AlertDialogBuilder extends AlertDialog.Builder {
|
||||
@BindView(R.id.positive) Button positive;
|
||||
@BindView(R.id.neutral) Button neutral;
|
||||
@BindView(R.id.message) TextView messageView;
|
||||
@BindView(R.id.custom_view) ViewStub custom;
|
||||
|
||||
private DialogInterface.OnClickListener positiveListener;
|
||||
private DialogInterface.OnClickListener negativeListener;
|
||||
@@ -59,20 +56,15 @@ public class AlertDialogBuilder extends AlertDialog.Builder {
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDialog.Builder setView(int layoutResId) {
|
||||
custom.setLayoutResource(layoutResId);
|
||||
custom.inflate();
|
||||
return this;
|
||||
public AlertDialog.Builder setTitle(int titleId) {
|
||||
return super.setTitle(titleId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDialog.Builder setView(View view) {
|
||||
ViewGroup parent = (ViewGroup) custom.getParent();
|
||||
int idx = parent.indexOfChild(custom);
|
||||
parent.removeView(custom);
|
||||
parent.addView(view, idx);
|
||||
return this;
|
||||
}
|
||||
public AlertDialog.Builder setView(int layoutResId) { return this; }
|
||||
|
||||
@Override
|
||||
public AlertDialog.Builder setView(View view) { return this; }
|
||||
|
||||
@Override
|
||||
public AlertDialog.Builder setMessage(@Nullable CharSequence message) {
|
||||
@@ -1,21 +1,24 @@
|
||||
package com.topjohnwu.magisk.container;
|
||||
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class Module extends BaseModule {
|
||||
|
||||
private String mRemoveFile, mDisableFile, mUpdateFile;
|
||||
private SuFile mRemoveFile, mDisableFile, mUpdateFile;
|
||||
private boolean mEnable, mRemove, mUpdated;
|
||||
|
||||
public Module(String path) {
|
||||
|
||||
try {
|
||||
parseProps(Utils.readFile(path + "/module.prop"));
|
||||
parseProps(Shell.Sync.su("cat " + path + "/module.prop"));
|
||||
} catch (NumberFormatException ignored) {}
|
||||
|
||||
mRemoveFile = path + "/remove";
|
||||
mDisableFile = path + "/disable";
|
||||
mUpdateFile = path + "/update";
|
||||
mRemoveFile = new SuFile(path + "/remove", true);
|
||||
mDisableFile = new SuFile(path + "/disable", true);
|
||||
mUpdateFile = new SuFile(path + "/update", true);
|
||||
|
||||
if (getId() == null) {
|
||||
int sep = path.lastIndexOf('/');
|
||||
@@ -26,19 +29,21 @@ public class Module extends BaseModule {
|
||||
setName(getId());
|
||||
}
|
||||
|
||||
mEnable = !Utils.itemExist(mDisableFile);
|
||||
mRemove = Utils.itemExist(mRemoveFile);
|
||||
mUpdated = Utils.itemExist(mUpdateFile);
|
||||
mEnable = !mDisableFile.exists();
|
||||
mRemove = mRemoveFile.exists();
|
||||
mUpdated = mUpdateFile.exists();
|
||||
}
|
||||
|
||||
public void createDisableFile() {
|
||||
mEnable = false;
|
||||
Utils.createFile(mDisableFile);
|
||||
try {
|
||||
mDisableFile.createNewFile();
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
|
||||
public void removeDisableFile() {
|
||||
mEnable = true;
|
||||
Utils.removeItem(mDisableFile);
|
||||
mDisableFile.delete();
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
@@ -47,12 +52,14 @@ public class Module extends BaseModule {
|
||||
|
||||
public void createRemoveFile() {
|
||||
mRemove = true;
|
||||
Utils.createFile(mRemoveFile);
|
||||
try {
|
||||
mRemoveFile.createNewFile();
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
|
||||
public void deleteRemoveFile() {
|
||||
mRemove = false;
|
||||
Utils.removeItem(mRemoveFile);
|
||||
mRemoveFile.delete();
|
||||
}
|
||||
|
||||
public boolean willBeRemoved() {
|
||||
@@ -21,7 +21,7 @@ public class Policy implements Comparable<Policy>{
|
||||
public Policy(int uid, PackageManager pm) throws PackageManager.NameNotFoundException {
|
||||
String[] pkgs = pm.getPackagesForUid(uid);
|
||||
if (pkgs == null || pkgs.length == 0) throw new PackageManager.NameNotFoundException();
|
||||
this.uid = uid % 100000;
|
||||
this.uid = uid;
|
||||
packageName = pkgs[0];
|
||||
info = pm.getApplicationInfo(packageName, 0);
|
||||
appName = info.loadLabel(pm).toString();
|
||||
@@ -3,9 +3,11 @@ package com.topjohnwu.magisk.container;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
public class Repo extends BaseModule {
|
||||
@@ -40,7 +42,7 @@ public class Repo extends BaseModule {
|
||||
if (getVersionCode() < 0) {
|
||||
throw new IllegalRepoException("Repo [" + repoName + "] does not contain versionCode");
|
||||
}
|
||||
if (getMinMagiskVersion() < Const.Value.MIN_MODULE_VER) {
|
||||
if (getMinMagiskVersion() < Const.MIN_MODULE_VER()) {
|
||||
throw new IllegalRepoException("Repo [" + repoName + "] is outdated");
|
||||
}
|
||||
}
|
||||
@@ -78,6 +80,11 @@ public class Repo extends BaseModule {
|
||||
return String.format(Const.Url.FILE_URL, repoName, "README.md");
|
||||
}
|
||||
|
||||
public String getLastUpdateString() {
|
||||
return DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM,
|
||||
MagiskManager.locale).format(mLastUpdate);
|
||||
}
|
||||
|
||||
public Date getLastUpdate() {
|
||||
return mLastUpdate;
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
package com.topjohnwu.magisk.database;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.os.Build;
|
||||
import android.os.Process;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.container.Policy;
|
||||
import com.topjohnwu.magisk.container.SuLogEntry;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.DateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class MagiskDatabaseHelper {
|
||||
|
||||
private static final int DATABASE_VER = 5;
|
||||
private static final String POLICY_TABLE = "policies";
|
||||
private static final String LOG_TABLE = "logs";
|
||||
private static final String SETTINGS_TABLE = "settings";
|
||||
private static final String STRINGS_TABLE = "strings";
|
||||
|
||||
private PackageManager pm;
|
||||
private SQLiteDatabase db;
|
||||
|
||||
@NonNull
|
||||
public static MagiskDatabaseHelper getInstance(MagiskManager mm) {
|
||||
try {
|
||||
return new MagiskDatabaseHelper(mm);
|
||||
} catch (Exception e) {
|
||||
// Let's cleanup everything and try again
|
||||
Shell.Sync.su("db_clean '*'");
|
||||
return new MagiskDatabaseHelper(mm);
|
||||
}
|
||||
}
|
||||
|
||||
private MagiskDatabaseHelper(MagiskManager mm) {
|
||||
pm = mm.getPackageManager();
|
||||
db = openDatabase(mm);
|
||||
db.disableWriteAheadLogging();
|
||||
int version = db.getVersion();
|
||||
if (version < DATABASE_VER) {
|
||||
onUpgrade(db, version);
|
||||
} else if (version > DATABASE_VER) {
|
||||
onDowngrade(db);
|
||||
}
|
||||
db.setVersion(DATABASE_VER);
|
||||
clearOutdated();
|
||||
}
|
||||
|
||||
private SQLiteDatabase openDatabase(MagiskManager mm) {
|
||||
final File DB_FILE = new File(Utils.fmt("/sbin/.core/db-%d/magisk.db", Const.USER_ID));
|
||||
Context de = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
|
||||
? mm.createDeviceProtectedStorageContext() : mm;
|
||||
if (!DB_FILE.canWrite()) {
|
||||
if (!Shell.rootAccess()) {
|
||||
// We don't want the app to crash, create a db and return
|
||||
return mm.openOrCreateDatabase("su.db", Context.MODE_PRIVATE, null);
|
||||
}
|
||||
mm.loadMagiskInfo();
|
||||
// Cleanup
|
||||
Shell.Sync.su("db_clean " + Const.USER_ID);
|
||||
if (mm.magiskVersionCode < Const.MAGISK_VER.FBE_AWARE) {
|
||||
// Super old legacy mode
|
||||
return mm.openOrCreateDatabase("su.db", Context.MODE_PRIVATE, null);
|
||||
} else if (mm.magiskVersionCode < Const.MAGISK_VER.LEGACY_GLOBAL_DB) {
|
||||
// Legacy mode with FBE aware
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
de.moveDatabaseFrom(mm, "su.db");
|
||||
}
|
||||
return de.openOrCreateDatabase("su.db", Context.MODE_PRIVATE, null);
|
||||
} else {
|
||||
// Global database
|
||||
final SuFile GLOBAL_DB = new SuFile("/data/adb/magisk.db", true);
|
||||
mm.deleteDatabase("su.db");
|
||||
de.deleteDatabase("su.db");
|
||||
if (mm.magiskVersionCode < Const.MAGISK_VER.HIDDEN_PATH) {
|
||||
// Link to new path
|
||||
File oldDB = new File(de.getFilesDir().getParentFile().getParentFile(),
|
||||
"magisk.db");
|
||||
Shell.Sync.su(Utils.fmt("mv -f %s %s; ln -s %s %s",
|
||||
oldDB, GLOBAL_DB, GLOBAL_DB, oldDB));
|
||||
}
|
||||
if (mm.magiskVersionCode < Const.MAGISK_VER.SEPOL_REFACTOR) {
|
||||
// We need some additional policies on old versions
|
||||
Shell.Sync.su("db_sepatch");
|
||||
}
|
||||
if (!GLOBAL_DB.exists()) {
|
||||
Shell.Sync.su("db_init");
|
||||
SQLiteDatabase.openOrCreateDatabase(GLOBAL_DB, null).close();
|
||||
Shell.Sync.su("db_restore");
|
||||
}
|
||||
Shell.Sync.su("db_setup " + Process.myUid());
|
||||
}
|
||||
}
|
||||
// Not using legacy mode, open the mounted global DB
|
||||
return SQLiteDatabase.openOrCreateDatabase(DB_FILE, null);
|
||||
}
|
||||
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion) {
|
||||
if (oldVersion == 0) {
|
||||
createTables(db);
|
||||
oldVersion = 3;
|
||||
}
|
||||
if (oldVersion == 1) {
|
||||
// We're dropping column app_name, rename and re-construct table
|
||||
db.execSQL(Utils.fmt("ALTER TABLE %s RENAME TO %s_old", POLICY_TABLE));
|
||||
|
||||
// Create the new tables
|
||||
createTables(db);
|
||||
|
||||
// Migrate old data to new tables
|
||||
db.execSQL(Utils.fmt("INSERT INTO %s SELECT " +
|
||||
"uid, package_name, policy, until, logging, notification FROM %s_old",
|
||||
POLICY_TABLE, POLICY_TABLE));
|
||||
db.execSQL(Utils.fmt("DROP TABLE %s_old", POLICY_TABLE));
|
||||
|
||||
MagiskManager.get().deleteDatabase("sulog.db");
|
||||
++oldVersion;
|
||||
}
|
||||
if (oldVersion == 2) {
|
||||
db.execSQL(Utils.fmt("UPDATE %s SET time=time*1000", LOG_TABLE));
|
||||
++oldVersion;
|
||||
}
|
||||
if (oldVersion == 3) {
|
||||
db.execSQL(Utils.fmt("CREATE TABLE IF NOT EXISTS %s (key TEXT, value TEXT, PRIMARY KEY(key))", STRINGS_TABLE));
|
||||
++oldVersion;
|
||||
}
|
||||
if (oldVersion == 4) {
|
||||
db.execSQL(Utils.fmt("UPDATE %s SET uid=uid%%100000", POLICY_TABLE));
|
||||
++oldVersion;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove everything, we do not support downgrade
|
||||
public void onDowngrade(SQLiteDatabase db) {
|
||||
MagiskManager.toast(R.string.su_db_corrupt, Toast.LENGTH_LONG);
|
||||
db.execSQL("DROP TABLE IF EXISTS " + POLICY_TABLE);
|
||||
db.execSQL("DROP TABLE IF EXISTS " + LOG_TABLE);
|
||||
db.execSQL("DROP TABLE IF EXISTS " + SETTINGS_TABLE);
|
||||
db.execSQL("DROP TABLE IF EXISTS " + STRINGS_TABLE);
|
||||
onUpgrade(db, 0);
|
||||
}
|
||||
|
||||
private void createTables(SQLiteDatabase db) {
|
||||
// Policies
|
||||
db.execSQL(
|
||||
"CREATE TABLE IF NOT EXISTS " + POLICY_TABLE + " " +
|
||||
"(uid INT, package_name TEXT, policy INT, " +
|
||||
"until INT, logging INT, notification INT, " +
|
||||
"PRIMARY KEY(uid))");
|
||||
|
||||
// Logs
|
||||
db.execSQL(
|
||||
"CREATE TABLE IF NOT EXISTS " + LOG_TABLE + " " +
|
||||
"(from_uid INT, package_name TEXT, app_name TEXT, from_pid INT, " +
|
||||
"to_uid INT, action INT, time INT, command TEXT)");
|
||||
|
||||
// Settings
|
||||
db.execSQL(
|
||||
"CREATE TABLE IF NOT EXISTS " + SETTINGS_TABLE + " " +
|
||||
"(key TEXT, value INT, PRIMARY KEY(key))");
|
||||
}
|
||||
|
||||
public void clearOutdated() {
|
||||
// Clear outdated policies
|
||||
db.delete(POLICY_TABLE, Utils.fmt("until > 0 AND until < %d", System.currentTimeMillis() / 1000), null);
|
||||
// Clear outdated logs
|
||||
db.delete(LOG_TABLE, Utils.fmt("time < %d", System.currentTimeMillis() - MagiskManager.get().suLogTimeout * 86400000), null);
|
||||
}
|
||||
|
||||
public void deletePolicy(Policy policy) {
|
||||
deletePolicy(policy.uid);
|
||||
}
|
||||
|
||||
public void deletePolicy(String pkg) {
|
||||
db.delete(POLICY_TABLE, "package_name=?", new String[] { pkg });
|
||||
}
|
||||
|
||||
public void deletePolicy(int uid) {
|
||||
db.delete(POLICY_TABLE, Utils.fmt("uid=%d", uid), null);
|
||||
}
|
||||
|
||||
public Policy getPolicy(int uid) {
|
||||
Policy policy = null;
|
||||
try (Cursor c = db.query(POLICY_TABLE, null, Utils.fmt("uid=%d", uid), null, null, null, null)) {
|
||||
if (c.moveToNext()) {
|
||||
policy = new Policy(c, pm);
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
deletePolicy(uid);
|
||||
return null;
|
||||
}
|
||||
return policy;
|
||||
}
|
||||
|
||||
public void addPolicy(Policy policy) {
|
||||
db.replace(POLICY_TABLE, null, policy.getContentValues());
|
||||
}
|
||||
|
||||
public void updatePolicy(Policy policy) {
|
||||
db.update(POLICY_TABLE, policy.getContentValues(), Utils.fmt("uid=%d", policy.uid), null);
|
||||
}
|
||||
|
||||
public List<Policy> getPolicyList(PackageManager pm) {
|
||||
try (Cursor c = db.query(POLICY_TABLE, null, Utils.fmt("uid/100000=%d", Const.USER_ID),
|
||||
null, null, null, null)) {
|
||||
List<Policy> ret = new ArrayList<>(c.getCount());
|
||||
while (c.moveToNext()) {
|
||||
try {
|
||||
Policy policy = new Policy(c, pm);
|
||||
ret.add(policy);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
// The app no longer exist, remove from DB
|
||||
deletePolicy(c.getInt(c.getColumnIndex("uid")));
|
||||
}
|
||||
}
|
||||
Collections.sort(ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
public List<List<Integer>> getLogStructure() {
|
||||
try (Cursor c = db.query(LOG_TABLE, new String[] { "time" }, Utils.fmt("from_uid/100000=%d", Const.USER_ID),
|
||||
null, null, null, "time DESC")) {
|
||||
List<List<Integer>> ret = new ArrayList<>();
|
||||
List<Integer> list = null;
|
||||
String dateString = null, newString;
|
||||
while (c.moveToNext()) {
|
||||
Date date = new Date(c.getLong(c.getColumnIndex("time")));
|
||||
newString = DateFormat.getDateInstance(DateFormat.MEDIUM, MagiskManager.locale).format(date);
|
||||
if (!TextUtils.equals(dateString, newString)) {
|
||||
dateString = newString;
|
||||
list = new ArrayList<>();
|
||||
ret.add(list);
|
||||
}
|
||||
list.add(c.getPosition());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
public Cursor getLogCursor() {
|
||||
return db.query(LOG_TABLE, null, Utils.fmt("from_uid/100000=%d", Const.USER_ID),
|
||||
null, null, null, "time DESC");
|
||||
}
|
||||
|
||||
public void addLog(SuLogEntry log) {
|
||||
db.insert(LOG_TABLE, null, log.getContentValues());
|
||||
}
|
||||
|
||||
public void clearLogs() {
|
||||
db.delete(LOG_TABLE, null, null);
|
||||
}
|
||||
|
||||
public void setSettings(String key, int value) {
|
||||
ContentValues data = new ContentValues();
|
||||
data.put("key", key);
|
||||
data.put("value", value);
|
||||
db.replace(SETTINGS_TABLE, null, data);
|
||||
}
|
||||
|
||||
public int getSettings(String key, int defaultValue) {
|
||||
int value = defaultValue;
|
||||
try (Cursor c = db.query(SETTINGS_TABLE, null, "key=?",new String[] { key }, null, null, null)) {
|
||||
if (c.moveToNext()) {
|
||||
value = c.getInt(c.getColumnIndex("value"));
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setStrings(String key, String value) {
|
||||
if (value == null) {
|
||||
db.delete(STRINGS_TABLE, "key=?", new String[] { key });
|
||||
} else {
|
||||
ContentValues data = new ContentValues();
|
||||
data.put("key", key);
|
||||
data.put("value", value);
|
||||
db.replace(STRINGS_TABLE, null, data);
|
||||
}
|
||||
}
|
||||
|
||||
public String getStrings(String key, String defaultValue) {
|
||||
String value = defaultValue;
|
||||
try (Cursor c = db.query(STRINGS_TABLE, null, "key=?",new String[] { key }, null, null, null)) {
|
||||
if (c.moveToNext()) {
|
||||
value = c.getString(c.getColumnIndex("value"));
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -26,9 +26,9 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||
mm = Utils.getMagiskManager(context);
|
||||
mDb = getWritableDatabase();
|
||||
|
||||
// Clear bad repos
|
||||
// Remove outdated repos
|
||||
mDb.delete(TABLE_NAME, "minMagisk<?",
|
||||
new String[] { String.valueOf(Const.Value.MIN_MODULE_VER) });
|
||||
new String[] { String.valueOf(Const.MIN_MODULE_VER()) });
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -95,9 +95,17 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||
}
|
||||
|
||||
public Cursor getRepoCursor() {
|
||||
String orderBy = null;
|
||||
switch (mm.repoOrder) {
|
||||
case Const.Value.ORDER_NAME:
|
||||
orderBy = "name COLLATE NOCASE";
|
||||
break;
|
||||
case Const.Value.ORDER_DATE:
|
||||
orderBy = "last_update DESC";
|
||||
}
|
||||
return mDb.query(TABLE_NAME, null, "minMagisk<=?",
|
||||
new String[] { String.valueOf(mm.magiskVersionCode) },
|
||||
null, null, "name COLLATE NOCASE");
|
||||
null, null, orderBy);
|
||||
}
|
||||
|
||||
public List<String> getRepoIDList() {
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user