Compare commits
298 Commits
manager-v5
...
manager-v5
Author | SHA1 | Date | |
---|---|---|---|
![]() |
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 | ||
![]() |
f5ceee547c | ||
![]() |
b612bce779 | ||
![]() |
2e88e5e9c7 | ||
![]() |
9a7aa25c90 | ||
![]() |
c4420fe932 | ||
![]() |
a5260f3a95 | ||
![]() |
47ccf4b1f5 | ||
![]() |
a356b21895 | ||
![]() |
614a36c888 | ||
![]() |
f520fe36bd | ||
![]() |
7273a1c34d | ||
![]() |
dc45cbce37 | ||
![]() |
708d8f75c0 | ||
![]() |
bd37d90228 | ||
![]() |
b1ad691464 | ||
![]() |
f4e7baf31e | ||
![]() |
c0e60c41f2 | ||
![]() |
c8dad43e00 | ||
![]() |
a8f124704d | ||
![]() |
eed2816491 | ||
![]() |
a6334b3e35 | ||
![]() |
334beebfeb | ||
![]() |
13dad848bd | ||
![]() |
e518f4cef8 | ||
![]() |
c8fd5da2da | ||
![]() |
3a74729ecc | ||
![]() |
49c672ac4d | ||
![]() |
b570cb5b77 | ||
![]() |
97bf388471 | ||
![]() |
1a32aaea6f | ||
![]() |
4635883dec | ||
![]() |
3ba6db4a50 | ||
![]() |
2f1de25747 | ||
![]() |
f60fd42ac0 | ||
![]() |
ecc8f9c792 | ||
![]() |
e295dfdcf7 | ||
![]() |
fc42c25390 | ||
![]() |
27d5858e06 | ||
![]() |
e1ef732b60 | ||
![]() |
9840b95c21 | ||
![]() |
a6f8446d81 | ||
![]() |
c1c844c830 | ||
![]() |
389299afd1 | ||
![]() |
826543a291 | ||
![]() |
4ac83cfded | ||
![]() |
64c363ce53 | ||
![]() |
cca4347bf9 | ||
![]() |
3ae3d4926a | ||
![]() |
36025d6d9f | ||
![]() |
e171362e3e | ||
![]() |
3e0bf2ae15 | ||
![]() |
07aa9f4b8b | ||
![]() |
b2d9f3fc64 | ||
![]() |
5fb3e9167e | ||
![]() |
99c74b31be | ||
![]() |
ce5b13824e | ||
![]() |
c39170c42e | ||
![]() |
fd19fbf300 | ||
![]() |
166469827f | ||
![]() |
a34ed538b6 | ||
![]() |
5f22d3e055 | ||
![]() |
fdd700f3e5 | ||
![]() |
adf930f126 | ||
![]() |
05f41928cd | ||
![]() |
2ee0829871 | ||
![]() |
743560825d | ||
![]() |
e3d84ac349 | ||
![]() |
266c832b30 | ||
![]() |
f5374a024e | ||
![]() |
4956d826fb | ||
![]() |
f5cc2af5d0 | ||
![]() |
5880d4a6ec | ||
![]() |
ae05dce958 | ||
![]() |
9ebe372a9a | ||
![]() |
e6e04cc5b3 | ||
![]() |
12352510fd | ||
![]() |
2b3d927937 | ||
![]() |
a8890740f5 | ||
![]() |
f60d7ee54b | ||
![]() |
896ca2ef6b | ||
![]() |
c036f6d529 | ||
![]() |
6f457c0c59 | ||
![]() |
13bf1b27b4 | ||
![]() |
f742bb1c47 | ||
![]() |
aa0b9e2db2 | ||
![]() |
c10076f7ed | ||
![]() |
bcd92499f2 | ||
![]() |
b2bb0d4f72 | ||
![]() |
e140481f14 | ||
![]() |
186bd11463 | ||
![]() |
a0490d6687 | ||
![]() |
beef740ade | ||
![]() |
2ac7786a90 | ||
![]() |
a3fb5e910f | ||
![]() |
319afe86b5 | ||
![]() |
762ab66b86 | ||
![]() |
0c239a42de | ||
![]() |
e9322fba26 | ||
![]() |
39b6df27b3 | ||
![]() |
b1ee284e7f | ||
![]() |
e986332bf2 | ||
![]() |
48f9b27381 | ||
![]() |
42a6e0dd10 | ||
![]() |
d4798b02ac | ||
![]() |
963edfe8ab | ||
![]() |
53237f3ae0 | ||
![]() |
64da9281a4 | ||
![]() |
ab7fd9799d | ||
![]() |
f6bcc84251 | ||
![]() |
35dc3d9df9 | ||
![]() |
566714a75d | ||
![]() |
c92f30b122 | ||
![]() |
294ad094c4 | ||
![]() |
c1a0f520f9 | ||
![]() |
773c24b7fc | ||
![]() |
8f926c7ca9 | ||
![]() |
c562cbc2bb | ||
![]() |
3fbbb0865a | ||
![]() |
7d5f612a48 | ||
![]() |
4a5a36440b | ||
![]() |
43dd5cfea1 | ||
![]() |
7b5fec1842 | ||
![]() |
5762ded601 | ||
![]() |
a3abb86daa | ||
![]() |
4f5c656b05 | ||
![]() |
a31cddbe7b | ||
![]() |
b4ecd93f1c | ||
![]() |
0acc23e058 | ||
![]() |
cdd5f9b628 | ||
![]() |
4c9f5f4655 | ||
![]() |
b80ba13cb4 | ||
![]() |
8260bdc09c | ||
![]() |
24f856e02b | ||
![]() |
3aa619b928 | ||
![]() |
4cb5e98d94 | ||
![]() |
272910575e | ||
![]() |
a15a62f4bc | ||
![]() |
53cf11db8c | ||
![]() |
01052fbe47 | ||
![]() |
a5e1e075c7 | ||
![]() |
6be32ac688 |
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 +0,0 @@
|
||||
/build
|
@@ -1,68 +0,0 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 26
|
||||
buildToolsVersion "26.0.1"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.topjohnwu.magisk"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 26
|
||||
versionCode 54
|
||||
versionName "5.3.0"
|
||||
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()
|
||||
maven { url "https://jitpack.io" }
|
||||
maven { url "https://maven.google.com" }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation project(':resource')
|
||||
implementation 'com.android.support:recyclerview-v7:26.0.2'
|
||||
implementation 'com.android.support:cardview-v7:26.0.2'
|
||||
implementation 'com.android.support:design:26.0.2'
|
||||
implementation 'com.android.support:support-v4:26.0.2'
|
||||
implementation 'com.jakewharton:butterknife:8.8.1'
|
||||
implementation 'com.atlassian.commonmark:commonmark:0.9.0'
|
||||
implementation 'org.bouncycastle:bcprov-jdk15on:1.57'
|
||||
implementation 'org.bouncycastle:bcpkix-jdk15on:1.57'
|
||||
implementation 'org.kamranzafar:jtar:2.3'
|
||||
implementation 'com.google.android.gms:play-services-safetynet:9.0.1'
|
||||
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
|
||||
}
|
Before Width: | Height: | Size: 73 KiB |
@@ -1,133 +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 java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class AboutActivity extends Activity {
|
||||
|
||||
private static final String DONATION_URL = "https://www.paypal.me/topjohnwu";
|
||||
private static final String XDA_THREAD = "http://forum.xda-developers.com/showthread.php?t=3432382";
|
||||
private static final String SOURCE_CODE_URL = "https://github.com/topjohnwu/MagiskManager";
|
||||
|
||||
@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(BuildConfig.VERSION_NAME);
|
||||
|
||||
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(SOURCE_CODE_URL))));
|
||||
|
||||
supportThread.removeSummary();
|
||||
supportThread.setOnClickListener(view -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(XDA_THREAD))));
|
||||
|
||||
donation.removeSummary();
|
||||
donation.setOnClickListener(view -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(DONATION_URL))));
|
||||
|
||||
setFloating();
|
||||
}
|
||||
|
||||
}
|
@@ -1,151 +0,0 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.asyncs.FlashZip;
|
||||
import com.topjohnwu.magisk.asyncs.InstallMagisk;
|
||||
import com.topjohnwu.magisk.components.Activity;
|
||||
import com.topjohnwu.magisk.utils.AdaptiveList;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.OnClick;
|
||||
|
||||
public class FlashActivity extends Activity {
|
||||
|
||||
public static final String SET_ACTION = "action";
|
||||
public static final String SET_BOOT = "boot";
|
||||
public static final String SET_ENC = "enc";
|
||||
public static final String SET_VERITY = "verity";
|
||||
|
||||
public static final String FLASH_ZIP = "flash";
|
||||
public static final String PATCH_BOOT = "patch";
|
||||
public static final String FLASH_MAGISK = "magisk";
|
||||
|
||||
@BindView(R.id.toolbar) Toolbar toolbar;
|
||||
@BindView(R.id.flash_logs) RecyclerView flashLogs;
|
||||
@BindView(R.id.button_panel) LinearLayout buttonPanel;
|
||||
@BindView(R.id.reboot) Button reboot;
|
||||
|
||||
@OnClick(R.id.no_thanks)
|
||||
public void dismiss() {
|
||||
finish();
|
||||
}
|
||||
|
||||
@OnClick(R.id.reboot)
|
||||
public void reboot() {
|
||||
getShell().su_raw("reboot");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_flash);
|
||||
ButterKnife.bind(this);
|
||||
AdaptiveList<String> rootShellOutput = new AdaptiveList<>(flashLogs);
|
||||
setSupportActionBar(toolbar);
|
||||
ActionBar ab = getSupportActionBar();
|
||||
if (ab != null) {
|
||||
ab.setTitle(R.string.flashing);
|
||||
}
|
||||
setFloating();
|
||||
if (!Shell.rootAccess())
|
||||
reboot.setVisibility(View.GONE);
|
||||
|
||||
flashLogs.setAdapter(new FlashLogAdapter(rootShellOutput));
|
||||
|
||||
// We must receive a Uri of the target zip
|
||||
Intent intent = getIntent();
|
||||
Uri uri = intent.getData();
|
||||
|
||||
boolean keepEnc = intent.getBooleanExtra(SET_ENC, false);
|
||||
boolean keepVerity = intent.getBooleanExtra(SET_VERITY, false);
|
||||
|
||||
switch (getIntent().getStringExtra(SET_ACTION)) {
|
||||
case FLASH_ZIP:
|
||||
new FlashZip(this, uri, rootShellOutput)
|
||||
.setCallBack(() -> buttonPanel.setVisibility(View.VISIBLE))
|
||||
.exec();
|
||||
break;
|
||||
case PATCH_BOOT:
|
||||
new InstallMagisk(this, rootShellOutput, uri, keepEnc, keepVerity, (Uri) intent.getParcelableExtra(SET_BOOT))
|
||||
.setCallBack(() -> buttonPanel.setVisibility(View.VISIBLE))
|
||||
.exec();
|
||||
break;
|
||||
case FLASH_MAGISK:
|
||||
String boot = intent.getStringExtra(SET_BOOT);
|
||||
if (getMagiskManager().remoteMagiskVersionCode < 1370) {
|
||||
// Use legacy installation method
|
||||
getShell().su_raw(
|
||||
"echo \"BOOTIMAGE=" + boot + "\" > /dev/.magisk",
|
||||
"echo \"KEEPFORCEENCRYPT=" + keepEnc + "\" >> /dev/.magisk",
|
||||
"echo \"KEEPVERITY=" + keepVerity + "\" >> /dev/.magisk"
|
||||
);
|
||||
new FlashZip(this, uri, rootShellOutput)
|
||||
.setCallBack(() -> buttonPanel.setVisibility(View.VISIBLE))
|
||||
.exec();
|
||||
} else {
|
||||
// Use new installation method
|
||||
new InstallMagisk(this, rootShellOutput, uri, keepEnc, keepVerity, boot)
|
||||
.setCallBack(() -> buttonPanel.setVisibility(View.VISIBLE))
|
||||
.exec();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
// Prevent user accidentally press back button
|
||||
}
|
||||
|
||||
private static class FlashLogAdapter extends RecyclerView.Adapter<ViewHolder> {
|
||||
|
||||
private List<String> mList;
|
||||
|
||||
FlashLogAdapter(List<String> list) {
|
||||
mList = list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.list_item_flashlog, parent, false);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
holder.text.setText(mList.get(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mList.size();
|
||||
}
|
||||
}
|
||||
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
@BindView(R.id.textView) TextView text;
|
||||
|
||||
public ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
ButterKnife.bind(this, itemView);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,309 +0,0 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.app.Application;
|
||||
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.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.asyncs.CheckUpdates;
|
||||
import com.topjohnwu.magisk.asyncs.DownloadBusybox;
|
||||
import com.topjohnwu.magisk.asyncs.LoadModules;
|
||||
import com.topjohnwu.magisk.asyncs.ParallelTask;
|
||||
import com.topjohnwu.magisk.asyncs.UpdateRepos;
|
||||
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
|
||||
import com.topjohnwu.magisk.database.SuDatabaseHelper;
|
||||
import com.topjohnwu.magisk.module.Module;
|
||||
import com.topjohnwu.magisk.services.UpdateCheckService;
|
||||
import com.topjohnwu.magisk.superuser.SuReceiver;
|
||||
import com.topjohnwu.magisk.superuser.SuRequestActivity;
|
||||
import com.topjohnwu.magisk.utils.SafetyNetHelper;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public class MagiskManager extends Application {
|
||||
|
||||
public static final String MAGISK_DISABLE_FILE = "/cache/.disable_magisk";
|
||||
public static final String TMP_FOLDER_PATH = "/dev/tmp";
|
||||
public static final String MAGISK_PATH = "/magisk";
|
||||
public static final String INTENT_SECTION = "section";
|
||||
public static final String INTENT_VERSION = "version";
|
||||
public static final String INTENT_LINK = "link";
|
||||
public static final String MAGISKHIDE_PROP = "persist.magisk.hide";
|
||||
public static final String DISABLE_INDICATION_PROP = "ro.magisk.disable";
|
||||
public static final String NOTIFICATION_CHANNEL = "magisk_update_notice";
|
||||
public static final String BUSYBOXPATH = "/dev/magisk/bin";
|
||||
public static final int UPDATE_SERVICE_ID = 1;
|
||||
|
||||
// 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 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 SafetyNetHelper.Result SNCheckResult;
|
||||
public String bootBlock = null;
|
||||
public boolean isSuClient = false;
|
||||
public String suVersion = null;
|
||||
public boolean disabled;
|
||||
|
||||
// Data
|
||||
public Map<String, Module> moduleMap;
|
||||
public List<String> blockList;
|
||||
public List<Locale> locales;
|
||||
|
||||
// Configurations
|
||||
public static boolean shellLogging;
|
||||
public static boolean devLogging;
|
||||
public static Locale locale;
|
||||
public static Locale defaultLocale;
|
||||
|
||||
public boolean magiskHide;
|
||||
public boolean isDarkTheme;
|
||||
public boolean updateNotification;
|
||||
public boolean suReauth;
|
||||
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;
|
||||
|
||||
// Global resources
|
||||
public SharedPreferences prefs;
|
||||
public SuDatabaseHelper suDB;
|
||||
public RepoDatabaseHelper repoDB;
|
||||
public Shell shell;
|
||||
|
||||
private static Handler mHandler = new Handler();
|
||||
private boolean started = false;
|
||||
|
||||
private static class LoadLocale extends ParallelTask<Void, Void, Void> {
|
||||
|
||||
LoadLocale(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
getMagiskManager().locales = Utils.getAvailableLocale(getMagiskManager());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid) {
|
||||
getMagiskManager().localeDone.publish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
new File(getApplicationInfo().dataDir).mkdirs(); /* Create the app data directory */
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
suDB = new SuDatabaseHelper(this);
|
||||
repoDB = new RepoDatabaseHelper(this);
|
||||
defaultLocale = Locale.getDefault();
|
||||
setLocale();
|
||||
loadConfig();
|
||||
}
|
||||
|
||||
public void setLocale() {
|
||||
localeConfig = prefs.getString("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("dark_theme", false);
|
||||
if (BuildConfig.DEBUG) {
|
||||
devLogging = prefs.getBoolean("developer_logging", false);
|
||||
shellLogging = prefs.getBoolean("shell_logging", false);
|
||||
} else {
|
||||
devLogging = false;
|
||||
shellLogging = false;
|
||||
}
|
||||
|
||||
// su
|
||||
suRequestTimeout = Utils.getPrefsInt(prefs, "su_request_timeout", 10);
|
||||
suResponseType = Utils.getPrefsInt(prefs, "su_auto_response", SuRequestActivity.PROMPT);
|
||||
suNotificationType = Utils.getPrefsInt(prefs, "su_notification", SuReceiver.TOAST);
|
||||
suReauth = prefs.getBoolean("su_reauth", false);
|
||||
suAccessState = suDB.getSettings(SuDatabaseHelper.ROOT_ACCESS, SuDatabaseHelper.ROOT_ACCESS_APPS_AND_ADB);
|
||||
multiuserMode = suDB.getSettings(SuDatabaseHelper.MULTIUSER_MODE, SuDatabaseHelper.MULTIUSER_MODE_OWNER_ONLY);
|
||||
suNamespaceMode = suDB.getSettings(SuDatabaseHelper.MNT_NS, SuDatabaseHelper.NAMESPACE_MODE_REQUESTER);
|
||||
|
||||
updateNotification = prefs.getBoolean("notification", true);
|
||||
updateChannel = Utils.getPrefsInt(prefs, "update_channel", CheckUpdates.STABLE_CHANNEL);
|
||||
bootFormat = prefs.getString("boot_format", ".img");
|
||||
}
|
||||
|
||||
public void toast(String msg, int duration) {
|
||||
mHandler.post(() -> Toast.makeText(this, msg, duration).show());
|
||||
}
|
||||
|
||||
public void toast(int resId, int duration) {
|
||||
mHandler.post(() -> Toast.makeText(this, resId, duration).show());
|
||||
}
|
||||
|
||||
public void startup() {
|
||||
if (started)
|
||||
return;
|
||||
started = true;
|
||||
|
||||
boolean hasNetwork = Utils.checkNetworkStatus(this);
|
||||
|
||||
getMagiskInfo();
|
||||
new LoadLocale(this).exec();
|
||||
|
||||
// Force synchronous, make sure we have busybox to use
|
||||
if (hasNetwork && Shell.rootAccess()
|
||||
&& !Utils.itemExist(shell, BUSYBOXPATH + "/busybox")) {
|
||||
try {
|
||||
new DownloadBusybox(this).exec().get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
shell.su_raw("export PATH=" + BUSYBOXPATH + ":$PATH");
|
||||
|
||||
updateBlockInfo();
|
||||
|
||||
// Write back default values
|
||||
prefs.edit()
|
||||
.putBoolean("dark_theme", isDarkTheme)
|
||||
.putBoolean("magiskhide", magiskHide)
|
||||
.putBoolean("notification", updateNotification)
|
||||
.putBoolean("hosts", new File("/magisk/.core/hosts").exists())
|
||||
.putBoolean("disable", Utils.itemExist(shell, MAGISK_DISABLE_FILE))
|
||||
.putBoolean("su_reauth", suReauth)
|
||||
.putString("su_request_timeout", String.valueOf(suRequestTimeout))
|
||||
.putString("su_auto_response", String.valueOf(suResponseType))
|
||||
.putString("su_notification", String.valueOf(suNotificationType))
|
||||
.putString("su_access", String.valueOf(suAccessState))
|
||||
.putString("multiuser_mode", String.valueOf(multiuserMode))
|
||||
.putString("mnt_ns", String.valueOf(suNamespaceMode))
|
||||
.putString("update_channel", String.valueOf(updateChannel))
|
||||
.putString("locale", localeConfig)
|
||||
.putString("boot_format", bootFormat)
|
||||
.apply();
|
||||
|
||||
// Create notification channel on Android O
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL,
|
||||
getString(R.string.magisk_updates), NotificationManager.IMPORTANCE_DEFAULT);
|
||||
((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);
|
||||
}
|
||||
|
||||
LoadModules loadModuleTask = new LoadModules(this);
|
||||
// Start update check job
|
||||
if (hasNetwork) {
|
||||
ComponentName service = new ComponentName(this, UpdateCheckService.class);
|
||||
JobInfo jobInfo = new JobInfo.Builder(UPDATE_SERVICE_ID, service)
|
||||
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
|
||||
.setPersisted(true)
|
||||
.setPeriodic(8 * 60 * 60 * 1000)
|
||||
.build();
|
||||
((JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE)).schedule(jobInfo);
|
||||
loadModuleTask.setCallBack(() -> new UpdateRepos(this).exec());
|
||||
}
|
||||
// Fire asynctasks
|
||||
loadModuleTask.exec();
|
||||
|
||||
}
|
||||
|
||||
public void getMagiskInfo() {
|
||||
List<String> ret;
|
||||
Shell.getShell(this);
|
||||
ret = shell.sh("su -v");
|
||||
if (Utils.isValidShellResponse(ret)) {
|
||||
suVersion = ret.get(0);
|
||||
isSuClient = suVersion.toUpperCase().contains("MAGISK");
|
||||
}
|
||||
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) {}
|
||||
}
|
||||
ret = shell.sh("getprop " + DISABLE_INDICATION_PROP);
|
||||
try {
|
||||
disabled = Utils.isValidShellResponse(ret) && Integer.parseInt(ret.get(0)) != 0;
|
||||
} catch (NumberFormatException e) {
|
||||
disabled = false;
|
||||
}
|
||||
ret = shell.sh("getprop " + MAGISKHIDE_PROP);
|
||||
try {
|
||||
magiskHide = !Utils.isValidShellResponse(ret) || Integer.parseInt(ret.get(0)) != 0;
|
||||
} catch (NumberFormatException e) {
|
||||
magiskHide = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateBlockInfo() {
|
||||
List<String> res = shell.su(
|
||||
"for BLOCK in boot_a kern-a android_boot kernel boot lnx; do",
|
||||
" BOOTIMAGE=`find /dev/block -iname $BLOCK | head -n 1` 2>/dev/null",
|
||||
" [ ! -z $BOOTIMAGE ] && break",
|
||||
"done",
|
||||
"[ ! -z \"$BOOTIMAGE\" -a -L \"$BOOTIMAGE\" ] && BOOTIMAGE=`readlink $BOOTIMAGE`",
|
||||
"echo \"$BOOTIMAGE\""
|
||||
);
|
||||
if (Utils.isValidShellResponse(res)) {
|
||||
bootBlock = res.get(0);
|
||||
} else {
|
||||
blockList = shell.su("find /dev/block -type b | grep -vE 'dm-0|ram|loop'");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,24 +0,0 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.topjohnwu.magisk.components.Activity;
|
||||
|
||||
public class SplashActivity extends Activity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
getMagiskManager().startup();
|
||||
|
||||
Intent intent = new Intent(this, MainActivity.class);
|
||||
String section = getIntent().getStringExtra(MagiskManager.INTENT_SECTION);
|
||||
if (section != null) {
|
||||
intent.putExtra(MagiskManager.INTENT_SECTION, section);
|
||||
}
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
}
|
@@ -1,65 +0,0 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class DownloadBusybox extends ParallelTask<Void, Void, Void> {
|
||||
|
||||
private static final String BUSYBOX_ARM = "https://github.com/topjohnwu/ndk-busybox/releases/download/1.27.2/busybox-arm";
|
||||
private static final String BUSYBOX_X86 = "https://github.com/topjohnwu/ndk-busybox/releases/download/1.27.2/busybox-x86";
|
||||
|
||||
private File busybox;
|
||||
|
||||
public DownloadBusybox(Context context) {
|
||||
super(context);
|
||||
busybox = new File(context.getCacheDir(), "busybox");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
Context context = getMagiskManager();
|
||||
Utils.removeItem(getShell(), context.getApplicationInfo().dataDir + "/busybox");
|
||||
try {
|
||||
FileOutputStream out = new FileOutputStream(busybox);
|
||||
InputStream in = WebService.request(WebService.GET,
|
||||
Build.SUPPORTED_32_BIT_ABIS[0].contains("x86") ?
|
||||
BUSYBOX_X86 :
|
||||
BUSYBOX_ARM,
|
||||
null
|
||||
);
|
||||
if (in == null) throw new IOException();
|
||||
BufferedInputStream bis = new BufferedInputStream(in);
|
||||
byte[] buffer = new byte[4096];
|
||||
int len;
|
||||
while ((len = bis.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, len);
|
||||
}
|
||||
out.close();
|
||||
bis.close();
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (busybox.exists()) {
|
||||
getShell().su_raw(
|
||||
"rm -rf " + MagiskManager.BUSYBOXPATH,
|
||||
"mkdir -p " + MagiskManager.BUSYBOXPATH,
|
||||
"cp " + busybox + " " + MagiskManager.BUSYBOXPATH,
|
||||
"chmod -R 755 " + MagiskManager.BUSYBOXPATH,
|
||||
MagiskManager.BUSYBOXPATH + "/busybox --install -s " + MagiskManager.BUSYBOXPATH
|
||||
);
|
||||
busybox.delete();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -1,121 +0,0 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.AdaptiveList;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.utils.ZipUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
|
||||
public class FlashZip extends ParallelTask<Void, Void, Integer> {
|
||||
|
||||
private Uri mUri;
|
||||
private File mCachedFile, mScriptFile, mCheckFile;
|
||||
|
||||
private String mFilename;
|
||||
private AdaptiveList<String> mList;
|
||||
|
||||
public FlashZip(Activity context, Uri uri, AdaptiveList<String> list) {
|
||||
super(context);
|
||||
mUri = uri;
|
||||
mList = list;
|
||||
|
||||
mCachedFile = new File(context.getCacheDir(), "install.zip");
|
||||
mScriptFile = new File(context.getCacheDir(), "/META-INF/com/google/android/update-binary");
|
||||
mCheckFile = new File(mScriptFile.getParent(), "updater-script");
|
||||
|
||||
// Try to get the filename ourselves
|
||||
mFilename = Utils.getNameFromUri(context, mUri);
|
||||
}
|
||||
|
||||
private boolean unzipAndCheck() throws Exception {
|
||||
ZipUtils.unzip(mCachedFile, mCachedFile.getParentFile(), "META-INF/com/google/android", false);
|
||||
List<String> ret = Utils.readFile(getShell(), mCheckFile.getPath());
|
||||
return Utils.isValidShellResponse(ret) && ret.get(0).contains("#MAGISK");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
// UI updates must run in the UI thread
|
||||
mList.setCallback(this::publishProgress);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(Void... values) {
|
||||
mList.updateView();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer doInBackground(Void... voids) {
|
||||
MagiskManager mm = getMagiskManager();
|
||||
if (mm == null) return -1;
|
||||
try {
|
||||
mList.add("- Copying zip to temp directory");
|
||||
|
||||
mCachedFile.delete();
|
||||
try (
|
||||
InputStream in = mm.getContentResolver().openInputStream(mUri);
|
||||
OutputStream out = new FileOutputStream(mCachedFile)
|
||||
) {
|
||||
if (in == null) throw new FileNotFoundException();
|
||||
byte buffer[] = new byte[1024];
|
||||
int length;
|
||||
while ((length = in.read(buffer)) > 0)
|
||||
out.write(buffer, 0, length);
|
||||
} catch (FileNotFoundException e) {
|
||||
mList.add("! Invalid Uri");
|
||||
throw e;
|
||||
} catch (IOException e) {
|
||||
mList.add("! Cannot copy to cache");
|
||||
throw e;
|
||||
}
|
||||
if (!unzipAndCheck()) return 0;
|
||||
mList.add("- Installing " + mFilename);
|
||||
getShell().su(mList,
|
||||
"BOOTMODE=true sh " + mScriptFile + " dummy 1 " + mCachedFile +
|
||||
" && echo 'Success!' || echo 'Failed!'"
|
||||
);
|
||||
if (TextUtils.equals(mList.get(mList.size() - 1), "Success!"))
|
||||
return 1;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// -1 = error, manual install; 0 = invalid zip; 1 = success
|
||||
@Override
|
||||
protected void onPostExecute(Integer result) {
|
||||
MagiskManager mm = getMagiskManager();
|
||||
if (mm == null) return;
|
||||
getShell().su_raw(
|
||||
"rm -rf " + mCachedFile.getParent(),
|
||||
"rm -rf " + MagiskManager.TMP_FOLDER_PATH
|
||||
);
|
||||
switch (result) {
|
||||
case -1:
|
||||
mList.add(mm.getString(R.string.install_error));
|
||||
Utils.showUriSnack(getActivity(), mUri);
|
||||
break;
|
||||
case 0:
|
||||
mList.add(mm.getString(R.string.invalid_zip));
|
||||
break;
|
||||
case 1:
|
||||
// Success
|
||||
new LoadModules(mm).exec();
|
||||
break;
|
||||
}
|
||||
super.onPostExecute(result);
|
||||
}
|
||||
}
|
@@ -1,74 +0,0 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Environment;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.superuser.Policy;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.utils.ZipUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
public class HideManager extends ParallelTask<Void, Void, Boolean> {
|
||||
|
||||
public HideManager(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
getMagiskManager().toast(R.string.hide_manager_toast, Toast.LENGTH_SHORT);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... voids) {
|
||||
MagiskManager mm = getMagiskManager();
|
||||
if (mm == null)
|
||||
return false;
|
||||
|
||||
// Generate a new unhide app with random package name
|
||||
File unhideAPK = new File(Environment.getExternalStorageDirectory() + "/MagiskManager", "unhide.apk");
|
||||
unhideAPK.getParentFile().mkdirs();
|
||||
String pkg = ZipUtils.generateUnhide(mm, unhideAPK);
|
||||
|
||||
// Install the application
|
||||
List<String> ret = getShell().su("pm install " + unhideAPK + ">/dev/null && echo true || echo false");
|
||||
unhideAPK.delete();
|
||||
if (!Utils.isValidShellResponse(ret) || !Boolean.parseBoolean(ret.get(0)))
|
||||
return false;
|
||||
|
||||
try {
|
||||
// Allow the application to gain root by default
|
||||
PackageManager pm = mm.getPackageManager();
|
||||
int uid = pm.getApplicationInfo(pkg, 0).uid;
|
||||
Policy policy = new Policy(uid, pm);
|
||||
policy.policy = Policy.ALLOW;
|
||||
policy.notification = false;
|
||||
policy.logging = false;
|
||||
mm.suDB.addPolicy(policy);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Hide myself!
|
||||
getShell().su_raw("pm hide " + mm.getPackageName());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean b) {
|
||||
MagiskManager mm = getMagiskManager();
|
||||
if (mm == null)
|
||||
return;
|
||||
if (!b) {
|
||||
mm.toast(R.string.hide_manager_fail_toast, Toast.LENGTH_LONG);
|
||||
}
|
||||
super.onPostExecute(b);
|
||||
}
|
||||
}
|
@@ -1,231 +0,0 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.utils.AdaptiveList;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.utils.TarEntry;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.utils.ZipUtils;
|
||||
|
||||
import org.kamranzafar.jtar.TarInputStream;
|
||||
import org.kamranzafar.jtar.TarOutputStream;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
|
||||
|
||||
private static final int PATCH_MODE = 0;
|
||||
private static final int DIRECT_MODE = 1;
|
||||
|
||||
private Uri mBootImg, mZip;
|
||||
private AdaptiveList<String> mList;
|
||||
private String mBootLocation;
|
||||
private boolean mKeepEnc, mKeepVerity;
|
||||
private int mode;
|
||||
|
||||
private InstallMagisk(Activity context, AdaptiveList<String> list, Uri zip, boolean enc, boolean verity) {
|
||||
super(context);
|
||||
mList = list;
|
||||
mZip = zip;
|
||||
mKeepEnc = enc;
|
||||
mKeepVerity = verity;
|
||||
}
|
||||
|
||||
public InstallMagisk(Activity context, AdaptiveList<String> list, Uri zip, boolean enc, boolean verity, Uri boot) {
|
||||
this(context, list, zip, enc, verity);
|
||||
mBootImg = boot;
|
||||
mode = PATCH_MODE;
|
||||
}
|
||||
|
||||
public InstallMagisk(Activity context, AdaptiveList<String> list, Uri zip, boolean enc, boolean verity, String boot) {
|
||||
this(context, list, zip, enc, verity);
|
||||
mBootLocation = boot;
|
||||
mode = DIRECT_MODE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
// UI updates must run in the UI thread
|
||||
mList.setCallback(this::publishProgress);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(Void... values) {
|
||||
mList.updateView();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... voids) {
|
||||
MagiskManager mm = getMagiskManager();
|
||||
if (mm == null) return false;
|
||||
|
||||
File install = new File(mm.getApplicationInfo().dataDir, "install");
|
||||
getShell().sh_raw("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";
|
||||
mList.add("- Device platform: " + arch);
|
||||
|
||||
try {
|
||||
// Unzip files
|
||||
mList.add("- Extracting files");
|
||||
try (InputStream in = mm.getContentResolver().openInputStream(mZip)) {
|
||||
if (in == null) throw new FileNotFoundException();
|
||||
BufferedInputStream buf = new BufferedInputStream(in);
|
||||
buf.mark(Integer.MAX_VALUE);
|
||||
ZipUtils.unzip(buf, install, arch + "/", true);
|
||||
buf.reset();
|
||||
ZipUtils.unzip(buf, install, "common/", true);
|
||||
buf.reset();
|
||||
ZipUtils.unzip(buf, install, "chromeos/", false);
|
||||
buf.reset();
|
||||
ZipUtils.unzip(buf, install, "META-INF/com/google/android/update-binary", true);
|
||||
} catch (FileNotFoundException e) {
|
||||
mList.add("! Invalid Uri");
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
mList.add("! Cannot unzip zip");
|
||||
throw e;
|
||||
}
|
||||
|
||||
File boot;
|
||||
switch (mode) {
|
||||
case PATCH_MODE:
|
||||
boot = new File(install, "boot.img");
|
||||
// Copy boot image to local
|
||||
try (
|
||||
InputStream in = mm.getContentResolver().openInputStream(mBootImg);
|
||||
OutputStream out = new FileOutputStream(boot)
|
||||
) {
|
||||
InputStream source;
|
||||
if (in == null) throw new FileNotFoundException();
|
||||
|
||||
if (Utils.getNameFromUri(mm, mBootImg).endsWith(".tar")) {
|
||||
// Extract boot.img from tar
|
||||
TarInputStream tar = new TarInputStream(new BufferedInputStream(in));
|
||||
org.kamranzafar.jtar.TarEntry entry;
|
||||
while ((entry = tar.getNextEntry()) != null) {
|
||||
if (entry.getName().equals("boot.img"))
|
||||
break;
|
||||
}
|
||||
source = tar;
|
||||
} else {
|
||||
// Direct copy raw image
|
||||
source = in;
|
||||
}
|
||||
byte buffer[] = new byte[1024];
|
||||
int length;
|
||||
while ((length = source.read(buffer)) > 0)
|
||||
out.write(buffer, 0, length);
|
||||
} catch (FileNotFoundException e) {
|
||||
mList.add("! Invalid Uri");
|
||||
throw e;
|
||||
} catch (IOException e) {
|
||||
mList.add("! Copy failed");
|
||||
throw e;
|
||||
}
|
||||
break;
|
||||
case DIRECT_MODE:
|
||||
boot = new File(mBootLocation);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
mList.add("- Use boot image: " + boot);
|
||||
|
||||
Shell shell;
|
||||
if (mode == PATCH_MODE && Shell.rootAccess()) {
|
||||
// Force non-root shell
|
||||
shell = Shell.getShell("sh");
|
||||
} else {
|
||||
shell = getShell();
|
||||
}
|
||||
|
||||
// Patch boot image
|
||||
shell.sh(mList,
|
||||
"cd " + install,
|
||||
"KEEPFORCEENCRYPT=" + mKeepEnc + " KEEPVERITY=" + mKeepVerity + " sh " +
|
||||
"update-binary indep boot_patch.sh " + boot +
|
||||
" && echo 'Success!' || echo 'Failed!'"
|
||||
);
|
||||
|
||||
if (!TextUtils.equals(mList.get(mList.size() - 1), "Success!"))
|
||||
return false;
|
||||
|
||||
File patched_boot = new File(install, "new-boot.img");
|
||||
mList.add("");
|
||||
switch (mode) {
|
||||
case PATCH_MODE:
|
||||
File dest = new File(Environment.getExternalStorageDirectory() + "/MagiskManager/patched_boot" + mm.bootFormat);
|
||||
dest.getParentFile().mkdirs();
|
||||
switch (mm.bootFormat) {
|
||||
case ".img":
|
||||
getShell().sh_raw("cp -f " + patched_boot + " " + dest);
|
||||
break;
|
||||
case ".img.tar":
|
||||
TarOutputStream tar = new TarOutputStream(new BufferedOutputStream(new FileOutputStream(dest)));
|
||||
tar.putNextEntry(new TarEntry(patched_boot, "boot.img"));
|
||||
byte buffer[] = new byte[4096];
|
||||
BufferedInputStream in = new BufferedInputStream(new FileInputStream(patched_boot));
|
||||
int len;
|
||||
while ((len = in.read(buffer)) != -1) {
|
||||
tar.write(buffer, 0, len);
|
||||
}
|
||||
tar.flush();
|
||||
tar.close();
|
||||
in.close();
|
||||
break;
|
||||
}
|
||||
mList.add("*********************************");
|
||||
mList.add(" Patched Boot Image is placed in ");
|
||||
mList.add(" " + dest + " ");
|
||||
mList.add("*********************************");
|
||||
break;
|
||||
case DIRECT_MODE:
|
||||
// Direct flash boot image
|
||||
getShell().su_raw("cat " + patched_boot + " /dev/zero | dd of=" + mBootLocation + " bs=4096");
|
||||
mList.add("Flashing patched boot to " + mBootLocation);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
// Finals
|
||||
getShell().sh_raw(
|
||||
"cd " + install,
|
||||
"mv bin/busybox busybox",
|
||||
"rm -rf bin *.img update-binary",
|
||||
"cd /");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
super.onPostExecute(result);
|
||||
}
|
||||
}
|
@@ -1,45 +0,0 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.module.BaseModule;
|
||||
import com.topjohnwu.magisk.module.Module;
|
||||
import com.topjohnwu.magisk.utils.Logger;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.utils.ValueSortedMap;
|
||||
|
||||
public class LoadModules extends ParallelTask<Void, Void, Void> {
|
||||
|
||||
public LoadModules(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
MagiskManager mm = getMagiskManager();
|
||||
if (mm == null) return null;
|
||||
Logger.dev("LoadModules: Loading modules");
|
||||
|
||||
mm.moduleMap = new ValueSortedMap<>();
|
||||
|
||||
for (String path : Utils.getModList(getShell(), MagiskManager.MAGISK_PATH)) {
|
||||
Logger.dev("LoadModules: Adding modules from " + path);
|
||||
try {
|
||||
Module module = new Module(getShell(), path);
|
||||
mm.moduleMap.put(module.getId(), module);
|
||||
} catch (BaseModule.CacheModException ignored) {}
|
||||
}
|
||||
|
||||
Logger.dev("LoadModules: Data load done");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void v) {
|
||||
MagiskManager mm = getMagiskManager();
|
||||
if (mm == null) return;
|
||||
mm.moduleLoadDone.publish();
|
||||
super.onPostExecute(v);
|
||||
}
|
||||
}
|
@@ -1,47 +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.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",
|
||||
getMagiskManager().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,131 +0,0 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Environment;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.Logger;
|
||||
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 java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class ProcessRepoZip extends ParallelTask<Void, Void, Boolean> {
|
||||
|
||||
private ProgressDialog progressDialog;
|
||||
private boolean mInstall;
|
||||
private String mLink, mFile;
|
||||
|
||||
public ProcessRepoZip(Activity context, String link, String filename, boolean install) {
|
||||
super(context);
|
||||
mLink = link;
|
||||
mFile = Environment.getExternalStorageDirectory() + "/MagiskManager/" + filename;
|
||||
mInstall = install;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
Activity activity = getActivity();
|
||||
progressDialog = ProgressDialog.show(activity,
|
||||
activity.getString(R.string.zip_download_title),
|
||||
activity.getString(R.string.zip_download_msg));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(Void... values) {
|
||||
progressDialog.setTitle(R.string.zip_process_title);
|
||||
progressDialog.setMessage(getActivity().getString(R.string.zip_process_msg));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... params) {
|
||||
Activity activity = getActivity();
|
||||
if (activity == null) return null;
|
||||
try {
|
||||
|
||||
// Request zip from Internet
|
||||
InputStream in = WebService.request(WebService.GET, mLink, null);
|
||||
if (in == null) return false;
|
||||
in = new BufferedInputStream(in);
|
||||
|
||||
// Temp files
|
||||
File temp1 = new File(activity.getCacheDir(), "1.zip");
|
||||
File temp2 = new File(temp1.getParentFile(), "2.zip");
|
||||
temp1.getParentFile().mkdir();
|
||||
|
||||
// First remove top folder in Github source zip, Web -> temp1
|
||||
ZipUtils.removeTopFolder(in, temp1);
|
||||
|
||||
publishProgress();
|
||||
|
||||
// Then sign the zip for the first time, temp1 -> temp2
|
||||
ZipUtils.signZip(activity, temp1, temp2, false);
|
||||
|
||||
// Adjust the zip to prevent unzip issues, temp2 -> temp1
|
||||
ZipUtils.zipAdjust(temp2.getPath(), temp1.getPath());
|
||||
|
||||
// Finally, sign the whole zip file again, temp1 -> temp2
|
||||
ZipUtils.signZip(activity, temp1, temp2, true);
|
||||
|
||||
// Write it to the target zip, temp2 -> file
|
||||
try (OutputStream out = new BufferedOutputStream(new FileOutputStream(mFile));
|
||||
InputStream source = new BufferedInputStream(new FileInputStream(temp2))
|
||||
) {
|
||||
byte[] buffer = new byte[4096];
|
||||
int length;
|
||||
while ((length = source.read(buffer)) > 0)
|
||||
out.write(buffer, 0, length);
|
||||
}
|
||||
|
||||
// Delete temp files
|
||||
temp1.delete();
|
||||
temp2.delete();
|
||||
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
Logger.error("ProcessRepoZip: Error!");
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
Activity activity = getActivity();
|
||||
if (activity == null) return;
|
||||
progressDialog.dismiss();
|
||||
Uri uri = Uri.fromFile(new File(mFile));
|
||||
if (result) {
|
||||
if (Shell.rootAccess() && mInstall) {
|
||||
Intent intent = new Intent(getActivity(), FlashActivity.class);
|
||||
intent.setData(uri).putExtra(FlashActivity.SET_ACTION, FlashActivity.FLASH_ZIP);
|
||||
activity.startActivity(intent);
|
||||
} else {
|
||||
Utils.showUriSnack(activity, uri);
|
||||
}
|
||||
} else {
|
||||
Utils.getMagiskManager(activity).toast(R.string.process_error, Toast.LENGTH_LONG);
|
||||
}
|
||||
super.onPostExecute(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParallelTask<Void, Void, Boolean> exec(Void... voids) {
|
||||
Utils.runWithPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE, () -> super.exec(voids));
|
||||
return this;
|
||||
}
|
||||
}
|
@@ -1,40 +0,0 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.content.Context;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class RestoreStockBoot extends ParallelTask<Void, Void, Boolean> {
|
||||
|
||||
private String mBoot;
|
||||
|
||||
public RestoreStockBoot(Context context, String boot) {
|
||||
super(context);
|
||||
mBoot = boot;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... voids) {
|
||||
List<String> ret = getShell().su("cat /init.magisk.rc | grep STOCKSHA1");
|
||||
if (!Utils.isValidShellResponse(ret))
|
||||
return false;
|
||||
String stock_boot = "/data/stock_boot_" + ret.get(0).substring(ret.get(0).indexOf('=') + 1) + ".img.gz";
|
||||
if (!Utils.itemExist(getShell(), stock_boot))
|
||||
return false;
|
||||
getShell().su_raw("gzip -d < " + stock_boot + " | cat - /dev/zero | dd of=" + mBoot + " bs=4096");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
if (result) {
|
||||
getMagiskManager().toast(R.string.restore_done, Toast.LENGTH_SHORT);
|
||||
} else {
|
||||
getMagiskManager().toast(R.string.restore_fail, Toast.LENGTH_LONG);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,190 +0,0 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
|
||||
import com.topjohnwu.magisk.module.BaseModule;
|
||||
import com.topjohnwu.magisk.module.Repo;
|
||||
import com.topjohnwu.magisk.utils.Logger;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
public class UpdateRepos extends ParallelTask<Void, Void, Void> {
|
||||
|
||||
public static final String ETAG_KEY = "ETag";
|
||||
|
||||
private static final String REPO_URL = "https://api.github.com/users/Magisk-Modules-Repo/repos?per_page=100&page=%d";
|
||||
private static final String IF_NONE_MATCH = "If-None-Match";
|
||||
private static final String LINK_KEY = "Link";
|
||||
|
||||
private static final int CHECK_ETAG = 0;
|
||||
private static final int LOAD_NEXT = 1;
|
||||
private static final int LOAD_PREV = 2;
|
||||
|
||||
private List<String> etags;
|
||||
private List<String> cached;
|
||||
private RepoDatabaseHelper repoDB;
|
||||
private SharedPreferences prefs;
|
||||
|
||||
public UpdateRepos(Context context) {
|
||||
super(context);
|
||||
prefs = getMagiskManager().prefs;
|
||||
repoDB = getMagiskManager().repoDB;
|
||||
String prefsPath = context.getApplicationInfo().dataDir + "/shared_prefs";
|
||||
// Legacy data cleanup
|
||||
File old = new File(prefsPath, "RepoMap.xml");
|
||||
if (old.exists() || !prefs.getString("repomap", "empty").equals("empty")) {
|
||||
old.delete();
|
||||
prefs.edit().remove("version").remove("repomap").remove(ETAG_KEY).apply();
|
||||
repoDB.clearRepo();
|
||||
}
|
||||
etags = new ArrayList<>(
|
||||
Arrays.asList(prefs.getString(ETAG_KEY, "").split(",")));
|
||||
}
|
||||
|
||||
private void loadJSON(String jsonString) throws Exception {
|
||||
JSONArray jsonArray = new JSONArray(jsonString);
|
||||
|
||||
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);
|
||||
Repo repo = repoDB.getRepo(id);
|
||||
try {
|
||||
Boolean updated;
|
||||
if (repo == null) {
|
||||
Logger.dev("UpdateRepos: Create new repo " + id);
|
||||
repo = new Repo(name, updatedDate);
|
||||
updated = true;
|
||||
} else {
|
||||
// Popout from cached
|
||||
cached.remove(id);
|
||||
updated = repo.update(updatedDate);
|
||||
}
|
||||
if (updated) {
|
||||
repoDB.addRepo(repo);
|
||||
}
|
||||
} catch (BaseModule.CacheModException ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean loadPage(int page, String url, int mode) {
|
||||
Logger.dev("UpdateRepos: Loading page: " + (page + 1));
|
||||
Map<String, String> header = new HashMap<>();
|
||||
if (mode == CHECK_ETAG && page < etags.size() && !TextUtils.isEmpty(etags.get(page))) {
|
||||
Logger.dev("ETAG: " + etags.get(page));
|
||||
header.put(IF_NONE_MATCH, etags.get(page));
|
||||
}
|
||||
if (url == null) {
|
||||
url = String.format(Locale.US, REPO_URL, page + 1);
|
||||
}
|
||||
String jsonString = WebService.getString(url, header);
|
||||
if (TextUtils.isEmpty(jsonString)) {
|
||||
// At least check the pages we know
|
||||
return page + 1 < etags.size() && loadPage(page + 1, null, CHECK_ETAG);
|
||||
}
|
||||
|
||||
// The getString succeed, parse the new stuffs
|
||||
try {
|
||||
loadJSON(jsonString);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update the ETAG
|
||||
String newEtag = header.get(ETAG_KEY);
|
||||
newEtag = newEtag.substring(newEtag.indexOf('\"'), newEtag.lastIndexOf('\"') + 1);
|
||||
Logger.dev("New ETAG: " + newEtag);
|
||||
if (page < etags.size()) {
|
||||
etags.set(page, newEtag);
|
||||
} else {
|
||||
etags.add(newEtag);
|
||||
}
|
||||
|
||||
String links = header.get(LINK_KEY);
|
||||
if (links != null) {
|
||||
if (mode == CHECK_ETAG || mode == LOAD_NEXT) {
|
||||
// Try to check next page URL
|
||||
url = null;
|
||||
for (String s : links.split(", ")) {
|
||||
if (s.contains("next")) {
|
||||
url = s.substring(s.indexOf("<") + 1, s.indexOf(">; "));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (url != null) {
|
||||
loadPage(page + 1, url, LOAD_NEXT);
|
||||
}
|
||||
}
|
||||
|
||||
if (mode == CHECK_ETAG || mode == LOAD_PREV) {
|
||||
// Try to check prev page URL
|
||||
url = null;
|
||||
for (String s : links.split(", ")) {
|
||||
if (s.contains("prev")) {
|
||||
url = s.substring(s.indexOf("<") + 1, s.indexOf(">; "));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (url != null) {
|
||||
loadPage(page - 1, url, LOAD_PREV);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
Logger.dev("UpdateRepos: Loading repos");
|
||||
|
||||
cached = repoDB.getRepoIDList();
|
||||
|
||||
if (!loadPage(0, null, CHECK_ETAG)) {
|
||||
Logger.dev("UpdateRepos: No updates, use DB");
|
||||
return null;
|
||||
}
|
||||
|
||||
// The leftover cached means they are removed from online repo
|
||||
repoDB.removeRepo(cached);
|
||||
|
||||
// Update ETag
|
||||
StringBuilder etagBuilder = new StringBuilder();
|
||||
for (int i = 0; i < etags.size(); ++i) {
|
||||
if (i != 0) etagBuilder.append(",");
|
||||
etagBuilder.append(etags.get(i));
|
||||
}
|
||||
prefs.edit().putString(ETAG_KEY, etagBuilder.toString()).apply();
|
||||
|
||||
Logger.dev("UpdateRepos: Done");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void v) {
|
||||
MagiskManager mm = getMagiskManager();
|
||||
if (mm == null) return;
|
||||
mm.repoLoadDone.publish();
|
||||
super.onPostExecute(v);
|
||||
}
|
||||
}
|
@@ -1,79 +0,0 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
|
||||
public class Activity extends AppCompatActivity {
|
||||
|
||||
private Runnable permissionGrantCallback;
|
||||
|
||||
public Activity() {
|
||||
super();
|
||||
Configuration configuration = new Configuration();
|
||||
configuration.setLocale(MagiskManager.locale);
|
||||
applyOverrideConfiguration(configuration);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (this instanceof Topic.Subscriber) {
|
||||
((Topic.Subscriber) this).subscribeTopics();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
if (this instanceof Topic.Subscriber) {
|
||||
((Topic.Subscriber) this).unsubscribeTopics();
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
if (permissionGrantCallback != null) {
|
||||
permissionGrantCallback.run();
|
||||
}
|
||||
}
|
||||
permissionGrantCallback = null;
|
||||
}
|
||||
|
||||
public void setPermissionGrantCallback(Runnable callback) {
|
||||
permissionGrantCallback = callback;
|
||||
}
|
||||
|
||||
public MagiskManager getMagiskManager() {
|
||||
return (MagiskManager) super.getApplicationContext();
|
||||
}
|
||||
|
||||
public Shell getShell() {
|
||||
return Shell.getShell(this);
|
||||
}
|
||||
|
||||
protected void setFloating() {
|
||||
boolean isTablet = getResources().getBoolean(R.bool.isTablet);
|
||||
if (isTablet) {
|
||||
WindowManager.LayoutParams params = getWindow().getAttributes();
|
||||
params.height = getResources().getDimensionPixelSize(R.dimen.floating_height);
|
||||
params.width = getResources().getDimensionPixelSize(R.dimen.floating_width);
|
||||
params.alpha = 1.0f;
|
||||
params.dimAmount = 0.6f;
|
||||
params.flags |= 2;
|
||||
getWindow().setAttributes(params);
|
||||
setFinishOnTouchOutside(true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,264 +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.text.TextUtils;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.superuser.Policy;
|
||||
import com.topjohnwu.magisk.superuser.SuLogEntry;
|
||||
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 ROOT_ACCESS = "root_access";
|
||||
public static final int ROOT_ACCESS_DISABLED = 0;
|
||||
public static final int ROOT_ACCESS_APPS_ONLY = 1;
|
||||
public static final int ROOT_ACCESS_ADB_ONLY = 2;
|
||||
public static final int ROOT_ACCESS_APPS_AND_ADB = 3;
|
||||
|
||||
public static final String MULTIUSER_MODE = "multiuser_mode";
|
||||
public static final int MULTIUSER_MODE_OWNER_ONLY = 0;
|
||||
public static final int MULTIUSER_MODE_OWNER_MANAGED = 1;
|
||||
public static final int MULTIUSER_MODE_USER = 2;
|
||||
|
||||
public static final String MNT_NS = "mnt_ns";
|
||||
public static final int NAMESPACE_MODE_GLOBAL = 0;
|
||||
public static final int NAMESPACE_MODE_REQUESTER = 1;
|
||||
public static final int NAMESPACE_MODE_ISOLATE = 2;
|
||||
|
||||
private static final int DATABASE_VER = 3;
|
||||
private static final String POLICY_TABLE = "policies";
|
||||
private static final String LOG_TABLE = "logs";
|
||||
private static final String SETTINGS_TABLE = "settings";
|
||||
|
||||
private MagiskManager magiskManager;
|
||||
private PackageManager pm;
|
||||
private SQLiteDatabase mDb;
|
||||
|
||||
public SuDatabaseHelper(Context context) {
|
||||
super(context, "su.db", null, DATABASE_VER);
|
||||
magiskManager = Utils.getMagiskManager(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) {
|
||||
if (oldVersion == 0) {
|
||||
createTables(db);
|
||||
oldVersion = 2;
|
||||
}
|
||||
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 = magiskManager.getDatabasePath("sulog.db");
|
||||
if (oldDB.exists()) {
|
||||
migrateLegacyLogList(oldDB, db);
|
||||
magiskManager.deleteDatabase("sulog.db");
|
||||
}
|
||||
++oldVersion;
|
||||
}
|
||||
if (oldVersion == 2) {
|
||||
db.execSQL("UPDATE " + LOG_TABLE + " SET time=time*1000");
|
||||
++oldVersion;
|
||||
}
|
||||
}
|
||||
|
||||
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.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) }, 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);
|
||||
// The application changed UID for some reason, check user config
|
||||
if (policy.info.uid != policy.uid) {
|
||||
if (magiskManager.suReauth) {
|
||||
// Reauth required, remove from DB
|
||||
deletePolicy(policy);
|
||||
continue;
|
||||
} else {
|
||||
// No reauth, update to use the new UID
|
||||
policy.uid = policy.info.uid;
|
||||
updatePolicy(policy);
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
@@ -1,68 +0,0 @@
|
||||
package com.topjohnwu.magisk.module;
|
||||
|
||||
import com.topjohnwu.magisk.utils.Logger;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
public class Module extends BaseModule {
|
||||
|
||||
private String mRemoveFile, mDisableFile, mUpdateFile;
|
||||
private boolean mEnable, mRemove, mUpdated;
|
||||
|
||||
public Module(Shell shell, String path) throws CacheModException {
|
||||
|
||||
parseProps(Utils.readFile(shell, path + "/module.prop"));
|
||||
|
||||
mRemoveFile = path + "/remove";
|
||||
mDisableFile = path + "/disable";
|
||||
mUpdateFile = path + "/update";
|
||||
|
||||
if (getId() == null) {
|
||||
int sep = path.lastIndexOf('/');
|
||||
setId(path.substring(sep + 1));
|
||||
}
|
||||
|
||||
if (getName() == null) {
|
||||
setName(getId());
|
||||
}
|
||||
|
||||
Logger.dev("Creating Module, id: " + getId());
|
||||
|
||||
mEnable = !Utils.itemExist(shell, mDisableFile);
|
||||
mRemove = Utils.itemExist(shell, mRemoveFile);
|
||||
mUpdated = Utils.itemExist(shell, mUpdateFile);
|
||||
}
|
||||
|
||||
public void createDisableFile(Shell shell) {
|
||||
mEnable = false;
|
||||
Utils.createFile(shell, mDisableFile);
|
||||
}
|
||||
|
||||
public void removeDisableFile(Shell shell) {
|
||||
mEnable = true;
|
||||
Utils.removeItem(shell, mDisableFile);
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return mEnable;
|
||||
}
|
||||
|
||||
public void createRemoveFile(Shell shell) {
|
||||
mRemove = true;
|
||||
Utils.createFile(shell, mRemoveFile);
|
||||
}
|
||||
|
||||
public void deleteRemoveFile(Shell shell) {
|
||||
mRemove = false;
|
||||
Utils.removeItem(shell, mRemoveFile);
|
||||
}
|
||||
|
||||
public boolean willBeRemoved() {
|
||||
return mRemove;
|
||||
}
|
||||
|
||||
public boolean isUpdated() {
|
||||
return mUpdated;
|
||||
}
|
||||
|
||||
}
|
@@ -1,76 +0,0 @@
|
||||
package com.topjohnwu.magisk.module;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
|
||||
import com.topjohnwu.magisk.utils.Logger;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class Repo extends BaseModule {
|
||||
|
||||
private static final String FILE_URL = "https://raw.githubusercontent.com/Magisk-Modules-Repo/%s/master/%s";
|
||||
private static final String ZIP_URL = "https://github.com/Magisk-Modules-Repo/%s/archive/master.zip";
|
||||
|
||||
private String repoName;
|
||||
private Date mLastUpdate;
|
||||
|
||||
public Repo(String name, Date lastUpdate) throws CacheModException {
|
||||
mLastUpdate = lastUpdate;
|
||||
repoName = name;
|
||||
update();
|
||||
}
|
||||
|
||||
public Repo(Cursor c) {
|
||||
super(c);
|
||||
repoName = c.getString(c.getColumnIndex("repo_name"));
|
||||
mLastUpdate = new Date(c.getLong(c.getColumnIndex("last_update")));
|
||||
}
|
||||
|
||||
public void update() throws CacheModException {
|
||||
String props = WebService.getString(getManifestUrl());
|
||||
String lines[] = props.split("\\n");
|
||||
parseProps(lines);
|
||||
Logger.dev("Repo: Fetching prop: " + getId());
|
||||
}
|
||||
|
||||
public boolean update(Date lastUpdate) throws CacheModException {
|
||||
if (lastUpdate.after(mLastUpdate)) {
|
||||
mLastUpdate = lastUpdate;
|
||||
update();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public ContentValues getContentValues() {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put("id", getId());
|
||||
values.put("name", getName());
|
||||
values.put("version", getVersion());
|
||||
values.put("versionCode", getVersionCode());
|
||||
values.put("author", getAuthor());
|
||||
values.put("description", getDescription());
|
||||
values.put("repo_name", repoName);
|
||||
values.put("last_update", mLastUpdate.getTime());
|
||||
values.put("template", getTemplateVersion());
|
||||
return values;
|
||||
}
|
||||
|
||||
public String getZipUrl() {
|
||||
return String.format(ZIP_URL, repoName);
|
||||
}
|
||||
|
||||
public String getManifestUrl() {
|
||||
return String.format(FILE_URL, repoName, "module.prop");
|
||||
}
|
||||
|
||||
public String getDetailUrl() {
|
||||
return String.format(FILE_URL, repoName, "README.md");
|
||||
}
|
||||
|
||||
public Date getLastUpdate() {
|
||||
return mLastUpdate;
|
||||
}
|
||||
}
|
@@ -1,40 +0,0 @@
|
||||
package com.topjohnwu.magisk.receivers;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.superuser.Policy;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
public class PackageReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
MagiskManager magiskManager = Utils.getMagiskManager(context);
|
||||
|
||||
String pkg = intent.getData().getEncodedSchemeSpecificPart();
|
||||
Policy policy = magiskManager.suDB.getPolicy(pkg);
|
||||
if (policy == null)
|
||||
return;
|
||||
|
||||
switch (intent.getAction()) {
|
||||
case Intent.ACTION_PACKAGE_REPLACED:
|
||||
// This will only work pre-O
|
||||
if (magiskManager.suReauth) {
|
||||
magiskManager.suDB.deletePolicy(policy);
|
||||
} else {
|
||||
int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
|
||||
// Update the UID if available
|
||||
if (uid > 0) {
|
||||
policy.uid = uid % 100000;
|
||||
}
|
||||
magiskManager.suDB.updatePolicy(policy);
|
||||
}
|
||||
break;
|
||||
case Intent.ACTION_PACKAGE_FULLY_REMOVED:
|
||||
magiskManager.suDB.deletePolicy(policy);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,34 +0,0 @@
|
||||
package com.topjohnwu.magisk.services;
|
||||
|
||||
import android.app.IntentService;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.support.v7.app.NotificationCompat;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
|
||||
public class OnBootIntentService extends IntentService {
|
||||
|
||||
private static final int ONBOOT_NOTIFICATION_ID = 3;
|
||||
|
||||
public OnBootIntentService() {
|
||||
super("OnBootIntentService");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
|
||||
builder.setSmallIcon(R.drawable.ic_magisk)
|
||||
.setContentTitle("onBoot")
|
||||
.setContentText("Running onBoot operations...");
|
||||
startForeground(ONBOOT_NOTIFICATION_ID, builder.build());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent) {
|
||||
// Currently nothing to do
|
||||
}
|
||||
}
|
@@ -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,36 +0,0 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class AdaptiveList<E> extends ArrayList<E> {
|
||||
|
||||
private Runnable callback;
|
||||
private RecyclerView mView;
|
||||
|
||||
public AdaptiveList(RecyclerView v) {
|
||||
mView = v;
|
||||
}
|
||||
|
||||
public void updateView() {
|
||||
mView.getAdapter().notifyDataSetChanged();
|
||||
mView.scrollToPosition(mView.getAdapter().getItemCount() - 1);
|
||||
}
|
||||
|
||||
public void setCallback(Runnable cb) {
|
||||
callback = cb;
|
||||
}
|
||||
|
||||
public boolean add(E e) {
|
||||
boolean ret = super.add(e);
|
||||
if (ret) {
|
||||
if (callback == null) {
|
||||
updateView();
|
||||
} else {
|
||||
callback.run();
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
@@ -1,49 +0,0 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class Logger {
|
||||
|
||||
public static final String MAIN_TAG = "Magisk";
|
||||
public static final String DEBUG_TAG = "MagiskManager";
|
||||
|
||||
public static void debug(String line) {
|
||||
Log.d(DEBUG_TAG, "DEBUG: " + line);
|
||||
}
|
||||
|
||||
public static void debug(String fmt, Object... args) {
|
||||
debug(String.format(Locale.US, fmt, args));
|
||||
}
|
||||
|
||||
public static void error(String line) {
|
||||
Log.e(MAIN_TAG, "MANAGERERROR: " + line);
|
||||
}
|
||||
|
||||
public static void error(String fmt, Object... args) {
|
||||
error(String.format(Locale.US, fmt, args));
|
||||
}
|
||||
|
||||
public static void dev(String line) {
|
||||
if (MagiskManager.devLogging) {
|
||||
Log.d(DEBUG_TAG, line);
|
||||
}
|
||||
}
|
||||
|
||||
public static void dev(String fmt, Object... args) {
|
||||
dev(String.format(Locale.US, fmt, args));
|
||||
}
|
||||
|
||||
public static void shell(String line) {
|
||||
if (MagiskManager.shellLogging) {
|
||||
Log.d(DEBUG_TAG, "SHELL: " + line);
|
||||
}
|
||||
}
|
||||
|
||||
public static void shell(String fmt, Object... args) {
|
||||
shell(String.format(Locale.US, fmt, args));
|
||||
}
|
||||
}
|
@@ -1,114 +0,0 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.util.Base64;
|
||||
|
||||
import com.google.android.gms.common.ConnectionResult;
|
||||
import com.google.android.gms.common.api.GoogleApiClient;
|
||||
import com.google.android.gms.common.api.Status;
|
||||
import com.google.android.gms.safetynet.SafetyNet;
|
||||
import com.topjohnwu.magisk.R;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public abstract class SafetyNetHelper
|
||||
implements GoogleApiClient.OnConnectionFailedListener, GoogleApiClient.ConnectionCallbacks {
|
||||
|
||||
private static boolean isRunning = false;
|
||||
|
||||
private GoogleApiClient mGoogleApiClient;
|
||||
private Result ret;
|
||||
protected FragmentActivity mActivity;
|
||||
|
||||
public SafetyNetHelper(FragmentActivity activity) {
|
||||
ret = new Result();
|
||||
mActivity = activity;
|
||||
}
|
||||
|
||||
// Entry point to start test
|
||||
public void requestTest() {
|
||||
if (isRunning)
|
||||
return;
|
||||
// Connect Google Service
|
||||
mGoogleApiClient = new GoogleApiClient.Builder(mActivity)
|
||||
.enableAutoManage(mActivity, this)
|
||||
.addApi(SafetyNet.API)
|
||||
.addConnectionCallbacks(this)
|
||||
.build();
|
||||
mGoogleApiClient.connect();
|
||||
isRunning = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionFailed(@NonNull ConnectionResult result) {
|
||||
Logger.dev("SN: Google API fail");
|
||||
ret.errmsg = result.getErrorMessage();
|
||||
handleResults(ret);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionSuspended(int i) {
|
||||
Logger.dev("SN: Google API Suspended");
|
||||
switch (i) {
|
||||
case CAUSE_NETWORK_LOST:
|
||||
ret.errmsg = mActivity.getString(R.string.safetyNet_network_loss);
|
||||
break;
|
||||
case CAUSE_SERVICE_DISCONNECTED:
|
||||
ret.errmsg = mActivity.getString(R.string.safetyNet_service_disconnected);
|
||||
break;
|
||||
}
|
||||
handleResults(ret);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnected(@Nullable Bundle bundle) {
|
||||
Logger.dev("SN: Google API Connected");
|
||||
// Create nonce
|
||||
byte[] nonce = new byte[24];
|
||||
new SecureRandom().nextBytes(nonce);
|
||||
|
||||
Logger.dev("SN: Check with nonce: " + Base64.encodeToString(nonce, Base64.DEFAULT));
|
||||
|
||||
// Call SafetyNet
|
||||
SafetyNet.SafetyNetApi.attest(mGoogleApiClient, nonce)
|
||||
.setResultCallback(result -> {
|
||||
Status status = result.getStatus();
|
||||
if (status.isSuccess()) {
|
||||
String json = new String(Base64.decode(result.getJwsResult().split("\\.")[1], Base64.DEFAULT));
|
||||
Logger.dev("SN: Response: " + json);
|
||||
try {
|
||||
JSONObject decoded = new JSONObject(json);
|
||||
ret.ctsProfile = decoded.getBoolean("ctsProfileMatch");
|
||||
ret.basicIntegrity = decoded.getBoolean("basicIntegrity");
|
||||
ret.failed = false;
|
||||
} catch (JSONException e) {
|
||||
ret.errmsg = mActivity.getString(R.string.safetyNet_res_invalid);
|
||||
}
|
||||
} else {
|
||||
Logger.dev("SN: No response");
|
||||
ret.errmsg = mActivity.getString(R.string.safetyNet_no_response);
|
||||
}
|
||||
// Disconnect
|
||||
mGoogleApiClient.stopAutoManage(mActivity);
|
||||
mGoogleApiClient.disconnect();
|
||||
isRunning = false;
|
||||
handleResults(ret);
|
||||
});
|
||||
}
|
||||
|
||||
// Callback function to save the results
|
||||
public abstract void handleResults(Result result);
|
||||
|
||||
public static class Result {
|
||||
public boolean failed = true;
|
||||
public String errmsg;
|
||||
public boolean ctsProfile = false;
|
||||
public boolean basicIntegrity = false;
|
||||
}
|
||||
}
|
@@ -1,211 +0,0 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Modified by topjohnwu, based on Chainfire's libsuperuser
|
||||
*/
|
||||
|
||||
public class Shell {
|
||||
|
||||
// -1 = problematic/unknown issue; 0 = not rooted; 1 = properly rooted
|
||||
public static int rootStatus;
|
||||
|
||||
private final Process shellProcess;
|
||||
private final DataOutputStream STDIN;
|
||||
private final DataInputStream STDOUT;
|
||||
|
||||
private boolean isValid;
|
||||
|
||||
private void testRootShell(DataOutputStream in, DataInputStream out) throws IOException {
|
||||
in.write(("id\n").getBytes("UTF-8"));
|
||||
in.flush();
|
||||
String s = new BufferedReader(new InputStreamReader(out)).readLine();
|
||||
if (TextUtils.isEmpty(s) || !s.contains("uid=0")) {
|
||||
in.close();
|
||||
out.close();
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
|
||||
private Shell() {
|
||||
rootStatus = 1;
|
||||
Process process = null;
|
||||
DataOutputStream in = null;
|
||||
DataInputStream out = null;
|
||||
|
||||
try {
|
||||
// Try getting global namespace
|
||||
process = Runtime.getRuntime().exec("su --mount-master");
|
||||
in = new DataOutputStream(process.getOutputStream());
|
||||
out = new DataInputStream(process.getInputStream());
|
||||
testRootShell(in, out);
|
||||
} catch (IOException e) {
|
||||
// Feature not implemented, normal root shell
|
||||
try {
|
||||
process = Runtime.getRuntime().exec("su");
|
||||
in = new DataOutputStream(process.getOutputStream());
|
||||
out = new DataInputStream(process.getInputStream());
|
||||
testRootShell(in, out);
|
||||
} catch (IOException e1) {
|
||||
rootStatus = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!rootAccess()) {
|
||||
// Try to gain non-root sh
|
||||
try {
|
||||
process = Runtime.getRuntime().exec("sh");
|
||||
in = new DataOutputStream(process.getOutputStream());
|
||||
out = new DataInputStream(process.getInputStream());
|
||||
} catch (IOException e) {
|
||||
// Nothing works....
|
||||
shellProcess = null;
|
||||
STDIN = null;
|
||||
STDOUT = null;
|
||||
isValid = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
isValid = true;
|
||||
shellProcess = process;
|
||||
STDIN = in;
|
||||
STDOUT = out;
|
||||
sh_raw("umask 022");
|
||||
}
|
||||
|
||||
private Shell(String command) {
|
||||
Process process;
|
||||
DataOutputStream in;
|
||||
DataInputStream out;
|
||||
|
||||
try {
|
||||
process = Runtime.getRuntime().exec(command);
|
||||
in = new DataOutputStream(process.getOutputStream());
|
||||
out = new DataInputStream(process.getInputStream());
|
||||
} catch (IOException e) {
|
||||
// Nothing works....
|
||||
shellProcess = null;
|
||||
STDIN = null;
|
||||
STDOUT = null;
|
||||
isValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
isValid = true;
|
||||
shellProcess = process;
|
||||
STDIN = in;
|
||||
STDOUT = out;
|
||||
}
|
||||
|
||||
public static Shell getShell() {
|
||||
return new Shell();
|
||||
}
|
||||
|
||||
public static Shell getShell(String command) {
|
||||
return new Shell(command);
|
||||
}
|
||||
|
||||
public static Shell getShell(Context context) {
|
||||
MagiskManager magiskManager = Utils.getMagiskManager(context);
|
||||
if (magiskManager.shell == null || !magiskManager.shell.isValid) {
|
||||
// Get new shell if needed
|
||||
magiskManager.shell = getShell();
|
||||
}
|
||||
return magiskManager.shell;
|
||||
}
|
||||
|
||||
public static boolean rootAccess() {
|
||||
return rootStatus > 0;
|
||||
}
|
||||
|
||||
public List<String> sh(String... commands) {
|
||||
List<String> res = new ArrayList<>();
|
||||
if (!isValid) return res;
|
||||
sh(res, commands);
|
||||
return res;
|
||||
}
|
||||
|
||||
public void sh_raw(String... commands) {
|
||||
sh_raw(false, commands);
|
||||
}
|
||||
|
||||
public void sh_raw(boolean stdout, String... commands) {
|
||||
if (!isValid) return;
|
||||
synchronized (shellProcess) {
|
||||
try {
|
||||
for (String command : commands) {
|
||||
Logger.shell(command);
|
||||
STDIN.write((command + (stdout ? "\n" : " >/dev/null\n")).getBytes("UTF-8"));
|
||||
STDIN.flush();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
shellProcess.destroy();
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void sh(Collection<String> output, String... commands) {
|
||||
if (!isValid) return;
|
||||
try {
|
||||
shellProcess.exitValue();
|
||||
isValid = false;
|
||||
return; // The process is dead, return
|
||||
} catch (IllegalThreadStateException ignored) {
|
||||
// This should be the expected result
|
||||
}
|
||||
synchronized (shellProcess) {
|
||||
StreamGobbler out = new StreamGobbler(STDOUT, output);
|
||||
out.start();
|
||||
sh_raw(true, commands);
|
||||
sh_raw(true, "echo \'-shell-done-\'");
|
||||
try { out.join(); } catch (InterruptedException ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> su(String... commands) {
|
||||
if (!rootAccess()) return sh();
|
||||
return sh(commands);
|
||||
}
|
||||
|
||||
public void su_raw(String... commands) {
|
||||
if (!rootAccess()) return;
|
||||
sh_raw(commands);
|
||||
}
|
||||
|
||||
public void su(Collection<String> output, String... commands) {
|
||||
if (!rootAccess()) return;
|
||||
sh(output, 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,62 +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;
|
||||
|
||||
/**
|
||||
* Modified by topjohnwu, based on Chainfire's libsuperuser
|
||||
*/
|
||||
|
||||
public class StreamGobbler extends Thread {
|
||||
|
||||
private BufferedReader reader = null;
|
||||
private Collection<String> writer = null;
|
||||
|
||||
/**
|
||||
* <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 inputStream InputStream to read from
|
||||
* @param outputList {@literal List<String>} to write to, or null
|
||||
*/
|
||||
public StreamGobbler(InputStream inputStream, Collection<String> outputList) {
|
||||
try {
|
||||
while (inputStream.available() != 0) {
|
||||
inputStream.skip(inputStream.available());
|
||||
}
|
||||
} catch (IOException ignored) {}
|
||||
reader = new BufferedReader(new InputStreamReader(inputStream));
|
||||
writer = outputList;
|
||||
}
|
||||
|
||||
@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;
|
||||
writer.add(line);
|
||||
Logger.shell(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,507 +0,0 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.app.DownloadManager;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.database.Cursor;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.CountDownTimer;
|
||||
import android.os.Environment;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.TaskStackBuilder;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
import com.topjohnwu.magisk.MagiskFragment;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.SplashActivity;
|
||||
import com.topjohnwu.magisk.asyncs.RestoreStockBoot;
|
||||
import com.topjohnwu.magisk.asyncs.UpdateRepos;
|
||||
import com.topjohnwu.magisk.components.AlertDialogBuilder;
|
||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.receivers.DownloadReceiver;
|
||||
import com.topjohnwu.magisk.receivers.ManagerUpdate;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class Utils {
|
||||
|
||||
public static final int SELECT_BOOT_IMG = 3;
|
||||
public static final String UNINSTALLER = "magisk_uninstaller.sh";
|
||||
public static final String UTIL_FUNCTIONS= "util_functions.sh";
|
||||
public static boolean isDownloading = false;
|
||||
|
||||
private static final int MAGISK_UPDATE_NOTIFICATION_ID = 1;
|
||||
private static final int APK_UPDATE_NOTIFICATION_ID = 2;
|
||||
|
||||
public static boolean itemExist(Shell shell, String path) {
|
||||
String command = "[ -e " + path + " ] && echo true || echo false";
|
||||
List<String> ret = shell.su(command);
|
||||
return isValidShellResponse(ret) && Boolean.parseBoolean(ret.get(0));
|
||||
}
|
||||
|
||||
public static void createFile(Shell shell, String path) {
|
||||
String folder = path.substring(0, path.lastIndexOf('/'));
|
||||
String command = "mkdir -p " + folder + " 2>/dev/null; touch " + path + " 2>/dev/null;";
|
||||
shell.su_raw(command);
|
||||
}
|
||||
|
||||
public static void removeItem(Shell shell, String path) {
|
||||
String command = "rm -rf " + path + " 2>/dev/null";
|
||||
shell.su_raw(command);
|
||||
}
|
||||
|
||||
public static List<String> getModList(Shell shell, String path) {
|
||||
String command = "ls -d " + path + "/* | grep -v lost+found";
|
||||
return shell.su(command);
|
||||
}
|
||||
|
||||
public static List<String> readFile(Shell shell, String path) {
|
||||
String command = "cat " + path + " | sed '$a\\ ' | sed '$d'";
|
||||
return shell.su(command);
|
||||
}
|
||||
|
||||
public static void dlAndReceive(Context context, DownloadReceiver receiver, String link, String filename) {
|
||||
if (isDownloading)
|
||||
return;
|
||||
|
||||
runWithPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE, () -> {
|
||||
File file = new File(Environment.getExternalStorageDirectory() + "/MagiskManager/" + filename);
|
||||
|
||||
if ((!file.getParentFile().exists() && !file.getParentFile().mkdirs())
|
||||
|| (file.exists() && !file.delete())) {
|
||||
Toast.makeText(context, R.string.permissionNotGranted, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.makeText(context, context.getString(R.string.downloading_toast, filename), Toast.LENGTH_LONG).show();
|
||||
isDownloading = true;
|
||||
|
||||
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
|
||||
|
||||
if (link != null) {
|
||||
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(link));
|
||||
request.setDestinationUri(Uri.fromFile(file));
|
||||
receiver.setDownloadID(downloadManager.enqueue(request));
|
||||
}
|
||||
receiver.setFilename(filename);
|
||||
context.getApplicationContext().registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
|
||||
});
|
||||
}
|
||||
|
||||
public static String getLegalFilename(CharSequence filename) {
|
||||
return filename.toString().replace(" ", "_").replace("'", "").replace("\"", "")
|
||||
.replace("$", "").replace("`", "").replace("(", "").replace(")", "")
|
||||
.replace("#", "").replace("@", "").replace("*", "");
|
||||
}
|
||||
|
||||
public static boolean lowercaseContains(CharSequence string, CharSequence nonNullLowercaseSearch) {
|
||||
return !TextUtils.isEmpty(string) && string.toString().toLowerCase().contains(nonNullLowercaseSearch);
|
||||
}
|
||||
|
||||
public static boolean isValidShellResponse(List<String> list) {
|
||||
if (list != null && list.size() != 0) {
|
||||
// Check if all empty
|
||||
for (String res : list) {
|
||||
if (!TextUtils.isEmpty(res)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static int getPrefsInt(SharedPreferences prefs, String key, int def) {
|
||||
return Integer.parseInt(prefs.getString(key, String.valueOf(def)));
|
||||
}
|
||||
|
||||
public static int getPrefsInt(SharedPreferences prefs, String key) {
|
||||
return getPrefsInt(prefs, key, 0);
|
||||
}
|
||||
|
||||
public static MagiskManager getMagiskManager(Context context) {
|
||||
return (MagiskManager) context.getApplicationContext();
|
||||
}
|
||||
|
||||
public static void checkSafetyNet(FragmentActivity activity) {
|
||||
new SafetyNetHelper(activity) {
|
||||
@Override
|
||||
public void handleResults(Result result) {
|
||||
getMagiskManager(mActivity).SNCheckResult = result;
|
||||
getMagiskManager(mActivity).safetyNetDone.publish(false);
|
||||
}
|
||||
}.requestTest();
|
||||
}
|
||||
|
||||
public static void clearRepoCache(Context context) {
|
||||
MagiskManager mm = getMagiskManager(context);
|
||||
mm.prefs.edit().remove(UpdateRepos.ETAG_KEY).apply();
|
||||
mm.repoDB.clearRepo();
|
||||
mm.toast(R.string.repo_cache_cleared, Toast.LENGTH_SHORT);
|
||||
}
|
||||
|
||||
public static String getNameFromUri(Context context, Uri uri) {
|
||||
String name = null;
|
||||
try (Cursor c = context.getContentResolver().query(uri, null, null, null, null)) {
|
||||
if (c != null) {
|
||||
int nameIndex = c.getColumnIndex(OpenableColumns.DISPLAY_NAME);
|
||||
if (nameIndex != -1) {
|
||||
c.moveToFirst();
|
||||
name = c.getString(nameIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (name == null) {
|
||||
int idx = uri.getPath().lastIndexOf('/');
|
||||
name = uri.getPath().substring(idx + 1);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
public static void showUriSnack(Activity activity, Uri uri) {
|
||||
SnackbarMaker.make(activity, activity.getString(R.string.internal_storage,
|
||||
"/MagiskManager/" + Utils.getNameFromUri(activity, uri)),
|
||||
Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.ok, (v)->{}).show();
|
||||
}
|
||||
|
||||
public static boolean checkNetworkStatus(Context context) {
|
||||
ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
NetworkInfo networkInfo = manager.getActiveNetworkInfo();
|
||||
return networkInfo != null && networkInfo.isConnected();
|
||||
}
|
||||
|
||||
public static void showMagiskUpdateNotification(MagiskManager mm) {
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(mm, MagiskManager.NOTIFICATION_CHANNEL);
|
||||
builder.setSmallIcon(R.drawable.ic_magisk)
|
||||
.setContentTitle(mm.getString(R.string.magisk_update_title))
|
||||
.setContentText(mm.getString(R.string.magisk_update_available, mm.remoteMagiskVersionString))
|
||||
.setVibrate(new long[]{0, 100, 100, 100})
|
||||
.setAutoCancel(true);
|
||||
Intent intent = new Intent(mm, SplashActivity.class);
|
||||
intent.putExtra(MagiskManager.INTENT_SECTION, "magisk");
|
||||
TaskStackBuilder stackBuilder = TaskStackBuilder.create(mm);
|
||||
stackBuilder.addParentStack(SplashActivity.class);
|
||||
stackBuilder.addNextIntent(intent);
|
||||
PendingIntent pendingIntent = stackBuilder.getPendingIntent(MAGISK_UPDATE_NOTIFICATION_ID,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
builder.setContentIntent(pendingIntent);
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.notify(MAGISK_UPDATE_NOTIFICATION_ID, builder.build());
|
||||
}
|
||||
|
||||
public static void showManagerUpdateNotification(MagiskManager mm) {
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(mm, MagiskManager.NOTIFICATION_CHANNEL);
|
||||
builder.setSmallIcon(R.drawable.ic_magisk)
|
||||
.setContentTitle(mm.getString(R.string.manager_update_title))
|
||||
.setContentText(mm.getString(R.string.manager_download_install))
|
||||
.setVibrate(new long[]{0, 100, 100, 100})
|
||||
.setAutoCancel(true);
|
||||
Intent intent = new Intent(mm, ManagerUpdate.class);
|
||||
intent.putExtra(MagiskManager.INTENT_LINK, mm.managerLink);
|
||||
intent.putExtra(MagiskManager.INTENT_VERSION, mm.remoteManagerVersionString);
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(mm,
|
||||
APK_UPDATE_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
builder.setContentIntent(pendingIntent);
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.notify(APK_UPDATE_NOTIFICATION_ID, builder.build());
|
||||
}
|
||||
|
||||
public static void enableMagiskHide(Shell shell) {
|
||||
shell.su_raw("magiskhide --enable");
|
||||
}
|
||||
|
||||
public static void disableMagiskHide(Shell shell) {
|
||||
shell.su_raw("magiskhide --disable");
|
||||
}
|
||||
|
||||
public static List<String> listMagiskHide(Shell shell) {
|
||||
return shell.su("magiskhide --ls");
|
||||
}
|
||||
|
||||
public static void addMagiskHide(Shell shell, String pkg) {
|
||||
shell.su_raw("magiskhide --add " + pkg);
|
||||
}
|
||||
|
||||
public static void rmMagiskHide(Shell shell, String pkg) {
|
||||
shell.su_raw("magiskhide --rm " + pkg);
|
||||
}
|
||||
|
||||
public static String getLocaleString(Context context, Locale locale, @StringRes int id) {
|
||||
Configuration config = context.getResources().getConfiguration();
|
||||
config.setLocale(locale);
|
||||
Context localizedContext = context.createConfigurationContext(config);
|
||||
return localizedContext.getString(id);
|
||||
}
|
||||
|
||||
public static List<Locale> getAvailableLocale(Context context) {
|
||||
List<Locale> locales = new ArrayList<>();
|
||||
HashSet<String> set = new HashSet<>();
|
||||
Locale locale;
|
||||
|
||||
int compareId = R.string.download_file_error;
|
||||
|
||||
// Add default locale
|
||||
locales.add(Locale.ENGLISH);
|
||||
set.add(getLocaleString(context, Locale.ENGLISH, compareId));
|
||||
|
||||
// Add some special locales
|
||||
locales.add(Locale.TAIWAN);
|
||||
set.add(getLocaleString(context, Locale.TAIWAN, compareId));
|
||||
locale = new Locale("pt", "BR");
|
||||
locales.add(locale);
|
||||
set.add(getLocaleString(context, locale, compareId));
|
||||
|
||||
// Other locales
|
||||
for (String s : context.getAssets().getLocales()) {
|
||||
locale = Locale.forLanguageTag(s);
|
||||
if (set.add(getLocaleString(context, locale, compareId))) {
|
||||
locales.add(locale);
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(locales, (l1, l2) -> l1.getDisplayName(l1).compareTo(l2.getDisplayName(l2)));
|
||||
|
||||
return locales;
|
||||
}
|
||||
|
||||
public static String genPackageName(String prefix, int length) {
|
||||
StringBuilder builder = new StringBuilder(length);
|
||||
builder.append(prefix);
|
||||
length -= prefix.length();
|
||||
SecureRandom random = new SecureRandom();
|
||||
String base = "abcdefghijklmnopqrstuvwxyz";
|
||||
String alpha = base + base.toUpperCase();
|
||||
String full = alpha + "0123456789..........";
|
||||
char next, prev = '\0';
|
||||
for (int i = 0; i < length; ++i) {
|
||||
if (prev == '.' || i == length - 1 || i == 0) {
|
||||
next = alpha.charAt(random.nextInt(alpha.length()));
|
||||
} else {
|
||||
next = full.charAt(random.nextInt(full.length()));
|
||||
}
|
||||
builder.append(next);
|
||||
prev = next;
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static void runWithPermission(Context context, String permission, Runnable callback) {
|
||||
if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
|
||||
// Passed in context should be an activity if not granted, need to show dialog!
|
||||
if (!(context instanceof com.topjohnwu.magisk.components.Activity))
|
||||
return;
|
||||
com.topjohnwu.magisk.components.Activity activity = (com.topjohnwu.magisk.components.Activity) context;
|
||||
activity.setPermissionGrantCallback(callback);
|
||||
ActivityCompat.requestPermissions(activity, new String[] { permission }, 0);
|
||||
} else {
|
||||
callback.run();
|
||||
}
|
||||
}
|
||||
|
||||
public static void showMagiskInstallDialog(MagiskFragment fragment, boolean enc, boolean verity) {
|
||||
MagiskManager mm = getMagiskManager(fragment.getActivity());
|
||||
String filename = getLegalFilename("Magisk-v" + mm.remoteMagiskVersionString + ".zip");
|
||||
new AlertDialogBuilder(fragment.getActivity())
|
||||
.setTitle(mm.getString(R.string.repo_install_title, mm.getString(R.string.magisk)))
|
||||
.setMessage(mm.getString(R.string.repo_install_msg, filename))
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.install, (d, i) -> {
|
||||
List<String> options = new ArrayList<>();
|
||||
options.add(mm.getString(R.string.download_zip_only));
|
||||
options.add(mm.getString(R.string.patch_boot_file));
|
||||
if (Shell.rootAccess()) {
|
||||
options.add(mm.getString(R.string.direct_install));
|
||||
}
|
||||
new AlertDialog.Builder(fragment.getActivity())
|
||||
.setTitle(R.string.select_method)
|
||||
.setItems(
|
||||
options.toArray(new String [0]),
|
||||
(dialog, idx) -> {
|
||||
DownloadReceiver receiver = null;
|
||||
switch (idx) {
|
||||
case 1:
|
||||
if (mm.remoteMagiskVersionCode < 1400) {
|
||||
mm.toast(R.string.no_boot_file_patch_support, Toast.LENGTH_LONG);
|
||||
return;
|
||||
}
|
||||
mm.toast(R.string.boot_file_patch_msg, Toast.LENGTH_LONG);
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.setType("*/*");
|
||||
fragment.startActivityForResult(intent, SELECT_BOOT_IMG,
|
||||
(requestCode, resultCode, data) -> {
|
||||
if (requestCode == SELECT_BOOT_IMG
|
||||
&& resultCode == Activity.RESULT_OK && data != null) {
|
||||
dlAndReceive(
|
||||
fragment.getActivity(),
|
||||
new DownloadReceiver() {
|
||||
@Override
|
||||
public void onDownloadDone(Uri uri) {
|
||||
Intent intent = new Intent(mm, FlashActivity.class);
|
||||
intent.setData(uri)
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.putExtra(FlashActivity.SET_BOOT, data.getData())
|
||||
.putExtra(FlashActivity.SET_ENC, enc)
|
||||
.putExtra(FlashActivity.SET_VERITY, verity)
|
||||
.putExtra(FlashActivity.SET_ACTION, FlashActivity.PATCH_BOOT);
|
||||
mm.startActivity(intent);
|
||||
}
|
||||
},
|
||||
mm.magiskLink,
|
||||
filename
|
||||
);
|
||||
}
|
||||
});
|
||||
return;
|
||||
case 0:
|
||||
receiver = new DownloadReceiver() {
|
||||
@Override
|
||||
public void onDownloadDone(Uri uri) {
|
||||
showUriSnack(fragment.getActivity(), uri);
|
||||
}
|
||||
};
|
||||
break;
|
||||
case 2:
|
||||
final String boot = fragment.getSelectedBootImage();
|
||||
if (boot == null)
|
||||
return;
|
||||
receiver = new DownloadReceiver() {
|
||||
@Override
|
||||
public void onDownloadDone(Uri uri) {
|
||||
Intent intent = new Intent(mm, FlashActivity.class);
|
||||
intent.setData(uri)
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.putExtra(FlashActivity.SET_BOOT, boot)
|
||||
.putExtra(FlashActivity.SET_ENC, enc)
|
||||
.putExtra(FlashActivity.SET_VERITY, verity)
|
||||
.putExtra(FlashActivity.SET_ACTION, FlashActivity.FLASH_MAGISK);
|
||||
mm.startActivity(intent);
|
||||
}
|
||||
};
|
||||
break;
|
||||
}
|
||||
Utils.dlAndReceive(
|
||||
mm,
|
||||
receiver,
|
||||
mm.magiskLink,
|
||||
filename
|
||||
);
|
||||
}
|
||||
).show();
|
||||
})
|
||||
.setNeutralButton(R.string.release_notes, (d, i) -> {
|
||||
if (mm.releaseNoteLink != null) {
|
||||
Intent openLink = new Intent(Intent.ACTION_VIEW, Uri.parse(mm.releaseNoteLink));
|
||||
openLink.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
mm.startActivity(openLink);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.no_thanks, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
public static void showManagerInstallDialog(Activity activity) {
|
||||
MagiskManager mm = Utils.getMagiskManager(activity);
|
||||
new AlertDialogBuilder(activity)
|
||||
.setTitle(mm.getString(R.string.repo_install_title, mm.getString(R.string.app_name)))
|
||||
.setMessage(mm.getString(R.string.repo_install_msg,
|
||||
Utils.getLegalFilename("MagiskManager-v" +
|
||||
mm.remoteManagerVersionString + ".apk")))
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.install, (d, i) -> {
|
||||
Intent intent = new Intent(mm, ManagerUpdate.class);
|
||||
intent.putExtra(MagiskManager.INTENT_LINK, mm.managerLink);
|
||||
intent.putExtra(MagiskManager.INTENT_VERSION, mm.remoteManagerVersionString);
|
||||
mm.sendBroadcast(intent);
|
||||
})
|
||||
.setNegativeButton(R.string.no_thanks, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
public static void showUninstallDialog(MagiskFragment fragment) {
|
||||
MagiskManager mm = Utils.getMagiskManager(fragment.getActivity());
|
||||
new AlertDialogBuilder(fragment.getActivity())
|
||||
.setTitle(R.string.uninstall_magisk_title)
|
||||
.setMessage(R.string.uninstall_magisk_msg)
|
||||
.setPositiveButton(R.string.complete_uninstall, (d, i) -> {
|
||||
try {
|
||||
InputStream in = mm.getAssets().open(UNINSTALLER);
|
||||
File uninstaller = new File(mm.getCacheDir(), UNINSTALLER);
|
||||
FileOutputStream out = new FileOutputStream(uninstaller);
|
||||
byte[] bytes = new byte[1024];
|
||||
int read;
|
||||
while ((read = in.read(bytes)) != -1) {
|
||||
out.write(bytes, 0, read);
|
||||
}
|
||||
in.close();
|
||||
out.close();
|
||||
in = mm.getAssets().open(UTIL_FUNCTIONS);
|
||||
File utils = new File(mm.getCacheDir(), UTIL_FUNCTIONS);
|
||||
out = new FileOutputStream(utils);
|
||||
while ((read = in.read(bytes)) != -1) {
|
||||
out.write(bytes, 0, read);
|
||||
}
|
||||
in.close();
|
||||
out.close();
|
||||
ProgressDialog progress = new ProgressDialog(fragment.getActivity());
|
||||
progress.setTitle(R.string.reboot);
|
||||
progress.show();
|
||||
new CountDownTimer(5000, 1000) {
|
||||
@Override
|
||||
public void onTick(long millisUntilFinished) {
|
||||
progress.setMessage(mm.getString(R.string.reboot_countdown,
|
||||
millisUntilFinished / 1000));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish() {
|
||||
progress.setMessage(mm.getString(R.string.reboot_countdown, 0));
|
||||
Shell.getShell(mm).su_raw(
|
||||
"mv -f " + uninstaller + " /cache/" + UNINSTALLER,
|
||||
"mv -f " + utils + " /data/magisk/" + UTIL_FUNCTIONS,
|
||||
"reboot"
|
||||
);
|
||||
}
|
||||
}.start();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
})
|
||||
.setNeutralButton(R.string.restore_stock_boot, (d, i) -> {
|
||||
String boot = fragment.getSelectedBootImage();
|
||||
if (boot == null) return;
|
||||
new RestoreStockBoot(mm, boot).exec();
|
||||
})
|
||||
.setNegativeButton(R.string.no_thanks, null)
|
||||
.show();
|
||||
}
|
||||
}
|
@@ -1,79 +0,0 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
|
||||
public class WebService {
|
||||
|
||||
public final static int GET = 1;
|
||||
public final static int POST = 2;
|
||||
|
||||
public static String getString(String url) {
|
||||
return getString(url, null);
|
||||
}
|
||||
|
||||
public static String getString(String url, Map<String, String> header) {
|
||||
InputStream in = request(GET, url, header);
|
||||
if (in == null) return "";
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(in));
|
||||
int len;
|
||||
StringBuilder builder = new StringBuilder();
|
||||
char buf[] = new char[4096];
|
||||
try {
|
||||
while ((len = br.read(buf)) != -1) {
|
||||
builder.append(buf, 0, len);
|
||||
}
|
||||
in.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static InputStream request(int method, String address, Map<String, String> header) {
|
||||
Logger.dev("WebService: Service call " + address);
|
||||
try {
|
||||
URL url = new URL(address);
|
||||
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setReadTimeout(15000);
|
||||
conn.setConnectTimeout(15000);
|
||||
conn.setDoInput(true);
|
||||
|
||||
if (method == POST) {
|
||||
conn.setRequestMethod("POST");
|
||||
} else if (method == GET) {
|
||||
conn.setRequestMethod("GET");
|
||||
}
|
||||
|
||||
if (header != null) {
|
||||
for (Map.Entry<String, String> entry : header.entrySet()) {
|
||||
conn.setRequestProperty(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
if (conn.getResponseCode() == HttpsURLConnection.HTTP_OK) {
|
||||
if (header != null) {
|
||||
header.clear();
|
||||
for (Map.Entry<String, List<String>> entry : conn.getHeaderFields().entrySet()) {
|
||||
List<String> l = entry.getValue();
|
||||
header.put(entry.getKey(), l.get(l.size() - 1));
|
||||
}
|
||||
}
|
||||
return conn.getInputStream();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@@ -1,695 +0,0 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.bouncycastle.asn1.ASN1InputStream;
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||
import org.bouncycastle.asn1.DEROutputStream;
|
||||
import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
|
||||
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
|
||||
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.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
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.KeyFactory;
|
||||
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.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
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.JarInputStream;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/*
|
||||
* Modified from from AOSP(Marshmallow) SignAPK.java
|
||||
* */
|
||||
|
||||
public class ZipUtils {
|
||||
// File name in assets
|
||||
private static final String PUBLIC_KEY_NAME = "public.certificate.x509.pem";
|
||||
private static final String PRIVATE_KEY_NAME = "private.key.pk8";
|
||||
private static final String UNHIDE_APK = "unhide.apk";
|
||||
|
||||
private static final String CERT_SF_NAME = "META-INF/CERT.SF";
|
||||
private static final String CERT_SIG_NAME = "META-INF/CERT.%s";
|
||||
|
||||
private static final String ANDROID_MANIFEST = "AndroidManifest.xml";
|
||||
private static final byte[] UNHIDE_PKG_NAME = "com.topjohnwu.unhide\0".getBytes();
|
||||
|
||||
private 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);
|
||||
System.loadLibrary("zipadjust");
|
||||
}
|
||||
|
||||
public native static void zipAdjust(String filenameIn, String filenameOut);
|
||||
|
||||
public static String generateUnhide(Context context, File output) {
|
||||
File temp = new File(context.getCacheDir(), "temp.apk");
|
||||
String pkg = "";
|
||||
try {
|
||||
JarInputStream source = new JarInputStream(context.getAssets().open(UNHIDE_APK));
|
||||
JarOutputStream dest = new JarOutputStream(new FileOutputStream(temp));
|
||||
JarEntry entry;
|
||||
int size;
|
||||
byte buffer[] = new byte[4096];
|
||||
while ((entry = source.getNextJarEntry()) != null) {
|
||||
dest.putNextEntry(new JarEntry(entry.getName()));
|
||||
if (TextUtils.equals(entry.getName(), ANDROID_MANIFEST)) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
while((size = source.read(buffer)) != -1) {
|
||||
baos.write(buffer, 0, size);
|
||||
}
|
||||
int offset = -1;
|
||||
byte xml[] = baos.toByteArray();
|
||||
|
||||
// Linear search pattern offset
|
||||
for (int i = 0; i < xml.length - UNHIDE_PKG_NAME.length; ++i) {
|
||||
boolean match = true;
|
||||
for (int j = 0; j < UNHIDE_PKG_NAME.length; ++j) {
|
||||
if (xml[i + j] != UNHIDE_PKG_NAME[j]) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
offset = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (offset < 0)
|
||||
return "";
|
||||
|
||||
// Patch binary XML with new package name
|
||||
pkg = Utils.genPackageName("com.", UNHIDE_PKG_NAME.length - 1);
|
||||
System.arraycopy(pkg.getBytes(), 0, xml, offset, pkg.length());
|
||||
dest.write(xml);
|
||||
} else {
|
||||
while((size = source.read(buffer)) != -1) {
|
||||
dest.write(buffer, 0, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
source.close();
|
||||
dest.close();
|
||||
signZip(context, temp, output, false);
|
||||
temp.delete();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return pkg;
|
||||
}
|
||||
return pkg;
|
||||
}
|
||||
|
||||
public static void removeTopFolder(InputStream in, File output) throws IOException {
|
||||
try {
|
||||
JarInputStream source = new JarInputStream(in);
|
||||
JarOutputStream dest = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(output)));
|
||||
JarEntry entry;
|
||||
String path;
|
||||
int size;
|
||||
byte buffer[] = new byte[4096];
|
||||
while ((entry = source.getNextJarEntry()) != null) {
|
||||
// Remove the top directory from the path
|
||||
path = entry.getName().substring(entry.getName().indexOf("/") + 1);
|
||||
// If it's the top folder, ignore it
|
||||
if (path.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
// Don't include placeholder
|
||||
if (path.equals("system/placeholder")) {
|
||||
continue;
|
||||
}
|
||||
dest.putNextEntry(new JarEntry(path));
|
||||
while((size = source.read(buffer)) != -1) {
|
||||
dest.write(buffer, 0, size);
|
||||
}
|
||||
}
|
||||
source.close();
|
||||
dest.close();
|
||||
in.close();
|
||||
} catch (IOException e) {
|
||||
Logger.dev("ZipUtils: removeTopFolder IO error!");
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public static void unzip(File zip, File folder, String path, boolean junkPath) throws Exception {
|
||||
InputStream in = new BufferedInputStream(new FileInputStream(zip));
|
||||
unzip(in, folder, path, junkPath);
|
||||
in.close();
|
||||
}
|
||||
|
||||
public static void unzip(InputStream zip, File folder, String path, boolean junkPath) throws Exception {
|
||||
byte data[] = new byte[4096];
|
||||
try {
|
||||
JarInputStream zipfile = new JarInputStream(zip);
|
||||
JarEntry entry;
|
||||
while ((entry = zipfile.getNextJarEntry()) != null) {
|
||||
if (!entry.getName().startsWith(path) || entry.isDirectory()){
|
||||
// Ignore directories, only create files
|
||||
continue;
|
||||
}
|
||||
String name;
|
||||
if (junkPath) {
|
||||
name = entry.getName().substring(entry.getName().lastIndexOf('/') + 1);
|
||||
} else {
|
||||
name = entry.getName();
|
||||
}
|
||||
Logger.dev("ZipUtils: Extracting: " + entry);
|
||||
File dest = new File(folder, name);
|
||||
dest.getParentFile().mkdirs();
|
||||
FileOutputStream out = new FileOutputStream(dest);
|
||||
int count;
|
||||
while ((count = zipfile.read(data)) != -1) {
|
||||
out.write(data, 0, count);
|
||||
}
|
||||
out.flush();
|
||||
out.close();
|
||||
}
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public static void signZip(Context context, File input, File output, boolean minSign) {
|
||||
int alignment = 4;
|
||||
JarFile inputJar = null;
|
||||
BufferedOutputStream outputFile = null;
|
||||
int hashes = 0;
|
||||
try {
|
||||
X509Certificate publicKey = readPublicKey(context.getAssets().open(PUBLIC_KEY_NAME));
|
||||
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 = readPrivateKey(context.getAssets().open(PRIVATE_KEY_NAME));
|
||||
|
||||
outputFile = new BufferedOutputStream(new FileOutputStream(output));
|
||||
if (minSign) {
|
||||
ZipUtils.signWholeFile(input, publicKey, privateKey, outputFile);
|
||||
} else {
|
||||
inputJar = new JarFile(input, false); // Don't verify.
|
||||
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(inputJar, hashes);
|
||||
copyFiles(manifest, inputJar, outputJar, timestamp, alignment);
|
||||
signFile(manifest, inputJar, publicKey, privateKey, outputJar);
|
||||
outputJar.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
if (inputJar != null) inputJar.close();
|
||||
if (outputFile != null) outputFile.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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) + ")$");
|
||||
private 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. */
|
||||
private 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();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Add the hash(es) of every file to the manifest, creating it if
|
||||
* necessary.
|
||||
*/
|
||||
private static Manifest addDigestsToManifest(JarFile 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, JarFile 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<String>(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 File file;
|
||||
private ASN1ObjectIdentifier type;
|
||||
private byte[] buffer;
|
||||
int bufferSize = 0;
|
||||
|
||||
CMSProcessableFile(File file) {
|
||||
this.file = file;
|
||||
type = new ASN1ObjectIdentifier(CMSObjectIdentifiers.data.getId());
|
||||
buffer = new byte[4096];
|
||||
}
|
||||
|
||||
@Override
|
||||
public ASN1ObjectIdentifier getContentType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutputStream out) throws IOException, CMSException {
|
||||
FileInputStream input = new FileInputStream(file);
|
||||
long len = file.length() - 2;
|
||||
while ((bufferSize = input.read(buffer)) > 0) {
|
||||
if (len <= bufferSize) {
|
||||
out.write(buffer, 0, (int) len);
|
||||
break;
|
||||
} else {
|
||||
out.write(buffer, 0, bufferSize);
|
||||
}
|
||||
len -= bufferSize;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getContent() {
|
||||
return file;
|
||||
}
|
||||
|
||||
byte[] getTail() {
|
||||
return Arrays.copyOfRange(buffer, 0, bufferSize);
|
||||
}
|
||||
}
|
||||
|
||||
private static void signWholeFile(File 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, JarFile 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,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,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:maxLines="1"
|
||||
android:paddingEnd="3dp"
|
||||
android:paddingStart="3dp"/>
|
||||
</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,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/textView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="monospace"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="10sp" />
|
@@ -1,200 +0,0 @@
|
||||
<resources>
|
||||
<!--Universal-->
|
||||
<!--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="cannot_check_updates">Impossible de vérifier les mises à jour, pas d\'Internet ?</string>
|
||||
<string name="root_error">Rooté mais aucune permission root, non autorisée ?</string>
|
||||
<string name="not_rooted">Non rooté</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_no_response">Impossible de contrôler SafetyNet, pas d\'Internet?</string>
|
||||
<string name="safetyNet_check_success">Contrôle SafetyNet passé avec succès</string>
|
||||
<string name="safetyNet_network_loss">Connexion réseau indisponible unavailable</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="auto_detect">(Auto) %1$s</string>
|
||||
<string name="cannot_auto_detect">(Auto détection impossible)</string>
|
||||
<string name="boot_image_title">Emplacement de l\'Image Boot</string>
|
||||
<string name="detect_button">Détection</string>
|
||||
<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 Magisk Installée : %1$s</string>
|
||||
<string name="install_magisk_title">Dernière Version Magisk : %1$s</string>
|
||||
<string name="uninstall">Désinstaller</string>
|
||||
<string name="reboot_countdown">Redémarrage dans %1$d</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 ce n'est actuellement pas le cas</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="menuSaveToSd">Enregistrer sur SD</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 SD card:</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" />
|
||||
<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">Sujet 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 ?</string>
|
||||
<string name="download">Télécharger</string>
|
||||
<string name="download_file_error">Erreur de téléchargement du fichier</string>
|
||||
<string name="install_error">Échec de l\'installation !</string>
|
||||
<string name="invalid_zip">Ce zip n\'est pas un Module Magisk !!</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 d\'exécution</string>
|
||||
<string name="internal_storage">Le zip est stocké dans :\n[Internal Storage]%1$s</string>
|
||||
<string name="zip_process_title">Exécution</string>
|
||||
<string name="manual_boot_image">Veuillez sélectionner l\'image boot manuellement !</string>
|
||||
<string name="zip_download_title">Téléchargement</string>
|
||||
<string name="zip_download_msg">Téléchargement du fichier zip...</string>
|
||||
<string name="manager_update_title">Nouvelle mise à jour Magisk Manager disponible !</string>
|
||||
<string name="manager_download_install">Appuyez pour télécharger et installer</string>
|
||||
<string name="magisk_updates">Mises à jour Magisk</string>
|
||||
<string name="flashing">Flash en-cours</string>
|
||||
<string name="hide_manager_toast">Masquage de Magisk Manager…</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="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="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>
|
||||
<!--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 en ligne, 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_development_category">Dévellopement de l\'application</string>
|
||||
<string name="settings_developer_logging_title">Activer le journal de débogage avancé</string>
|
||||
<string name="settings_developer_logging_summary">Cocher ceci pour activer la journalisation "verbose" (tout)</string>
|
||||
<string name="settings_shell_logging_title">Activer la journalisation de débogage de commande shell</string>
|
||||
<string name="settings_shell_logging_summary">Cocher ceci pour activer la journalisation de toutes les commandes shell et leurs données de sortie</string>
|
||||
<string name="settings_hide_manager_title">Masquer Magisk Manager</string>
|
||||
<string name="settings_hide_manager_summary">Masquer temporellement Magisk Manager.\nCeci installera une nouvelle appli appelée \"Démasquer Magisk Manager\"</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_boot_format_title">Format de sortie du boot patché</string>
|
||||
<string name="settings_boot_format_summary">Sélectionner le format de \'image boot patchée finale.\nChoisir .img pour flasher via fastboot/mode download ; chosiir .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">Cible UID:\u0020</string>
|
||||
<string name="command">Commande :\u0020</string>
|
||||
</resources>
|
@@ -1,186 +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="cannot_check_updates">Impossibile controllare aggiornamenti</string>
|
||||
<string name="root_error">Rootato ma senza permessi. Non sei autorizzato?</string>
|
||||
<string name="not_rooted">Non rootato</string>
|
||||
<string name="safetyNet_check_text">Tocca per avviare controllo SafetyNet</string>
|
||||
<string name="checking_safetyNet_status">Controllo stato SafetyNet</string>
|
||||
<string name="safetyNet_check_success">Controllo SafetyNet OK</string>
|
||||
<string name="safetyNet_no_response">Impossibile controllare SafetyNet. Nessuna connessione internet?</string>
|
||||
<string name="safetyNet_network_loss">persa conenssioen di rete</string>
|
||||
<string name="safetyNet_service_disconnected">Ils ervizio è stato terminato</string>
|
||||
<string name="safetyNet_res_invalid">La risposta non è valida</string>
|
||||
|
||||
<!--Install Fragment-->
|
||||
<string name="auto_detect">%1$s (auto)</string>
|
||||
<string name="cannot_auto_detect">(impossibile rilevare automaticamente)</string>
|
||||
<string name="boot_image_title">Percorso immagine boot</string>
|
||||
<string name="detect_button">Rileva</string>
|
||||
<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 Magisk installata: %1$s</string>
|
||||
<string name="install_magisk_title">Versione Magisk aggiornata: %1$s</string>
|
||||
<string name="uninstall">Disinstalla</string>
|
||||
<string name="reboot_countdown">Riavvio tra %1$d</string>
|
||||
<string name="uninstall_magisk_title">Disinstalla Magisk</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à disattivato 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="menuSaveToSd">Salva nella SD</string>
|
||||
<string name="menuReload">Ricarica</string>
|
||||
<string name="menuClearLog">Azzera 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">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="install_error">Errore di installazione!</string>
|
||||
<string name="invalid_zip">Lo ZIP non è un modulo Magisk!!</string>
|
||||
<string name="reboot">Riavvia</string>
|
||||
<string name="zip_process_msg">Elaborazione file ZIP…</string>
|
||||
<string name="downloading_toast">Download di %1$s</string>
|
||||
<string name="magisk_update_title">Disponibile nuovo aggiornamento Magisk!</string>
|
||||
<string name="settings_reboot_toast">Riavvia per applicare</string>
|
||||
<string name="release_notes">Note di rilascio</string>
|
||||
<string name="repo_cache_cleared">Cache reposititory azzerata</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">Lo ZIP si trova in:\n[memoria interna]%1$s</string>
|
||||
<string name="zip_process_title">Elaborazione</string>
|
||||
<string name="manual_boot_image">Seleziona manualmente l\'immagine di boot!</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="magisk_updates">Aggiornamenti Magisk</string>
|
||||
<string name="flashing">Flashando</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 tema scuro</string>
|
||||
<string name="settings_notification_title">Notifica aggiornamenti</string>
|
||||
<string name="settings_notification_summary">Visualizza notifica quando sono diponibili aggiornamenti</string>
|
||||
<string name="settings_clear_cache_title">Azzera cache repository</string>
|
||||
<string name="settings_clear_cache_summary">Azzera le informazioni nella cache per i repository online, e forza l\'aggiornamento online dell\'app</string>
|
||||
|
||||
<string name="settings_core_only_title">Solo modo core Magisk</string>
|
||||
<string name="settings_core_only_summary">Abilita solo le funzioni principali. Non tutti i moduli verranno caricati. 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 host systemless per app blocco 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">Risposta automatica</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-autentifica dopo aggiornamento</string>
|
||||
<string name="settings_su_reauth_summary">Ri-autentifica permessi superuser dopo aggiornamento applicazione</string>
|
||||
|
||||
<string name="multiuser_mode">Modo multiutente</string>
|
||||
<string name="settings_owner_only">Solo proprietario dispositivo</string>
|
||||
<string name="settings_owner_manage">gestito da propietario utente</string>
|
||||
<string name="settings_user_independent">Utente indipendente</string>
|
||||
<string name="owner_only_summary">Solo proprietario che ha accesso root</string>
|
||||
<string name="owner_manage_summary">Solo il propietario 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 richiestà è stata inviata al propietario dipositivo. Accedi come propietario dispositivo e concedi i permessi.</string>
|
||||
|
||||
<string name="settings_development_category">Sviluppo app</string>
|
||||
<string name="settings_developer_logging_title">Abilita registro eventi avanzato debug</string>
|
||||
<string name="settings_developer_logging_summary">Spunta questo per abilitare il registro eventi dettagliato</string>
|
||||
<string name="settings_shell_logging_title">Abilita registro eventi debug comandi shell</string>
|
||||
<string name="settings_shell_logging_summary">Spunta questo per abilitare il registro eventi dettagliato dei comandi shell e dei relativi risultati</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">Richiedi</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,166 +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="cannot_check_updates">更新を確認できませんでした。インターネットに接続されていますか?</string>
|
||||
<string name="root_error">Root化はされていますが権限がありません。拒否していませんか?</string>
|
||||
<string name="not_rooted">Root化されていません</string>
|
||||
<string name="safetyNet_check_text">タップしてSafetyNetチェックを開始</string>
|
||||
<string name="checking_safetyNet_status">SafetyNet Statusをチェック中…</string>
|
||||
<string name="safetyNet_no_response">SafetyNetをチェックできませんでした。インターネットに接続されていますか?</string>
|
||||
|
||||
<!--Install Fragment-->
|
||||
<string name="auto_detect">(自動) %1$s</string>
|
||||
<string name="cannot_auto_detect">(自動検出に失敗)</string>
|
||||
<string name="boot_image_title">Bootイメージの位置</string>
|
||||
<string name="detect_button">検出</string>
|
||||
<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">インストール済のMagisk: %1$s</string>
|
||||
<string name="install_magisk_title">最新のMagisk: %1$s</string>
|
||||
<string name="uninstall">アンインストール</string>
|
||||
<string name="reboot_countdown">%1$dで再起動します</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="menuSaveToSd">SDカードに保存</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="install_error">インストールエラー!</string>
|
||||
<string name="invalid_zip">このzipはMagiskモジュールではありません</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>
|
||||
<string name="manual_boot_image">ブートイメージを手動で選択してください!</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>
|
||||
|
||||
<string name="settings_development_category">アプリケーション開発</string>
|
||||
<string name="settings_developer_logging_title">高度なデバッグログを有効にする</string>
|
||||
<string name="settings_developer_logging_summary">詳細なログを有効にするにはここをチェック</string>
|
||||
<string name="settings_shell_logging_title">シェルコマンドのデバッグログを有効にする</string>
|
||||
<string name="settings_shell_logging_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>
|
@@ -1,196 +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="cannot_check_updates">Não é possível verificar se há atualizações</string>
|
||||
<string name="root_error">Rooteado mas sem permissão de root, o acesso foi permitido?</string>
|
||||
<string name="not_rooted">Sem root</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_no_response">Não é possível verificar o SafetyNet, sem Internet?</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="auto_detect">(Auto) %1$s</string>
|
||||
<string name="cannot_auto_detect">(Não foi auto detectado)</string>
|
||||
<string name="boot_image_title">Local da Boot Image</string>
|
||||
<string name="detect_button">Detectar</string>
|
||||
<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 do Magisk: %1$s</string>
|
||||
<string name="install_magisk_title">Última versão do Magisk: %1$s</string>
|
||||
<string name="uninstall">Desinstalar</string>
|
||||
<string name="reboot_countdown">Reiniciando em %1$d</string>
|
||||
<string name="uninstall_magisk_title">Desinstalar Magisk</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="menuSaveToSd">Salvar no SD</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="install_error">Erro na instalação!</string>
|
||||
<string name="invalid_zip">O zip não é um Módulo Magisk!!</string>
|
||||
<string name="reboot">Reiniciar</string>
|
||||
<string name="zip_process_msg">Processando arquivo zip …</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_process_title">Processando</string>
|
||||
<string name="manual_boot_image">Por Favor, selecione manualmente a Boot Image</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>
|
||||
|
||||
<!--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_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>
|
||||
|
||||
<string name="settings_development_category">Desenvolvimento</string>
|
||||
<string name="settings_developer_logging_title">Ativar registro mais detalhado</string>
|
||||
<string name="settings_developer_logging_summary">Marque essa opção para habilitar um registro mais detalhado</string>
|
||||
<string name="settings_shell_logging_title">Ativar registro da shell de comando</string>
|
||||
<string name="settings_shell_logging_summary">Marque esta opção para habilitar o registro de todos os comandos shell e de saída</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,204 +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="cannot_check_updates">Невозможно проверить наличие обновлений</string>
|
||||
<string name="root_error">Root присутствует, но отсутствует разрешение</string>
|
||||
<string name="not_rooted">Root отсутствует</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_no_response">Невозможно выполнить проверку SafetyNet</string>
|
||||
<string name="safetyNet_network_loss">Подключение к интернету прервано</string>
|
||||
<string name="safetyNet_service_disconnected">Служба была остановлена</string>
|
||||
<string name="safetyNet_res_invalid">Некорректный ответ</string>
|
||||
|
||||
<!--Install Fragment-->
|
||||
<string name="auto_detect">(Авто) %1$s</string>
|
||||
<string name="cannot_auto_detect">(Автоопределение невозможно)</string>
|
||||
<string name="boot_image_title">Распоожение boot-образа</string>
|
||||
<string name="detect_button">Определить</string>
|
||||
<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">Текущая версия Magisk: %1$s</string>
|
||||
<string name="install_magisk_title">Последняя версия Magisk: %1$s</string>
|
||||
<string name="uninstall">Удалить</string>
|
||||
<string name="reboot_countdown">Перезагрузка через %1$d</string>
|
||||
<string name="uninstall_magisk_title">Удалить Magisk</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="menuSaveToSd">Сохранить на карту памяти</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="install_error">Ошибка во время установки!</string>
|
||||
<string name="invalid_zip">В архив отсутствует модуль Magisk!</string>
|
||||
<string name="reboot">Перезагрузка</string>
|
||||
<string name="zip_process_msg">Обработка архива …</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_process_title">Обработка</string>
|
||||
<string name="manual_boot_image">Пожалуйста, выберите вручную boot-образ!</string>
|
||||
<string name="manager_update_title">Доступно новое обновление менеджера Magisk!</string>
|
||||
<string name="manager_download_install">Нажмите, чтобы скачать и установить</string>
|
||||
<string name="hide_manager_toast">Скрытие Менеджера Magisk…</string>
|
||||
<string name="hide_manager_fail_toast">Скрытие Менеджера Magisk неудачно…</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.\nБудет установлено новое приложение, называется \"Unhide Magisk Manager\"</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>
|
||||
|
||||
<string name="settings_development_category">Разработка</string>
|
||||
<string name="settings_developer_logging_title">Расширенная история</string>
|
||||
<string name="settings_developer_logging_summary">Включить подробнейшее ведение истории отладки</string>
|
||||
<string name="settings_shell_logging_title">История команд оболочки</string>
|
||||
<string name="settings_shell_logging_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>
|
65
build.gradle
@@ -1,26 +1,57 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
maven { url "https://maven.google.com" }
|
||||
android {
|
||||
compileSdkVersion 27
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.topjohnwu.magisk"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 27
|
||||
versionCode 108
|
||||
versionName "5.6.2"
|
||||
javaCompileOptions {
|
||||
annotationProcessorOptions {
|
||||
argument('butterknife.debuggable', 'false')
|
||||
}
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.0.0-beta4'
|
||||
|
||||
// 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.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
dexOptions {
|
||||
preDexLibraries true
|
||||
javaMaxHeapSize "2g"
|
||||
}
|
||||
lintOptions {
|
||||
disable 'MissingTranslation'
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
jcenter()
|
||||
maven { url "https://jitpack.io" }
|
||||
}
|
||||
repositories {
|
||||
jcenter()
|
||||
google()
|
||||
maven { url "https://jitpack.io" }
|
||||
}
|
||||
|
||||
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:27.0.2'
|
||||
implementation 'com.android.support:cardview-v7:27.0.2'
|
||||
implementation 'com.android.support:design:27.0.2'
|
||||
implementation 'com.android.support:support-v4:27.0.2'
|
||||
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,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
6
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +0,0 @@
|
||||
#Sat Sep 02 18:20:31 CST 2017
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
|
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
@@ -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
|
12
app/proguard-rules.pro → proguard-rules.pro
vendored
@@ -16,10 +16,14 @@
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Keep all names, we are open source anyway :)
|
||||
-keepnames class ** { *; }
|
||||
|
||||
# BouncyCastle
|
||||
-keep class org.bouncycastle.** { *; }
|
||||
-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.**
|
||||
|
||||
-dontwarn android.content.**
|
||||
-dontwarn android.animation.**
|
||||
|
||||
# Gson
|
||||
-keepattributes Signature
|
1
resource/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
/build
|
@@ -1,23 +0,0 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion 26
|
||||
buildToolsVersion "26.0.1"
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 26
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
}
|
@@ -1 +0,0 @@
|
||||
<manifest package="com.topjohnwu.resource" />
|
Before Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 8.3 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 20 KiB |
@@ -1 +0,0 @@
|
||||
include ':app', ':unhide', ':resource'
|
@@ -9,15 +9,18 @@
|
||||
<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"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
tools:ignore="AllowBackup,GoogleAppIndexingWarning">
|
||||
android:directBootAware="true"
|
||||
tools:ignore="UnusedAttribute">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="orientation|screenSize"
|
||||
@@ -50,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" />
|
||||
@@ -72,6 +70,7 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver android:name=".receivers.ManagerUpdate" />
|
||||
<receiver android:name=".receivers.RebootReceiver" />
|
||||
|
||||
<service android:name=".services.OnBootIntentService" />
|
||||
<service
|
||||
@@ -89,9 +88,10 @@
|
||||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
|
||||
<!-- Hardcode GMS version -->
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.version"
|
||||
android:value="@integer/google_play_services_version" />
|
||||
android:value="7095000" />
|
||||
|
||||
</application>
|
||||
|
BIN
src/main/ic_launcher-web.png
Normal file
After Width: | Height: | Size: 57 KiB |
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();
|
||||
}
|
||||
|
||||
}
|
131
src/main/java/com/topjohnwu/magisk/FlashActivity.java
Normal file
@@ -0,0 +1,131 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
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.utils.Const;
|
||||
import com.topjohnwu.superuser.CallbackList;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.OnClick;
|
||||
|
||||
public class FlashActivity extends Activity {
|
||||
|
||||
@BindView(R.id.toolbar) Toolbar toolbar;
|
||||
@BindView(R.id.txtLog) TextView flashLogs;
|
||||
@BindView(R.id.button_panel) public LinearLayout buttonPanel;
|
||||
@BindView(R.id.reboot) public Button reboot;
|
||||
@BindView(R.id.scrollView) ScrollView sv;
|
||||
|
||||
private List<String> logs;
|
||||
|
||||
@OnClick(R.id.no_thanks)
|
||||
void dismiss() {
|
||||
finish();
|
||||
}
|
||||
|
||||
@OnClick(R.id.reboot)
|
||||
void 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",
|
||||
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));
|
||||
|
||||
File logFile = new File(Const.EXTERNAL_PATH + "/logs", filename);
|
||||
logFile.getParentFile().mkdirs();
|
||||
try (FileWriter writer = new FileWriter(logFile)) {
|
||||
for (String s : logs) {
|
||||
writer.write(s);
|
||||
writer.write('\n');
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
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);
|
||||
setContentView(R.layout.activity_flash);
|
||||
ButterKnife.bind(this);
|
||||
setSupportActionBar(toolbar);
|
||||
ActionBar ab = getSupportActionBar();
|
||||
if (ab != null) {
|
||||
ab.setTitle(R.string.flashing);
|
||||
}
|
||||
setFloating();
|
||||
setFinishOnTouchOutside(false);
|
||||
if (!Shell.rootAccess())
|
||||
reboot.setVisibility(View.GONE);
|
||||
|
||||
logs = new ArrayList<>();
|
||||
CallbackList<String> console = new CallbackList<String>(new ArrayList<>()) {
|
||||
@Override
|
||||
public void onAddElement(String s) {
|
||||
logs.add(s);
|
||||
flashLogs.setText(TextUtils.join("\n", this));
|
||||
sv.postDelayed(() -> sv.fullScroll(ScrollView.FOCUS_DOWN), 10);
|
||||
}
|
||||
};
|
||||
|
||||
// We must receive a Uri of the target zip
|
||||
Intent intent = getIntent();
|
||||
Uri uri = intent.getData();
|
||||
|
||||
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, (Uri) intent.getParcelableExtra(Const.Key.FLASH_SET_BOOT))
|
||||
.exec();
|
||||
break;
|
||||
case Const.Value.FLASH_MAGISK:
|
||||
new InstallMagisk(this, console, logs, uri, intent.getStringExtra(Const.Key.FLASH_SET_BOOT))
|
||||
.exec();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
// Prevent user accidentally press back button
|
||||
}
|
||||
}
|
@@ -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,13 +34,12 @@ public class LogFragment extends Fragment {
|
||||
|
||||
TabFragmentAdapter adapter = new TabFragmentAdapter(getChildFragmentManager());
|
||||
|
||||
if (getApplication().isSuClient) {
|
||||
if (!(Const.USER_ID > 0 && getApplication().multiuserMode == Const.Value.MULTIUSER_MODE_OWNER_MANAGED)) {
|
||||
adapter.addTab(new SuLogFragment(), getString(R.string.superuser));
|
||||
tab.setupWithViewPager(viewPager);
|
||||
tab.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
adapter.addTab(new MagiskLogFragment(), getString(R.string.magisk));
|
||||
tab.setupWithViewPager(viewPager);
|
||||
tab.setVisibility(View.VISIBLE);
|
||||
|
||||
viewPager.setAdapter(adapter);
|
||||
|
@@ -4,31 +4,28 @@ import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.v4.widget.SwipeRefreshLayout;
|
||||
import android.support.v7.widget.CardView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.asyncs.CheckSafetyNet;
|
||||
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.components.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.utils.ShowUI;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import butterknife.BindColor;
|
||||
import butterknife.BindView;
|
||||
@@ -39,6 +36,14 @@ import butterknife.Unbinder;
|
||||
public class MagiskFragment extends Fragment
|
||||
implements Topic.Subscriber, SwipeRefreshLayout.OnRefreshListener, ExpandableView {
|
||||
|
||||
private static final int CAUSE_SERVICE_DISCONNECTED = 0x01;
|
||||
private static final int CAUSE_NETWORK_LOST = 0x02;
|
||||
private static final int RESPONSE_ERR = 0x04;
|
||||
private static final int CONNECTION_FAIL = 0x08;
|
||||
|
||||
private static final int BASIC_PASS = 0x10;
|
||||
private static final int CTS_PASS = 0x20;
|
||||
|
||||
private Container expandableContainer = new Container();
|
||||
|
||||
private MagiskManager mm;
|
||||
@@ -47,15 +52,12 @@ public class MagiskFragment extends Fragment
|
||||
|
||||
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
||||
|
||||
@BindView(R.id.magisk_update_card) CardView magiskUpdateCard;
|
||||
@BindView(R.id.magisk_update) RelativeLayout magiskUpdate;
|
||||
@BindView(R.id.magisk_update_icon) ImageView magiskUpdateIcon;
|
||||
@BindView(R.id.magisk_update_status) TextView magiskUpdateText;
|
||||
@BindView(R.id.magisk_update_progress) ProgressBar magiskUpdateProgress;
|
||||
|
||||
@BindView(R.id.magisk_status_icon) ImageView magiskStatusIcon;
|
||||
@BindView(R.id.magisk_version) TextView magiskVersionText;
|
||||
@BindView(R.id.root_status_icon) ImageView rootStatusIcon;
|
||||
@BindView(R.id.root_status) TextView rootStatusText;
|
||||
|
||||
@BindView(R.id.safetyNet_card) CardView safetyNetCard;
|
||||
@BindView(R.id.safetyNet_refresh) ImageView safetyNetRefreshIcon;
|
||||
@@ -67,9 +69,6 @@ public class MagiskFragment extends Fragment
|
||||
@BindView(R.id.basic_status_icon) ImageView basicStatusIcon;
|
||||
@BindView(R.id.basic_status) TextView basicStatusText;
|
||||
|
||||
@BindView(R.id.bootimage_card) CardView bootImageCard;
|
||||
@BindView(R.id.block_spinner) Spinner spinner;
|
||||
@BindView(R.id.detect_bootimage) Button detectButton;
|
||||
@BindView(R.id.install_option_card) CardView installOptionCard;
|
||||
@BindView(R.id.keep_force_enc) CheckBox keepEncChkbox;
|
||||
@BindView(R.id.keep_verity) CheckBox keepVerityChkbox;
|
||||
@@ -85,11 +84,26 @@ public class MagiskFragment extends Fragment
|
||||
|
||||
@OnClick(R.id.safetyNet_title)
|
||||
void safetyNet() {
|
||||
safetyNetProgress.setVisibility(View.VISIBLE);
|
||||
safetyNetRefreshIcon.setVisibility(View.GONE);
|
||||
safetyNetStatusText.setText(R.string.checking_safetyNet_status);
|
||||
Utils.checkSafetyNet(getActivity());
|
||||
collapse();
|
||||
Runnable task = () -> {
|
||||
safetyNetProgress.setVisibility(View.VISIBLE);
|
||||
safetyNetRefreshIcon.setVisibility(View.GONE);
|
||||
safetyNetStatusText.setText(R.string.checking_safetyNet_status);
|
||||
new CheckSafetyNet(getActivity()).exec();
|
||||
collapse();
|
||||
};
|
||||
if (!CheckSafetyNet.dexPath.exists()) {
|
||||
// Show dialog
|
||||
new AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.proprietary_title)
|
||||
.setMessage(R.string.proprietary_notice)
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.yes, (d, i) -> task.run())
|
||||
.setNegativeButton(R.string.no_thanks, null)
|
||||
.show();
|
||||
} else {
|
||||
task.run();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@OnClick(R.id.install_button)
|
||||
@@ -98,18 +112,17 @@ public class MagiskFragment extends Fragment
|
||||
|
||||
// Show Manager update first
|
||||
if (mm.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
|
||||
Utils.showManagerInstallDialog(getActivity());
|
||||
ShowUI.managerInstallDialog(getActivity());
|
||||
return;
|
||||
}
|
||||
|
||||
((NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE)).cancelAll();
|
||||
Utils.showMagiskInstallDialog(this,
|
||||
keepEncChkbox.isChecked(), keepVerityChkbox.isChecked());
|
||||
ShowUI.magiskInstallDialog(getActivity());
|
||||
}
|
||||
|
||||
@OnClick(R.id.uninstall_button)
|
||||
void uninstall() {
|
||||
Utils.showUninstallDialog(this);
|
||||
ShowUI.uninstallDialog(getActivity());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -124,6 +137,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();
|
||||
|
||||
@@ -132,7 +150,7 @@ public class MagiskFragment extends Fragment
|
||||
|
||||
@Override
|
||||
public void onRefresh() {
|
||||
mm.getMagiskInfo();
|
||||
mm.loadMagiskInfo();
|
||||
updateUI();
|
||||
|
||||
magiskUpdateText.setText(R.string.checking_for_updates);
|
||||
@@ -150,19 +168,19 @@ public class MagiskFragment extends Fragment
|
||||
shownDialog = false;
|
||||
|
||||
// Trigger state check
|
||||
if (Utils.checkNetworkStatus(mm)) {
|
||||
new CheckUpdates(getActivity()).exec();
|
||||
if (Utils.checkNetworkStatus()) {
|
||||
new CheckUpdates().exec();
|
||||
} else {
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTopicPublished(Topic topic) {
|
||||
public void onTopicPublished(Topic topic, Object result) {
|
||||
if (topic == mm.updateCheckDone) {
|
||||
updateCheckUI();
|
||||
} else if (topic == mm.safetyNetDone) {
|
||||
updateSafetyNetUI();
|
||||
updateSafetyNetUI((int) result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,35 +200,15 @@ public class MagiskFragment extends Fragment
|
||||
return expandableContainer;
|
||||
}
|
||||
|
||||
public String getSelectedBootImage() {
|
||||
if (Shell.rootAccess()) {
|
||||
if (mm.bootBlock != null) {
|
||||
return mm.bootBlock;
|
||||
} else {
|
||||
int idx = spinner.getSelectedItemPosition();
|
||||
if (idx > 0) {
|
||||
return mm.blockList.get(idx - 1);
|
||||
} else {
|
||||
SnackbarMaker.make(getActivity(),
|
||||
R.string.manual_boot_image, Snackbar.LENGTH_LONG).show();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateUI() {
|
||||
((MainActivity) getActivity()).checkHideSection();
|
||||
|
||||
boolean hasNetwork = Utils.checkNetworkStatus(getActivity());
|
||||
boolean hasNetwork = Utils.checkNetworkStatus();
|
||||
boolean hasRoot = Shell.rootAccess();
|
||||
boolean isUpToDate = mm.magiskVersionCode > 1300;
|
||||
|
||||
magiskUpdateCard.setVisibility(hasNetwork ? View.VISIBLE : View.GONE);
|
||||
magiskUpdate.setVisibility(hasNetwork ? View.VISIBLE : View.GONE);
|
||||
safetyNetCard.setVisibility(hasNetwork ? View.VISIBLE : View.GONE);
|
||||
bootImageCard.setVisibility(hasNetwork && hasRoot ? View.VISIBLE : View.GONE);
|
||||
installOptionCard.setVisibility(hasNetwork ? View.VISIBLE : View.GONE);
|
||||
uninstallButton.setVisibility(isUpToDate && hasRoot ? View.VISIBLE : View.GONE);
|
||||
|
||||
@@ -228,42 +226,6 @@ public class MagiskFragment extends Fragment
|
||||
|
||||
magiskStatusIcon.setImageResource(image);
|
||||
magiskStatusIcon.setColorFilter(color);
|
||||
|
||||
switch (Shell.rootStatus) {
|
||||
case 0:
|
||||
color = colorBad;
|
||||
image = R.drawable.ic_cancel;
|
||||
rootStatusText.setText(R.string.not_rooted);
|
||||
break;
|
||||
case 1:
|
||||
if (mm.suVersion != null) {
|
||||
color = colorOK;
|
||||
image = R.drawable.ic_check_circle;
|
||||
rootStatusText.setText(mm.suVersion);
|
||||
break;
|
||||
}
|
||||
case -1:
|
||||
default:
|
||||
color = colorNeutral;
|
||||
image = R.drawable.ic_help;
|
||||
rootStatusText.setText(R.string.root_error);
|
||||
}
|
||||
|
||||
rootStatusIcon.setImageResource(image);
|
||||
rootStatusIcon.setColorFilter(color);
|
||||
|
||||
List<String> items = new ArrayList<>();
|
||||
if (mm.bootBlock != null) {
|
||||
items.add(getString(R.string.auto_detect, mm.bootBlock));
|
||||
spinner.setEnabled(false);
|
||||
} else {
|
||||
items.add(getString(R.string.cannot_auto_detect));
|
||||
items.addAll(mm.blockList);
|
||||
}
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<>(getActivity(),
|
||||
android.R.layout.simple_spinner_item, items);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
spinner.setAdapter(adapter);
|
||||
}
|
||||
|
||||
private void updateCheckUI() {
|
||||
@@ -272,20 +234,20 @@ public class MagiskFragment extends Fragment
|
||||
if (mm.remoteMagiskVersionCode < 0) {
|
||||
color = colorNeutral;
|
||||
image = R.drawable.ic_help;
|
||||
magiskUpdateText.setText(R.string.cannot_check_updates);
|
||||
magiskUpdateText.setText(R.string.invalid_update_channel);
|
||||
installButton.setVisibility(View.GONE);
|
||||
} else {
|
||||
color = colorOK;
|
||||
image = R.drawable.ic_check_circle;
|
||||
magiskUpdateText.setText(getString(R.string.install_magisk_title, "v" + mm.remoteMagiskVersionString));
|
||||
}
|
||||
|
||||
installButton.setVisibility(View.VISIBLE);
|
||||
if (mm.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
|
||||
installText.setText(getString(R.string.update, getString(R.string.app_name)));
|
||||
} else if (mm.magiskVersionCode > 0 && mm.remoteMagiskVersionCode > mm.magiskVersionCode) {
|
||||
installText.setText(getString(R.string.update, getString(R.string.magisk)));
|
||||
} else {
|
||||
installText.setText(R.string.install);
|
||||
installButton.setVisibility(View.VISIBLE);
|
||||
if (mm.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
|
||||
installText.setText(getString(R.string.update, getString(R.string.app_name)));
|
||||
} else if (mm.magiskVersionCode > 0 && mm.remoteMagiskVersionCode > mm.magiskVersionCode) {
|
||||
installText.setText(getString(R.string.update, getString(R.string.magisk)));
|
||||
} else {
|
||||
installText.setText(R.string.install);
|
||||
}
|
||||
}
|
||||
|
||||
if (!shownDialog && (mm.remoteMagiskVersionCode > mm.magiskVersionCode
|
||||
@@ -301,37 +263,42 @@ public class MagiskFragment extends Fragment
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
}
|
||||
|
||||
private void updateSafetyNetUI() {
|
||||
int image, color;
|
||||
private void updateSafetyNetUI(int response) {
|
||||
safetyNetProgress.setVisibility(View.GONE);
|
||||
safetyNetRefreshIcon.setVisibility(View.VISIBLE);
|
||||
if (mm.SNCheckResult.failed) {
|
||||
safetyNetStatusText.setText(mm.SNCheckResult.errmsg);
|
||||
collapse();
|
||||
} else {
|
||||
if ((response & 0x0F) == 0) {
|
||||
safetyNetStatusText.setText(R.string.safetyNet_check_success);
|
||||
if (mm.SNCheckResult.ctsProfile) {
|
||||
color = colorOK;
|
||||
image = R.drawable.ic_check_circle;
|
||||
} else {
|
||||
color = colorBad;
|
||||
image = R.drawable.ic_cancel;
|
||||
}
|
||||
ctsStatusText.setText("ctsProfile: " + mm.SNCheckResult.ctsProfile);
|
||||
ctsStatusIcon.setImageResource(image);
|
||||
ctsStatusIcon.setColorFilter(color);
|
||||
|
||||
if (mm.SNCheckResult.basicIntegrity) {
|
||||
color = colorOK;
|
||||
image = R.drawable.ic_check_circle;
|
||||
} else {
|
||||
color = colorBad;
|
||||
image = R.drawable.ic_cancel;
|
||||
}
|
||||
basicStatusText.setText("basicIntegrity: " + mm.SNCheckResult.basicIntegrity);
|
||||
basicStatusIcon.setImageResource(image);
|
||||
basicStatusIcon.setColorFilter(color);
|
||||
boolean b;
|
||||
b = (response & CTS_PASS) != 0;
|
||||
ctsStatusText.setText("ctsProfile: " + b);
|
||||
ctsStatusIcon.setImageResource(b ? R.drawable.ic_check_circle : R.drawable.ic_cancel);
|
||||
ctsStatusIcon.setColorFilter(b ? colorOK : colorBad);
|
||||
|
||||
b = (response & BASIC_PASS) != 0;
|
||||
basicStatusText.setText("basicIntegrity: " + b);
|
||||
basicStatusIcon.setImageResource(b ? R.drawable.ic_check_circle : R.drawable.ic_cancel);
|
||||
basicStatusIcon.setColorFilter(b ? colorOK : colorBad);
|
||||
|
||||
expand();
|
||||
} else {
|
||||
@StringRes int resid;
|
||||
switch (response) {
|
||||
case CAUSE_SERVICE_DISCONNECTED:
|
||||
resid = R.string.safetyNet_network_loss;
|
||||
break;
|
||||
case CAUSE_NETWORK_LOST:
|
||||
resid = R.string.safetyNet_service_disconnected;
|
||||
break;
|
||||
case RESPONSE_ERR:
|
||||
resid = R.string.safetyNet_res_invalid;
|
||||
break;
|
||||
case CONNECTION_FAIL:
|
||||
default:
|
||||
resid = R.string.safetyNet_api_error;
|
||||
break;
|
||||
}
|
||||
safetyNetStatusText.setText(resid);
|
||||
}
|
||||
}
|
||||
}
|
@@ -84,7 +84,7 @@ public class MagiskHideFragment extends Fragment implements Topic.Subscriber {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTopicPublished(Topic topic) {
|
||||
public void onTopicPublished(Topic topic, Object result) {
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
appAdapter.filter(lastFilter);
|
||||
}
|
@@ -1,9 +1,7 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.text.TextUtils;
|
||||
@@ -22,13 +20,16 @@ import android.widget.Toast;
|
||||
import com.topjohnwu.magisk.asyncs.ParallelTask;
|
||||
import com.topjohnwu.magisk.components.Fragment;
|
||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.CallbackList;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.Calendar;
|
||||
import java.util.Locale;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
@@ -36,8 +37,6 @@ import butterknife.Unbinder;
|
||||
|
||||
public class MagiskLogFragment extends Fragment {
|
||||
|
||||
private static final String MAGISK_LOG = "/cache/magisk.log";
|
||||
|
||||
private Unbinder unbinder;
|
||||
|
||||
@BindView(R.id.txtLog) TextView txtLog;
|
||||
@@ -110,30 +109,35 @@ public class MagiskLogFragment extends Fragment {
|
||||
super(MagiskLogFragment.this.getActivity());
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
@Override
|
||||
protected Object doInBackground(Object... params) {
|
||||
mode = (int) params[0];
|
||||
switch (mode) {
|
||||
case 0:
|
||||
StringBuildingList logList = new StringBuildingList();
|
||||
getShell().su(logList, "cat " + MAGISK_LOG);
|
||||
return logList.toString();
|
||||
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:
|
||||
getShell().su_raw("echo -n > " + 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(
|
||||
"magisk_%s_%04d%02d%02d_%02d%02d%02d.log", "error",
|
||||
String filename = String.format(Locale.US,
|
||||
"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));
|
||||
|
||||
targetFile = new File(Environment.getExternalStorageDirectory() + "/MagiskManager/" + filename);
|
||||
targetFile = new File(Const.EXTERNAL_PATH + "/logs", filename);
|
||||
|
||||
if ((!targetFile.getParentFile().exists() && !targetFile.getParentFile().mkdirs())
|
||||
|| (targetFile.exists() && !targetFile.delete())) {
|
||||
@@ -141,8 +145,16 @@ public class MagiskLogFragment extends Fragment {
|
||||
}
|
||||
|
||||
try (FileWriter out = new FileWriter(targetFile)) {
|
||||
FileWritingList fileWritingList = new FileWritingList(out);
|
||||
getShell().su(fileWritingList, "cat " + 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;
|
||||
@@ -158,21 +170,21 @@ public class MagiskLogFragment extends Fragment {
|
||||
switch (mode) {
|
||||
case 0:
|
||||
case 1:
|
||||
String llog = (String) o;
|
||||
CharSequence llog = (CharSequence) o;
|
||||
progressBar.setVisibility(View.GONE);
|
||||
if (TextUtils.isEmpty(llog))
|
||||
txtLog.setText(R.string.log_is_empty);
|
||||
else
|
||||
txtLog.setText(llog);
|
||||
svLog.post(() -> svLog.scrollTo(0, txtLog.getHeight()));
|
||||
hsvLog.post(() -> hsvLog.scrollTo(0, 0));
|
||||
svLog.postDelayed(() -> svLog.fullScroll(ScrollView.FOCUS_DOWN), 100);
|
||||
hsvLog.postDelayed(() -> hsvLog.fullScroll(ScrollView.FOCUS_LEFT), 100);
|
||||
break;
|
||||
case 2:
|
||||
boolean bool = (boolean) o;
|
||||
if (bool) {
|
||||
getMagiskManager().toast(targetFile.toString(), Toast.LENGTH_LONG);
|
||||
MagiskManager.toast(targetFile.getPath(), Toast.LENGTH_LONG);
|
||||
} else {
|
||||
getMagiskManager().toast(R.string.logs_save_failed, Toast.LENGTH_LONG);
|
||||
MagiskManager.toast(R.string.logs_save_failed, Toast.LENGTH_LONG);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -190,42 +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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
252
src/main/java/com/topjohnwu/magisk/MagiskManager.java
Normal file
@@ -0,0 +1,252 @@
|
||||
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.RepoDatabaseHelper;
|
||||
import com.topjohnwu.magisk.database.SuDatabaseHelper;
|
||||
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 SuDatabaseHelper suDB;
|
||||
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 sudb = getResources().openRawResource(R.raw.sudb)) {
|
||||
shell.loadInputStream(null, null, utils);
|
||||
shell.loadInputStream(null, null, sudb);
|
||||
} 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 */ }
|
||||
}
|
||||
|
||||
suDB = SuDatabaseHelper.getInstance(this);
|
||||
|
||||
String pkg = suDB.getStrings(Const.Key.SU_REQUESTER, Const.ORIG_PKG_NAME);
|
||||
if (getPackageName().equals(Const.ORIG_PKG_NAME) && !pkg.equals(Const.ORIG_PKG_NAME)) {
|
||||
suDB.setStrings(Const.Key.SU_REQUESTER, null);
|
||||
Utils.uninstallPkg(pkg);
|
||||
suDB = SuDatabaseHelper.getInstance(this);
|
||||
}
|
||||
|
||||
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() {
|
||||
// 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 = 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);
|
||||
|
||||
// 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 > 1435 ? "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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -6,6 +6,7 @@ import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.design.widget.NavigationView;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v4.widget.DrawerLayout;
|
||||
@@ -15,10 +16,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.Shell;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
@@ -36,15 +39,32 @@ public class MainActivity extends Activity
|
||||
|
||||
private float toolbarElevation;
|
||||
|
||||
@Override
|
||||
public int getDarkTheme() {
|
||||
return R.style.AppTheme_Dark;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
getMagiskManager().startup();
|
||||
|
||||
prefs = getMagiskManager().prefs;
|
||||
MagiskManager mm = getMagiskManager();
|
||||
prefs = mm.prefs;
|
||||
|
||||
if (getMagiskManager().isDarkTheme) {
|
||||
setTheme(R.style.AppTheme_Dark);
|
||||
if (!mm.hasInit) {
|
||||
Intent intent = new Intent(this, SplashActivity.class);
|
||||
String section = getIntent().getStringExtra(Const.Key.OPEN_SECTION);
|
||||
if (section != null) {
|
||||
intent.putExtra(Const.Key.OPEN_SECTION, section);
|
||||
}
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
String perm = getIntent().getStringExtra(Const.Key.INTENT_PERM);
|
||||
if (perm != null) {
|
||||
ActivityCompat.requestPermissions(this, new String[] { perm }, 0);
|
||||
}
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
ButterKnife.bind(this);
|
||||
@@ -70,10 +90,15 @@ public class MainActivity extends Activity
|
||||
toggle.syncState();
|
||||
|
||||
if (savedInstanceState == null)
|
||||
navigate(getIntent().getStringExtra(MagiskManager.INTENT_SECTION));
|
||||
navigate(getIntent().getStringExtra(Const.Key.OPEN_SECTION));
|
||||
|
||||
navigationView.setNavigationItemSelectedListener(this);
|
||||
|
||||
if (mm.prefs.getInt(Const.Key.APP_VER, -1) < BuildConfig.VERSION_CODE) {
|
||||
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
|
||||
@@ -102,7 +127,7 @@ public class MainActivity extends Activity
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTopicPublished(Topic topic) {
|
||||
public void onTopicPublished(Topic topic, Object result) {
|
||||
recreate();
|
||||
}
|
||||
|
||||
@@ -112,17 +137,18 @@ public class MainActivity extends Activity
|
||||
}
|
||||
|
||||
public void checkHideSection() {
|
||||
MagiskManager mm = getMagiskManager();
|
||||
Menu menu = navigationView.getMenu();
|
||||
menu.findItem(R.id.magiskhide).setVisible(
|
||||
Shell.rootAccess() && getMagiskManager().magiskVersionCode >= 1300
|
||||
&& prefs.getBoolean("magiskhide", false));
|
||||
menu.findItem(R.id.modules).setVisible(
|
||||
Shell.rootAccess() && getMagiskManager().magiskVersionCode >= 0);
|
||||
menu.findItem(R.id.downloads).setVisible(Utils.checkNetworkStatus(this) &&
|
||||
Shell.rootAccess() && getMagiskManager().magiskVersionCode >= 0);
|
||||
Shell.rootAccess() && mm.magiskVersionCode >= 1300
|
||||
&& 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(!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() && getMagiskManager().isSuClient);
|
||||
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) {
|
||||
@@ -176,7 +202,7 @@ public class MainActivity extends Activity
|
||||
displayFragment(new ReposFragment(), "downloads", true);
|
||||
break;
|
||||
case R.id.magiskhide:
|
||||
displayFragment(new MagiskHideFragment(), "magiskhide", true);
|
||||
displayFragment(new MagiskHideFragment(), Const.Key.MAGISKHIDE, true);
|
||||
break;
|
||||
case R.id.log:
|
||||
displayFragment(new LogFragment(), "log", false);
|
@@ -1,5 +1,6 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
@@ -7,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;
|
||||
@@ -14,9 +18,11 @@ import android.widget.TextView;
|
||||
import com.topjohnwu.magisk.adapters.ModulesAdapter;
|
||||
import com.topjohnwu.magisk.asyncs.LoadModules;
|
||||
import com.topjohnwu.magisk.components.Fragment;
|
||||
import com.topjohnwu.magisk.module.Module;
|
||||
import com.topjohnwu.magisk.utils.Logger;
|
||||
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;
|
||||
@@ -28,17 +34,17 @@ import butterknife.Unbinder;
|
||||
|
||||
public class ModulesFragment extends Fragment implements Topic.Subscriber {
|
||||
|
||||
private static final int FETCH_ZIP_CODE = 2;
|
||||
|
||||
private Unbinder unbinder;
|
||||
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
||||
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
||||
@BindView(R.id.empty_rv) TextView emptyRv;
|
||||
@OnClick(R.id.fab)
|
||||
public void selectFile() {
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.setType("application/zip");
|
||||
startActivityForResult(intent, FETCH_ZIP_CODE);
|
||||
Utils.runWithPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE, () -> {
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.setType("application/zip");
|
||||
startActivityForResult(intent, Const.ID.FETCH_ZIP);
|
||||
});
|
||||
}
|
||||
|
||||
private List<Module> listModules = new ArrayList<>();
|
||||
@@ -48,10 +54,11 @@ 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);
|
||||
new LoadModules(getActivity()).exec();
|
||||
new LoadModules().exec();
|
||||
});
|
||||
|
||||
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
@@ -72,8 +79,7 @@ public class ModulesFragment extends Fragment implements Topic.Subscriber {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTopicPublished(Topic topic) {
|
||||
Logger.dev("ModulesFragment: UI refresh triggered");
|
||||
public void onTopicPublished(Topic topic, Object result) {
|
||||
updateUI();
|
||||
}
|
||||
|
||||
@@ -84,10 +90,10 @@ public class ModulesFragment extends Fragment implements Topic.Subscriber {
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == FETCH_ZIP_CODE && resultCode == Activity.RESULT_OK && data != null) {
|
||||
if (requestCode == Const.ID.FETCH_ZIP && resultCode == Activity.RESULT_OK && data != null) {
|
||||
// Get the URI of the selected file
|
||||
Intent intent = new Intent(getActivity(), FlashActivity.class);
|
||||
intent.setData(data.getData()).putExtra(FlashActivity.SET_ACTION, FlashActivity.FLASH_ZIP);
|
||||
intent.setData(data.getData()).putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_ZIP);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
@@ -98,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,7 +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.Logger;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
|
||||
import butterknife.BindView;
|
||||
@@ -29,7 +31,7 @@ public class ReposFragment extends Fragment implements Topic.Subscriber {
|
||||
@BindView(R.id.empty_rv) TextView emptyRv;
|
||||
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
||||
|
||||
private ReposAdapter adapter;
|
||||
public static ReposAdapter adapter;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
@@ -43,14 +45,12 @@ public class ReposFragment extends Fragment implements Topic.Subscriber {
|
||||
View view = inflater.inflate(R.layout.fragment_repos, container, false);
|
||||
unbinder = ButterKnife.bind(this, view);
|
||||
|
||||
adapter = new ReposAdapter(getApplication().repoDB, getApplication().moduleMap);
|
||||
recyclerView.setAdapter(adapter);
|
||||
|
||||
mSwipeRefreshLayout.setRefreshing(true);
|
||||
|
||||
mSwipeRefreshLayout.setOnRefreshListener(() -> {
|
||||
recyclerView.setVisibility(View.GONE);
|
||||
new UpdateRepos(getActivity()).exec();
|
||||
recyclerView.setVisibility(View.VISIBLE);
|
||||
emptyRv.setVisibility(View.GONE);
|
||||
new UpdateRepos(true).exec();
|
||||
});
|
||||
|
||||
getActivity().setTitle(R.string.downloads);
|
||||
@@ -59,10 +59,21 @@ public class ReposFragment extends Fragment implements Topic.Subscriber {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTopicPublished(Topic topic) {
|
||||
Logger.dev("ReposFragment: UI refresh triggered");
|
||||
public void onResume() {
|
||||
adapter = new ReposAdapter(getApplication().repoDB, getApplication().moduleMap);
|
||||
recyclerView.setAdapter(adapter);
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
adapter = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTopicPublished(Topic topic, Object result) {
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
adapter.notifyDBChanged();
|
||||
recyclerView.setVisibility(adapter.getItemCount() == 0 ? View.GONE : View.VISIBLE);
|
||||
emptyRv.setVisibility(adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
@@ -90,6 +101,22 @@ public class ReposFragment extends Fragment implements Topic.Subscriber {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
MagiskManager mm = getApplication();
|
||||
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;
|
||||
@@ -8,22 +9,27 @@ import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceCategory;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.preference.PreferenceManager;
|
||||
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.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
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.database.SuDatabaseHelper;
|
||||
import com.topjohnwu.magisk.utils.Logger;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.receivers.ManagerUpdate;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
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;
|
||||
@@ -33,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);
|
||||
|
||||
@@ -62,7 +69,7 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTopicPublished(Topic topic) {
|
||||
public void onTopicPublished(Topic topic, Object result) {
|
||||
recreate();
|
||||
}
|
||||
|
||||
@@ -72,8 +79,7 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
|
||||
}
|
||||
|
||||
public static class SettingsFragment extends PreferenceFragment
|
||||
implements SharedPreferences.OnSharedPreferenceChangeListener,
|
||||
Topic.Subscriber {
|
||||
implements SharedPreferences.OnSharedPreferenceChangeListener, Topic.Subscriber {
|
||||
|
||||
private SharedPreferences prefs;
|
||||
private PreferenceScreen prefScreen;
|
||||
@@ -87,66 +93,109 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
addPreferencesFromResource(R.xml.app_settings);
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||
prefScreen = getPreferenceScreen();
|
||||
mm = Utils.getMagiskManager(getActivity());
|
||||
prefs = mm.prefs;
|
||||
prefScreen = getPreferenceScreen();
|
||||
|
||||
generalCatagory = (PreferenceCategory) findPreference("general");
|
||||
PreferenceCategory magiskCategory = (PreferenceCategory) findPreference("magisk");
|
||||
PreferenceCategory suCategory = (PreferenceCategory) findPreference("superuser");
|
||||
PreferenceCategory developer = (PreferenceCategory) findPreference("developer");
|
||||
|
||||
updateChannel = (ListPreference) findPreference("update_channel");
|
||||
suAccess = (ListPreference) findPreference("su_access");
|
||||
autoRes = (ListPreference) findPreference("su_auto_response");
|
||||
requestTimeout = (ListPreference) findPreference("su_request_timeout");
|
||||
suNotification = (ListPreference) findPreference("su_notification");
|
||||
multiuserMode = (ListPreference) findPreference("multiuser_mode");
|
||||
namespaceMode = (ListPreference) findPreference("mnt_ns");
|
||||
SwitchPreference reauth = (SwitchPreference) findPreference("su_reauth");
|
||||
Preference hideManager = findPreference("hide");
|
||||
Preference restoreManager = findPreference("restore");
|
||||
findPreference("clear").setOnPreferenceClickListener((pref) -> {
|
||||
prefs.edit().remove(Const.Key.ETAG_KEY).apply();
|
||||
mm.repoDB.clearRepo();
|
||||
MagiskManager.toast(R.string.repo_cache_cleared, Toast.LENGTH_SHORT);
|
||||
return true;
|
||||
});
|
||||
|
||||
updateChannel = (ListPreference) findPreference(Const.Key.UPDATE_CHANNEL);
|
||||
suAccess = (ListPreference) findPreference(Const.Key.ROOT_ACCESS);
|
||||
autoRes = (ListPreference) findPreference(Const.Key.SU_AUTO_RESPONSE);
|
||||
requestTimeout = (ListPreference) findPreference(Const.Key.SU_REQUEST_TIMEOUT);
|
||||
suNotification = (ListPreference) findPreference(Const.Key.SU_NOTIFICATION);
|
||||
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) {
|
||||
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)
|
||||
.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();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
setSummary();
|
||||
|
||||
// Disable dangerous settings in user mode if selected owner manage
|
||||
if (getActivity().getApplicationInfo().uid > 99999) {
|
||||
prefScreen.removePreference(magiskCategory);
|
||||
prefScreen.removePreference(suCategory);
|
||||
// Disable dangerous settings in secondary user
|
||||
if (Const.USER_ID > 0) {
|
||||
suCategory.removePreference(multiuserMode);
|
||||
}
|
||||
|
||||
// Disable re-authentication option on Android O, it will not work
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
reauth.setEnabled(false);
|
||||
reauth.setSummary(R.string.android_o_not_support);
|
||||
}
|
||||
|
||||
// Disable fingerprint option if not possible
|
||||
if (!FingerprintHelper.canUseFingerprint()) {
|
||||
fingerprint.setEnabled(false);
|
||||
fingerprint.setSummary(R.string.disable_fingerprint);
|
||||
}
|
||||
|
||||
if (mm.magiskVersionCode >= 1440) {
|
||||
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);
|
||||
}
|
||||
|
||||
// Remove re-authentication option on Android O, it will not work
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
suCategory.removePreference(reauth);
|
||||
}
|
||||
|
||||
findPreference("clear").setOnPreferenceClickListener((pref) -> {
|
||||
Utils.clearRepoCache(getActivity());
|
||||
return true;
|
||||
});
|
||||
|
||||
hideManager.setOnPreferenceClickListener((pref) -> {
|
||||
Utils.runWithPermission(getActivity(),
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
() -> new HideManager(getActivity()).exec());
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!BuildConfig.DEBUG) {
|
||||
prefScreen.removePreference(developer);
|
||||
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.isSuClient) {
|
||||
prefScreen.removePreference(suCategory);
|
||||
}
|
||||
if (mm.magiskVersionCode < 1300) {
|
||||
prefScreen.removePreference(magiskCategory);
|
||||
}
|
||||
} else if (mm.magiskVersionCode < 1300) {
|
||||
prefScreen.removePreference(magiskCategory);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,7 +216,7 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
|
||||
lp.setEntries(entries);
|
||||
lp.setEntryValues(entryValues);
|
||||
lp.setTitle(R.string.language);
|
||||
lp.setKey("locale");
|
||||
lp.setKey(Const.Key.LOCALE);
|
||||
lp.setSummary(MagiskManager.locale.getDisplayName(MagiskManager.locale));
|
||||
if (isNew) {
|
||||
generalCatagory.addPreference(lp);
|
||||
@@ -190,71 +239,60 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
|
||||
Logger.dev("Settings: Prefs change " + key);
|
||||
boolean enabled;
|
||||
|
||||
switch (key) {
|
||||
case "dark_theme":
|
||||
enabled = prefs.getBoolean("dark_theme", false);
|
||||
if (mm.isDarkTheme != enabled) {
|
||||
mm.reloadActivity.publish(false);
|
||||
}
|
||||
case Const.Key.DARK_THEME:
|
||||
mm.isDarkTheme = prefs.getBoolean(key, false);
|
||||
mm.reloadActivity.publish(false);
|
||||
break;
|
||||
case "disable":
|
||||
enabled = prefs.getBoolean("disable", false);
|
||||
if (enabled) {
|
||||
Utils.createFile(getShell(), MagiskManager.MAGISK_DISABLE_FILE);
|
||||
case Const.Key.COREONLY:
|
||||
if (prefs.getBoolean(key, false)) {
|
||||
try {
|
||||
Const.MAGISK_DISABLE_FILE.createNewFile();
|
||||
} catch (IOException ignored) {}
|
||||
} else {
|
||||
Utils.removeItem(getShell(), MagiskManager.MAGISK_DISABLE_FILE);
|
||||
Const.MAGISK_DISABLE_FILE.delete();
|
||||
}
|
||||
Toast.makeText(getActivity(), R.string.settings_reboot_toast, Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
case "magiskhide":
|
||||
enabled = prefs.getBoolean("magiskhide", false);
|
||||
if (enabled) {
|
||||
Utils.enableMagiskHide(getShell());
|
||||
case Const.Key.MAGISKHIDE:
|
||||
if (prefs.getBoolean(key, false)) {
|
||||
Shell.Async.su("magiskhide --enable");
|
||||
} else {
|
||||
Utils.disableMagiskHide(getShell());
|
||||
Shell.Async.su("magiskhide --disable");
|
||||
}
|
||||
break;
|
||||
case "hosts":
|
||||
enabled = prefs.getBoolean("hosts", false);
|
||||
if (enabled) {
|
||||
getShell().su_raw(
|
||||
"cp -af /system/etc/hosts /magisk/.core/hosts",
|
||||
"mount -o bind /magisk/.core/hosts /system/etc/hosts");
|
||||
case Const.Key.HOSTS:
|
||||
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 {
|
||||
getShell().su_raw(
|
||||
Shell.Async.su(
|
||||
"umount -l /system/etc/hosts",
|
||||
"rm -f /magisk/.core/hosts");
|
||||
"rm -f " + Const.MAGISK_HOST_FILE());
|
||||
}
|
||||
break;
|
||||
case "su_access":
|
||||
mm.suDB.setSettings(SuDatabaseHelper.ROOT_ACCESS, Utils.getPrefsInt(prefs, "su_access"));
|
||||
case Const.Key.ROOT_ACCESS:
|
||||
case Const.Key.SU_MULTIUSER_MODE:
|
||||
case Const.Key.SU_MNT_NS:
|
||||
mm.suDB.setSettings(key, Utils.getPrefsInt(prefs, key));
|
||||
break;
|
||||
case "multiuser_mode":
|
||||
mm.suDB.setSettings(SuDatabaseHelper.MULTIUSER_MODE, Utils.getPrefsInt(prefs, "multiuser_mode"));
|
||||
break;
|
||||
case "mnt_ns":
|
||||
mm.suDB.setSettings(SuDatabaseHelper.MNT_NS, Utils.getPrefsInt(prefs, "mnt_ns"));
|
||||
break;
|
||||
case "locale":
|
||||
case Const.Key.LOCALE:
|
||||
mm.setLocale();
|
||||
mm.reloadActivity.publish(false);
|
||||
break;
|
||||
case "update_channel":
|
||||
mm.updateChannel = Utils.getPrefsInt(prefs, "update_channel");
|
||||
new CheckUpdates(mm, true).exec();
|
||||
case Const.Key.UPDATE_CHANNEL:
|
||||
new CheckUpdates().exec();
|
||||
break;
|
||||
case Const.Key.CHECK_UPDATES:
|
||||
mm.setupUpdateCheck();
|
||||
break;
|
||||
}
|
||||
mm.loadConfig();
|
||||
setSummary();
|
||||
}
|
||||
|
||||
private Shell getShell() {
|
||||
return Shell.getShell(getActivity());
|
||||
}
|
||||
|
||||
private void setSummary() {
|
||||
updateChannel.setSummary(getResources()
|
||||
.getStringArray(R.array.update_channel)[mm.updateChannel]);
|
||||
@@ -265,7 +303,7 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
|
||||
suNotification.setSummary(getResources()
|
||||
.getStringArray(R.array.su_notification)[mm.suNotificationType]);
|
||||
requestTimeout.setSummary(
|
||||
getString(R.string.request_timeout_summary, prefs.getString("su_request_timeout", "10")));
|
||||
getString(R.string.request_timeout_summary, prefs.getString(Const.Key.SU_REQUEST_TIMEOUT, "10")));
|
||||
multiuserMode.setSummary(getResources()
|
||||
.getStringArray(R.array.multiuser_summary)[mm.multiuserMode]);
|
||||
namespaceMode.setSummary(getResources()
|
||||
@@ -273,8 +311,8 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTopicPublished(Topic topic) {
|
||||
setLocalePreference((ListPreference) findPreference("locale"));
|
||||
public void onTopicPublished(Topic topic, Object result) {
|
||||
setLocalePreference((ListPreference) findPreference(Const.Key.LOCALE));
|
||||
}
|
||||
|
||||
@Override
|
87
src/main/java/com/topjohnwu/magisk/SplashActivity.java
Normal file
@@ -0,0 +1,87 @@
|
||||
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.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
public class SplashActivity extends Activity {
|
||||
|
||||
@Override
|
||||
public int getDarkTheme() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
MagiskManager mm = getMagiskManager();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -11,7 +11,7 @@ import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.adapters.PolicyAdapter;
|
||||
import com.topjohnwu.magisk.components.Fragment;
|
||||
import com.topjohnwu.magisk.superuser.Policy;
|
||||
import com.topjohnwu.magisk.container.Policy;
|
||||
|
||||
import java.util.List;
|
||||
|
@@ -5,6 +5,7 @@ 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;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -16,12 +17,12 @@ 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.Shell;
|
||||
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.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -31,23 +32,11 @@ import butterknife.ButterKnife;
|
||||
|
||||
public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.ViewHolder> {
|
||||
|
||||
public static final List<String> BLACKLIST = Arrays.asList(
|
||||
"android",
|
||||
"com.topjohnwu.magisk",
|
||||
"com.google.android.gms"
|
||||
);
|
||||
|
||||
private static final List<String> SNLIST = Arrays.asList(
|
||||
"com.google.android.apps.walletnfcrel",
|
||||
"com.nianticlabs.pokemongo"
|
||||
);
|
||||
|
||||
private List<ApplicationInfo> mOriginalList, mList;
|
||||
private List<String> mHideList;
|
||||
private PackageManager pm;
|
||||
private ApplicationFilter filter;
|
||||
private Topic magiskHideDone;
|
||||
private Shell shell;
|
||||
|
||||
public ApplicationAdapter(Context context) {
|
||||
mOriginalList = mList = Collections.emptyList();
|
||||
@@ -55,10 +44,13 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
||||
filter = new ApplicationFilter();
|
||||
pm = context.getPackageManager();
|
||||
magiskHideDone = Utils.getMagiskManager(context).magiskHideDone;
|
||||
shell = Shell.getShell(context);
|
||||
new LoadApps().exec();
|
||||
}
|
||||
|
||||
private boolean lowercaseContains(CharSequence string, CharSequence nonNullLowercaseSearch) {
|
||||
return !TextUtils.isEmpty(string) && string.toString().toLowerCase().contains(nonNullLowercaseSearch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View mView = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_app, parent, false);
|
||||
@@ -77,7 +69,7 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
||||
holder.itemView.setOnClickListener(null);
|
||||
holder.checkBox.setOnCheckedChangeListener(null);
|
||||
|
||||
if (SNLIST.contains(info.packageName)) {
|
||||
if (Const.SN_DEFAULTLIST.contains(info.packageName)) {
|
||||
holder.checkBox.setChecked(true);
|
||||
holder.checkBox.setEnabled(false);
|
||||
holder.itemView.setOnClickListener(v ->
|
||||
@@ -89,10 +81,10 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
||||
holder.checkBox.setChecked(mHideList.contains(info.packageName));
|
||||
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> {
|
||||
if (isChecked) {
|
||||
Utils.addMagiskHide(shell, info.packageName);
|
||||
Shell.Async.su("magiskhide --add " + info.packageName);
|
||||
mHideList.add(info.packageName);
|
||||
} else {
|
||||
Utils.rmMagiskHide(shell, info.packageName);
|
||||
Shell.Async.su("magiskhide --rm " + info.packageName);
|
||||
mHideList.remove(info.packageName);
|
||||
}
|
||||
});
|
||||
@@ -116,7 +108,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) {
|
||||
@@ -135,8 +127,8 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
||||
mList = new ArrayList<>();
|
||||
String filter = constraint.toString().toLowerCase();
|
||||
for (ApplicationInfo info : mOriginalList) {
|
||||
if (Utils.lowercaseContains(info.loadLabel(pm), filter)
|
||||
|| Utils.lowercaseContains(info.packageName, filter)) {
|
||||
if (lowercaseContains(info.loadLabel(pm), filter)
|
||||
|| lowercaseContains(info.packageName, filter)) {
|
||||
mList.add(info);
|
||||
}
|
||||
}
|
||||
@@ -157,13 +149,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 (ApplicationAdapter.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 = Utils.listMagiskHide(shell);
|
||||
mHideList = Shell.Sync.su("magiskhide --ls");
|
||||
return null;
|
||||
}
|
||||
|
@@ -13,8 +13,8 @@ import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.module.Module;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.container.Module;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -38,7 +38,6 @@ public class ModulesAdapter extends RecyclerView.Adapter<ModulesAdapter.ViewHold
|
||||
@Override
|
||||
public void onBindViewHolder(final ViewHolder holder, int position) {
|
||||
Context context = holder.itemView.getContext();
|
||||
Shell rootShell = Shell.getShell(context);
|
||||
final Module module = mList.get(position);
|
||||
|
||||
String version = module.getVersion();
|
||||
@@ -56,10 +55,10 @@ public class ModulesAdapter extends RecyclerView.Adapter<ModulesAdapter.ViewHold
|
||||
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> {
|
||||
int snack;
|
||||
if (isChecked) {
|
||||
module.removeDisableFile(rootShell);
|
||||
module.removeDisableFile();
|
||||
snack = R.string.disable_file_removed;
|
||||
} else {
|
||||
module.createDisableFile(rootShell);
|
||||
module.createDisableFile();
|
||||
snack = R.string.disable_file_created;
|
||||
}
|
||||
SnackbarMaker.make(holder.itemView, snack, Snackbar.LENGTH_SHORT).show();
|
||||
@@ -69,10 +68,10 @@ public class ModulesAdapter extends RecyclerView.Adapter<ModulesAdapter.ViewHold
|
||||
boolean removed = module.willBeRemoved();
|
||||
int snack;
|
||||
if (removed) {
|
||||
module.deleteRemoveFile(rootShell);
|
||||
module.deleteRemoveFile();
|
||||
snack = R.string.remove_file_deleted;
|
||||
} else {
|
||||
module.createRemoveFile(rootShell);
|
||||
module.createRemoveFile();
|
||||
snack = R.string.remove_file_created;
|
||||
}
|
||||
SnackbarMaker.make(holder.itemView, snack, Snackbar.LENGTH_SHORT).show();
|
@@ -15,8 +15,8 @@ import com.topjohnwu.magisk.R;
|
||||
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.superuser.Policy;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
@@ -17,9 +17,9 @@ import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.asyncs.MarkDownWindow;
|
||||
import com.topjohnwu.magisk.asyncs.ProcessRepoZip;
|
||||
import com.topjohnwu.magisk.components.AlertDialogBuilder;
|
||||
import com.topjohnwu.magisk.container.Module;
|
||||
import com.topjohnwu.magisk.container.Repo;
|
||||
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
|
||||
import com.topjohnwu.magisk.module.Module;
|
||||
import com.topjohnwu.magisk.module.Repo;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -44,6 +44,7 @@ public class ReposAdapter extends SectionedAdapter<ReposAdapter.SectionHolder, R
|
||||
repoDB = db;
|
||||
moduleMap = map;
|
||||
repoPairs = new ArrayList<>();
|
||||
notifyDBChanged();
|
||||
}
|
||||
|
||||
|
||||
@@ -94,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());
|
||||
@@ -179,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);
|
@@ -12,8 +12,8 @@ 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.superuser.SuLogEntry;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
@@ -0,0 +1,97 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
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;
|
||||
import java.net.HttpURLConnection;
|
||||
|
||||
import dalvik.system.DexClassLoader;
|
||||
|
||||
public class CheckSafetyNet extends ParallelTask<Void, Void, Exception> {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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 loadClasses() throws ClassNotFoundException {
|
||||
loader = new DexClassLoader(dexPath.toString(), dexPath.getParent(),
|
||||
null, ClassLoader.getSystemClassLoader());
|
||||
helperClazz = loader.loadClass(Const.SNET_PKG + ".SafetyNetHelper");
|
||||
callbackClazz = loader.loadClass(Const.SNET_PKG + ".SafetyNetCallback");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Exception doInBackground(Void... voids) {
|
||||
int snet_ver = -1;
|
||||
|
||||
try {
|
||||
if (!dexPath.exists())
|
||||
dlSnet();
|
||||
loadClasses();
|
||||
|
||||
try {
|
||||
snet_ver = (int) helperClazz.getMethod("getVersion").invoke(null);
|
||||
} catch (NoSuchMethodException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (snet_ver != Const.SNET_VER) {
|
||||
dlSnet();
|
||||
loadClasses();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return e;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Exception err) {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
try {
|
||||
if (err != null) throw err;
|
||||
Object helper = helperClazz.getConstructors()[0].newInstance(
|
||||
getActivity(), dexPath.getPath(), Proxy.newProxyInstance(
|
||||
loader, new Class[] { callbackClazz }, (proxy, method, args) -> {
|
||||
mm.safetyNetDone.publish(false, args[0]);
|
||||
return null;
|
||||
}));
|
||||
helperClazz.getMethod("attest").invoke(helper);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
mm.safetyNetDone.publish(false, -1);
|
||||
}
|
||||
super.onPostExecute(err);
|
||||
}
|
||||
}
|
@@ -1,10 +1,9 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.topjohnwu.magisk.BuildConfig;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.ShowUI;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
|
||||
import org.json.JSONException;
|
||||
@@ -12,37 +11,30 @@ import org.json.JSONObject;
|
||||
|
||||
public class CheckUpdates extends ParallelTask<Void, Void, Void> {
|
||||
|
||||
public static final int STABLE_CHANNEL = 0;
|
||||
public static final int BETA_CHANNEL = 1;
|
||||
private boolean showNotification;
|
||||
|
||||
private static final String STABLE_URL = "https://raw.githubusercontent.com/topjohnwu/MagiskManager/update/stable.json";
|
||||
private static final String BETA_URL = "https://raw.githubusercontent.com/topjohnwu/MagiskManager/update/beta.json";
|
||||
|
||||
private boolean showNotification = false;
|
||||
|
||||
public CheckUpdates(Context context) {
|
||||
super(context);
|
||||
public CheckUpdates() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
public CheckUpdates(Context context, boolean b) {
|
||||
super(context);
|
||||
public CheckUpdates(boolean b) {
|
||||
showNotification = b;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
MagiskManager mm = getMagiskManager();
|
||||
if (mm == null) return null;
|
||||
String jsonStr;
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
String jsonStr = "";
|
||||
switch (mm.updateChannel) {
|
||||
case STABLE_CHANNEL:
|
||||
jsonStr = WebService.getString(STABLE_URL);
|
||||
case Const.Value.STABLE_CHANNEL:
|
||||
jsonStr = WebService.getString(Const.Url.STABLE_URL);
|
||||
break;
|
||||
case BETA_CHANNEL:
|
||||
jsonStr = WebService.getString(BETA_URL);
|
||||
case Const.Value.BETA_CHANNEL:
|
||||
jsonStr = WebService.getString(Const.Url.BETA_URL);
|
||||
break;
|
||||
case Const.Value.CUSTOM_CHANNEL:
|
||||
jsonStr = WebService.getString(mm.prefs.getString(Const.Key.CUSTOM_CHANNEL, ""));
|
||||
break;
|
||||
default:
|
||||
jsonStr = null;
|
||||
}
|
||||
try {
|
||||
JSONObject json = new JSONObject(jsonStr);
|
||||
@@ -61,13 +53,12 @@ public class CheckUpdates extends ParallelTask<Void, Void, Void> {
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void v) {
|
||||
MagiskManager mm = getMagiskManager();
|
||||
if (mm == null) return;
|
||||
if (showNotification && mm.updateNotification) {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
if (showNotification) {
|
||||
if (BuildConfig.VERSION_CODE < mm.remoteManagerVersionCode) {
|
||||
Utils.showManagerUpdateNotification(mm);
|
||||
ShowUI.managerUpdateNotification();
|
||||
} else if (mm.magiskVersionCode < mm.remoteMagiskVersionCode) {
|
||||
Utils.showMagiskUpdateNotification(mm);
|
||||
ShowUI.magiskUpdateNotification();
|
||||
}
|
||||
}
|
||||
mm.updateCheckDone.publish();
|
109
src/main/java/com/topjohnwu/magisk/asyncs/FlashZip.java
Normal file
@@ -0,0 +1,109 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
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.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;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
|
||||
public class FlashZip extends ParallelTask<Void, Void, Integer> {
|
||||
|
||||
private Uri mUri;
|
||||
private File mCachedFile;
|
||||
private List<String> console, logs;
|
||||
|
||||
public FlashZip(Activity context, Uri uri, List<String> console, List<String> logs) {
|
||||
super(context);
|
||||
mUri = uri;
|
||||
this.console = console;
|
||||
this.logs = logs;
|
||||
mCachedFile = new File(context.getCacheDir(), "install.zip");
|
||||
}
|
||||
|
||||
private boolean unzipAndCheck() throws Exception {
|
||||
ZipUtils.unzip(mCachedFile, mCachedFile.getParentFile(), "META-INF/com/google/android", true);
|
||||
String s = Utils.cmd("head -n 1 " + new File(mCachedFile.getParentFile(), "updater-script"));
|
||||
return s != null && s.contains("#MAGISK");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer doInBackground(Void... voids) {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
try {
|
||||
console.add("- Copying zip to temp directory");
|
||||
|
||||
mCachedFile.delete();
|
||||
try (
|
||||
InputStream in = mm.getContentResolver().openInputStream(mUri);
|
||||
OutputStream out = new BufferedOutputStream(new FileOutputStream(mCachedFile))
|
||||
) {
|
||||
if (in == null) throw new FileNotFoundException();
|
||||
InputStream buf= new BufferedInputStream(in);
|
||||
ShellUtils.pump(buf, out);
|
||||
} catch (FileNotFoundException e) {
|
||||
console.add("! Invalid Uri");
|
||||
throw e;
|
||||
} catch (IOException e) {
|
||||
console.add("! Cannot copy to cache");
|
||||
throw e;
|
||||
}
|
||||
if (!unzipAndCheck()) return 0;
|
||||
console.add("- Installing " + Utils.getNameFromUri(mm, mUri));
|
||||
Shell.Sync.su(console, logs,
|
||||
"cd " + mCachedFile.getParent(),
|
||||
"BOOTMODE=true sh update-binary dummy 1 " + mCachedFile + " || echo 'Failed!'"
|
||||
);
|
||||
|
||||
if (TextUtils.equals(console.get(console.size() - 1), "Failed!"))
|
||||
return -1;
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return -1;
|
||||
}
|
||||
console.add("- All done!");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// -1 = error, manual install; 0 = invalid zip; 1 = success
|
||||
@Override
|
||||
protected void onPostExecute(Integer result) {
|
||||
FlashActivity activity = (FlashActivity) getActivity();
|
||||
Shell.Async.su(
|
||||
"rm -rf " + mCachedFile.getParent(),
|
||||
"rm -rf " + Const.TMP_FOLDER_PATH
|
||||
);
|
||||
switch (result) {
|
||||
case -1:
|
||||
console.add("! Installation failed");
|
||||
Utils.showUriSnack(getActivity(), mUri);
|
||||
break;
|
||||
case 0:
|
||||
console.add("! This zip is not a Magisk Module!");
|
||||
break;
|
||||
case 1:
|
||||
// Success
|
||||
new LoadModules().exec();
|
||||
break;
|
||||
}
|
||||
activity.reboot.setVisibility(result > 0 ? View.VISIBLE : View.GONE);
|
||||
activity.buttonPanel.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
153
src/main/java/com/topjohnwu/magisk/asyncs/HideManager.java
Normal file
@@ -0,0 +1,153 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
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.FileInputStream;
|
||||
import java.security.SecureRandom;
|
||||
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);
|
||||
length -= prefix.length();
|
||||
SecureRandom random = new SecureRandom();
|
||||
String base = "abcdefghijklmnopqrstuvwxyz";
|
||||
String alpha = base + base.toUpperCase();
|
||||
String full = alpha + "0123456789..........";
|
||||
char next, prev = '\0';
|
||||
for (int i = 0; i < length; ++i) {
|
||||
if (prev == '.' || i == length - 1 || i == 0) {
|
||||
next = alpha.charAt(random.nextInt(alpha.length()));
|
||||
} else {
|
||||
next = full.charAt(random.nextInt(full.length()));
|
||||
}
|
||||
builder.append(next);
|
||||
prev = next;
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private int findOffset(byte buf[], byte pattern[]) {
|
||||
int offset = -1;
|
||||
for (int i = 0; i < buf.length - pattern.length; ++i) {
|
||||
boolean match = true;
|
||||
for (int j = 0; j < pattern.length; ++j) {
|
||||
if (buf[i + j] != pattern[j]) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
offset = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
/* It seems that AAPT sometimes generate another type of string format */
|
||||
private boolean fallbackPatch(byte xml[], String from, String to) {
|
||||
|
||||
byte[] target = new byte[from.length() * 2 + 2];
|
||||
for (int i = 0; i < from.length(); ++i) {
|
||||
target[i * 2] = (byte) from.charAt(i);
|
||||
}
|
||||
int offset = findOffset(xml, target);
|
||||
if (offset < 0)
|
||||
return false;
|
||||
byte[] dest = new byte[target.length - 2];
|
||||
for (int i = 0; i < to.length(); ++i) {
|
||||
dest[i * 2] = (byte) to.charAt(i);
|
||||
}
|
||||
System.arraycopy(dest, 0, xml, offset, dest.length);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean findAndPatch(byte xml[], String from, String to) {
|
||||
byte target[] = (from + '\0').getBytes();
|
||||
int offset = findOffset(xml, target);
|
||||
if (offset < 0)
|
||||
return fallbackPatch(xml, from, to);
|
||||
System.arraycopy(to.getBytes(), 0, xml, offset, to.length());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
dialog = ProgressDialog.show(getActivity(),
|
||||
getActivity().getString(R.string.hide_manager_toast),
|
||||
getActivity().getString(R.string.hide_manager_toast2));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... voids) {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
|
||||
// Generate a new unhide app with random package name
|
||||
SuFile repack = new SuFile("/data/local/tmp/repack.apk", true);
|
||||
String pkg = genPackageName("com.", Const.ORIG_PKG_NAME.length());
|
||||
|
||||
try {
|
||||
// Read whole APK into memory
|
||||
JarMap apk = new JarMap(new FileInputStream(mm.getPackageCodePath()));
|
||||
JarEntry je = new JarEntry(Const.ANDROID_MANIFEST);
|
||||
byte xml[] = apk.getRawData(je);
|
||||
|
||||
if (!findAndPatch(xml, Const.ORIG_PKG_NAME, pkg))
|
||||
return false;
|
||||
if (!findAndPatch(xml, Const.ORIG_PKG_NAME + ".provider", pkg + ".provider"))
|
||||
return false;
|
||||
|
||||
// Write in changes
|
||||
apk.getOutputStream(je).write(xml);
|
||||
|
||||
// Sign the APK
|
||||
ZipUtils.signZip(apk, new SuFileOutputStream(repack));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Install the application
|
||||
if (!ShellUtils.fastCmdResult(Shell.getShell(), "pm install " + repack))
|
||||
return false;
|
||||
|
||||
repack.delete();
|
||||
|
||||
mm.suDB.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);
|
||||
}
|
||||
super.onPostExecute(b);
|
||||
}
|
||||
}
|
259
src/main/java/com/topjohnwu/magisk/asyncs/InstallMagisk.java
Normal file
@@ -0,0 +1,259 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
|
||||
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.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;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
|
||||
|
||||
private static final int PATCH_MODE = 0;
|
||||
private static final int DIRECT_MODE = 1;
|
||||
|
||||
private Uri mBootImg, mZip;
|
||||
private List<String> console, logs;
|
||||
private String mBootLocation;
|
||||
private int mode;
|
||||
private File install;
|
||||
|
||||
private InstallMagisk(Activity context, List<String> console, List<String> logs, Uri zip) {
|
||||
super(context);
|
||||
this.console = console;
|
||||
this.logs = logs;
|
||||
mZip = zip;
|
||||
}
|
||||
|
||||
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, String boot) {
|
||||
this(context, console, logs, zip);
|
||||
mBootLocation = boot;
|
||||
mode = DIRECT_MODE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... voids) {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
|
||||
install = new File(
|
||||
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ?
|
||||
mm.createDeviceProtectedStorageContext() : mm)
|
||||
.getFilesDir().getParent()
|
||||
, "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);
|
||||
|
||||
try {
|
||||
// Unzip files
|
||||
console.add("- Extracting files");
|
||||
try (InputStream in = mm.getContentResolver().openInputStream(mZip)) {
|
||||
if (in == null) throw new FileNotFoundException();
|
||||
BufferedInputStream buf = new BufferedInputStream(in);
|
||||
buf.mark(Integer.MAX_VALUE);
|
||||
ZipUtils.unzip(buf, install, arch + "/", true);
|
||||
buf.reset();
|
||||
ZipUtils.unzip(buf, install, "common/", true);
|
||||
buf.reset();
|
||||
ZipUtils.unzip(buf, install, "chromeos/", false);
|
||||
buf.reset();
|
||||
ZipUtils.unzip(buf, install, "META-INF/com/google/android/update-binary", true);
|
||||
buf.close();
|
||||
} catch (FileNotFoundException e) {
|
||||
console.add("! Invalid Uri");
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
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:
|
||||
// Copy boot image to local
|
||||
try (InputStream in = mm.getContentResolver().openInputStream(mBootImg);
|
||||
OutputStream out = new FileOutputStream(boot)
|
||||
) {
|
||||
InputStream source;
|
||||
if (in == null) throw new FileNotFoundException();
|
||||
|
||||
if (Utils.getNameFromUri(mm, mBootImg).endsWith(".tar")) {
|
||||
// Extract boot.img from tar
|
||||
TarInputStream tar = new TarInputStream(new BufferedInputStream(in));
|
||||
org.kamranzafar.jtar.TarEntry entry;
|
||||
while ((entry = tar.getNextEntry()) != null) {
|
||||
if (entry.getName().equals("boot.img"))
|
||||
break;
|
||||
}
|
||||
source = tar;
|
||||
} else {
|
||||
// Direct copy raw image
|
||||
source = new BufferedInputStream(in);
|
||||
}
|
||||
ShellUtils.pump(source, out);
|
||||
} catch (FileNotFoundException e) {
|
||||
console.add("! Invalid Uri");
|
||||
throw e;
|
||||
} catch (IOException e) {
|
||||
console.add("! Copy failed");
|
||||
throw e;
|
||||
}
|
||||
break;
|
||||
case DIRECT_MODE:
|
||||
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.Sync.su("cat " + mBootLocation + " > " + boot);
|
||||
} else {
|
||||
console.add("! Dump boot image failed");
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean isSigned;
|
||||
try (InputStream in = new FileInputStream(boot)) {
|
||||
isSigned = SignBoot.verifySignature(in, null);
|
||||
if (isSigned) {
|
||||
console.add("- Boot image is signed with AVB 1.0");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
console.add("! Unable to check signature");
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Patch boot image
|
||||
Shell.Sync.sh(console, logs,
|
||||
"cd " + install,
|
||||
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.Sync.sh("mv -f new-boot.img ../",
|
||||
"mv bin/busybox busybox",
|
||||
"rm -rf bin *.img update-binary",
|
||||
"cd /");
|
||||
|
||||
SuFile patched_boot = new SuFile(install.getParent(), "new-boot.img");
|
||||
|
||||
if (isSigned) {
|
||||
console.add("- Signing boot image with test keys");
|
||||
File signed = new File(install.getParent(), "signed.img");
|
||||
try (InputStream in = new SuFileInputStream(patched_boot);
|
||||
OutputStream out = new BufferedOutputStream(new FileOutputStream(signed))
|
||||
) {
|
||||
SignBoot.doSignature("/boot", in, out, null, null);
|
||||
}
|
||||
Shell.Sync.sh("mv -f " + signed + " " + patched_boot);
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
case PATCH_MODE:
|
||||
File dest = new File(Const.EXTERNAL_PATH, "patched_boot" + mm.bootFormat);
|
||||
dest.getParentFile().mkdirs();
|
||||
OutputStream out;
|
||||
switch (mm.bootFormat) {
|
||||
case ".img.tar":
|
||||
out = new TarOutputStream(new BufferedOutputStream(new FileOutputStream(dest)));
|
||||
((TarOutputStream) out).putNextEntry(new TarEntry(patched_boot, "boot.img"));
|
||||
break;
|
||||
default:
|
||||
case ".img":
|
||||
out = new BufferedOutputStream(new FileOutputStream(dest));
|
||||
break;
|
||||
}
|
||||
try (InputStream in = new SuFileInputStream(patched_boot)) {
|
||||
ShellUtils.pump(in, out);
|
||||
out.close();
|
||||
}
|
||||
console.add("");
|
||||
console.add("*********************************");
|
||||
console.add(" Patched Boot Image is placed in ");
|
||||
console.add(" " + dest + " ");
|
||||
console.add("*********************************");
|
||||
break;
|
||||
case DIRECT_MODE:
|
||||
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;
|
||||
}
|
||||
|
||||
patched_boot.delete();
|
||||
|
||||
console.add("- All done!");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
activity.buttonPanel.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
36
src/main/java/com/topjohnwu/magisk/asyncs/LoadModules.java
Normal file
@@ -0,0 +1,36 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
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.superuser.Shell;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
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.Sync.su(command);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
mm.moduleMap = new ValueSortedMap<>();
|
||||
|
||||
for (String path : getModList()) {
|
||||
Module module = new Module(path);
|
||||
mm.moduleMap.put(module.getId(), module);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void v) {
|
||||
MagiskManager.get().moduleLoadDone.publish();
|
||||
super.onPostExecute(v);
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -1,30 +1,19 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
public abstract class ParallelTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
|
||||
|
||||
private WeakReference<Activity> weakActivity;
|
||||
private WeakReference<MagiskManager> weakMagiskManager;
|
||||
|
||||
private Runnable callback = null;
|
||||
|
||||
public ParallelTask() {}
|
||||
|
||||
public ParallelTask(Context context) {
|
||||
weakMagiskManager = new WeakReference<>(Utils.getMagiskManager(context));
|
||||
}
|
||||
|
||||
public ParallelTask(Activity context) {
|
||||
this((Context) context);
|
||||
weakActivity = new WeakReference<>(context);
|
||||
}
|
||||
|
||||
@@ -32,15 +21,6 @@ public abstract class ParallelTask<Params, Progress, Result> extends AsyncTask<P
|
||||
return weakActivity.get();
|
||||
}
|
||||
|
||||
protected MagiskManager getMagiskManager() {
|
||||
return weakMagiskManager.get();
|
||||
}
|
||||
|
||||
protected Shell getShell() {
|
||||
MagiskManager magiskManager = getMagiskManager();
|
||||
return magiskManager == null ? null : Shell.getShell(magiskManager);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public ParallelTask<Params, Progress, Result> exec(Params... params) {
|
||||
executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
|
199
src/main/java/com/topjohnwu/magisk/asyncs/ProcessRepoZip.java
Normal file
@@ -0,0 +1,199 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
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;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarInputStream;
|
||||
import java.util.jar.JarOutputStream;
|
||||
|
||||
public class ProcessRepoZip extends ParallelTask<Void, Object, Boolean> {
|
||||
|
||||
private ProgressDialog progressDialog;
|
||||
private boolean mInstall;
|
||||
private String mLink;
|
||||
private File mFile;
|
||||
private int progress = 0, total = -1;
|
||||
private Handler mHandler;
|
||||
|
||||
public ProcessRepoZip(Activity context, String link, String filename, boolean install) {
|
||||
super(context);
|
||||
mLink = link;
|
||||
mFile = new File(Const.EXTERNAL_PATH, filename);
|
||||
mInstall = install;
|
||||
mHandler = new Handler();
|
||||
}
|
||||
|
||||
private void removeTopFolder(File input, File output) throws IOException {
|
||||
JarEntry entry;
|
||||
try (
|
||||
JarInputStream in = new JarInputStream(new BufferedInputStream(new FileInputStream(input)));
|
||||
JarOutputStream out = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(output)))
|
||||
) {
|
||||
String path;
|
||||
while ((entry = in.getNextJarEntry()) != null) {
|
||||
// Remove the top directory from the path
|
||||
path = entry.getName().substring(entry.getName().indexOf("/") + 1);
|
||||
// If it's the top folder, ignore it
|
||||
if (path.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
// Don't include placeholder
|
||||
if (path.equals("system/placeholder")) {
|
||||
continue;
|
||||
}
|
||||
out.putNextEntry(new JarEntry(path));
|
||||
ShellUtils.pump(in, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
Activity activity = getActivity();
|
||||
mFile.getParentFile().mkdirs();
|
||||
progressDialog = ProgressDialog.show(activity, activity.getString(R.string.zip_download_title), activity.getString(R.string.zip_download_msg, 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... params) {
|
||||
Activity activity = getActivity();
|
||||
if (activity == null) return null;
|
||||
try {
|
||||
// Request zip from Internet
|
||||
HttpURLConnection conn;
|
||||
do {
|
||||
conn = WebService.request(mLink, null);
|
||||
if (conn == null) return null;
|
||||
total = conn.getContentLength();
|
||||
if (total < 0)
|
||||
conn.disconnect();
|
||||
else
|
||||
break;
|
||||
} while (true);
|
||||
|
||||
// Temp files
|
||||
File temp1 = new File(activity.getCacheDir(), "1.zip");
|
||||
File temp2 = new File(temp1.getParentFile(), "2.zip");
|
||||
temp1.getParentFile().mkdir();
|
||||
|
||||
// First download the zip, Web -> temp1
|
||||
try (
|
||||
InputStream in = new BufferedInputStream(new ProgressInputStream(conn.getInputStream()));
|
||||
OutputStream out = new BufferedOutputStream(new FileOutputStream(temp1))
|
||||
) {
|
||||
ShellUtils.pump(in, out);
|
||||
in.close();
|
||||
}
|
||||
conn.disconnect();
|
||||
|
||||
mHandler.post(() -> {
|
||||
progressDialog.setTitle(R.string.zip_process_title);
|
||||
progressDialog.setMessage(getActivity().getString(R.string.zip_process_msg));
|
||||
});
|
||||
|
||||
// First remove top folder in Github source zip, temp1 -> temp2
|
||||
removeTopFolder(temp1, temp2);
|
||||
|
||||
// Then sign the zip
|
||||
ZipUtils.signZip(temp2, mFile);
|
||||
|
||||
// Delete temp files
|
||||
temp1.delete();
|
||||
temp2.delete();
|
||||
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
Activity activity = getActivity();
|
||||
if (activity == null) return;
|
||||
progressDialog.dismiss();
|
||||
if (result) {
|
||||
Uri uri = Uri.fromFile(mFile);
|
||||
if (Shell.rootAccess() && mInstall) {
|
||||
Intent intent = new Intent(activity, FlashActivity.class);
|
||||
intent.setData(uri).putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_ZIP);
|
||||
activity.startActivity(intent);
|
||||
} else {
|
||||
Utils.showUriSnack(activity, uri);
|
||||
}
|
||||
} else {
|
||||
MagiskManager.toast(R.string.process_error, Toast.LENGTH_LONG);
|
||||
}
|
||||
super.onPostExecute(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParallelTask<Void, Object, Boolean> exec(Void... voids) {
|
||||
Utils.runWithPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
() -> super.exec(voids));
|
||||
return this;
|
||||
}
|
||||
|
||||
private class ProgressInputStream extends FilterInputStream {
|
||||
|
||||
ProgressInputStream(InputStream in) {
|
||||
super(in);
|
||||
}
|
||||
|
||||
private void updateDlProgress(int step) {
|
||||
progress += step;
|
||||
progressDialog.setMessage(getActivity().getString(R.string.zip_download_msg, (int) (100 * (double) progress / total + 0.5)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int read() throws IOException {
|
||||
int b = super.read();
|
||||
if (b > 0) {
|
||||
mHandler.post(() -> updateDlProgress(1));
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(@NonNull byte[] b) throws IOException {
|
||||
return read(b, 0, b.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int read(@NonNull byte[] b, int off, int len) throws IOException {
|
||||
int read = super.read(b, off, len);
|
||||
if (read > 0) {
|
||||
mHandler.post(() -> updateDlProgress(read));
|
||||
}
|
||||
return read;
|
||||
}
|
||||
}
|
||||
}
|
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);
|
||||
}
|
||||
}
|
||||
}
|
211
src/main/java/com/topjohnwu/magisk/asyncs/UpdateRepos.java
Normal file
@@ -0,0 +1,211 @@
|
||||
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;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
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 List<String> cached, etags, newEtags = new ArrayList<>();
|
||||
private RepoDatabaseHelper repoDB;
|
||||
private SharedPreferences prefs;
|
||||
private boolean forceUpdate;
|
||||
|
||||
private int tasks = 0;
|
||||
|
||||
public UpdateRepos(boolean force) {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
prefs = mm.prefs;
|
||||
repoDB = mm.repoDB;
|
||||
mm.repoLoadDone.hasPublished = false;
|
||||
// Legacy data cleanup
|
||||
File old = new File(mm.getApplicationInfo().dataDir + "/shared_prefs", "RepoMap.xml");
|
||||
if (old.exists() || prefs.getString("repomap", null) != null) {
|
||||
old.delete();
|
||||
prefs.edit().remove("version").remove("repomap").remove(Const.Key.ETAG_KEY).apply();
|
||||
repoDB.clearRepo();
|
||||
}
|
||||
forceUpdate = force;
|
||||
}
|
||||
|
||||
private void loadJSON(String jsonString) throws Exception {
|
||||
JSONArray jsonArray = new JSONArray(jsonString);
|
||||
|
||||
// Empty page, throw error
|
||||
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);
|
||||
Boolean updated;
|
||||
try {
|
||||
if (repo == null) {
|
||||
repo = new Repo(name, updatedDate);
|
||||
updated = true;
|
||||
} else {
|
||||
// Popout from cached
|
||||
cached.remove(id);
|
||||
if (forceUpdate) {
|
||||
repo.update();
|
||||
updated = true;
|
||||
} else {
|
||||
updated = repo.update(updatedDate);
|
||||
}
|
||||
}
|
||||
if (updated) {
|
||||
repoDB.addRepo(repo);
|
||||
publishProgress();
|
||||
}
|
||||
if (!id.equals(repo.getId())) {
|
||||
Logger.error("Repo [" + name + "] id=[" + repo.getId() + "] has illegal repo id");
|
||||
}
|
||||
} catch (Repo.IllegalRepoException e) {
|
||||
Logger.error(e.getMessage());
|
||||
repoDB.removeRepo(id);
|
||||
}
|
||||
--tasks;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private boolean loadPage(int page, int mode) {
|
||||
Map<String, String> header = new HashMap<>();
|
||||
String etag = "";
|
||||
if (mode == CHECK_ETAG && page < etags.size()) {
|
||||
etag = etags.get(page);
|
||||
}
|
||||
header.put(Const.Key.IF_NONE_MATCH, etag);
|
||||
String url = String.format(Locale.US, Const.Url.REPO_URL, page + 1);
|
||||
HttpURLConnection conn = WebService.request(url, header);
|
||||
|
||||
try {
|
||||
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);
|
||||
}
|
||||
loadJSON(WebService.getString(conn));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
// Don't continue
|
||||
return true;
|
||||
}
|
||||
|
||||
// Update ETAG
|
||||
etag = header.get(Const.Key.ETAG_KEY);
|
||||
etag = etag.substring(etag.indexOf('\"'), etag.lastIndexOf('\"') + 1);
|
||||
newEtags.add(etag);
|
||||
|
||||
String links = header.get(Const.Key.LINK_KEY);
|
||||
if (links != null) {
|
||||
for (String s : links.split(", ")) {
|
||||
if (mode != LOAD_PREV && s.contains("next")) {
|
||||
// Force load all next pages
|
||||
loadPage(page + 1, LOAD_NEXT);
|
||||
} else if (mode != LOAD_NEXT && s.contains("prev")) {
|
||||
// Back propagation
|
||||
loadPage(page - 1, LOAD_PREV);
|
||||
}
|
||||
}
|
||||
}
|
||||
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 doInBackground(Void... voids) {
|
||||
etags = new ArrayList<>(Arrays.asList(prefs.getString(Const.Key.ETAG_KEY, "").split(",")));
|
||||
cached = 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);
|
||||
try {
|
||||
repo.update();
|
||||
repoDB.addRepo(repo);
|
||||
} catch (Repo.IllegalRepoException e) {
|
||||
Logger.error(e.getMessage());
|
||||
repoDB.removeRepo(repo);
|
||||
}
|
||||
--tasks;
|
||||
});
|
||||
}
|
||||
}
|
||||
return waitTasks();
|
||||
}
|
||||
|
||||
// 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();
|
||||
super.onPostExecute(v);
|
||||
}
|
||||
}
|
135
src/main/java/com/topjohnwu/magisk/components/Activity.java
Normal file
@@ -0,0 +1,135 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.AssetManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
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;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
public abstract class Activity extends AppCompatActivity {
|
||||
|
||||
private AssetManager swappedAssetManager = null;
|
||||
private Resources swappedResources = null;
|
||||
private Resources.Theme swappedTheme = null;
|
||||
private ActivityResultListener activityResultListener;
|
||||
|
||||
public Activity() {
|
||||
super();
|
||||
Configuration configuration = new Configuration();
|
||||
configuration.setLocale(MagiskManager.locale);
|
||||
applyOverrideConfiguration(configuration);
|
||||
}
|
||||
|
||||
@StyleRes
|
||||
abstract public int getDarkTheme();
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (this instanceof Topic.Subscriber) {
|
||||
((Topic.Subscriber) this).subscribeTopics();
|
||||
}
|
||||
if (getMagiskManager().isDarkTheme && getDarkTheme() > 0) {
|
||||
setTheme(getDarkTheme());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
if (this instanceof Topic.Subscriber) {
|
||||
((Topic.Subscriber) this).unsubscribeTopics();
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
MagiskManager mm = getMagiskManager();
|
||||
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
if (mm.permissionGrantCallback != null) {
|
||||
mm.permissionGrantCallback.run();
|
||||
}
|
||||
}
|
||||
mm.permissionGrantCallback = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resources.Theme getTheme() {
|
||||
return swappedTheme == null ? super.getTheme() : swappedTheme;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AssetManager getAssets() {
|
||||
return swappedAssetManager == null ? super.getAssets() : swappedAssetManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resources getResources() {
|
||||
return swappedResources == null ? super.getResources() : swappedResources;
|
||||
}
|
||||
|
||||
public MagiskManager getMagiskManager() {
|
||||
return (MagiskManager) super.getApplicationContext();
|
||||
}
|
||||
|
||||
protected void setFloating() {
|
||||
boolean isTablet = getResources().getBoolean(R.bool.isTablet);
|
||||
if (isTablet) {
|
||||
WindowManager.LayoutParams params = getWindow().getAttributes();
|
||||
params.height = getResources().getDimensionPixelSize(R.dimen.floating_height);
|
||||
params.width = getResources().getDimensionPixelSize(R.dimen.floating_width);
|
||||
params.alpha = 1.0f;
|
||||
params.dimAmount = 0.6f;
|
||||
params.flags |= 2;
|
||||
getWindow().setAttributes(params);
|
||||
setFinishOnTouchOutside(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (activityResultListener != null)
|
||||
activityResultListener.onActivityResult(requestCode, resultCode, data);
|
||||
activityResultListener = null;
|
||||
}
|
||||
|
||||
public void startActivityForResult(Intent intent, int requestCode, ActivityResultListener listener) {
|
||||
activityResultListener = listener;
|
||||
super.startActivityForResult(intent, requestCode);
|
||||
}
|
||||
|
||||
@Keep
|
||||
public void swapResources(String dexPath, int resId) {
|
||||
swappedAssetManager = Utils.getAssets(dexPath);
|
||||
if (swappedAssetManager == null)
|
||||
return;
|
||||
Resources res = super.getResources();
|
||||
swappedResources = new Resources(swappedAssetManager, res.getDisplayMetrics(), res.getConfiguration());
|
||||
swappedTheme = swappedResources.newTheme();
|
||||
swappedTheme.applyStyle(resId, true);
|
||||
}
|
||||
|
||||
@Keep
|
||||
public void restoreResources() {
|
||||
swappedAssetManager = null;
|
||||
swappedResources = null;
|
||||
swappedTheme = null;
|
||||
}
|
||||
|
||||
public interface ActivityResultListener {
|
||||
void onActivityResult(int requestCode, int resultCode, Intent data);
|
||||
}
|
||||
|
||||
}
|
@@ -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) {
|
@@ -3,22 +3,15 @@ package com.topjohnwu.magisk.components;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
public class Fragment extends android.support.v4.app.Fragment {
|
||||
|
||||
private ActivityResultListener activityResultListener;
|
||||
|
||||
public MagiskManager getApplication() {
|
||||
return Utils.getMagiskManager(getActivity());
|
||||
}
|
||||
|
||||
public Shell getShell() {
|
||||
return Shell.getShell(getActivity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
@@ -36,18 +29,11 @@ public class Fragment extends android.support.v4.app.Fragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (activityResultListener != null)
|
||||
activityResultListener.onActivityResult(requestCode, resultCode, data);
|
||||
activityResultListener = null;
|
||||
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);
|
||||
}
|
||||
|
||||
public interface ActivityResultListener {
|
||||
void onActivityResult(int requestCode, int resultCode, Intent data);
|
||||
public void startActivityForResult(Intent intent, int requestCode, Activity.ActivityResultListener listener) {
|
||||
((Activity) getActivity()).startActivityForResult(intent, requestCode, listener);
|
||||
}
|
||||
}
|