diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 000000000..0c1a13c1e
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1,12 @@
+*.iml
+.gradle
+/local.properties
+.idea/
+/build
+app/release
+*.hprof
+.externalNativeBuild/
+src/full/res/raw/util_functions.sh
+public.certificate.x509.pem
+private.key.pk8
+*.apk
diff --git a/app/README.md b/app/README.md
new file mode 100644
index 000000000..19a1f6142
--- /dev/null
+++ b/app/README.md
@@ -0,0 +1,7 @@
+# Magisk Manager
+This repo is no longer an independent component. It is a submodule of the [Magisk Project](https://github.com/topjohnwu/Magisk).
+
+# Translations
+The default (English) string resources are scattered in these files: `src/full/res/values/strings.xml`, `src/main/res/values/strings.xml`, `src/stub/res/values/strings.xml`.
+Place the translated XMLs in the corresponding folder to the locale.
+Translations are highly appreciated via pull requests here on Github.
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 000000000..7621a2f68
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,65 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.ext.compileSdkVersion
+ buildToolsVersion rootProject.ext.buildToolsVersion
+
+ defaultConfig {
+ applicationId "com.topjohnwu.magisk"
+ minSdkVersion 21
+ targetSdkVersion rootProject.ext.compileSdkVersion
+ javaCompileOptions {
+ annotationProcessorOptions {
+ argument('butterknife.debuggable', 'false')
+ }
+ }
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled true
+ shrinkResources true
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ flavorDimensions "mode"
+
+ productFlavors {
+ full {
+ versionCode 127
+ versionName "5.8.1"
+ }
+ stub {
+ versionCode 1
+ versionName "stub"
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ dexOptions {
+ preDexLibraries true
+ javaMaxHeapSize "2g"
+ }
+ lintOptions {
+ disable 'MissingTranslation'
+ }
+}
+
+dependencies {
+ implementation fileTree(include: ['*.jar'], dir: 'libs')
+ fullImplementation project(':utils')
+ implementation "com.android.support:support-core-utils:${rootProject.ext.supportLibVersion}"
+ fullImplementation "com.android.support:preference-v7:${rootProject.ext.supportLibVersion}"
+ fullImplementation "com.android.support:recyclerview-v7:${rootProject.ext.supportLibVersion}"
+ fullImplementation "com.android.support:cardview-v7:${rootProject.ext.supportLibVersion}"
+ fullImplementation "com.android.support:design:${rootProject.ext.supportLibVersion}"
+ fullImplementation 'com.github.topjohnwu:libsu:1.3.0'
+ fullImplementation 'com.atlassian.commonmark:commonmark:0.11.0'
+ fullImplementation 'org.kamranzafar:jtar:2.3'
+ fullImplementation 'com.jakewharton:butterknife:8.8.1'
+ annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
+}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 000000000..6002ebcc6
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,29 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/topjohnwu/Library/Android/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Keep all names, we are open source anyway :)
+-keepnames class ** { *; }
+
+# 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.**
+
+# Gson
+-keepattributes Signature
\ No newline at end of file
diff --git a/app/src/full/AndroidManifest.xml b/app/src/full/AndroidManifest.xml
new file mode 100644
index 000000000..089a603e0
--- /dev/null
+++ b/app/src/full/AndroidManifest.xml
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/full/java/com/topjohnwu/magisk/AboutActivity.java b/app/src/full/java/com/topjohnwu/magisk/AboutActivity.java
new file mode 100644
index 000000000..b32f703b6
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/AboutActivity.java
@@ -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_StatusBar_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();
+ }
+
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/FlashActivity.java b/app/src/full/java/com/topjohnwu/magisk/FlashActivity.java
new file mode 100644
index 000000000..8c892d724
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/FlashActivity.java
@@ -0,0 +1,155 @@
+package com.topjohnwu.magisk;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+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.magisk.utils.RootUtils;
+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 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_StatusBar_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 console = new CallbackList(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.UNINSTALL:
+ new UninstallMagisk(this, uri, console, logs).exec();
+ break;
+ case Const.Value.FLASH_MAGISK:
+ new InstallMagisk(this, console, logs, uri, InstallMagisk.DIRECT_MODE).exec();
+ break;
+ case Const.Value.FLASH_SECOND_SLOT:
+ new InstallMagisk(this, console, logs, uri, InstallMagisk.SECOND_SLOT_MODE).exec();
+ break;
+ case Const.Value.PATCH_BOOT:
+ new InstallMagisk(this, console, logs, uri,
+ intent.getParcelableExtra(Const.Key.FLASH_SET_BOOT)).exec();
+ break;
+ }
+ }
+
+ @Override
+ public void onBackPressed() {
+ // Prevent user accidentally press back button
+ }
+
+ private static class UninstallMagisk extends FlashZip {
+
+ private UninstallMagisk(Activity context, Uri uri, List console, List logs) {
+ super(context, uri, console, logs);
+ }
+
+ @Override
+ protected void onPostExecute(Integer result) {
+ if (result == 1) {
+ new Handler().postDelayed(() ->
+ RootUtils.uninstallPkg(getActivity().getPackageName()), 3000);
+ } else {
+ super.onPostExecute(result);
+ }
+ }
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/LogFragment.java b/app/src/full/java/com/topjohnwu/magisk/LogFragment.java
new file mode 100644
index 000000000..e61e275e1
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/LogFragment.java
@@ -0,0 +1,55 @@
+package com.topjohnwu.magisk;
+
+
+import android.os.Bundle;
+import android.support.design.widget.TabLayout;
+import android.support.v4.view.ViewPager;
+import android.view.LayoutInflater;
+import android.view.View;
+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;
+import butterknife.Unbinder;
+
+public class LogFragment extends Fragment {
+
+ private Unbinder unbinder;
+
+ @BindView(R.id.container) ViewPager viewPager;
+ @BindView(R.id.tab) TabLayout tab;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ // Inflate the layout for this fragment
+ View v = inflater.inflate(R.layout.fragment_log, container, false);
+ unbinder = ButterKnife.bind(this, v);
+
+ ((MainActivity) getActivity()).toolbar.setElevation(0);
+
+ TabFragmentAdapter adapter = new TabFragmentAdapter(getChildFragmentManager());
+
+ if (!(Const.USER_ID > 0 && getApplication().multiuserMode == Const.Value.MULTIUSER_MODE_OWNER_MANAGED)) {
+ adapter.addTab(new SuLogFragment(), getString(R.string.superuser));
+ }
+ adapter.addTab(new MagiskLogFragment(), getString(R.string.magisk));
+ tab.setupWithViewPager(viewPager);
+ tab.setVisibility(View.VISIBLE);
+
+ viewPager.setAdapter(adapter);
+
+ return v;
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ unbinder.unbind();
+ }
+
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/MagiskFragment.java b/app/src/full/java/com/topjohnwu/magisk/MagiskFragment.java
new file mode 100644
index 000000000..46567a9f2
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/MagiskFragment.java
@@ -0,0 +1,313 @@
+package com.topjohnwu.magisk;
+
+import android.app.NotificationManager;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.annotation.StringRes;
+import android.support.v4.widget.SwipeRefreshLayout;
+import android.support.v7.widget.CardView;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CheckBox;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+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.utils.Const;
+import com.topjohnwu.magisk.utils.ISafetyNetHelper;
+import com.topjohnwu.magisk.utils.ShowUI;
+import com.topjohnwu.magisk.utils.Topic;
+import com.topjohnwu.magisk.utils.Utils;
+import com.topjohnwu.superuser.Shell;
+import com.topjohnwu.superuser.ShellUtils;
+
+import butterknife.BindColor;
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.OnClick;
+import butterknife.Unbinder;
+
+public class MagiskFragment extends Fragment
+ implements Topic.Subscriber, SwipeRefreshLayout.OnRefreshListener, ExpandableView {
+
+ private Container expandableContainer = new Container();
+
+ private MagiskManager mm;
+ private Unbinder unbinder;
+ private static boolean shownDialog = false;
+
+ @BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
+
+ @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.safetyNet_card) CardView safetyNetCard;
+ @BindView(R.id.safetyNet_refresh) ImageView safetyNetRefreshIcon;
+ @BindView(R.id.safetyNet_status) TextView safetyNetStatusText;
+ @BindView(R.id.safetyNet_check_progress) ProgressBar safetyNetProgress;
+ @BindView(R.id.expand_layout) LinearLayout expandLayout;
+ @BindView(R.id.cts_status_icon) ImageView ctsStatusIcon;
+ @BindView(R.id.cts_status) TextView ctsStatusText;
+ @BindView(R.id.basic_status_icon) ImageView basicStatusIcon;
+ @BindView(R.id.basic_status) TextView basicStatusText;
+
+ @BindView(R.id.install_option_card) CardView installOptionCard;
+ @BindView(R.id.keep_force_enc) CheckBox keepEncChkbox;
+ @BindView(R.id.keep_verity) CheckBox keepVerityChkbox;
+ @BindView(R.id.install_button) CardView installButton;
+ @BindView(R.id.install_text) TextView installText;
+ @BindView(R.id.uninstall_button) CardView uninstallButton;
+
+ @BindColor(R.color.red500) int colorBad;
+ @BindColor(R.color.green500) int colorOK;
+ @BindColor(R.color.yellow500) int colorWarn;
+ @BindColor(R.color.grey500) int colorNeutral;
+ @BindColor(R.color.blue500) int colorInfo;
+
+ @OnClick(R.id.safetyNet_title)
+ void safetyNet() {
+ Runnable task = () -> {
+ safetyNetProgress.setVisibility(View.VISIBLE);
+ safetyNetRefreshIcon.setVisibility(View.GONE);
+ safetyNetStatusText.setText(R.string.checking_safetyNet_status);
+ new CheckSafetyNet(getActivity()).exec();
+ collapse();
+ };
+ if (!TextUtils.equals(mm.getPackageName(), Const.ORIG_PKG_NAME)) {
+ new AlertDialogBuilder(getActivity())
+ .setTitle(R.string.cannot_check_sn_title)
+ .setMessage(R.string.cannot_check_sn_notice)
+ .setCancelable(true)
+ .setPositiveButton(R.string.ok, null)
+ .show();
+ } else if (!CheckSafetyNet.dexPath.exists()) {
+ // Show dialog
+ new AlertDialogBuilder(getActivity())
+ .setTitle(R.string.proprietary_title)
+ .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)
+ void install() {
+ shownDialog = true;
+
+ // Show Manager update first
+ if (mm.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
+ ShowUI.managerInstallDialog(getActivity());
+ return;
+ }
+
+ ((NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE)).cancelAll();
+ ShowUI.magiskInstallDialog(getActivity());
+ }
+
+ @OnClick(R.id.uninstall_button)
+ void uninstall() {
+ ShowUI.uninstallDialog(getActivity());
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ View v = inflater.inflate(R.layout.fragment_magisk, container, false);
+ unbinder = ButterKnife.bind(this, v);
+ getActivity().setTitle(R.string.magisk);
+
+ mm = getApplication();
+
+ 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();
+
+ return v;
+ }
+
+ @Override
+ public void onRefresh() {
+ mm.loadMagiskInfo();
+ updateUI();
+
+ magiskUpdateText.setText(R.string.checking_for_updates);
+ magiskUpdateProgress.setVisibility(View.VISIBLE);
+ magiskUpdateIcon.setVisibility(View.GONE);
+
+ safetyNetStatusText.setText(R.string.safetyNet_check_text);
+
+ mm.safetyNetDone.reset();
+ mm.updateCheckDone.reset();
+ mm.remoteMagiskVersionString = null;
+ mm.remoteMagiskVersionCode = -1;
+ collapse();
+
+ shownDialog = false;
+
+ // Trigger state check
+ if (Utils.checkNetworkStatus()) {
+ new CheckUpdates().exec();
+ } else {
+ mSwipeRefreshLayout.setRefreshing(false);
+ }
+ }
+
+ @Override
+ public void onTopicPublished(Topic topic) {
+ if (topic == mm.updateCheckDone) {
+ updateCheckUI();
+ } else if (topic == mm.safetyNetDone) {
+ updateSafetyNetUI((int) topic.getResults()[0]);
+ }
+ }
+
+ @Override
+ public Topic[] getSubscription() {
+ return new Topic[] { mm.updateCheckDone, mm.safetyNetDone };
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ unbinder.unbind();
+ }
+
+ @Override
+ public Container getContainer() {
+ return expandableContainer;
+ }
+
+ private void updateUI() {
+ ((MainActivity) getActivity()).checkHideSection();
+
+ boolean hasNetwork = Utils.checkNetworkStatus();
+ boolean hasRoot = Shell.rootAccess();
+ boolean isUpToDate = mm.magiskVersionCode > Const.MAGISK_VER.UNIFIED;
+
+ magiskUpdate.setVisibility(hasNetwork ? View.VISIBLE : View.GONE);
+ safetyNetCard.setVisibility(hasNetwork ? View.VISIBLE : View.GONE);
+ installOptionCard.setVisibility(hasNetwork ? View.VISIBLE : View.GONE);
+ uninstallButton.setVisibility(isUpToDate && hasRoot ? View.VISIBLE : View.GONE);
+
+ int image, color;
+
+ if (mm.magiskVersionCode < 0) {
+ color = colorBad;
+ image = R.drawable.ic_cancel;
+ magiskVersionText.setText(R.string.magisk_version_error);
+ } else {
+ color = colorOK;
+ image = R.drawable.ic_check_circle;
+ magiskVersionText.setText(getString(R.string.current_magisk_title, "v" + mm.magiskVersionString));
+ }
+
+ magiskStatusIcon.setImageResource(image);
+ magiskStatusIcon.setColorFilter(color);
+ }
+
+ private void updateCheckUI() {
+ int image, color;
+
+ if (mm.remoteMagiskVersionCode < 0) {
+ color = colorNeutral;
+ image = R.drawable.ic_help;
+ 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);
+ }
+ }
+
+ magiskUpdateIcon.setImageResource(image);
+ magiskUpdateIcon.setColorFilter(color);
+ magiskUpdateIcon.setVisibility(View.VISIBLE);
+
+ magiskUpdateProgress.setVisibility(View.GONE);
+ mSwipeRefreshLayout.setRefreshing(false);
+
+ if (!shownDialog) {
+ if (mm.remoteMagiskVersionCode > mm.magiskVersionCode
+ || mm.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
+ install();
+ } else if (mm.remoteMagiskVersionCode >= Const.MAGISK_VER.FIX_ENV &&
+ !ShellUtils.fastCmdResult("env_check")) {
+ ShowUI.envFixDialog(getActivity());
+ }
+ }
+ }
+
+ private void updateSafetyNetUI(int response) {
+ safetyNetProgress.setVisibility(View.GONE);
+ safetyNetRefreshIcon.setVisibility(View.VISIBLE);
+ if ((response & 0x0F) == 0) {
+ safetyNetStatusText.setText(R.string.safetyNet_check_success);
+
+ boolean b;
+ b = (response & ISafetyNetHelper.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 & ISafetyNetHelper.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 ISafetyNetHelper.CAUSE_SERVICE_DISCONNECTED:
+ resid = R.string.safetyNet_network_loss;
+ break;
+ case ISafetyNetHelper.CAUSE_NETWORK_LOST:
+ resid = R.string.safetyNet_service_disconnected;
+ break;
+ case ISafetyNetHelper.RESPONSE_ERR:
+ resid = R.string.safetyNet_res_invalid;
+ break;
+ case ISafetyNetHelper.CONNECTION_FAIL:
+ default:
+ resid = R.string.safetyNet_api_error;
+ break;
+ }
+ safetyNetStatusText.setText(resid);
+ }
+ }
+}
+
diff --git a/app/src/full/java/com/topjohnwu/magisk/MagiskHideFragment.java b/app/src/full/java/com/topjohnwu/magisk/MagiskHideFragment.java
new file mode 100644
index 000000000..679fe89e0
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/MagiskHideFragment.java
@@ -0,0 +1,92 @@
+package com.topjohnwu.magisk;
+
+import android.os.Bundle;
+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.View;
+import android.view.ViewGroup;
+import android.widget.SearchView;
+
+import com.topjohnwu.magisk.adapters.ApplicationAdapter;
+import com.topjohnwu.magisk.components.Fragment;
+import com.topjohnwu.magisk.utils.Topic;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.Unbinder;
+
+public class MagiskHideFragment extends Fragment implements Topic.Subscriber {
+
+ private Unbinder unbinder;
+ @BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
+ @BindView(R.id.recyclerView) RecyclerView recyclerView;
+
+ private ApplicationAdapter appAdapter;
+
+ private SearchView.OnQueryTextListener searchListener;
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setHasOptionsMenu(true);
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_magisk_hide, container, false);
+ unbinder = ButterKnife.bind(this, view);
+
+ mSwipeRefreshLayout.setRefreshing(true);
+ mSwipeRefreshLayout.setOnRefreshListener(() -> appAdapter.refresh());
+
+ appAdapter = new ApplicationAdapter();
+ recyclerView.setAdapter(appAdapter);
+
+ searchListener = new SearchView.OnQueryTextListener() {
+ @Override
+ public boolean onQueryTextSubmit(String query) {
+ appAdapter.filter(query);
+ return false;
+ }
+
+ @Override
+ public boolean onQueryTextChange(String newText) {
+ appAdapter.filter(newText);
+ return false;
+ }
+ };
+
+ getActivity().setTitle(R.string.magiskhide);
+
+ return view;
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ inflater.inflate(R.menu.menu_magiskhide, menu);
+ SearchView search = (SearchView) menu.findItem(R.id.app_search).getActionView();
+ search.setOnQueryTextListener(searchListener);
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ unbinder.unbind();
+ }
+
+ @Override
+ public void onTopicPublished(Topic topic) {
+ mSwipeRefreshLayout.setRefreshing(false);
+ appAdapter.filter(null);
+ }
+
+ @Override
+ public Topic[] getSubscription() {
+ return new Topic[] { getApplication().magiskHideDone };
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/MagiskLogFragment.java b/app/src/full/java/com/topjohnwu/magisk/MagiskLogFragment.java
new file mode 100644
index 000000000..eef4e01c6
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/MagiskLogFragment.java
@@ -0,0 +1,145 @@
+package com.topjohnwu.magisk;
+
+import android.Manifest;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.design.widget.Snackbar;
+import android.text.TextUtils;
+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.HorizontalScrollView;
+import android.widget.ProgressBar;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+import com.topjohnwu.magisk.components.Fragment;
+import com.topjohnwu.magisk.components.SnackbarMaker;
+import com.topjohnwu.magisk.utils.Const;
+import com.topjohnwu.magisk.utils.Utils;
+import com.topjohnwu.superuser.Shell;
+import com.topjohnwu.superuser.ShellUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Calendar;
+import java.util.List;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.Unbinder;
+
+public class MagiskLogFragment extends Fragment {
+
+ private Unbinder unbinder;
+
+ @BindView(R.id.txtLog) TextView txtLog;
+ @BindView(R.id.svLog) ScrollView svLog;
+ @BindView(R.id.hsvLog) HorizontalScrollView hsvLog;
+ @BindView(R.id.progressBar) ProgressBar progressBar;
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_magisk_log, container, false);
+ unbinder = ButterKnife.bind(this, view);
+ setHasOptionsMenu(true);
+ txtLog.setTextIsSelectable(true);
+ return view;
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ getActivity().setTitle(R.string.log);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ readLogs();
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ unbinder.unbind();
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ inflater.inflate(R.menu.menu_log, menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_refresh:
+ readLogs();
+ return true;
+ case R.id.menu_save:
+ runWithPermission(new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, this::saveLogs);
+ return true;
+ case R.id.menu_clear:
+ clearLogs();
+ return true;
+ default:
+ return true;
+ }
+ }
+
+ public void readLogs() {
+ Shell.Async.su(new Shell.Async.Callback() {
+ @Override
+ public void onTaskResult(@Nullable List out, @Nullable List err) {
+ progressBar.setVisibility(View.GONE);
+ if (ShellUtils.isValidOutput(out)) {
+ txtLog.setText(TextUtils.join("\n", out));
+ } else {
+ txtLog.setText(R.string.log_is_empty);
+ }
+ svLog.postDelayed(() -> svLog.fullScroll(ScrollView.FOCUS_DOWN), 100);
+ hsvLog.postDelayed(() -> hsvLog.fullScroll(ScrollView.FOCUS_LEFT), 100);
+ }
+
+ @Override
+ public void onTaskError(@NonNull Throwable throwable) {
+ txtLog.setText(R.string.log_is_empty);
+ }
+ }, "cat " + Const.MAGISK_LOG + " | tail -n 5000");
+ }
+
+ public void saveLogs() {
+ Calendar now = Calendar.getInstance();
+ String filename = Utils.fmt("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));
+
+ File targetFile = new File(Const.EXTERNAL_PATH + "/logs", filename);
+ targetFile.getParentFile().mkdirs();
+ try {
+ targetFile.createNewFile();
+ } catch (IOException e) {
+ return;
+ }
+ Shell.Async.su(new Shell.Async.Callback() {
+ @Override
+ public void onTaskResult(@Nullable List out, @Nullable List err) {
+ SnackbarMaker.make(txtLog, targetFile.getPath(), Snackbar.LENGTH_SHORT).show();
+ }
+
+ @Override
+ public void onTaskError(@NonNull Throwable throwable) {}
+ }, "cat " + Const.MAGISK_LOG + " > " + targetFile);
+ }
+
+ public void clearLogs() {
+ Shell.Async.su("echo -n > " + Const.MAGISK_LOG);
+ SnackbarMaker.make(txtLog, R.string.logs_cleared, Snackbar.LENGTH_SHORT).show();
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/MagiskManager.java b/app/src/full/java/com/topjohnwu/magisk/MagiskManager.java
new file mode 100644
index 000000000..2f777a188
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/MagiskManager.java
@@ -0,0 +1,292 @@
+package com.topjohnwu.magisk;
+
+import android.app.job.JobInfo;
+import android.app.job.JobScheduler;
+import android.content.ComponentName;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.preference.PreferenceManager;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+import android.util.Xml;
+
+import com.topjohnwu.magisk.components.Application;
+import com.topjohnwu.magisk.container.Module;
+import com.topjohnwu.magisk.database.MagiskDatabaseHelper;
+import com.topjohnwu.magisk.database.RepoDatabaseHelper;
+import com.topjohnwu.magisk.services.UpdateCheckService;
+import com.topjohnwu.magisk.utils.Const;
+import com.topjohnwu.magisk.utils.RootUtils;
+import com.topjohnwu.magisk.utils.ShellInitializer;
+import com.topjohnwu.magisk.utils.Topic;
+import com.topjohnwu.magisk.utils.Utils;
+import com.topjohnwu.superuser.Shell;
+import com.topjohnwu.superuser.ShellUtils;
+import com.topjohnwu.superuser.io.SuFile;
+import com.topjohnwu.superuser.io.SuFileInputStream;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+public class MagiskManager extends Application implements Shell.Container {
+
+ // 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 remoteManagerVersionString;
+ public int remoteManagerVersionCode = -1;
+
+ public String magiskLink;
+ public String magiskNoteLink;
+ public String managerLink;
+ public String managerNoteLink;
+ public String uninstallerLink;
+
+ public boolean keepVerity = false;
+ public boolean keepEnc = false;
+
+ // Data
+ public Map moduleMap;
+ public List locales;
+
+ public boolean magiskHide;
+ public boolean isDarkTheme;
+ public int suRequestTimeout;
+ public int suLogTimeout = 14;
+ public int suAccessState;
+ public int multiuserMode;
+ public int suResponseType;
+ public int suNotificationType;
+ public int suNamespaceMode;
+ public String localeConfig;
+ public int updateChannel;
+ public String bootFormat;
+ public int repoOrder;
+
+ // Global resources
+ public SharedPreferences prefs;
+ public MagiskDatabaseHelper mDB;
+ public RepoDatabaseHelper repoDB;
+
+ private volatile Shell mShell;
+
+ public MagiskManager() {
+ weakSelf = new WeakReference<>(this);
+ Shell.setContainer(this);
+ }
+
+ @Nullable
+ @Override
+ public Shell getShell() {
+ return mShell;
+ }
+
+ @Override
+ public void setShell(@Nullable Shell shell) {
+ mShell = shell;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ Shell.setFlags(Shell.FLAG_MOUNT_MASTER);
+ Shell.verboseLogging(BuildConfig.DEBUG);
+ Shell.setInitializer(ShellInitializer.class);
+
+ prefs = PreferenceManager.getDefaultSharedPreferences(this);
+ mDB = MagiskDatabaseHelper.getInstance(this);
+
+ String pkg = mDB.getStrings(Const.Key.SU_MANAGER, null);
+ if (pkg != null && getPackageName().equals(Const.ORIG_PKG_NAME)) {
+ mDB.setStrings(Const.Key.SU_MANAGER, null);
+ RootUtils.uninstallPkg(pkg);
+ }
+ if (TextUtils.equals(pkg, getPackageName())) {
+ try {
+ // We are the manager, remove com.topjohnwu.magisk as it could be malware
+ getPackageManager().getApplicationInfo(Const.ORIG_PKG_NAME, 0);
+ RootUtils.uninstallPkg(Const.ORIG_PKG_NAME);
+ } catch (PackageManager.NameNotFoundException ignored) {}
+ }
+
+ setLocale();
+ loadConfig();
+ }
+
+ public static MagiskManager get() {
+ return (MagiskManager) weakSelf.get();
+ }
+
+ public void setLocale() {
+ localeConfig = prefs.getString(Const.Key.LOCALE, "");
+ if (localeConfig.isEmpty()) {
+ locale = defaultLocale;
+ } else {
+ locale = Locale.forLanguageTag(localeConfig);
+ }
+ Resources res = getBaseContext().getResources();
+ Configuration config = new Configuration(res.getConfiguration());
+ config.setLocale(locale);
+ res.updateConfiguration(config, res.getDisplayMetrics());
+ }
+
+ public void loadConfig() {
+ // su
+ suRequestTimeout = Utils.getPrefsInt(prefs, Const.Key.SU_REQUEST_TIMEOUT, Const.Value.timeoutList[2]);
+ suResponseType = Utils.getPrefsInt(prefs, Const.Key.SU_AUTO_RESPONSE, Const.Value.SU_PROMPT);
+ suNotificationType = Utils.getPrefsInt(prefs, Const.Key.SU_NOTIFICATION, Const.Value.NOTIFICATION_TOAST);
+ suAccessState = mDB.getSettings(Const.Key.ROOT_ACCESS, Const.Value.ROOT_ACCESS_APPS_AND_ADB);
+ multiuserMode = mDB.getSettings(Const.Key.SU_MULTIUSER_MODE, Const.Value.MULTIUSER_MODE_OWNER_ONLY);
+ suNamespaceMode = mDB.getSettings(Const.Key.SU_MNT_NS, Const.Value.NAMESPACE_MODE_REQUESTER);
+
+ // config
+ isDarkTheme = prefs.getBoolean(Const.Key.DARK_THEME, false);
+ updateChannel = Utils.getPrefsInt(prefs, Const.Key.UPDATE_CHANNEL, Const.Value.STABLE_CHANNEL);
+ bootFormat = prefs.getString(Const.Key.BOOT_FORMAT, ".img");
+ repoOrder = prefs.getInt(Const.Key.REPO_ORDER, Const.Value.ORDER_NAME);
+ }
+
+ public void writeConfig() {
+ prefs.edit()
+ .putBoolean(Const.Key.DARK_THEME, isDarkTheme)
+ .putBoolean(Const.Key.MAGISKHIDE, magiskHide)
+ .putBoolean(Const.Key.HOSTS, Const.MAGISK_HOST_FILE.exists())
+ .putBoolean(Const.Key.COREONLY, Const.MAGISK_DISABLE_FILE.exists())
+ .putString(Const.Key.SU_REQUEST_TIMEOUT, String.valueOf(suRequestTimeout))
+ .putString(Const.Key.SU_AUTO_RESPONSE, String.valueOf(suResponseType))
+ .putString(Const.Key.SU_NOTIFICATION, String.valueOf(suNotificationType))
+ .putString(Const.Key.ROOT_ACCESS, String.valueOf(suAccessState))
+ .putString(Const.Key.SU_MULTIUSER_MODE, String.valueOf(multiuserMode))
+ .putString(Const.Key.SU_MNT_NS, String.valueOf(suNamespaceMode))
+ .putString(Const.Key.UPDATE_CHANNEL, String.valueOf(updateChannel))
+ .putString(Const.Key.LOCALE, localeConfig)
+ .putString(Const.Key.BOOT_FORMAT, bootFormat)
+ .putInt(Const.Key.UPDATE_SERVICE_VER, Const.UPDATE_SERVICE_VER)
+ .putInt(Const.Key.REPO_ORDER, repoOrder)
+ .apply();
+ }
+
+ public void loadMagiskInfo() {
+ try {
+ magiskVersionString = ShellUtils.fastCmd("magisk -v").split(":")[0];
+ magiskVersionCode = Integer.parseInt(ShellUtils.fastCmd("magisk -V"));
+ String s = ShellUtils.fastCmd((magiskVersionCode >= Const.MAGISK_VER.RESETPROP_PERSIST ?
+ "resetprop -p " : "getprop ") + Const.MAGISKHIDE_PROP);
+ magiskHide = s == null || Integer.parseInt(s) != 0;
+ } catch (Exception ignored) {}
+ }
+
+ public void getDefaultInstallFlags() {
+ keepVerity = Boolean.parseBoolean(ShellUtils.fastCmd("echo $KEEPVERITY"));
+ keepEnc = Boolean.parseBoolean(ShellUtils.fastCmd("echo $KEEPFORCEENCRYPT"));
+ }
+
+ 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);
+ }
+ }
+
+ public void dumpPrefs() {
+ // Flush prefs to disk
+ prefs.edit().commit();
+ File xml = new File(getFilesDir().getParent() + "/shared_prefs",
+ getPackageName() + "_preferences.xml");
+ Shell.Sync.su(Utils.fmt("for usr in /data/user/*; do cat %s > ${usr}/%s; done", xml, Const.MANAGER_CONFIGS));
+ }
+
+ public void loadPrefs() {
+ SuFile config = new SuFile(Utils.fmt("/data/user/%d/%s", Const.USER_ID, Const.MANAGER_CONFIGS));
+ if (config.exists()) {
+ SharedPreferences.Editor editor = prefs.edit();
+ try {
+ SuFileInputStream is = new SuFileInputStream(config);
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
+ parser.setInput(is, "UTF-8");
+ parser.nextTag();
+ parser.require(XmlPullParser.START_TAG, null, "map");
+ while (parser.next() != XmlPullParser.END_TAG) {
+ if (parser.getEventType() != XmlPullParser.START_TAG)
+ continue;
+ String key = parser.getAttributeValue(null, "name");
+ String value = parser.getAttributeValue(null, "value");
+ switch (parser.getName()) {
+ case "string":
+ parser.require(XmlPullParser.START_TAG, null, "string");
+ editor.putString(key, parser.nextText());
+ parser.require(XmlPullParser.END_TAG, null, "string");
+ break;
+ case "boolean":
+ parser.require(XmlPullParser.START_TAG, null, "boolean");
+ editor.putBoolean(key, Boolean.parseBoolean(value));
+ parser.nextTag();
+ parser.require(XmlPullParser.END_TAG, null, "boolean");
+ break;
+ case "int":
+ parser.require(XmlPullParser.START_TAG, null, "int");
+ editor.putInt(key, Integer.parseInt(value));
+ parser.nextTag();
+ parser.require(XmlPullParser.END_TAG, null, "int");
+ break;
+ case "long":
+ parser.require(XmlPullParser.START_TAG, null, "long");
+ editor.putLong(key, Long.parseLong(value));
+ parser.nextTag();
+ parser.require(XmlPullParser.END_TAG, null, "long");
+ break;
+ case "float":
+ parser.require(XmlPullParser.START_TAG, null, "int");
+ editor.putFloat(key, Float.parseFloat(value));
+ parser.nextTag();
+ parser.require(XmlPullParser.END_TAG, null, "int");
+ break;
+ default:
+ parser.next();
+ }
+ }
+ } catch (IOException | XmlPullParserException e) {
+ e.printStackTrace();
+ }
+ editor.remove(Const.Key.ETAG_KEY);
+ editor.apply();
+ loadConfig();
+ config.delete();
+ }
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/MainActivity.java b/app/src/full/java/com/topjohnwu/magisk/MainActivity.java
new file mode 100644
index 000000000..75fa63866
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/MainActivity.java
@@ -0,0 +1,219 @@
+package com.topjohnwu.magisk;
+
+import android.content.Intent;
+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;
+import android.support.v7.app.ActionBarDrawerToggle;
+import android.support.v7.widget.Toolbar;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+
+import com.topjohnwu.magisk.components.Activity;
+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;
+
+public class MainActivity extends Activity
+ implements NavigationView.OnNavigationItemSelectedListener, Topic.Subscriber {
+
+ private final Handler mDrawerHandler = new Handler();
+ private int mDrawerItem;
+ private boolean fromShortcut = true;
+
+ @BindView(R.id.toolbar) Toolbar toolbar;
+ @BindView(R.id.drawer_layout) DrawerLayout drawer;
+ @BindView(R.id.nav_view) public NavigationView navigationView;
+
+ private float toolbarElevation;
+
+ @Override
+ public int getDarkTheme() {
+ return R.style.AppTheme_Dark;
+ }
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+
+ MagiskManager mm = getMagiskManager();
+
+ 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);
+
+ setSupportActionBar(toolbar);
+
+ ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.magisk, R.string.magisk) {
+ @Override
+ public void onDrawerOpened(View drawerView) {
+ super.onDrawerOpened(drawerView);
+ super.onDrawerSlide(drawerView, 0); // this disables the arrow @ completed tate
+ }
+
+ @Override
+ public void onDrawerSlide(View drawerView, float slideOffset) {
+ super.onDrawerSlide(drawerView, 0); // this disables the animation
+ }
+ };
+
+ toolbarElevation = toolbar.getElevation();
+
+ drawer.addDrawerListener(toggle);
+ toggle.syncState();
+
+ if (savedInstanceState == null)
+ navigate(getIntent().getStringExtra(Const.Key.OPEN_SECTION));
+
+ navigationView.setNavigationItemSelectedListener(this);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ checkHideSection();
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (drawer.isDrawerOpen(navigationView)) {
+ drawer.closeDrawer(navigationView);
+ } else if (mDrawerItem != R.id.magisk && !fromShortcut) {
+ navigate(R.id.magisk);
+ } else {
+ finish();
+ }
+ }
+
+ @Override
+ public boolean onNavigationItemSelected(@NonNull final MenuItem menuItem) {
+ mDrawerHandler.removeCallbacksAndMessages(null);
+ mDrawerHandler.postDelayed(() -> navigate(menuItem.getItemId()), 250);
+ drawer.closeDrawer(navigationView);
+ return true;
+ }
+
+ @Override
+ public void onTopicPublished(Topic topic) {
+ recreate();
+ }
+
+ @Override
+ public Topic[] getSubscription() {
+ return new Topic[] { getMagiskManager().reloadActivity };
+ }
+
+ public void checkHideSection() {
+ MagiskManager mm = getMagiskManager();
+ Menu menu = navigationView.getMenu();
+ menu.findItem(R.id.magiskhide).setVisible(
+ Shell.rootAccess() && mm.magiskVersionCode >= Const.MAGISK_VER.UNIFIED
+ && mm.prefs.getBoolean(Const.Key.MAGISKHIDE, false));
+ menu.findItem(R.id.modules).setVisible(!mm.prefs.getBoolean(Const.Key.COREONLY, false) &&
+ Shell.rootAccess() && mm.magiskVersionCode >= 0);
+ menu.findItem(R.id.downloads).setVisible(!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() &&
+ !(Const.USER_ID > 0 && mm.multiuserMode == Const.Value.MULTIUSER_MODE_OWNER_MANAGED));
+ }
+
+ public void navigate(String item) {
+ int itemId = R.id.magisk;
+ if (item != null) {
+ switch (item) {
+ case "superuser":
+ itemId = R.id.superuser;
+ break;
+ case "modules":
+ itemId = R.id.modules;
+ break;
+ case "downloads":
+ itemId = R.id.downloads;
+ break;
+ case "magiskhide":
+ itemId = R.id.magiskhide;
+ break;
+ case "log":
+ itemId = R.id.log;
+ break;
+ case "settings":
+ itemId = R.id.settings;
+ break;
+ case "about":
+ itemId = R.id.app_about;
+ break;
+ }
+ }
+ navigate(itemId);
+ }
+
+ public void navigate(int itemId) {
+ int bak = mDrawerItem;
+ mDrawerItem = itemId;
+ navigationView.setCheckedItem(itemId);
+ switch (itemId) {
+ case R.id.magisk:
+ fromShortcut = false;
+ displayFragment(new MagiskFragment(), true);
+ break;
+ case R.id.superuser:
+ displayFragment(new SuperuserFragment(), true);
+ break;
+ case R.id.modules:
+ displayFragment(new ModulesFragment(), true);
+ break;
+ case R.id.downloads:
+ displayFragment(new ReposFragment(), true);
+ break;
+ case R.id.magiskhide:
+ displayFragment(new MagiskHideFragment(), true);
+ break;
+ case R.id.log:
+ displayFragment(new LogFragment(), false);
+ break;
+ case R.id.settings:
+ startActivity(new Intent(this, SettingsActivity.class));
+ mDrawerItem = bak;
+ break;
+ case R.id.app_about:
+ startActivity(new Intent(this, AboutActivity.class));
+ mDrawerItem = bak;
+ break;
+ }
+ }
+
+ private void displayFragment(@NonNull Fragment navFragment, boolean setElevation) {
+ supportInvalidateOptionsMenu();
+ getSupportFragmentManager()
+ .beginTransaction()
+ .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
+ .replace(R.id.content_frame, navFragment)
+ .commitNow();
+ toolbar.setElevation(setElevation ? toolbarElevation : 0);
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/ModulesFragment.java b/app/src/full/java/com/topjohnwu/magisk/ModulesFragment.java
new file mode 100644
index 000000000..db3a79ebd
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/ModulesFragment.java
@@ -0,0 +1,144 @@
+package com.topjohnwu.magisk;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+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;
+
+import com.topjohnwu.magisk.adapters.ModulesAdapter;
+import com.topjohnwu.magisk.asyncs.LoadModules;
+import com.topjohnwu.magisk.components.Fragment;
+import com.topjohnwu.magisk.container.Module;
+import com.topjohnwu.magisk.utils.Const;
+import com.topjohnwu.magisk.utils.Topic;
+import com.topjohnwu.superuser.Shell;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.OnClick;
+import butterknife.Unbinder;
+
+public class ModulesFragment extends Fragment implements Topic.Subscriber {
+
+ 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() {
+ runWithPermission(new String[] { 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 listModules = new ArrayList<>();
+
+ @Nullable
+ @Override
+ 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().exec();
+ });
+
+ recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ mSwipeRefreshLayout.setEnabled(recyclerView.getChildAt(0).getTop() >= 0);
+ }
+
+ @Override
+ public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+ super.onScrollStateChanged(recyclerView, newState);
+ }
+ });
+
+ getActivity().setTitle(R.string.modules);
+
+ return view;
+ }
+
+ @Override
+ public void onTopicPublished(Topic topic) {
+ updateUI();
+ }
+
+ @Override
+ public Topic[] getSubscription() {
+ return new Topic[] { getApplication().moduleLoadDone };
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ 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(Const.Key.FLASH_ACTION, Const.Value.FLASH_ZIP);
+ startActivity(intent);
+ }
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ 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());
+ if (listModules.size() == 0) {
+ emptyRv.setVisibility(View.VISIBLE);
+ recyclerView.setVisibility(View.GONE);
+ } else {
+ emptyRv.setVisibility(View.GONE);
+ recyclerView.setVisibility(View.VISIBLE);
+ recyclerView.setAdapter(new ModulesAdapter(listModules));
+ }
+ mSwipeRefreshLayout.setRefreshing(false);
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/NoUIActivity.java b/app/src/full/java/com/topjohnwu/magisk/NoUIActivity.java
new file mode 100644
index 000000000..00f5714b8
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/NoUIActivity.java
@@ -0,0 +1,26 @@
+package com.topjohnwu.magisk;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.ActivityCompat;
+
+import com.topjohnwu.magisk.components.Activity;
+import com.topjohnwu.magisk.utils.Const;
+
+public class NoUIActivity extends Activity {
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ String[] perms = getIntent().getStringArrayExtra(Const.Key.INTENT_PERM);
+ if (perms != null) {
+ ActivityCompat.requestPermissions(this, perms, 0);
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ finish();
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/ReposFragment.java b/app/src/full/java/com/topjohnwu/magisk/ReposFragment.java
new file mode 100644
index 000000000..d80dfc855
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/ReposFragment.java
@@ -0,0 +1,126 @@
+package com.topjohnwu.magisk;
+
+import android.app.AlertDialog;
+import android.os.Bundle;
+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.SearchView;
+import android.widget.TextView;
+
+import com.topjohnwu.magisk.adapters.ReposAdapter;
+import com.topjohnwu.magisk.asyncs.UpdateRepos;
+import com.topjohnwu.magisk.components.Fragment;
+import com.topjohnwu.magisk.utils.Const;
+import com.topjohnwu.magisk.utils.Topic;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.Unbinder;
+
+public class ReposFragment extends Fragment implements Topic.Subscriber {
+
+ private Unbinder unbinder;
+ private MagiskManager mm;
+ @BindView(R.id.recyclerView) RecyclerView recyclerView;
+ @BindView(R.id.empty_rv) TextView emptyRv;
+ @BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
+
+ public static ReposAdapter adapter;
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setHasOptionsMenu(true);
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_repos, container, false);
+ unbinder = ButterKnife.bind(this, view);
+ mm = getApplication();
+
+ mSwipeRefreshLayout.setRefreshing(mm.repoLoadDone.isPending());
+
+ mSwipeRefreshLayout.setOnRefreshListener(() -> {
+ recyclerView.setVisibility(View.VISIBLE);
+ emptyRv.setVisibility(View.GONE);
+ new UpdateRepos(true).exec();
+ });
+
+ getActivity().setTitle(R.string.downloads);
+
+ return view;
+ }
+
+ @Override
+ public void onResume() {
+ adapter = new ReposAdapter(mm.repoDB, mm.moduleMap);
+ recyclerView.setAdapter(adapter);
+ super.onResume();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ adapter = null;
+ }
+
+ @Override
+ public void onTopicPublished(Topic topic) {
+ mSwipeRefreshLayout.setRefreshing(false);
+ recyclerView.setVisibility(adapter.getItemCount() == 0 ? View.GONE : View.VISIBLE);
+ emptyRv.setVisibility(adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
+ }
+
+ @Override
+ public Topic[] getSubscription() {
+ return new Topic[] { mm.repoLoadDone };
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ inflater.inflate(R.menu.menu_repo, menu);
+ SearchView search = (SearchView) menu.findItem(R.id.repo_search).getActionView();
+ search.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
+ @Override
+ public boolean onQueryTextSubmit(String query) {
+ return false;
+ }
+
+ @Override
+ public boolean onQueryTextChange(String newText) {
+ adapter.filter(newText);
+ return false;
+ }
+ });
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == R.id.repo_sort) {
+ new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.sorting_order)
+ .setSingleChoiceItems(R.array.sorting_orders, mm.repoOrder, (d, which) -> {
+ mm.repoOrder = which;
+ mm.prefs.edit().putInt(Const.Key.REPO_ORDER, mm.repoOrder).apply();
+ adapter.notifyDBChanged();
+ d.dismiss();
+ }).show();
+ }
+ return true;
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ unbinder.unbind();
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/SettingsActivity.java b/app/src/full/java/com/topjohnwu/magisk/SettingsActivity.java
new file mode 100644
index 000000000..ef0d06170
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/SettingsActivity.java
@@ -0,0 +1,321 @@
+package com.topjohnwu.magisk;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.preference.ListPreference;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceCategory;
+import android.support.v7.preference.PreferenceFragmentCompat;
+import android.support.v7.preference.PreferenceScreen;
+import android.support.v7.widget.Toolbar;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+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.receivers.DownloadReceiver;
+import com.topjohnwu.magisk.utils.Const;
+import com.topjohnwu.magisk.utils.FingerprintHelper;
+import com.topjohnwu.magisk.utils.RootUtils;
+import com.topjohnwu.magisk.utils.Topic;
+import com.topjohnwu.magisk.utils.Utils;
+import com.topjohnwu.superuser.Shell;
+import com.topjohnwu.superuser.ShellUtils;
+
+import java.io.IOException;
+import java.util.Locale;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+
+public class SettingsActivity extends Activity implements Topic.Subscriber {
+
+ @BindView(R.id.toolbar) Toolbar toolbar;
+
+ @Override
+ public int getDarkTheme() {
+ return R.style.AppTheme_StatusBar_Dark;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_settings);
+ ButterKnife.bind(this);
+
+ setSupportActionBar(toolbar);
+
+ toolbar.setNavigationOnClickListener(view -> finish());
+
+ ActionBar ab = getSupportActionBar();
+ if (ab != null) {
+ ab.setTitle(R.string.settings);
+ ab.setDisplayHomeAsUpEnabled(true);
+ }
+
+ setFloating();
+
+ if (savedInstanceState == null) {
+ getSupportFragmentManager().beginTransaction().add(R.id.container, new SettingsFragment()).commit();
+ }
+
+ }
+
+ @Override
+ public void onTopicPublished(Topic topic) {
+ recreate();
+ }
+
+ @Override
+ public Topic[] getSubscription() {
+ return new Topic[] { getMagiskManager().reloadActivity };
+ }
+
+ public static class SettingsFragment extends PreferenceFragmentCompat
+ implements SharedPreferences.OnSharedPreferenceChangeListener, Topic.Subscriber {
+
+ private SharedPreferences prefs;
+ private PreferenceScreen prefScreen;
+
+ private ListPreference updateChannel, suAccess, autoRes, suNotification,
+ requestTimeout, multiuserMode, namespaceMode;
+ private MagiskManager mm;
+ private PreferenceCategory generalCatagory;
+
+ @Override
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ setPreferencesFromResource(R.xml.app_settings, rootKey);
+ mm = Utils.getMagiskManager(getActivity());
+ prefs = mm.prefs;
+ prefScreen = getPreferenceScreen();
+
+ generalCatagory = (PreferenceCategory) findPreference("general");
+ PreferenceCategory magiskCategory = (PreferenceCategory) findPreference("magisk");
+ PreferenceCategory suCategory = (PreferenceCategory) findPreference("superuser");
+ Preference hideManager = findPreference("hide");
+ Preference restoreManager = findPreference("restore");
+ findPreference("clear").setOnPreferenceClickListener((pref) -> {
+ prefs.edit().remove(Const.Key.ETAG_KEY).apply();
+ mm.repoDB.clearRepo();
+ 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 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 >= Const.MAGISK_VER.MANAGER_HIDE) {
+ if (mm.getPackageName().equals(Const.ORIG_PKG_NAME)) {
+ hideManager.setOnPreferenceClickListener((pref) -> {
+ new HideManager(getActivity()).exec();
+ return true;
+ });
+ generalCatagory.removePreference(restoreManager);
+ } else {
+ if (Utils.checkNetworkStatus()) {
+ restoreManager.setOnPreferenceClickListener((pref) -> {
+ Utils.dlAndReceive(
+ getActivity(), new DownloadReceiver() {
+ @Override
+ public void onDownloadDone(Context context, Uri uri) {
+ mm.dumpPrefs();
+ if (ShellUtils.fastCmdResult("pm install " + uri.getPath()))
+ RootUtils.uninstallPkg(context.getPackageName());
+ }
+ },
+ mm.managerLink,
+ Utils.fmt("MagiskManager-v%s.apk", mm.remoteManagerVersionString)
+ );
+ return true;
+ });
+ } else {
+ generalCatagory.removePreference(restoreManager);
+ }
+ generalCatagory.removePreference(hideManager);
+ }
+ } else {
+ generalCatagory.removePreference(restoreManager);
+ generalCatagory.removePreference(hideManager);
+ }
+
+ if (!Shell.rootAccess() || (Const.USER_ID > 0 &&
+ mm.multiuserMode == Const.Value.MULTIUSER_MODE_OWNER_MANAGED)) {
+ prefScreen.removePreference(suCategory);
+ }
+
+ if (!Shell.rootAccess()) {
+ prefScreen.removePreference(magiskCategory);
+ generalCatagory.removePreference(hideManager);
+ } else if (mm.magiskVersionCode < Const.MAGISK_VER.UNIFIED) {
+ prefScreen.removePreference(magiskCategory);
+ }
+ }
+
+ private void setLocalePreference(ListPreference lp) {
+ CharSequence[] entries = new CharSequence[mm.locales.size() + 1];
+ CharSequence[] entryValues = new CharSequence[mm.locales.size() + 1];
+ entries[0] = Utils.getLocaleString(MagiskManager.defaultLocale, R.string.system_default);
+ entryValues[0] = "";
+ int i = 1;
+ for (Locale locale : mm.locales) {
+ entries[i] = locale.getDisplayName(locale);
+ entryValues[i++] = locale.toLanguageTag();
+ }
+ lp.setEntries(entries);
+ lp.setEntryValues(entryValues);
+ lp.setSummary(MagiskManager.locale.getDisplayName(MagiskManager.locale));
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ prefs.registerOnSharedPreferenceChangeListener(this);
+ subscribeTopics();
+ return super.onCreateView(inflater, container, savedInstanceState);
+ }
+
+ @Override
+ public void onDestroyView() {
+ prefs.unregisterOnSharedPreferenceChangeListener(this);
+ unsubscribeTopics();
+ super.onDestroyView();
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+
+ switch (key) {
+ case Const.Key.DARK_THEME:
+ mm.isDarkTheme = prefs.getBoolean(key, false);
+ mm.reloadActivity.publish(false);
+ return;
+ case Const.Key.COREONLY:
+ if (prefs.getBoolean(key, false)) {
+ try {
+ Const.MAGISK_DISABLE_FILE.createNewFile();
+ } catch (IOException ignored) {}
+ } else {
+ Const.MAGISK_DISABLE_FILE.delete();
+ }
+ Toast.makeText(getActivity(), R.string.settings_reboot_toast, Toast.LENGTH_LONG).show();
+ break;
+ case Const.Key.MAGISKHIDE:
+ if (prefs.getBoolean(key, false)) {
+ Shell.Async.su("magiskhide --enable");
+ } else {
+ Shell.Async.su("magiskhide --disable");
+ }
+ break;
+ 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 {
+ Shell.Async.su(
+ "umount -l /system/etc/hosts",
+ "rm -f " + Const.MAGISK_HOST_FILE);
+ }
+ break;
+ case Const.Key.ROOT_ACCESS:
+ case Const.Key.SU_MULTIUSER_MODE:
+ case Const.Key.SU_MNT_NS:
+ mm.mDB.setSettings(key, Utils.getPrefsInt(prefs, key));
+ break;
+ case Const.Key.LOCALE:
+ mm.setLocale();
+ mm.reloadActivity.publish(false);
+ break;
+ case Const.Key.UPDATE_CHANNEL:
+ new CheckUpdates().exec();
+ break;
+ case Const.Key.CHECK_UPDATES:
+ mm.setupUpdateCheck();
+ break;
+ }
+ mm.loadConfig();
+ setSummary();
+ }
+
+ private void setSummary() {
+ updateChannel.setSummary(getResources()
+ .getStringArray(R.array.update_channel)[mm.updateChannel]);
+ suAccess.setSummary(getResources()
+ .getStringArray(R.array.su_access)[mm.suAccessState]);
+ autoRes.setSummary(getResources()
+ .getStringArray(R.array.auto_response)[mm.suResponseType]);
+ suNotification.setSummary(getResources()
+ .getStringArray(R.array.su_notification)[mm.suNotificationType]);
+ requestTimeout.setSummary(
+ 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()
+ .getStringArray(R.array.namespace_summary)[mm.suNamespaceMode]);
+ }
+
+ @Override
+ public void onTopicPublished(Topic topic) {
+ setLocalePreference((ListPreference) findPreference(Const.Key.LOCALE));
+ }
+
+ @Override
+ public Topic[] getSubscription() {
+ return new Topic[] { mm.localeDone };
+ }
+ }
+
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/SplashActivity.java b/app/src/full/java/com/topjohnwu/magisk/SplashActivity.java
new file mode 100644
index 000000000..2bb1e3c50
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/SplashActivity.java
@@ -0,0 +1,88 @@
+package com.topjohnwu.magisk;
+
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+
+import com.topjohnwu.magisk.asyncs.CheckUpdates;
+import com.topjohnwu.magisk.asyncs.LoadModules;
+import com.topjohnwu.magisk.asyncs.ParallelTask;
+import com.topjohnwu.magisk.asyncs.UpdateRepos;
+import com.topjohnwu.magisk.components.Activity;
+import com.topjohnwu.magisk.database.RepoDatabaseHelper;
+import com.topjohnwu.magisk.receivers.ShortcutReceiver;
+import com.topjohnwu.magisk.utils.Const;
+import com.topjohnwu.magisk.utils.RootUtils;
+import com.topjohnwu.magisk.utils.Utils;
+import com.topjohnwu.superuser.Shell;
+
+public class SplashActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ RootUtils.init();
+ MagiskManager mm = getMagiskManager();
+
+ mm.repoDB = new RepoDatabaseHelper(this);
+ mm.loadMagiskInfo();
+ mm.getDefaultInstallFlags();
+ mm.loadPrefs();
+
+ // Dynamic detect all locales
+ new LoadLocale().exec();
+
+ // Create notification channel on Android O
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ NotificationChannel channel = new NotificationChannel(Const.ID.NOTIFICATION_CHANNEL,
+ getString(R.string.magisk_updates), NotificationManager.IMPORTANCE_DEFAULT);
+ getSystemService(NotificationManager.class).createNotificationChannel(channel);
+ }
+
+ // Setup shortcuts
+ sendBroadcast(new Intent(this, ShortcutReceiver.class));
+
+ LoadModules loadModuleTask = new LoadModules();
+
+ if (Utils.checkNetworkStatus()) {
+ // Fire update check
+ new CheckUpdates().exec();
+ // Add repo update check
+ loadModuleTask.setCallBack(() -> new UpdateRepos(false).exec());
+ }
+
+ // Magisk working as expected
+ if (Shell.rootAccess() && mm.magiskVersionCode > 0) {
+ // Update check service
+ mm.setupUpdateCheck();
+ // Fire asynctasks
+ loadModuleTask.exec();
+ }
+
+ // 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 {
+ @Override
+ protected Void doInBackground(Void... voids) {
+ MagiskManager.get().locales = Utils.getAvailableLocale();
+ return null;
+ }
+ @Override
+ protected void onPostExecute(Void aVoid) {
+ MagiskManager.get().localeDone.publish();
+ }
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/SuLogFragment.java b/app/src/full/java/com/topjohnwu/magisk/SuLogFragment.java
new file mode 100644
index 000000000..44bb9a932
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/SuLogFragment.java
@@ -0,0 +1,89 @@
+package com.topjohnwu.magisk;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+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;
+
+import com.topjohnwu.magisk.adapters.SuLogAdapter;
+import com.topjohnwu.magisk.components.Fragment;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.Unbinder;
+
+public class SuLogFragment extends Fragment {
+
+ @BindView(R.id.empty_rv) TextView emptyRv;
+ @BindView(R.id.recyclerView) RecyclerView recyclerView;
+
+ private Unbinder unbinder;
+ private MagiskManager mm;
+ private SuLogAdapter adapter;
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setHasOptionsMenu(true);
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ inflater.inflate(R.menu.menu_log, menu);
+ menu.findItem(R.id.menu_save).setVisible(false);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ // Inflate the layout for this fragment
+ View v = inflater.inflate(R.layout.fragment_su_log, container, false);
+ unbinder = ButterKnife.bind(this, v);
+ mm = getApplication();
+ adapter = new SuLogAdapter(mm.mDB);
+ recyclerView.setAdapter(adapter);
+
+ updateList();
+
+ return v;
+ }
+
+ private void updateList() {
+ adapter.notifyDBChanged();
+
+ if (adapter.getSectionCount() == 0) {
+ emptyRv.setVisibility(View.VISIBLE);
+ recyclerView.setVisibility(View.GONE);
+ } else {
+ emptyRv.setVisibility(View.GONE);
+ recyclerView.setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_refresh:
+ updateList();
+ return true;
+ case R.id.menu_clear:
+ mm.mDB.clearLogs();
+ updateList();
+ return true;
+ default:
+ return true;
+ }
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ unbinder.unbind();
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/SuperuserFragment.java b/app/src/full/java/com/topjohnwu/magisk/SuperuserFragment.java
new file mode 100644
index 000000000..1d6f2d79e
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/SuperuserFragment.java
@@ -0,0 +1,63 @@
+package com.topjohnwu.magisk;
+
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.topjohnwu.magisk.adapters.PolicyAdapter;
+import com.topjohnwu.magisk.components.Fragment;
+import com.topjohnwu.magisk.container.Policy;
+
+import java.util.List;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.Unbinder;
+
+public class SuperuserFragment extends Fragment {
+
+ private Unbinder unbinder;
+ @BindView(R.id.recyclerView) RecyclerView recyclerView;
+ @BindView(R.id.empty_rv) TextView emptyRv;
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_superuser, container, false);
+ unbinder = ButterKnife.bind(this, view);
+
+ PackageManager pm = getActivity().getPackageManager();
+ MagiskManager mm = getApplication();
+
+ List policyList = mm.mDB.getPolicyList(pm);
+
+ if (policyList.size() == 0) {
+ emptyRv.setVisibility(View.VISIBLE);
+ recyclerView.setVisibility(View.GONE);
+ } else {
+ recyclerView.setAdapter(new PolicyAdapter(policyList, mm.mDB, pm));
+ emptyRv.setVisibility(View.GONE);
+ recyclerView.setVisibility(View.VISIBLE);
+ }
+
+ return view;
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ getActivity().setTitle(getString(R.string.superuser));
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ unbinder.unbind();
+ }
+
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/adapters/ApplicationAdapter.java b/app/src/full/java/com/topjohnwu/magisk/adapters/ApplicationAdapter.java
new file mode 100644
index 000000000..8cad5b196
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/adapters/ApplicationAdapter.java
@@ -0,0 +1,158 @@
+package com.topjohnwu.magisk.adapters;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.support.v7.widget.RecyclerView;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CheckBox;
+import android.widget.Filter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.topjohnwu.magisk.MagiskManager;
+import com.topjohnwu.magisk.R;
+import com.topjohnwu.magisk.asyncs.ParallelTask;
+import com.topjohnwu.magisk.utils.Const;
+import com.topjohnwu.superuser.Shell;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+
+public class ApplicationAdapter extends RecyclerView.Adapter {
+
+ private List fullList, showList;
+ private List hideList;
+ private PackageManager pm;
+ private ApplicationFilter filter;
+
+ public ApplicationAdapter() {
+ fullList = showList = Collections.emptyList();
+ hideList = Collections.emptyList();
+ filter = new ApplicationFilter();
+ pm = MagiskManager.get().getPackageManager();
+ new LoadApps().exec();
+ }
+
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View mView = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_app, parent, false);
+ return new ViewHolder(mView);
+ }
+
+ @Override
+ public void onBindViewHolder(final ViewHolder holder, int position) {
+ ApplicationInfo info = showList.get(position);
+
+ holder.appIcon.setImageDrawable(info.loadIcon(pm));
+ holder.appName.setText(info.loadLabel(pm));
+ holder.appPackage.setText(info.packageName);
+
+ holder.checkBox.setOnCheckedChangeListener(null);
+ holder.checkBox.setChecked(hideList.contains(info.packageName));
+ holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> {
+ if (isChecked) {
+ Shell.Async.su("magiskhide --add " + info.packageName);
+ hideList.add(info.packageName);
+ } else {
+ Shell.Async.su("magiskhide --rm " + info.packageName);
+ hideList.remove(info.packageName);
+ }
+ });
+ }
+
+ @Override
+ public int getItemCount() {
+ return showList.size();
+ }
+
+ public void filter(String constraint) {
+ filter.filter(constraint);
+ }
+
+ public void refresh() {
+ new LoadApps().exec();
+ }
+
+ static class ViewHolder extends RecyclerView.ViewHolder {
+
+ @BindView(R.id.app_icon) ImageView appIcon;
+ @BindView(R.id.app_name) TextView appName;
+ @BindView(R.id.package_name) TextView appPackage;
+ @BindView(R.id.checkbox) CheckBox checkBox;
+
+ ViewHolder(View itemView) {
+ super(itemView);
+ ButterKnife.bind(this, itemView);
+ }
+ }
+
+ private class ApplicationFilter extends Filter {
+
+ private boolean lowercaseContains(String s, CharSequence filter) {
+ return !TextUtils.isEmpty(s) && s.toLowerCase().contains(filter);
+ }
+
+ @Override
+ protected FilterResults performFiltering(CharSequence constraint) {
+ if (constraint == null || constraint.length() == 0) {
+ showList = fullList;
+ } else {
+ showList = new ArrayList<>();
+ String filter = constraint.toString().toLowerCase();
+ for (ApplicationInfo info : fullList) {
+ if (lowercaseContains(info.loadLabel(pm).toString(), filter)
+ || lowercaseContains(info.packageName, filter)) {
+ showList.add(info);
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected void publishResults(CharSequence constraint, FilterResults results) {
+ notifyDataSetChanged();
+ }
+ }
+
+ private class LoadApps extends ParallelTask {
+
+ @Override
+ protected Void doInBackground(Void... voids) {
+ fullList = pm.getInstalledApplications(0);
+ hideList = Shell.Sync.su("magiskhide --ls");
+ for (Iterator i = fullList.iterator(); i.hasNext(); ) {
+ ApplicationInfo info = i.next();
+ if (Const.HIDE_BLACKLIST.contains(info.packageName) || !info.enabled) {
+ i.remove();
+ }
+ }
+ Collections.sort(fullList, (a, b) -> {
+ boolean ah = hideList.contains(a.packageName);
+ boolean bh = hideList.contains(b.packageName);
+ if (ah == bh) {
+ return a.loadLabel(pm).toString().toLowerCase().compareTo(
+ b.loadLabel(pm).toString().toLowerCase());
+ } else if (ah) {
+ return -1;
+ } else {
+ return 1;
+ }
+ });
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void v) {
+ MagiskManager.get().magiskHideDone.publish(false);
+ }
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/adapters/ModulesAdapter.java b/app/src/full/java/com/topjohnwu/magisk/adapters/ModulesAdapter.java
new file mode 100644
index 000000000..9ce3614f6
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/adapters/ModulesAdapter.java
@@ -0,0 +1,125 @@
+package com.topjohnwu.magisk.adapters;
+
+import android.content.Context;
+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;
+import android.widget.CheckBox;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.topjohnwu.magisk.R;
+import com.topjohnwu.magisk.components.SnackbarMaker;
+import com.topjohnwu.magisk.container.Module;
+import com.topjohnwu.superuser.Shell;
+
+import java.util.List;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+
+public class ModulesAdapter extends RecyclerView.Adapter {
+
+ private final List mList;
+
+ public ModulesAdapter(List list) {
+ mList = list;
+ }
+
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_module, parent, false);
+ return new ViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(final ViewHolder holder, int position) {
+ Context context = holder.itemView.getContext();
+ final Module module = mList.get(position);
+
+ String version = module.getVersion();
+ String author = module.getAuthor();
+ String description = module.getDescription();
+ String noInfo = context.getString(R.string.no_info_provided);
+
+ holder.title.setText(module.getName());
+ holder.versionName.setText( TextUtils.isEmpty(version) ? noInfo : version);
+ holder.author.setText( TextUtils.isEmpty(author) ? noInfo : context.getString(R.string.author, author));
+ holder.description.setText( TextUtils.isEmpty(description) ? noInfo : description);
+
+ holder.checkBox.setOnCheckedChangeListener(null);
+ holder.checkBox.setChecked(module.isEnabled());
+ holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> {
+ int snack;
+ if (isChecked) {
+ module.removeDisableFile();
+ snack = R.string.disable_file_removed;
+ } else {
+ module.createDisableFile();
+ snack = R.string.disable_file_created;
+ }
+ SnackbarMaker.make(holder.itemView, snack, Snackbar.LENGTH_SHORT).show();
+ });
+
+ holder.delete.setOnClickListener(v -> {
+ boolean removed = module.willBeRemoved();
+ int snack;
+ if (removed) {
+ module.deleteRemoveFile();
+ snack = R.string.remove_file_deleted;
+ } else {
+ module.createRemoveFile();
+ snack = R.string.remove_file_created;
+ }
+ SnackbarMaker.make(holder.itemView, snack, Snackbar.LENGTH_SHORT).show();
+ updateDeleteButton(holder, module);
+ });
+
+ if (module.isUpdated()) {
+ holder.notice.setVisibility(View.VISIBLE);
+ holder.notice.setText(R.string.update_file_created);
+ holder.delete.setEnabled(false);
+ } else {
+ updateDeleteButton(holder, module);
+ }
+ }
+
+ private void updateDeleteButton(ViewHolder holder, Module module) {
+ holder.notice.setVisibility(module.willBeRemoved() ? View.VISIBLE : View.GONE);
+
+ if (module.willBeRemoved()) {
+ holder.delete.setImageResource(R.drawable.ic_undelete);
+ } else {
+ holder.delete.setImageResource(R.drawable.ic_delete);
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return mList.size();
+ }
+
+ static class ViewHolder extends RecyclerView.ViewHolder {
+
+ @BindView(R.id.title) TextView title;
+ @BindView(R.id.version_name) TextView versionName;
+ @BindView(R.id.description) TextView description;
+ @BindView(R.id.notice) TextView notice;
+ @BindView(R.id.checkbox) CheckBox checkBox;
+ @BindView(R.id.author) TextView author;
+ @BindView(R.id.delete) ImageView delete;
+
+ ViewHolder(View itemView) {
+ super(itemView);
+ ButterKnife.bind(this, itemView);
+
+ if (!Shell.rootAccess()) {
+ checkBox.setEnabled(false);
+ delete.setEnabled(false);
+ }
+ }
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/adapters/PolicyAdapter.java b/app/src/full/java/com/topjohnwu/magisk/adapters/PolicyAdapter.java
new file mode 100644
index 000000000..abaa4e8c3
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/adapters/PolicyAdapter.java
@@ -0,0 +1,150 @@
+package com.topjohnwu.magisk.adapters;
+
+import android.app.Activity;
+import android.content.pm.PackageManager;
+import android.support.design.widget.Snackbar;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.Switch;
+import android.widget.TextView;
+
+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.MagiskDatabaseHelper;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+
+public class PolicyAdapter extends RecyclerView.Adapter {
+
+ private List policyList;
+ private MagiskDatabaseHelper dbHelper;
+ private PackageManager pm;
+ private Set expandList = new HashSet<>();
+
+ public PolicyAdapter(List list, MagiskDatabaseHelper db, PackageManager pm) {
+ policyList = list;
+ dbHelper = db;
+ this.pm = pm;
+ }
+
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_policy, parent, false);
+ return new ViewHolder(v);
+ }
+
+ @Override
+ public void onBindViewHolder(ViewHolder holder, int position) {
+ Policy policy = policyList.get(position);
+
+ holder.setExpanded(expandList.contains(policy));
+
+ holder.itemView.setOnClickListener(view -> {
+ if (holder.isExpanded()) {
+ holder.collapse();
+ expandList.remove(policy);
+ } else {
+ holder.expand();
+ expandList.add(policy);
+ }
+ });
+
+ holder.appName.setText(policy.appName);
+ holder.packageName.setText(policy.packageName);
+ holder.appIcon.setImageDrawable(policy.info.loadIcon(pm));
+ holder.masterSwitch.setOnCheckedChangeListener((v, isChecked) -> {
+ if ((isChecked && policy.policy == Policy.DENY) ||
+ (!isChecked && policy.policy == Policy.ALLOW)) {
+ policy.policy = isChecked ? Policy.ALLOW : Policy.DENY;
+ String message = v.getContext().getString(
+ isChecked ? R.string.su_snack_grant : R.string.su_snack_deny, policy.appName);
+ SnackbarMaker.make(holder.itemView, message, Snackbar.LENGTH_SHORT).show();
+ dbHelper.updatePolicy(policy);
+ }
+ });
+ holder.notificationSwitch.setOnCheckedChangeListener((v, isChecked) -> {
+ if ((isChecked && !policy.notification) ||
+ (!isChecked && policy.notification)) {
+ policy.notification = isChecked;
+ String message = v.getContext().getString(
+ isChecked ? R.string.su_snack_notif_on : R.string.su_snack_notif_off, policy.appName);
+ SnackbarMaker.make(holder.itemView, message, Snackbar.LENGTH_SHORT).show();
+ dbHelper.updatePolicy(policy);
+ }
+ });
+ holder.loggingSwitch.setOnCheckedChangeListener((v, isChecked) -> {
+ if ((isChecked && !policy.logging) ||
+ (!isChecked && policy.logging)) {
+ policy.logging = isChecked;
+ String message = v.getContext().getString(
+ isChecked ? R.string.su_snack_log_on : R.string.su_snack_log_off, policy.appName);
+ SnackbarMaker.make(holder.itemView, message, Snackbar.LENGTH_SHORT).show();
+ dbHelper.updatePolicy(policy);
+ }
+ });
+ holder.delete.setOnClickListener(v -> new AlertDialogBuilder((Activity) v.getContext())
+ .setTitle(R.string.su_revoke_title)
+ .setMessage(v.getContext().getString(R.string.su_revoke_msg, policy.appName))
+ .setPositiveButton(R.string.yes, (dialog, which) -> {
+ policyList.remove(position);
+ notifyItemRemoved(position);
+ notifyItemRangeChanged(position, policyList.size());
+ SnackbarMaker.make(holder.itemView, v.getContext().getString(R.string.su_snack_revoke, policy.appName),
+ Snackbar.LENGTH_SHORT).show();
+ dbHelper.deletePolicy(policy);
+ })
+ .setNegativeButton(R.string.no_thanks, null)
+ .setCancelable(true)
+ .show());
+ holder.masterSwitch.setChecked(policy.policy == Policy.ALLOW);
+ holder.notificationSwitch.setChecked(policy.notification);
+ holder.loggingSwitch.setChecked(policy.logging);
+
+ // Hide for now
+ holder.moreInfo.setVisibility(View.GONE);
+ }
+
+ @Override
+ public int getItemCount() {
+ return policyList.size();
+ }
+
+ static class ViewHolder extends RecyclerView.ViewHolder implements ExpandableView {
+
+ @BindView(R.id.app_name) TextView appName;
+ @BindView(R.id.package_name) TextView packageName;
+ @BindView(R.id.app_icon) ImageView appIcon;
+ @BindView(R.id.master_switch) Switch masterSwitch;
+ @BindView(R.id.notification_switch) Switch notificationSwitch;
+ @BindView(R.id.logging_switch) Switch loggingSwitch;
+ @BindView(R.id.expand_layout) ViewGroup expandLayout;
+
+ @BindView(R.id.delete) ImageView delete;
+ @BindView(R.id.more_info) ImageView moreInfo;
+
+ private Container container = new Container();
+
+ public ViewHolder(View itemView) {
+ super(itemView);
+ ButterKnife.bind(this, itemView);
+ container.expandLayout = expandLayout;
+ setupExpandable();
+ }
+
+ @Override
+ public Container getContainer() {
+ return container;
+ }
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/adapters/ReposAdapter.java b/app/src/full/java/com/topjohnwu/magisk/adapters/ReposAdapter.java
new file mode 100644
index 000000000..959d074dd
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/adapters/ReposAdapter.java
@@ -0,0 +1,192 @@
+package com.topjohnwu.magisk.adapters;
+
+import android.app.Activity;
+import android.content.Context;
+import android.database.Cursor;
+import android.support.v7.widget.RecyclerView;
+import android.text.TextUtils;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+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.utils.Utils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+
+public class ReposAdapter extends SectionedAdapter {
+
+ private static final int UPDATES = 0;
+ private static final int INSTALLED = 1;
+ private static final int OTHERS = 2;
+
+ private Cursor repoCursor = null;
+ private Map moduleMap;
+ private RepoDatabaseHelper repoDB;
+ private List>> repoPairs;
+
+ public ReposAdapter(RepoDatabaseHelper db, Map map) {
+ repoDB = db;
+ moduleMap = map;
+ repoPairs = new ArrayList<>();
+ notifyDBChanged();
+ }
+
+
+ @Override
+ public int getSectionCount() {
+ return repoPairs.size();
+ }
+
+ @Override
+ public int getItemCount(int section) {
+ return repoPairs.get(section).second.size();
+ }
+
+ @Override
+ public SectionHolder onCreateSectionViewHolder(ViewGroup parent) {
+ View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.section, parent, false);
+ return new SectionHolder(v);
+ }
+
+ @Override
+ public RepoHolder onCreateItemViewHolder(ViewGroup parent, int viewType) {
+ View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_repo, parent, false);
+ return new RepoHolder(v);
+ }
+
+ @Override
+ public void onBindSectionViewHolder(SectionHolder holder, int section) {
+ switch (repoPairs.get(section).first) {
+ case UPDATES:
+ holder.sectionText.setText(R.string.update_available);
+ break;
+ case INSTALLED:
+ holder.sectionText.setText(R.string.installed);
+ break;
+ case OTHERS:
+ holder.sectionText.setText(R.string.not_installed);
+ break;
+ }
+ }
+
+ @Override
+ public void onBindItemViewHolder(RepoHolder holder, int section, int position) {
+ Repo repo = repoPairs.get(section).second.get(position);
+ Context context = holder.itemView.getContext();
+
+ holder.title.setText(repo.getName());
+ holder.versionName.setText(repo.getVersion());
+ 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());
+
+ holder.downloadImage.setOnClickListener(v -> {
+ String filename = repo.getName() + "-" + repo.getVersion() + ".zip";
+ new AlertDialogBuilder((Activity) context)
+ .setTitle(context.getString(R.string.repo_install_title, repo.getName()))
+ .setMessage(context.getString(R.string.repo_install_msg, filename))
+ .setCancelable(true)
+ .setPositiveButton(R.string.install, (d, i) ->
+ new ProcessRepoZip((Activity) context, repo.getZipUrl(),
+ Utils.getLegalFilename(filename), true).exec()
+ )
+ .setNeutralButton(R.string.download, (d, i) ->
+ new ProcessRepoZip((Activity) context, repo.getZipUrl(),
+ Utils.getLegalFilename(filename), false).exec())
+ .setNegativeButton(R.string.no_thanks, null)
+ .show();
+ });
+ }
+
+ public void notifyDBChanged() {
+ if (repoCursor != null)
+ repoCursor.close();
+ repoCursor = repoDB.getRepoCursor();
+ filter("");
+ }
+
+ public void filter(String s) {
+ List updates = new ArrayList<>();
+ List installed = new ArrayList<>();
+ List others = new ArrayList<>();
+
+ repoPairs.clear();
+ while (repoCursor.moveToNext()) {
+ Repo repo = new Repo(repoCursor);
+ if (repo.getName().toLowerCase().contains(s.toLowerCase())
+ || repo.getAuthor().toLowerCase().contains(s.toLowerCase())
+ || repo.getDescription().toLowerCase().contains(s.toLowerCase())
+ ) {
+ // Passed the repoFilter
+ Module module = moduleMap.get(repo.getId());
+ if (module != null) {
+ if (repo.getVersionCode() > module.getVersionCode()) {
+ // Updates
+ updates.add(repo);
+ } else {
+ installed.add(repo);
+ }
+ } else {
+ others.add(repo);
+ }
+ }
+ }
+ repoCursor.moveToFirst();
+
+ if (!updates.isEmpty())
+ repoPairs.add(new Pair<>(UPDATES, updates));
+ if (!installed.isEmpty())
+ repoPairs.add(new Pair<>(INSTALLED, installed));
+ if (!others.isEmpty())
+ repoPairs.add(new Pair<>(OTHERS, others));
+
+ notifyDataSetChanged();
+ }
+
+ static class SectionHolder extends RecyclerView.ViewHolder {
+
+ @BindView(R.id.section_text) TextView sectionText;
+
+ SectionHolder(View itemView) {
+ super(itemView);
+ ButterKnife.bind(this, itemView);
+ }
+ }
+
+ static class RepoHolder extends RecyclerView.ViewHolder {
+
+ @BindView(R.id.title) TextView title;
+ @BindView(R.id.version_name) TextView versionName;
+ @BindView(R.id.description) TextView description;
+ @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);
+ ButterKnife.bind(this, itemView);
+ }
+
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/adapters/SectionedAdapter.java b/app/src/full/java/com/topjohnwu/magisk/adapters/SectionedAdapter.java
new file mode 100644
index 000000000..cbfab0698
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/adapters/SectionedAdapter.java
@@ -0,0 +1,93 @@
+package com.topjohnwu.magisk.adapters;
+
+import android.support.v7.widget.RecyclerView;
+import android.view.ViewGroup;
+
+public abstract class SectionedAdapter
+ extends RecyclerView.Adapter {
+
+ private static final int SECTION_TYPE = Integer.MIN_VALUE;
+
+ @Override
+ final public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ if (viewType == SECTION_TYPE)
+ return onCreateSectionViewHolder(parent);
+ return onCreateItemViewHolder(parent, viewType);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ final public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+ PositionInfo info = getPositionInfo(position);
+ if (info.position == -1)
+ onBindSectionViewHolder((S) holder, info.section);
+ else
+ onBindItemViewHolder((C) holder, info.section, info.position);
+ }
+
+ @Override
+ final public int getItemCount() {
+ int size, sec;
+ size = sec = getSectionCount();
+ for (int i = 0; i < sec; ++i){
+ size += getItemCount(i);
+ }
+ return size;
+ }
+
+ @Override
+ final public int getItemViewType(int position) {
+ PositionInfo info = getPositionInfo(position);
+ if (info.position == -1)
+ return SECTION_TYPE;
+ else
+ return getItemViewType(info.section, info.position);
+ }
+
+ public int getItemViewType(int section, int position) {
+ return 0;
+ }
+
+ protected int getSectionPosition(int section) {
+ return getItemPosition(section, -1);
+ }
+
+ protected int getItemPosition(int section, int position) {
+ int realPosition = 0;
+ // Previous sections
+ for (int i = 0; i < section; ++i) {
+ realPosition += getItemCount(i) + 1;
+ }
+ // Current section
+ realPosition += position + 1;
+ return realPosition;
+ }
+
+ private PositionInfo getPositionInfo(int position) {
+ int section = 0;
+ while (true) {
+ if (position == 0)
+ return new PositionInfo(section, -1);
+ position -= 1;
+ if (position < getItemCount(section))
+ return new PositionInfo(section, position);
+ position -= getItemCount(section++);
+ }
+ }
+
+ private static class PositionInfo {
+ int section;
+ int position;
+ PositionInfo(int section, int position) {
+ this.section = section;
+ this.position = position;
+ }
+ }
+
+ public abstract int getSectionCount();
+ public abstract int getItemCount(int section);
+ public abstract S onCreateSectionViewHolder(ViewGroup parent);
+ public abstract C onCreateItemViewHolder(ViewGroup parent, int viewType);
+ public abstract void onBindSectionViewHolder(S holder, int section);
+ public abstract void onBindItemViewHolder(C holder, int section, int position);
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/adapters/SuLogAdapter.java b/app/src/full/java/com/topjohnwu/magisk/adapters/SuLogAdapter.java
new file mode 100644
index 000000000..0d9138d9b
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/adapters/SuLogAdapter.java
@@ -0,0 +1,155 @@
+package com.topjohnwu.magisk.adapters;
+
+import android.database.Cursor;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Animation;
+import android.view.animation.RotateAnimation;
+import android.widget.ImageView;
+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.MagiskDatabaseHelper;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+
+public class SuLogAdapter extends SectionedAdapter {
+
+ private List> logEntryList;
+ private Set itemExpanded, sectionExpanded;
+ private MagiskDatabaseHelper suDB;
+ private Cursor suLogCursor = null;
+
+ public SuLogAdapter(MagiskDatabaseHelper db) {
+ suDB = db;
+ logEntryList = Collections.emptyList();
+ sectionExpanded = new HashSet<>();
+ itemExpanded = new HashSet<>();
+ }
+
+ @Override
+ public int getSectionCount() {
+ return logEntryList.size();
+ }
+
+ @Override
+ public int getItemCount(int section) {
+ return sectionExpanded.contains(section) ? logEntryList.get(section).size() : 0;
+ }
+
+ @Override
+ public SectionHolder onCreateSectionViewHolder(ViewGroup parent) {
+ View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_sulog_group, parent, false);
+ return new SectionHolder(v);
+ }
+
+ @Override
+ public LogViewHolder onCreateItemViewHolder(ViewGroup parent, int viewType) {
+ View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_sulog, parent, false);
+ return new LogViewHolder(v);
+ }
+
+ @Override
+ public void onBindSectionViewHolder(SectionHolder holder, int section) {
+ suLogCursor.moveToPosition(logEntryList.get(section).get(0));
+ SuLogEntry entry = new SuLogEntry(suLogCursor);
+ holder.arrow.setRotation(sectionExpanded.contains(section) ? 180 : 0);
+ holder.itemView.setOnClickListener(v -> {
+ RotateAnimation rotate;
+ if (sectionExpanded.contains(section)) {
+ holder.arrow.setRotation(0);
+ rotate = new RotateAnimation(180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
+ sectionExpanded.remove(section);
+ notifyItemRangeRemoved(getItemPosition(section, 0), logEntryList.get(section).size());
+ } else {
+ rotate = new RotateAnimation(0, 180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
+ sectionExpanded.add(section);
+ notifyItemRangeInserted(getItemPosition(section, 0), logEntryList.get(section).size());
+ }
+ rotate.setDuration(300);
+ rotate.setFillAfter(true);
+ holder.arrow.setAnimation(rotate);
+ });
+ holder.date.setText(entry.getDateString());
+ }
+
+ @Override
+ public void onBindItemViewHolder(LogViewHolder holder, int section, int position) {
+ int sqlPosition = logEntryList.get(section).get(position);
+ suLogCursor.moveToPosition(sqlPosition);
+ SuLogEntry entry = new SuLogEntry(suLogCursor);
+ holder.setExpanded(itemExpanded.contains(sqlPosition));
+ holder.itemView.setOnClickListener(view -> {
+ if (holder.isExpanded()) {
+ holder.collapse();
+ itemExpanded.remove(sqlPosition);
+ } else {
+ holder.expand();
+ itemExpanded.add(sqlPosition);
+ }
+ });
+ holder.appName.setText(entry.appName);
+ holder.action.setText(entry.action ? R.string.grant : R.string.deny);
+ holder.command.setText(entry.command);
+ holder.fromPid.setText(String.valueOf(entry.fromPid));
+ holder.toUid.setText(String.valueOf(entry.toUid));
+ holder.time.setText(entry.getTimeString());
+ }
+
+ public void notifyDBChanged() {
+ if (suLogCursor != null)
+ suLogCursor.close();
+ suLogCursor = suDB.getLogCursor();
+ logEntryList = suDB.getLogStructure();
+ itemExpanded.clear();
+ sectionExpanded.clear();
+ sectionExpanded.add(0);
+ notifyDataSetChanged();
+ }
+
+ static class SectionHolder extends RecyclerView.ViewHolder {
+
+ @BindView(R.id.date) TextView date;
+ @BindView(R.id.arrow) ImageView arrow;
+
+ SectionHolder(View itemView) {
+ super(itemView);
+ ButterKnife.bind(this, itemView);
+ }
+ }
+
+ static class LogViewHolder extends RecyclerView.ViewHolder implements ExpandableView {
+
+ @BindView(R.id.app_name) TextView appName;
+ @BindView(R.id.action) TextView action;
+ @BindView(R.id.time) TextView time;
+ @BindView(R.id.fromPid) TextView fromPid;
+ @BindView(R.id.toUid) TextView toUid;
+ @BindView(R.id.command) TextView command;
+ @BindView(R.id.expand_layout) ViewGroup expandLayout;
+
+ private Container container = new Container();
+
+ LogViewHolder(View itemView) {
+ super(itemView);
+ ButterKnife.bind(this, itemView);
+ container.expandLayout = expandLayout;
+ setupExpandable();
+ }
+
+ @Override
+ public Container getContainer() {
+ return container;
+ }
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/adapters/TabFragmentAdapter.java b/app/src/full/java/com/topjohnwu/magisk/adapters/TabFragmentAdapter.java
new file mode 100644
index 000000000..c7daba71f
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/adapters/TabFragmentAdapter.java
@@ -0,0 +1,41 @@
+package com.topjohnwu.magisk.adapters;
+
+
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentPagerAdapter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class TabFragmentAdapter extends FragmentPagerAdapter {
+
+ private List fragmentList;
+ private List titleList;
+
+ public TabFragmentAdapter(FragmentManager fm) {
+ super(fm);
+ fragmentList = new ArrayList<>();
+ titleList = new ArrayList<>();
+ }
+
+ @Override
+ public Fragment getItem(int position) {
+ return fragmentList.get(position);
+ }
+
+ @Override
+ public int getCount() {
+ return fragmentList.size();
+ }
+
+ @Override
+ public CharSequence getPageTitle(int position) {
+ return titleList.get(position);
+ }
+
+ public void addTab(Fragment fragment, String title) {
+ fragmentList.add(fragment);
+ titleList.add(title);
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/asyncs/CheckSafetyNet.java b/app/src/full/java/com/topjohnwu/magisk/asyncs/CheckSafetyNet.java
new file mode 100644
index 000000000..9fb81bbac
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/asyncs/CheckSafetyNet.java
@@ -0,0 +1,84 @@
+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.ISafetyNetHelper;
+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.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+
+import dalvik.system.DexClassLoader;
+
+public class CheckSafetyNet extends ParallelTask {
+
+ public static final File dexPath =
+ new File(MagiskManager.get().getFilesDir().getParent() + "/snet", "snet.apk");
+ private ISafetyNetHelper helper;
+
+ public CheckSafetyNet(Activity activity) {
+ super(activity);
+ }
+
+ private void dlSnet() throws Exception {
+ Shell.Sync.sh("rm -rf " + dexPath.getParent());
+ dexPath.getParentFile().mkdir();
+ HttpURLConnection conn = WebService.request(Const.Url.SNET_URL, null);
+ try (
+ OutputStream out = new BufferedOutputStream(new FileOutputStream(dexPath));
+ InputStream in = new BufferedInputStream(conn.getInputStream())) {
+ ShellUtils.pump(in, out);
+ } finally {
+ conn.disconnect();
+ }
+ }
+
+ private void dyload() throws Exception {
+ DexClassLoader loader = new DexClassLoader(dexPath.getPath(), dexPath.getParent(),
+ null, ISafetyNetHelper.class.getClassLoader());
+ Class> clazz = loader.loadClass("com.topjohnwu.snet.SafetyNetHelper");
+ helper = (ISafetyNetHelper) clazz.getConstructors()[0]
+ .newInstance(getActivity(), (ISafetyNetHelper.Callback)
+ code -> MagiskManager.get().safetyNetDone.publish(false, code));
+ if (helper.getVersion() != Const.SNET_VER) {
+ throw new Exception();
+ }
+ }
+
+ @Override
+ protected Exception doInBackground(Void... voids) {
+ try {
+ try {
+ dyload();
+ } catch (Exception e) {
+ // If dynamic load failed, try re-downloading and reload
+ dlSnet();
+ dyload();
+ }
+ } catch (Exception e) {
+ return e;
+ }
+
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Exception e) {
+ if (e == null) {
+ helper.attest();
+ } else {
+ e.printStackTrace();
+ MagiskManager.get().safetyNetDone.publish(false, -1);
+ }
+ super.onPostExecute(e);
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/asyncs/CheckUpdates.java b/app/src/full/java/com/topjohnwu/magisk/asyncs/CheckUpdates.java
new file mode 100644
index 000000000..b8e6495a4
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/asyncs/CheckUpdates.java
@@ -0,0 +1,70 @@
+package com.topjohnwu.magisk.asyncs;
+
+import com.topjohnwu.magisk.BuildConfig;
+import com.topjohnwu.magisk.MagiskManager;
+import com.topjohnwu.magisk.utils.Const;
+import com.topjohnwu.magisk.utils.ShowUI;
+import com.topjohnwu.magisk.utils.WebService;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class CheckUpdates extends ParallelTask {
+
+ private boolean showNotification;
+
+ public CheckUpdates() {
+ this(false);
+ }
+
+ public CheckUpdates(boolean b) {
+ showNotification = b;
+ }
+
+ @Override
+ protected Void doInBackground(Void... voids) {
+ MagiskManager mm = MagiskManager.get();
+ String jsonStr = "";
+ switch (mm.updateChannel) {
+ case Const.Value.STABLE_CHANNEL:
+ jsonStr = WebService.getString(Const.Url.STABLE_URL);
+ break;
+ 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;
+ }
+ try {
+ JSONObject json = new JSONObject(jsonStr);
+ JSONObject magisk = json.getJSONObject("magisk");
+ mm.remoteMagiskVersionString = magisk.getString("version");
+ mm.remoteMagiskVersionCode = magisk.getInt("versionCode");
+ mm.magiskLink = magisk.getString("link");
+ mm.magiskNoteLink = magisk.getString("note");
+ JSONObject manager = json.getJSONObject("app");
+ mm.remoteManagerVersionString = manager.getString("version");
+ mm.remoteManagerVersionCode = manager.getInt("versionCode");
+ mm.managerLink = manager.getString("link");
+ mm.managerNoteLink = manager.getString("note");
+ JSONObject uninstaller = json.getJSONObject("uninstaller");
+ mm.uninstallerLink = uninstaller.getString("link");
+ } catch (JSONException ignored) {}
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void v) {
+ MagiskManager mm = MagiskManager.get();
+ if (showNotification) {
+ if (BuildConfig.VERSION_CODE < mm.remoteManagerVersionCode) {
+ ShowUI.managerUpdateNotification();
+ } else if (mm.magiskVersionCode < mm.remoteMagiskVersionCode) {
+ ShowUI.magiskUpdateNotification();
+ }
+ }
+ mm.updateCheckDone.publish();
+ super.onPostExecute(v);
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/asyncs/FlashZip.java b/app/src/full/java/com/topjohnwu/magisk/asyncs/FlashZip.java
new file mode 100644
index 000000000..85255425a
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/asyncs/FlashZip.java
@@ -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.components.SnackbarMaker;
+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 {
+
+ private Uri mUri;
+ private File mCachedFile;
+ private List console, logs;
+
+ public FlashZip(Activity context, Uri uri, List console, List 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);
+ return ShellUtils.fastCmdResult("grep -q '#MAGISK' " + new File(mCachedFile.getParentFile(), "updater-script"));
+ }
+
+ @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");
+ SnackbarMaker.showUri(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);
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/asyncs/HideManager.java b/app/src/full/java/com/topjohnwu/magisk/asyncs/HideManager.java
new file mode 100644
index 000000000..92c7bf8a6
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/asyncs/HideManager.java
@@ -0,0 +1,95 @@
+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.PatchAPK;
+import com.topjohnwu.magisk.utils.RootUtils;
+import com.topjohnwu.superuser.Shell;
+import com.topjohnwu.superuser.ShellUtils;
+import com.topjohnwu.superuser.io.SuFile;
+import com.topjohnwu.superuser.io.SuFileOutputStream;
+
+import java.io.FileNotFoundException;
+import java.security.SecureRandom;
+
+public class HideManager extends ParallelTask {
+
+ 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();
+ }
+
+ @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 app with random package name
+ SuFile repack = new SuFile("/data/local/tmp/repack.apk");
+ String pkg = genPackageName("com.", Const.ORIG_PKG_NAME.length());
+
+ try {
+ if (!PatchAPK.patchPackageID(
+ mm.getPackageCodePath(),
+ new SuFileOutputStream(repack),
+ Const.ORIG_PKG_NAME, pkg))
+ return false;
+ } catch (FileNotFoundException e) {
+ return false;
+ }
+
+ // Install the application
+ if (!ShellUtils.fastCmdResult(Shell.getShell(), "pm install " + repack))
+ return false;
+
+ repack.delete();
+
+ mm.mDB.setStrings(Const.Key.SU_MANAGER, pkg);
+ mm.dumpPrefs();
+ RootUtils.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);
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/asyncs/InstallMagisk.java b/app/src/full/java/com/topjohnwu/magisk/asyncs/InstallMagisk.java
new file mode 100644
index 000000000..988d37632
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/asyncs/InstallMagisk.java
@@ -0,0 +1,327 @@
+package com.topjohnwu.magisk.asyncs;
+
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.net.Uri;
+import android.os.Build;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.Toast;
+
+import com.topjohnwu.magisk.FlashActivity;
+import com.topjohnwu.magisk.MagiskManager;
+import com.topjohnwu.magisk.R;
+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.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.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.AbstractList;
+import java.util.Arrays;
+import java.util.List;
+
+public class InstallMagisk extends ParallelTask {
+
+ private static final int PATCH_MODE = 0;
+ public static final int DIRECT_MODE = 1;
+ private static final int FIX_ENV_MODE = 2;
+ public static final int SECOND_SLOT_MODE = 3;
+
+ private Uri bootUri, mZip;
+ private List console, logs;
+ private String mBoot;
+ private int mode;
+ private File installDir;
+ private ProgressDialog dialog;
+ private MagiskManager mm;
+
+ public InstallMagisk(Activity context, Uri zip) {
+ super(context);
+ mZip = zip;
+ mm = MagiskManager.get();
+ mode = FIX_ENV_MODE;
+ }
+
+ public InstallMagisk(Activity context, List console, List logs, Uri zip, int mode) {
+ this(context, zip);
+ this.console = console;
+ this.logs = logs;
+ this.mode = mode;
+ }
+
+ public InstallMagisk(FlashActivity context, List console, List logs, Uri zip, Uri boot) {
+ this(context, console, logs, zip, PATCH_MODE);
+ bootUri = boot;
+ }
+
+ @Override
+ protected void onPreExecute() {
+ if (mode == FIX_ENV_MODE) {
+ Activity a = getActivity();
+ dialog = ProgressDialog.show(a, a.getString(R.string.setup_title), a.getString(R.string.setup_msg));
+ console = new NOPList<>();
+ }
+ }
+
+ private void extractFiles(String arch) throws IOException {
+ 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, installDir, arch + "/", true);
+ buf.reset();
+ ZipUtils.unzip(buf, installDir, "common/", true);
+ buf.reset();
+ ZipUtils.unzip(buf, installDir, "chromeos/", false);
+ buf.reset();
+ ZipUtils.unzip(buf, installDir, "META-INF/com/google/android/update-binary", true);
+ buf.close();
+ } catch (FileNotFoundException e) {
+ console.add("! Invalid Uri");
+ throw e;
+ } catch (IOException e) {
+ console.add("! Cannot unzip zip");
+ throw e;
+ }
+ Shell.Sync.sh(Utils.fmt("chmod -R 755 %s/*; %s/magiskinit -x magisk %s/magisk",
+ installDir, installDir, installDir));
+ }
+
+ private boolean dumpBoot() {
+ console.add("- Copying image locally");
+ // Copy boot image to local
+ try (InputStream in = mm.getContentResolver().openInputStream(bootUri);
+ OutputStream out = new FileOutputStream(mBoot)
+ ) {
+ if (in == null)
+ throw new FileNotFoundException();
+
+ InputStream src;
+ if (Utils.getNameFromUri(mm, bootUri).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;
+ }
+ src = tar;
+ } else {
+ // Direct copy raw image
+ src = new BufferedInputStream(in);
+ }
+ ShellUtils.pump(src, out);
+ } catch (FileNotFoundException e) {
+ console.add("! Invalid Uri");
+ return false;
+ } catch (IOException e) {
+ console.add("! Copy failed");
+ return false;
+ }
+ return true;
+ }
+
+ private File patchBoot() throws IOException {
+ boolean isSigned;
+ try (InputStream in = new SuFileInputStream(mBoot)) {
+ isSigned = SignBoot.verifySignature(in, null);
+ if (isSigned) {
+ console.add("- Boot image is signed with AVB 1.0");
+ }
+ } catch (IOException e) {
+ console.add("! Unable to check signature");
+ throw e;
+ }
+
+ // Patch boot image
+ Shell.Sync.sh(console, logs,
+ "cd " + installDir,
+ Utils.fmt("KEEPFORCEENCRYPT=%b KEEPVERITY=%b sh update-binary indep " +
+ "boot_patch.sh %s || echo 'Failed!'",
+ mm.keepEnc, mm.keepVerity, mBoot));
+
+ if (TextUtils.equals(console.get(console.size() - 1), "Failed!"))
+ return null;
+
+ Shell.Sync.sh("mv bin/busybox busybox",
+ "rm -rf magisk.apk bin boot.img update-binary",
+ "cd /");
+
+ File patched = new File(installDir, "new-boot.img");
+ if (isSigned) {
+ console.add("- Signing boot image with test keys");
+ File signed = new File(installDir, "signed.img");
+ try (InputStream in = new SuFileInputStream(patched);
+ OutputStream out = new BufferedOutputStream(new FileOutputStream(signed))
+ ) {
+ SignBoot.doSignature("/boot", in, out, null, null);
+ }
+ Shell.Sync.su("mv -f " + signed + " " + patched);
+ }
+ return patched;
+ }
+
+ private void outputBoot(File patched) throws IOException {
+ 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.img"));
+ break;
+ default:
+ case ".img":
+ out = new BufferedOutputStream(new FileOutputStream(dest));
+ break;
+ }
+ try (InputStream in = new SuFileInputStream(patched)) {
+ ShellUtils.pump(in, out);
+ out.close();
+ }
+ Shell.Sync.su("rm -f " + patched);
+ console.add("");
+ console.add("****************************");
+ console.add(" Patched image is placed in ");
+ console.add(" " + dest + " ");
+ console.add("****************************");
+ break;
+ case SECOND_SLOT_MODE:
+ case DIRECT_MODE:
+ Shell.Sync.sh(console, logs,
+ Utils.fmt("direct_install %s %s %s", patched, mBoot, installDir));
+ if (!mm.keepVerity)
+ Shell.Sync.sh(console, logs, "find_dtbo_image", "patch_dtbo_image");
+ break;
+ }
+ }
+
+ @Override
+ protected Boolean doInBackground(Void... voids) {
+ if (mode == FIX_ENV_MODE) {
+ installDir = new File("/data/adb/magisk");
+ Shell.Sync.sh("rm -rf /data/adb/magisk/*");
+ } else {
+ installDir = new File(
+ (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ?
+ mm.createDeviceProtectedStorageContext() : mm)
+ .getFilesDir().getParent()
+ , "install");
+ Shell.Sync.sh("rm -rf " + installDir);
+ installDir.mkdirs();
+ }
+
+ switch (mode) {
+ case PATCH_MODE:
+ mBoot = new File(installDir, "boot.img").getAbsolutePath();
+ if (!dumpBoot())
+ return false;
+ break;
+ case DIRECT_MODE:
+ console.add("- Detecting target image");
+ mBoot = ShellUtils.fastCmd("find_boot_image", "echo \"$BOOTIMAGE\"");
+ break;
+ case SECOND_SLOT_MODE:
+ console.add("- Detecting target image");
+ char slot[] = ShellUtils.fastCmd("echo $SLOT").toCharArray();
+ if (slot[1] == 'a') slot[1] = 'b';
+ else slot[1] = 'a';
+ mBoot = ShellUtils.fastCmd("SLOT=" + String.valueOf(slot),
+ "find_boot_image", "echo \"$BOOTIMAGE\"");
+ Shell.Async.su("mount_partitions");
+ break;
+ case FIX_ENV_MODE:
+ mBoot = "";
+ break;
+ }
+ if (mBoot == null) {
+ console.add("! Unable to detect target image");
+ return false;
+ }
+
+ console.add("- Target image: " + mBoot);
+
+ List abis = Arrays.asList(Build.SUPPORTED_ABIS);
+ String arch;
+
+ if (mm.remoteMagiskVersionCode >= Const.MAGISK_VER.SEPOL_REFACTOR) {
+ // 32-bit only
+ if (abis.contains("x86")) arch = "x86";
+ else arch = "arm";
+ } else {
+ if (abis.contains("x86_64")) arch = "x64";
+ else if (abis.contains("arm64-v8a")) arch = "arm64";
+ else if (abis.contains("x86")) arch = "x86";
+ else arch = "arm";
+ }
+
+ console.add("- Device platform: " + Build.SUPPORTED_ABIS[0]);
+
+ try {
+ extractFiles(arch);
+ if (mode == FIX_ENV_MODE) {
+ Shell.Sync.sh("fix_env");
+ } else {
+ File patched = patchBoot();
+ if (patched == null)
+ return false;
+ outputBoot(patched);
+ console.add("- All done!");
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ if (mode == FIX_ENV_MODE) {
+ dialog.dismiss();
+ MagiskManager.toast(result ? R.string.setup_done : R.string.setup_fail, Toast.LENGTH_LONG);
+ } else {
+ // Running in FlashActivity
+ FlashActivity activity = (FlashActivity) getActivity();
+ if (!result) {
+ Shell.Async.sh("rm -rf " + installDir);
+ console.add("! Installation failed");
+ activity.reboot.setVisibility(View.GONE);
+ }
+ activity.buttonPanel.setVisibility(View.VISIBLE);
+ }
+ }
+
+ private static class NOPList extends AbstractList {
+ @Override
+ public E get(int index) {
+ return null;
+ }
+
+ @Override
+ public int size() {
+ return 0;
+ }
+
+ @Override
+ public void add(int index, E element) {}
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/asyncs/LoadModules.java b/app/src/full/java/com/topjohnwu/magisk/asyncs/LoadModules.java
new file mode 100644
index 000000000..06c95df49
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/asyncs/LoadModules.java
@@ -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 {
+
+ private List 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);
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/asyncs/MarkDownWindow.java b/app/src/full/java/com/topjohnwu/magisk/asyncs/MarkDownWindow.java
new file mode 100644
index 000000000..06a31ef92
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/asyncs/MarkDownWindow.java
@@ -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 {
+
+ 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("%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();
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/asyncs/ParallelTask.java b/app/src/full/java/com/topjohnwu/magisk/asyncs/ParallelTask.java
new file mode 100644
index 000000000..bd94e6365
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/asyncs/ParallelTask.java
@@ -0,0 +1,39 @@
+package com.topjohnwu.magisk.asyncs;
+
+import android.app.Activity;
+import android.os.AsyncTask;
+
+import java.lang.ref.WeakReference;
+
+public abstract class ParallelTask extends AsyncTask {
+
+ private WeakReference weakActivity;
+
+ private Runnable callback = null;
+
+ public ParallelTask() {}
+
+ public ParallelTask(Activity context) {
+ weakActivity = new WeakReference<>(context);
+ }
+
+ protected Activity getActivity() {
+ return weakActivity.get();
+ }
+
+ @SuppressWarnings("unchecked")
+ public ParallelTask exec(Params... params) {
+ executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
+ return this;
+ }
+
+ @Override
+ protected void onPostExecute(Result result) {
+ if (callback != null) callback.run();
+ }
+
+ public ParallelTask setCallBack(Runnable next) {
+ callback = next;
+ return this;
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/asyncs/ProcessRepoZip.java b/app/src/full/java/com/topjohnwu/magisk/asyncs/ProcessRepoZip.java
new file mode 100644
index 000000000..f0f564b68
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/asyncs/ProcessRepoZip.java
@@ -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.components.SnackbarMaker;
+import com.topjohnwu.magisk.utils.Const;
+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 {
+
+ 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);
+ 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 {
+ SnackbarMaker.showUri(activity, uri);
+ }
+ } else {
+ MagiskManager.toast(R.string.process_error, Toast.LENGTH_LONG);
+ }
+ super.onPostExecute(result);
+ }
+
+ @Override
+ public ParallelTask exec(Void... voids) {
+ com.topjohnwu.magisk.components.Activity.runWithPermission(
+ getActivity(), new String[] { 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;
+ }
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/asyncs/RestoreImages.java b/app/src/full/java/com/topjohnwu/magisk/asyncs/RestoreImages.java
new file mode 100644
index 000000000..fb28a2dd5
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/asyncs/RestoreImages.java
@@ -0,0 +1,39 @@
+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.superuser.ShellUtils;
+
+public class RestoreImages extends ParallelTask {
+
+ private ProgressDialog dialog;
+
+ public RestoreImages(Activity activity) {
+ super(activity);
+ }
+
+ @Override
+ protected void onPreExecute() {
+ Activity a = getActivity();
+ dialog = ProgressDialog.show(a, a.getString(R.string.restore_img), a.getString(R.string.restore_img_msg));
+ }
+
+ @Override
+ protected Boolean doInBackground(Void... voids) {
+ return ShellUtils.fastCmdResult("restore_imgs");
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ dialog.cancel();
+ if (result) {
+ MagiskManager.toast(R.string.restore_done, Toast.LENGTH_SHORT);
+ } else {
+ MagiskManager.toast(R.string.restore_fail, Toast.LENGTH_LONG);
+ }
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/asyncs/UpdateRepos.java b/app/src/full/java/com/topjohnwu/magisk/asyncs/UpdateRepos.java
new file mode 100644
index 000000000..55b7685f5
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/asyncs/UpdateRepos.java
@@ -0,0 +1,210 @@
+package com.topjohnwu.magisk.asyncs;
+
+import android.database.Cursor;
+import android.os.AsyncTask;
+import android.text.TextUtils;
+
+import com.topjohnwu.magisk.MagiskManager;
+import com.topjohnwu.magisk.ReposFragment;
+import com.topjohnwu.magisk.container.Repo;
+import com.topjohnwu.magisk.utils.Const;
+import com.topjohnwu.magisk.utils.Logger;
+import com.topjohnwu.magisk.utils.Utils;
+import com.topjohnwu.magisk.utils.WebService;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.net.HttpURLConnection;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class UpdateRepos extends ParallelTask {
+
+ private static final int CHECK_ETAG = 0;
+ private static final int LOAD_NEXT = 1;
+ private static final int LOAD_PREV = 2;
+ private static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
+
+ private MagiskManager mm;
+ private List etags, newEtags = new LinkedList<>();
+ private Set cached;
+ private boolean forceUpdate;
+ private AtomicInteger taskCount = new AtomicInteger(0);
+ final private Object allDone = new Object();
+
+ public UpdateRepos(boolean force) {
+ mm = MagiskManager.get();
+ mm.repoLoadDone.reset();
+ forceUpdate = force;
+ }
+
+ private void queueTask(Runnable task) {
+ // Thread pool's queue has an upper bound, batch it with 64 tasks
+ while (taskCount.get() >= 64) {
+ waitTasks();
+ }
+ taskCount.incrementAndGet();
+ AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
+ task.run();
+ if (taskCount.decrementAndGet() == 0) {
+ synchronized (allDone) {
+ allDone.notify();
+ }
+ }
+ });
+ }
+
+ private void waitTasks() {
+ if (taskCount.get() == 0)
+ return;
+ synchronized (allDone) {
+ try {
+ allDone.wait();
+ } catch (InterruptedException e) {
+ // Wait again
+ waitTasks();
+ }
+ }
+ }
+
+ private boolean loadJSON(String jsonString) throws JSONException, ParseException {
+ JSONArray jsonArray = new JSONArray(jsonString);
+
+ // Empty page, halt
+ if (jsonArray.length() == 0)
+ return false;
+
+ for (int i = 0; i < jsonArray.length(); i++) {
+ JSONObject rawRepo = jsonArray.getJSONObject(i);
+ String id = rawRepo.getString("description");
+ String name = rawRepo.getString("name");
+ Date date = dateFormat.parse(rawRepo.getString("pushed_at"));
+ Set set = Collections.synchronizedSet(cached);
+ queueTask(() -> {
+ Repo repo = mm.repoDB.getRepo(id);
+ try {
+ if (repo == null)
+ repo = new Repo(name);
+ else
+ set.remove(id);
+ repo.update(date);
+ mm.repoDB.addRepo(repo);
+ publishProgress();
+ } catch (Repo.IllegalRepoException e) {
+ Logger.debug(e.getMessage());
+ mm.repoDB.removeRepo(id);
+ }
+ });
+ }
+ return true;
+ }
+
+ private boolean loadPage(int page, int mode) {
+ Map header = new HashMap<>();
+ if (mode == CHECK_ETAG && page < etags.size())
+ header.put(Const.Key.IF_NONE_MATCH, etags.get(page));
+ String url = Utils.fmt(Const.Url.REPO_URL, page + 1);
+
+ try {
+ HttpURLConnection conn = WebService.request(url, header);
+ if (conn.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
+ // Current page is not updated, check the next page
+ return loadPage(page + 1, CHECK_ETAG);
+ }
+ if (!loadJSON(WebService.getString(conn)))
+ return mode != CHECK_ETAG;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ }
+
+ /* If one page is updated, we force update all pages */
+
+ // Update ETAG
+ String etag = header.get(Const.Key.ETAG_KEY);
+ etag = etag.substring(etag.indexOf('\"'), etag.lastIndexOf('\"') + 1);
+ if (mode == LOAD_PREV) {
+ // We are loading a previous page, push the new tag to the front
+ newEtags.add(0, etag);
+ } else {
+ 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);
+ }
+ if (mode != LOAD_NEXT && s.contains("prev")) {
+ // Back propagation
+ loadPage(page - 1, LOAD_PREV);
+ }
+ }
+ }
+ return true;
+ }
+
+ @Override
+ protected void onProgressUpdate(Void... values) {
+ if (ReposFragment.adapter != null)
+ ReposFragment.adapter.notifyDBChanged();
+ }
+
+ @Override
+ protected void onPreExecute() {
+ mm.repoLoadDone.setPending();
+ }
+
+ @Override
+ protected Void doInBackground(Void... voids) {
+ etags = Arrays.asList(mm.prefs.getString(Const.Key.ETAG_KEY, "").split(","));
+ cached = mm.repoDB.getRepoIDSet();
+
+ if (loadPage(0, CHECK_ETAG)) {
+ waitTasks();
+
+ // The leftover cached means they are removed from online repo
+ mm.repoDB.removeRepo(cached);
+
+ // Update ETag
+ mm.prefs.edit().putString(Const.Key.ETAG_KEY, TextUtils.join(",", newEtags)).apply();
+ } else if (forceUpdate) {
+ Cursor c = mm.repoDB.getRawCursor();
+ while (c.moveToNext()) {
+ Repo repo = new Repo(c);
+ queueTask(() -> {
+ try {
+ repo.update();
+ mm.repoDB.addRepo(repo);
+ } catch (Repo.IllegalRepoException e) {
+ Logger.debug(e.getMessage());
+ mm.repoDB.removeRepo(repo);
+ }
+ });
+ }
+ waitTasks();
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void v) {
+ mm.repoLoadDone.publish();
+ super.onPostExecute(v);
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/components/AboutCardRow.java b/app/src/full/java/com/topjohnwu/magisk/components/AboutCardRow.java
new file mode 100644
index 000000000..db022cc97
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/components/AboutCardRow.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2016 dvdandroid
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.topjohnwu.magisk.components;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.topjohnwu.magisk.R;
+
+/**
+ * @author dvdandroid
+ */
+public class AboutCardRow extends LinearLayout {
+
+ private final String title;
+ private final Drawable icon;
+
+ private final TextView mTitle;
+ private final TextView mSummary;
+ private final ImageView mIcon;
+
+ private final View mView;
+
+ public AboutCardRow(Context context) {
+ this(context, null);
+ }
+
+ public AboutCardRow(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public AboutCardRow(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ LayoutInflater.from(context).inflate(R.layout.info_item_row, this);
+ TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.AboutCardRow, 0, 0);
+
+ try {
+ title = a.getString(R.styleable.AboutCardRow_text);
+ icon = a.getDrawable(R.styleable.AboutCardRow_icon);
+ } finally {
+ a.recycle();
+ }
+
+ mView = findViewById(R.id.container);
+
+ mTitle = (TextView) findViewById(android.R.id.title);
+ mSummary = (TextView) findViewById(android.R.id.summary);
+ mIcon = (ImageView) findViewById(android.R.id.icon);
+
+ mTitle.setText(title);
+ mIcon.setImageDrawable(icon);
+ }
+
+ @Override
+ public void setOnClickListener(OnClickListener l) {
+ super.setOnClickListener(l);
+
+ mView.setOnClickListener(l);
+ }
+
+ public void setSummary(String s) {
+ mSummary.setText(s);
+ }
+
+ public void removeSummary() {
+ mSummary.setVisibility(GONE);
+ }
+}
\ No newline at end of file
diff --git a/app/src/full/java/com/topjohnwu/magisk/components/AlertDialogBuilder.java b/app/src/full/java/com/topjohnwu/magisk/components/AlertDialogBuilder.java
new file mode 100644
index 000000000..568d69444
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/components/AlertDialogBuilder.java
@@ -0,0 +1,153 @@
+package com.topjohnwu.magisk.components;
+
+import android.app.Activity;
+import android.content.DialogInterface;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.StringRes;
+import android.support.annotation.StyleRes;
+import android.support.v7.app.AlertDialog;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.topjohnwu.magisk.R;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+
+public class AlertDialogBuilder extends AlertDialog.Builder {
+
+ @BindView(R.id.button_panel) LinearLayout buttons;
+ @BindView(R.id.message_panel) LinearLayout messagePanel;
+
+ @BindView(R.id.negative) Button negative;
+ @BindView(R.id.positive) Button positive;
+ @BindView(R.id.neutral) Button neutral;
+ @BindView(R.id.message) TextView messageView;
+
+ private DialogInterface.OnClickListener positiveListener;
+ private DialogInterface.OnClickListener negativeListener;
+ private DialogInterface.OnClickListener neutralListener;
+
+ private AlertDialog dialog;
+
+ public AlertDialogBuilder(@NonNull Activity context) {
+ super(context);
+ setup();
+ }
+
+ public AlertDialogBuilder(@NonNull Activity context, @StyleRes int themeResId) {
+ super(context, themeResId);
+ setup();
+ }
+
+ private void setup() {
+ View v = LayoutInflater.from(getContext()).inflate(R.layout.alert_dialog, null);
+ ButterKnife.bind(this, v);
+ super.setView(v);
+ negative.setVisibility(View.GONE);
+ positive.setVisibility(View.GONE);
+ neutral.setVisibility(View.GONE);
+ buttons.setVisibility(View.GONE);
+ messagePanel.setVisibility(View.GONE);
+ }
+
+ @Override
+ public AlertDialog.Builder setTitle(int titleId) {
+ return super.setTitle(titleId);
+ }
+
+ @Override
+ 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) {
+ messageView.setText(message);
+ messagePanel.setVisibility(View.VISIBLE);
+ return this;
+ }
+
+ @Override
+ public AlertDialog.Builder setMessage(@StringRes int messageId) {
+ return setMessage(getContext().getString(messageId));
+ }
+
+ @Override
+ public AlertDialog.Builder setPositiveButton(CharSequence text, DialogInterface.OnClickListener listener) {
+ buttons.setVisibility(View.VISIBLE);
+ positive.setVisibility(View.VISIBLE);
+ positive.setText(text);
+ positiveListener = listener;
+ positive.setOnClickListener((v) -> {
+ if (positiveListener != null) {
+ positiveListener.onClick(dialog, DialogInterface.BUTTON_POSITIVE);
+ }
+ dialog.dismiss();
+ });
+ return this;
+ }
+
+ @Override
+ public AlertDialog.Builder setPositiveButton(@StringRes int textId, DialogInterface.OnClickListener listener) {
+ return setPositiveButton(getContext().getString(textId), listener);
+ }
+
+ @Override
+ public AlertDialog.Builder setNegativeButton(CharSequence text, DialogInterface.OnClickListener listener) {
+ buttons.setVisibility(View.VISIBLE);
+ negative.setVisibility(View.VISIBLE);
+ negative.setText(text);
+ negativeListener = listener;
+ negative.setOnClickListener((v) -> {
+ if (negativeListener != null) {
+ negativeListener.onClick(dialog, DialogInterface.BUTTON_NEGATIVE);
+ }
+ dialog.dismiss();
+ });
+ return this;
+ }
+
+ @Override
+ public AlertDialog.Builder setNegativeButton(@StringRes int textId, DialogInterface.OnClickListener listener) {
+ return setNegativeButton(getContext().getString(textId), listener);
+ }
+
+ @Override
+ public AlertDialog.Builder setNeutralButton(CharSequence text, DialogInterface.OnClickListener listener) {
+ buttons.setVisibility(View.VISIBLE);
+ neutral.setVisibility(View.VISIBLE);
+ neutral.setText(text);
+ neutralListener = listener;
+ neutral.setOnClickListener((v) -> {
+ if (neutralListener != null) {
+ neutralListener.onClick(dialog, DialogInterface.BUTTON_NEUTRAL);
+ }
+ dialog.dismiss();
+ });
+ return this;
+ }
+
+ @Override
+ public AlertDialog.Builder setNeutralButton(@StringRes int textId, DialogInterface.OnClickListener listener) {
+ return setNeutralButton(getContext().getString(textId), listener);
+ }
+
+ @Override
+ public AlertDialog create() {
+ dialog = super.create();
+ return dialog;
+ }
+
+ @Override
+ public AlertDialog show() {
+ create();
+ dialog.show();
+ return dialog;
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/components/ExpandableView.java b/app/src/full/java/com/topjohnwu/magisk/components/ExpandableView.java
new file mode 100644
index 000000000..121611eab
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/components/ExpandableView.java
@@ -0,0 +1,84 @@
+package com.topjohnwu.magisk.components;
+
+import android.animation.ValueAnimator;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+
+public interface ExpandableView {
+
+ class Container {
+ public ViewGroup expandLayout;
+ ValueAnimator expandAnimator, collapseAnimator;
+ boolean mExpanded = false;
+ int expandHeight = 0;
+ }
+
+ // Provide state info
+ Container getContainer();
+
+ default void setupExpandable() {
+ Container container = getContainer();
+ container.expandLayout.getViewTreeObserver().addOnPreDrawListener(
+ new ViewTreeObserver.OnPreDrawListener() {
+
+ @Override
+ public boolean onPreDraw() {
+ if (container.expandHeight == 0) {
+ final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+ final int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+ container.expandLayout.measure(widthSpec, heightSpec);
+ container.expandHeight = container.expandLayout.getMeasuredHeight();
+ }
+
+ container.expandLayout.getViewTreeObserver().removeOnPreDrawListener(this);
+ container.expandLayout.setVisibility(View.GONE);
+ container.expandAnimator = slideAnimator(0, container.expandHeight);
+ container.collapseAnimator = slideAnimator(container.expandHeight, 0);
+ return true;
+ }
+
+ });
+ }
+
+ default boolean isExpanded() {
+ return getContainer().mExpanded;
+ }
+
+ default void setExpanded(boolean expanded) {
+ Container container = getContainer();
+ container.mExpanded = expanded;
+ ViewGroup.LayoutParams layoutParams = container.expandLayout.getLayoutParams();
+ layoutParams.height = expanded ? container.expandHeight : 0;
+ container.expandLayout.setLayoutParams(layoutParams);
+ container.expandLayout.setVisibility(expanded ? View.VISIBLE : View.GONE);
+ }
+
+ default void expand() {
+ Container container = getContainer();
+ if (container.mExpanded) return;
+ container.expandLayout.setVisibility(View.VISIBLE);
+ container.expandAnimator.start();
+ container.mExpanded = true;
+ }
+
+ default void collapse() {
+ Container container = getContainer();
+ if (!container.mExpanded) return;
+ container.collapseAnimator.start();
+ container.mExpanded = false;
+ }
+
+ default ValueAnimator slideAnimator(int start, int end) {
+ Container container = getContainer();
+ ValueAnimator animator = ValueAnimator.ofInt(start, end);
+
+ animator.addUpdateListener(valueAnimator -> {
+ int value = (Integer) valueAnimator.getAnimatedValue();
+ ViewGroup.LayoutParams layoutParams = container.expandLayout.getLayoutParams();
+ layoutParams.height = value;
+ container.expandLayout.setLayoutParams(layoutParams);
+ });
+ return animator;
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/components/FlavorActivity.java b/app/src/full/java/com/topjohnwu/magisk/components/FlavorActivity.java
new file mode 100644
index 000000000..7b7f307db
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/components/FlavorActivity.java
@@ -0,0 +1,108 @@
+package com.topjohnwu.magisk.components;
+
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.support.annotation.Keep;
+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;
+
+public abstract class FlavorActivity extends AppCompatActivity {
+
+ private AssetManager swappedAssetManager = null;
+ private Resources swappedResources = null;
+ private Resources.Theme backupTheme = null;
+
+ @StyleRes
+ public int getDarkTheme() {
+ return -1;
+ }
+
+ public MagiskManager getMagiskManager() {
+ return (MagiskManager) super.getApplication();
+ }
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (this instanceof Topic.Subscriber) {
+ ((Topic.Subscriber) this).subscribeTopics();
+ }
+
+ if (getMagiskManager().isDarkTheme && getDarkTheme() != -1) {
+ setTheme(getDarkTheme());
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ if (this instanceof Topic.Subscriber) {
+ ((Topic.Subscriber) this).unsubscribeTopics();
+ }
+ super.onDestroy();
+ }
+
+ 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
+ public Resources.Theme getTheme() {
+ return backupTheme == null ? super.getTheme() : backupTheme;
+ }
+
+ @Override
+ public AssetManager getAssets() {
+ return swappedAssetManager == null ? super.getAssets() : swappedAssetManager;
+ }
+
+ private AssetManager getAssets(String apk) {
+ try {
+ AssetManager asset = AssetManager.class.newInstance();
+ AssetManager.class.getMethod("addAssetPath", String.class).invoke(asset, apk);
+ return asset;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ @Override
+ public Resources getResources() {
+ return swappedResources == null ? super.getResources() : swappedResources;
+ }
+
+ @Keep
+ public void swapResources(String dexPath) {
+ AssetManager asset = getAssets(dexPath);
+ if (asset != null) {
+ backupTheme = super.getTheme();
+ Resources res = super.getResources();
+ swappedResources = new Resources(asset, res.getDisplayMetrics(), res.getConfiguration());
+ swappedAssetManager = asset;
+ }
+ }
+
+ @Keep
+ public void restoreResources() {
+ swappedAssetManager = null;
+ swappedResources = null;
+ backupTheme = null;
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/components/Fragment.java b/app/src/full/java/com/topjohnwu/magisk/components/Fragment.java
new file mode 100644
index 000000000..3ca503821
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/components/Fragment.java
@@ -0,0 +1,43 @@
+package com.topjohnwu.magisk.components;
+
+import android.content.Intent;
+
+import com.topjohnwu.magisk.MagiskManager;
+import com.topjohnwu.magisk.utils.Topic;
+import com.topjohnwu.magisk.utils.Utils;
+
+public class Fragment extends android.support.v4.app.Fragment {
+
+ public MagiskManager getApplication() {
+ return Utils.getMagiskManager(getActivity());
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (this instanceof Topic.Subscriber) {
+ ((Topic.Subscriber) this).subscribeTopics();
+ }
+ }
+
+ @Override
+ public void onPause() {
+ if (this instanceof Topic.Subscriber) {
+ ((Topic.Subscriber) this).unsubscribeTopics();
+ }
+ super.onPause();
+ }
+
+ @Override
+ public void startActivityForResult(Intent intent, int requestCode) {
+ startActivityForResult(intent, requestCode, this::onActivityResult);
+ }
+
+ public void startActivityForResult(Intent intent, int requestCode, Activity.ActivityResultListener listener) {
+ ((Activity) getActivity()).startActivityForResult(intent, requestCode, listener);
+ }
+
+ public void runWithPermission(String[] permissions, Runnable callback) {
+ Activity.runWithPermission(getActivity(), permissions, callback);
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/components/SnackbarMaker.java b/app/src/full/java/com/topjohnwu/magisk/components/SnackbarMaker.java
new file mode 100644
index 000000000..bf873790f
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/components/SnackbarMaker.java
@@ -0,0 +1,47 @@
+package com.topjohnwu.magisk.components;
+
+import android.app.Activity;
+import android.net.Uri;
+import android.support.annotation.StringRes;
+import android.support.design.widget.Snackbar;
+import android.view.View;
+import android.widget.TextView;
+
+import com.topjohnwu.magisk.R;
+import com.topjohnwu.magisk.utils.Utils;
+
+public class SnackbarMaker {
+
+ public static Snackbar make(Activity activity, CharSequence text, int duration) {
+ View view = activity.findViewById(android.R.id.content);
+ return make(view, text, duration);
+ }
+
+ public static Snackbar make(Activity activity, @StringRes int resId, int duration) {
+ return make(activity, activity.getString(resId), duration);
+ }
+
+ public static Snackbar make(View view, CharSequence text, int duration) {
+ Snackbar snack = Snackbar.make(view, text, duration);
+ setup(snack);
+ return snack;
+ }
+
+ public static Snackbar make(View view, @StringRes int resId, int duration) {
+ Snackbar snack = Snackbar.make(view, resId, duration);
+ setup(snack);
+ return snack;
+ }
+
+ private static void setup(Snackbar snack) {
+ TextView text = snack.getView().findViewById(android.support.design.R.id.snackbar_text);
+ text.setMaxLines(Integer.MAX_VALUE);
+ }
+
+ public static void showUri(Activity activity, Uri uri) {
+ make(activity, activity.getString(R.string.internal_storage,
+ "/MagiskManager/" + Utils.getNameFromUri(activity, uri)),
+ Snackbar.LENGTH_LONG)
+ .setAction(R.string.ok, (v)->{}).show();
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/container/BaseModule.java b/app/src/full/java/com/topjohnwu/magisk/container/BaseModule.java
new file mode 100644
index 000000000..1aeea8c4c
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/container/BaseModule.java
@@ -0,0 +1,121 @@
+package com.topjohnwu.magisk.container;
+
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.support.annotation.NonNull;
+
+import java.util.List;
+
+public abstract class BaseModule implements Comparable {
+
+ private String mId = null, mName, mVersion, mAuthor, mDescription;
+ private int mVersionCode = -1, minMagiskVersion = -1;
+
+ protected BaseModule() {}
+
+ protected BaseModule(Cursor c) {
+ mId = c.getString(c.getColumnIndex("id"));
+ mName = c.getString(c.getColumnIndex("name"));
+ mVersion = c.getString(c.getColumnIndex("version"));
+ mVersionCode = c.getInt(c.getColumnIndex("versionCode"));
+ mAuthor = c.getString(c.getColumnIndex("author"));
+ mDescription = c.getString(c.getColumnIndex("description"));
+ minMagiskVersion = c.getInt(c.getColumnIndex("minMagisk"));
+ }
+
+ public ContentValues getContentValues() {
+ ContentValues values = new ContentValues();
+ values.put("id", mId);
+ values.put("name", mName);
+ values.put("version", mVersion);
+ values.put("versionCode", mVersionCode);
+ values.put("author", mAuthor);
+ values.put("description", mDescription);
+ values.put("minMagisk", minMagiskVersion);
+ return values;
+ }
+
+ protected void parseProps(List props) { parseProps(props.toArray(new String[0])); }
+
+ protected void parseProps(String[] props) throws NumberFormatException {
+ for (String line : props) {
+ String[] prop = line.split("=", 2);
+ if (prop.length != 2)
+ continue;
+
+ String key = prop[0].trim();
+ String value = prop[1].trim();
+ if (key.isEmpty() || key.charAt(0) == '#')
+ continue;
+
+ switch (key) {
+ case "id":
+ mId = value;
+ break;
+ case "name":
+ mName = value;
+ break;
+ case "version":
+ mVersion = value;
+ break;
+ case "versionCode":
+ mVersionCode = Integer.parseInt(value);
+ break;
+ case "author":
+ mAuthor = value;
+ break;
+ case "description":
+ mDescription = value;
+ break;
+ case "minMagisk":
+ case "template":
+ minMagiskVersion = Integer.parseInt(value);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public void setName(String name) {
+ mName = name;
+ }
+
+ public String getVersion() {
+ return mVersion;
+ }
+
+ public String getAuthor() {
+ return mAuthor;
+ }
+
+ public String getId() {
+ return mId;
+ }
+
+ public void setId(String id) {
+ mId = id;
+ }
+
+ public String getDescription() {
+ return mDescription;
+ }
+
+ public int getVersionCode() {
+ return mVersionCode;
+ }
+
+ public int getMinMagiskVersion() {
+ return minMagiskVersion;
+ }
+
+ @Override
+ public int compareTo(@NonNull BaseModule module) {
+ return this.getName().toLowerCase().compareTo(module.getName().toLowerCase());
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/container/Module.java b/app/src/full/java/com/topjohnwu/magisk/container/Module.java
new file mode 100644
index 000000000..399854cf9
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/container/Module.java
@@ -0,0 +1,67 @@
+package com.topjohnwu.magisk.container;
+
+import com.topjohnwu.superuser.Shell;
+import com.topjohnwu.superuser.io.SuFile;
+
+public class Module extends BaseModule {
+
+ private SuFile mRemoveFile, mDisableFile, mUpdateFile;
+ private boolean mEnable, mRemove, mUpdated;
+
+ public Module(String path) {
+
+ try {
+ parseProps(Shell.Sync.su("dos2unix < " + path + "/module.prop"));
+ } catch (NumberFormatException ignored) {}
+
+ mRemoveFile = new SuFile(path + "/remove");
+ mDisableFile = new SuFile(path + "/disable");
+ mUpdateFile = new SuFile(path + "/update");
+
+ if (getId() == null) {
+ int sep = path.lastIndexOf('/');
+ setId(path.substring(sep + 1));
+ }
+
+ if (getName() == null) {
+ setName(getId());
+ }
+
+ mEnable = !mDisableFile.exists();
+ mRemove = mRemoveFile.exists();
+ mUpdated = mUpdateFile.exists();
+ }
+
+ public void createDisableFile() {
+ mEnable = false;
+ mDisableFile.createNewFile();
+ }
+
+ public void removeDisableFile() {
+ mEnable = true;
+ mDisableFile.delete();
+ }
+
+ public boolean isEnabled() {
+ return mEnable;
+ }
+
+ public void createRemoveFile() {
+ mRemove = true;
+ mRemoveFile.createNewFile();
+ }
+
+ public void deleteRemoveFile() {
+ mRemove = false;
+ mRemoveFile.delete();
+ }
+
+ public boolean willBeRemoved() {
+ return mRemove;
+ }
+
+ public boolean isUpdated() {
+ return mUpdated;
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/full/java/com/topjohnwu/magisk/container/Policy.java b/app/src/full/java/com/topjohnwu/magisk/container/Policy.java
new file mode 100644
index 000000000..3c3cf60e5
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/container/Policy.java
@@ -0,0 +1,59 @@
+package com.topjohnwu.magisk.container;
+
+import android.content.ContentValues;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.support.annotation.NonNull;
+
+
+public class Policy implements Comparable{
+ public static final int INTERACTIVE = 0;
+ public static final int DENY = 1;
+ public static final int ALLOW = 2;
+
+ public int uid, policy = INTERACTIVE;
+ public long until;
+ public boolean logging = true, notification = true;
+ public String packageName, appName;
+ public ApplicationInfo info;
+
+ public Policy(int uid, PackageManager pm) throws PackageManager.NameNotFoundException {
+ String[] pkgs = pm.getPackagesForUid(uid);
+ if (pkgs == null || pkgs.length == 0)
+ throw new PackageManager.NameNotFoundException();
+ this.uid = uid;
+ packageName = pkgs[0];
+ info = pm.getApplicationInfo(packageName, 0);
+ appName = info.loadLabel(pm).toString();
+ }
+
+ public Policy(Cursor c, PackageManager pm) throws PackageManager.NameNotFoundException {
+ uid = c.getInt(c.getColumnIndex("uid"));
+ packageName = c.getString(c.getColumnIndex("package_name"));
+ policy = c.getInt(c.getColumnIndex("policy"));
+ until = c.getLong(c.getColumnIndex("until"));
+ logging = c.getInt(c.getColumnIndex("logging")) != 0;
+ notification = c.getInt(c.getColumnIndex("notification")) != 0;
+ info = pm.getApplicationInfo(packageName, 0);
+ if (info.uid != uid)
+ throw new PackageManager.NameNotFoundException();
+ appName = info.loadLabel(pm).toString();
+ }
+
+ public ContentValues getContentValues() {
+ ContentValues values = new ContentValues();
+ values.put("uid", uid);
+ values.put("package_name", packageName);
+ values.put("policy", policy);
+ values.put("until", until);
+ values.put("logging", logging ? 1 : 0);
+ values.put("notification", notification ? 1 : 0);
+ return values;
+ }
+
+ @Override
+ public int compareTo(@NonNull Policy policy) {
+ return appName.toLowerCase().compareTo(policy.appName.toLowerCase());
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/container/Repo.java b/app/src/full/java/com/topjohnwu/magisk/container/Repo.java
new file mode 100644
index 000000000..a422983a6
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/container/Repo.java
@@ -0,0 +1,92 @@
+package com.topjohnwu.magisk.container;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+
+import com.topjohnwu.magisk.MagiskManager;
+import com.topjohnwu.magisk.utils.Const;
+import com.topjohnwu.magisk.utils.Logger;
+import com.topjohnwu.magisk.utils.Utils;
+import com.topjohnwu.magisk.utils.WebService;
+
+import java.text.DateFormat;
+import java.util.Date;
+
+public class Repo extends BaseModule {
+
+ private String repoName;
+ private Date mLastUpdate;
+
+ public Repo(String name) {
+ repoName = name;
+ }
+
+ 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 IllegalRepoException {
+ String props[] = Utils.dos2unix(WebService.getString(getManifestUrl())).split("\\n");
+ try {
+ parseProps(props);
+ } catch (NumberFormatException e) {
+ throw new IllegalRepoException("Repo [" + repoName + "] parse error: " + e.getMessage());
+ }
+
+ if (getId() == null) {
+ throw new IllegalRepoException("Repo [" + repoName + "] does not contain id");
+ }
+ if (getVersionCode() < 0) {
+ throw new IllegalRepoException("Repo [" + repoName + "] does not contain versionCode");
+ }
+ if (getMinMagiskVersion() < Const.MIN_MODULE_VER()) {
+ Logger.debug("Repo [" + repoName + "] is outdated");
+ }
+ }
+
+ public void update(Date lastUpdate) throws IllegalRepoException {
+ mLastUpdate = lastUpdate;
+ update();
+ }
+
+ @Override
+ public ContentValues getContentValues() {
+ ContentValues values = super.getContentValues();
+ values.put("repo_name", repoName);
+ values.put("last_update", mLastUpdate.getTime());
+ return values;
+ }
+
+ public String getRepoName() {
+ return repoName;
+ }
+
+ public String getZipUrl() {
+ return String.format(Const.Url.ZIP_URL, repoName);
+ }
+
+ public String getManifestUrl() {
+ return String.format(Const.Url.FILE_URL, repoName, "module.prop");
+ }
+
+ public String getDetailUrl() {
+ return String.format(Const.Url.FILE_URL, repoName, "README.md");
+ }
+
+ public String getLastUpdateString() {
+ return DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM,
+ MagiskManager.locale).format(mLastUpdate);
+ }
+
+ public Date getLastUpdate() {
+ return mLastUpdate;
+ }
+
+ public class IllegalRepoException extends Exception {
+ IllegalRepoException(String message) {
+ super(message);
+ }
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/container/SuLogEntry.java b/app/src/full/java/com/topjohnwu/magisk/container/SuLogEntry.java
new file mode 100644
index 000000000..4b3518468
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/container/SuLogEntry.java
@@ -0,0 +1,56 @@
+package com.topjohnwu.magisk.container;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+
+import com.topjohnwu.magisk.MagiskManager;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class SuLogEntry {
+
+ public int fromUid, toUid, fromPid;
+ public String packageName, appName, command;
+ public boolean action;
+ public Date date;
+
+ public SuLogEntry(Policy policy) {
+ fromUid = policy.uid;
+ packageName = policy.packageName;
+ appName = policy.appName;
+ }
+
+ public SuLogEntry(Cursor c) {
+ fromUid = c.getInt(c.getColumnIndex("from_uid"));
+ fromPid = c.getInt(c.getColumnIndex("from_pid"));
+ toUid = c.getInt(c.getColumnIndex("to_uid"));
+ packageName = c.getString(c.getColumnIndex("package_name"));
+ appName = c.getString(c.getColumnIndex("app_name"));
+ command = c.getString(c.getColumnIndex("command"));
+ action = c.getInt(c.getColumnIndex("action")) != 0;
+ date = new Date(c.getLong(c.getColumnIndex("time")));
+ }
+
+ public ContentValues getContentValues() {
+ ContentValues values = new ContentValues();
+ values.put("from_uid", fromUid);
+ values.put("package_name", packageName);
+ values.put("app_name", appName);
+ values.put("from_pid", fromPid);
+ values.put("command", command);
+ values.put("to_uid", toUid);
+ values.put("action", action ? 1 : 0);
+ values.put("time", date.getTime());
+ return values;
+ }
+
+ public String getDateString() {
+ return DateFormat.getDateInstance(DateFormat.MEDIUM, MagiskManager.locale).format(date);
+ }
+
+ public String getTimeString() {
+ return new SimpleDateFormat("h:mm a", MagiskManager.locale).format(date);
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/container/TarEntry.java b/app/src/full/java/com/topjohnwu/magisk/container/TarEntry.java
new file mode 100644
index 000000000..2a09ddd39
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/container/TarEntry.java
@@ -0,0 +1,64 @@
+package com.topjohnwu.magisk.container;
+
+import org.kamranzafar.jtar.TarHeader;
+
+import java.io.File;
+import java.util.Arrays;
+
+public class TarEntry extends org.kamranzafar.jtar.TarEntry {
+
+ public TarEntry(File file, String entryName) {
+ super(file, entryName);
+ }
+
+ /*
+ * Workaround missing java.nio.file.attribute.PosixFilePermission
+ * Simply just assign a default permission to the file
+ * */
+
+ @Override
+ public void extractTarHeader(String entryName) {
+ int permissions = file.isDirectory() ? 000755 : 000644;
+ header = TarHeader.createHeader(entryName, file.length(), file.lastModified() / 1000, file.isDirectory(), permissions);
+ header.userName = new StringBuffer("");
+ header.groupName = header.userName;
+ }
+
+ /*
+ * Rewrite the header to GNU format
+ * */
+
+ @Override
+ public void writeEntryHeader(byte[] outbuf) {
+ super.writeEntryHeader(outbuf);
+
+ System.arraycopy("ustar \0".getBytes(), 0, outbuf, 257, TarHeader.USTAR_MAGICLEN);
+ getOctalBytes(header.mode, outbuf, 100, TarHeader.MODELEN);
+ getOctalBytes(header.userId, outbuf, 108, TarHeader.UIDLEN);
+ getOctalBytes(header.groupId, outbuf, 116, TarHeader.GIDLEN);
+ getOctalBytes(header.size, outbuf, 124, TarHeader.SIZELEN);
+ getOctalBytes(header.modTime, outbuf, 136, TarHeader.MODTIMELEN);
+ Arrays.fill(outbuf, 148, 148 + TarHeader.CHKSUMLEN, (byte) ' ');
+ Arrays.fill(outbuf, 329, 329 + TarHeader.USTAR_DEVLEN, (byte) '\0');
+ Arrays.fill(outbuf, 337, 337 + TarHeader.USTAR_DEVLEN, (byte) '\0');
+
+ // Recalculate checksum
+ getOctalBytes(computeCheckSum(outbuf), outbuf, 148, TarHeader.CHKSUMLEN);
+ }
+
+ /*
+ * Proper octal to ASCII conversion
+ * */
+
+ private void getOctalBytes(long value, byte[] buf, int offset, int length) {
+ int idx = length - 1;
+
+ buf[offset + idx] = 0;
+ --idx;
+
+ for (long val = value; idx >= 0; --idx) {
+ buf[offset + idx] = (byte) ((byte) '0' + (byte) (val & 7));
+ val = val >> 3;
+ }
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/container/ValueSortedMap.java b/app/src/full/java/com/topjohnwu/magisk/container/ValueSortedMap.java
new file mode 100644
index 000000000..dccd6105c
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/container/ValueSortedMap.java
@@ -0,0 +1,43 @@
+package com.topjohnwu.magisk.container;
+
+import android.support.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ValueSortedMap> extends HashMap {
+
+ private List sorted = new ArrayList<>();
+
+ @NonNull
+ @Override
+ public Collection values() {
+ if (sorted.isEmpty()) {
+ sorted.addAll(super.values());
+ Collections.sort(sorted);
+ }
+ return sorted;
+ }
+
+ @Override
+ public V put(K key, V value) {
+ sorted.clear();
+ return super.put(key, value);
+ }
+
+ @Override
+ public void putAll(Map extends K, ? extends V> m) {
+ sorted.clear();
+ super.putAll(m);
+ }
+
+ @Override
+ public V remove(Object key) {
+ sorted.clear();
+ return super.remove(key);
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/database/MagiskDatabaseHelper.java b/app/src/full/java/com/topjohnwu/magisk/database/MagiskDatabaseHelper.java
new file mode 100644
index 000000000..36548119f
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/database/MagiskDatabaseHelper.java
@@ -0,0 +1,301 @@
+package com.topjohnwu.magisk.database;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.os.Build;
+import android.os.Process;
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
+import android.widget.Toast;
+
+import com.topjohnwu.magisk.MagiskManager;
+import com.topjohnwu.magisk.R;
+import com.topjohnwu.magisk.container.Policy;
+import com.topjohnwu.magisk.container.SuLogEntry;
+import com.topjohnwu.magisk.utils.Const;
+import com.topjohnwu.magisk.utils.Utils;
+import com.topjohnwu.superuser.Shell;
+import com.topjohnwu.superuser.io.SuFile;
+
+import java.io.File;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+public class MagiskDatabaseHelper {
+
+ private static final int DATABASE_VER = 5;
+ private static final String POLICY_TABLE = "policies";
+ private static final String LOG_TABLE = "logs";
+ private static final String SETTINGS_TABLE = "settings";
+ private static final String STRINGS_TABLE = "strings";
+
+ private PackageManager pm;
+ private SQLiteDatabase db;
+
+ @NonNull
+ public static MagiskDatabaseHelper getInstance(MagiskManager mm) {
+ try {
+ return new MagiskDatabaseHelper(mm);
+ } catch (Exception e) {
+ // Let's cleanup everything and try again
+ Shell.Sync.su("db_clean '*'");
+ return new MagiskDatabaseHelper(mm);
+ }
+ }
+
+ private MagiskDatabaseHelper(MagiskManager mm) {
+ pm = mm.getPackageManager();
+ db = openDatabase(mm);
+ db.disableWriteAheadLogging();
+ int version = db.getVersion();
+ if (version < DATABASE_VER) {
+ onUpgrade(db, version);
+ } else if (version > DATABASE_VER) {
+ onDowngrade(db);
+ }
+ db.setVersion(DATABASE_VER);
+ clearOutdated();
+ }
+
+ private SQLiteDatabase openDatabase(MagiskManager mm) {
+ final File DB_FILE = new File(Utils.fmt("/sbin/.core/db-%d/magisk.db", Const.USER_ID));
+ Context de = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
+ ? mm.createDeviceProtectedStorageContext() : mm;
+ if (!DB_FILE.canWrite()) {
+ if (!Shell.rootAccess()) {
+ // We don't want the app to crash, create a db and return
+ return mm.openOrCreateDatabase("su.db", Context.MODE_PRIVATE, null);
+ }
+ mm.loadMagiskInfo();
+ // Cleanup
+ Shell.Sync.su("db_clean " + Const.USER_ID);
+ if (mm.magiskVersionCode < Const.MAGISK_VER.FBE_AWARE) {
+ // Super old legacy mode
+ return mm.openOrCreateDatabase("su.db", Context.MODE_PRIVATE, null);
+ } else if (mm.magiskVersionCode < Const.MAGISK_VER.HIDDEN_PATH) {
+ // Legacy mode with FBE aware
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ de.moveDatabaseFrom(mm, "su.db");
+ }
+ return de.openOrCreateDatabase("su.db", Context.MODE_PRIVATE, null);
+ } else {
+ // Global database
+ final SuFile GLOBAL_DB = new SuFile("/data/adb/magisk.db");
+ mm.deleteDatabase("su.db");
+ de.deleteDatabase("su.db");
+ if (mm.magiskVersionCode < Const.MAGISK_VER.SEPOL_REFACTOR) {
+ // We need some additional policies on old versions
+ Shell.Sync.su("db_sepatch");
+ }
+ if (!GLOBAL_DB.exists()) {
+ Shell.Sync.su("db_init");
+ SQLiteDatabase.openOrCreateDatabase(GLOBAL_DB, null).close();
+ Shell.Sync.su("db_restore");
+ }
+ }
+ Shell.Sync.su("db_setup " + Process.myUid());
+ }
+ // Not using legacy mode, open the mounted global DB
+ return SQLiteDatabase.openOrCreateDatabase(DB_FILE, null);
+ }
+
+ public void onUpgrade(SQLiteDatabase db, int oldVersion) {
+ if (oldVersion == 0) {
+ createTables(db);
+ oldVersion = 3;
+ }
+ if (oldVersion == 1) {
+ // We're dropping column app_name, rename and re-construct table
+ db.execSQL(Utils.fmt("ALTER TABLE %s RENAME TO %s_old", POLICY_TABLE));
+
+ // Create the new tables
+ createTables(db);
+
+ // Migrate old data to new tables
+ db.execSQL(Utils.fmt("INSERT INTO %s SELECT " +
+ "uid, package_name, policy, until, logging, notification FROM %s_old",
+ POLICY_TABLE, POLICY_TABLE));
+ db.execSQL(Utils.fmt("DROP TABLE %s_old", POLICY_TABLE));
+
+ MagiskManager.get().deleteDatabase("sulog.db");
+ ++oldVersion;
+ }
+ if (oldVersion == 2) {
+ db.execSQL(Utils.fmt("UPDATE %s SET time=time*1000", LOG_TABLE));
+ ++oldVersion;
+ }
+ if (oldVersion == 3) {
+ db.execSQL(Utils.fmt("CREATE TABLE IF NOT EXISTS %s (key TEXT, value TEXT, PRIMARY KEY(key))", STRINGS_TABLE));
+ ++oldVersion;
+ }
+ if (oldVersion == 4) {
+ db.execSQL(Utils.fmt("UPDATE %s SET uid=uid%%100000", POLICY_TABLE));
+ ++oldVersion;
+ }
+ }
+
+ // Remove everything, we do not support downgrade
+ public void onDowngrade(SQLiteDatabase db) {
+ MagiskManager.toast(R.string.su_db_corrupt, Toast.LENGTH_LONG);
+ db.execSQL("DROP TABLE IF EXISTS " + POLICY_TABLE);
+ db.execSQL("DROP TABLE IF EXISTS " + LOG_TABLE);
+ db.execSQL("DROP TABLE IF EXISTS " + SETTINGS_TABLE);
+ db.execSQL("DROP TABLE IF EXISTS " + STRINGS_TABLE);
+ onUpgrade(db, 0);
+ }
+
+ private void createTables(SQLiteDatabase db) {
+ // Policies
+ db.execSQL(
+ "CREATE TABLE IF NOT EXISTS " + POLICY_TABLE + " " +
+ "(uid INT, package_name TEXT, policy INT, " +
+ "until INT, logging INT, notification INT, " +
+ "PRIMARY KEY(uid))");
+
+ // Logs
+ db.execSQL(
+ "CREATE TABLE IF NOT EXISTS " + LOG_TABLE + " " +
+ "(from_uid INT, package_name TEXT, app_name TEXT, from_pid INT, " +
+ "to_uid INT, action INT, time INT, command TEXT)");
+
+ // Settings
+ db.execSQL(
+ "CREATE TABLE IF NOT EXISTS " + SETTINGS_TABLE + " " +
+ "(key TEXT, value INT, PRIMARY KEY(key))");
+ }
+
+ public void clearOutdated() {
+ // Clear outdated policies
+ db.delete(POLICY_TABLE, Utils.fmt("until > 0 AND until < %d", System.currentTimeMillis() / 1000), null);
+ // Clear outdated logs
+ db.delete(LOG_TABLE, Utils.fmt("time < %d", System.currentTimeMillis() - MagiskManager.get().suLogTimeout * 86400000), null);
+ }
+
+ public void deletePolicy(Policy policy) {
+ deletePolicy(policy.uid);
+ }
+
+ public void deletePolicy(String pkg) {
+ db.delete(POLICY_TABLE, "package_name=?", new String[] { pkg });
+ }
+
+ public void deletePolicy(int uid) {
+ db.delete(POLICY_TABLE, Utils.fmt("uid=%d", uid), null);
+ }
+
+ public Policy getPolicy(int uid) {
+ Policy policy = null;
+ try (Cursor c = db.query(POLICY_TABLE, null, Utils.fmt("uid=%d", uid), null, null, null, null)) {
+ if (c.moveToNext()) {
+ policy = new Policy(c, pm);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ deletePolicy(uid);
+ return null;
+ }
+ return policy;
+ }
+
+ public void addPolicy(Policy policy) {
+ db.replace(POLICY_TABLE, null, policy.getContentValues());
+ }
+
+ public void updatePolicy(Policy policy) {
+ db.update(POLICY_TABLE, policy.getContentValues(), Utils.fmt("uid=%d", policy.uid), null);
+ }
+
+ public List getPolicyList(PackageManager pm) {
+ try (Cursor c = db.query(POLICY_TABLE, null, Utils.fmt("uid/100000=%d", Const.USER_ID),
+ null, null, null, null)) {
+ List ret = new ArrayList<>(c.getCount());
+ while (c.moveToNext()) {
+ try {
+ Policy policy = new Policy(c, pm);
+ ret.add(policy);
+ } catch (PackageManager.NameNotFoundException e) {
+ // The app no longer exist, remove from DB
+ deletePolicy(c.getInt(c.getColumnIndex("uid")));
+ }
+ }
+ Collections.sort(ret);
+ return ret;
+ }
+ }
+
+ public List> getLogStructure() {
+ try (Cursor c = db.query(LOG_TABLE, new String[] { "time" }, Utils.fmt("from_uid/100000=%d", Const.USER_ID),
+ null, null, null, "time DESC")) {
+ List> ret = new ArrayList<>();
+ List list = null;
+ String dateString = null, newString;
+ while (c.moveToNext()) {
+ Date date = new Date(c.getLong(c.getColumnIndex("time")));
+ newString = DateFormat.getDateInstance(DateFormat.MEDIUM, MagiskManager.locale).format(date);
+ if (!TextUtils.equals(dateString, newString)) {
+ dateString = newString;
+ list = new ArrayList<>();
+ ret.add(list);
+ }
+ list.add(c.getPosition());
+ }
+ return ret;
+ }
+ }
+
+ public Cursor getLogCursor() {
+ return db.query(LOG_TABLE, null, Utils.fmt("from_uid/100000=%d", Const.USER_ID),
+ null, null, null, "time DESC");
+ }
+
+ public void addLog(SuLogEntry log) {
+ db.insert(LOG_TABLE, null, log.getContentValues());
+ }
+
+ public void clearLogs() {
+ db.delete(LOG_TABLE, null, null);
+ }
+
+ public void setSettings(String key, int value) {
+ ContentValues data = new ContentValues();
+ data.put("key", key);
+ data.put("value", value);
+ db.replace(SETTINGS_TABLE, null, data);
+ }
+
+ public int getSettings(String key, int defaultValue) {
+ int value = defaultValue;
+ try (Cursor c = db.query(SETTINGS_TABLE, null, "key=?",new String[] { key }, null, null, null)) {
+ if (c.moveToNext()) {
+ value = c.getInt(c.getColumnIndex("value"));
+ }
+ }
+ return value;
+ }
+
+ public void setStrings(String key, String value) {
+ if (value == null) {
+ db.delete(STRINGS_TABLE, "key=?", new String[] { key });
+ } else {
+ ContentValues data = new ContentValues();
+ data.put("key", key);
+ data.put("value", value);
+ db.replace(STRINGS_TABLE, null, data);
+ }
+ }
+
+ public String getStrings(String key, String defaultValue) {
+ String value = defaultValue;
+ try (Cursor c = db.query(STRINGS_TABLE, null, "key=?",new String[] { key }, null, null, null)) {
+ if (c.moveToNext()) {
+ value = c.getString(c.getColumnIndex("value"));
+ }
+ }
+ return value;
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/database/RepoDatabaseHelper.java b/app/src/full/java/com/topjohnwu/magisk/database/RepoDatabaseHelper.java
new file mode 100644
index 000000000..acc4fecb0
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/database/RepoDatabaseHelper.java
@@ -0,0 +1,124 @@
+package com.topjohnwu.magisk.database;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+import com.topjohnwu.magisk.MagiskManager;
+import com.topjohnwu.magisk.container.Repo;
+import com.topjohnwu.magisk.utils.Const;
+import com.topjohnwu.magisk.utils.Utils;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class RepoDatabaseHelper extends SQLiteOpenHelper {
+
+ private static final int DATABASE_VER = 3;
+ private static final String TABLE_NAME = "repos";
+
+ private SQLiteDatabase mDb;
+ private MagiskManager mm;
+
+ public RepoDatabaseHelper(Context context) {
+ super(context, "repo.db", null, DATABASE_VER);
+ mm = Utils.getMagiskManager(context);
+ mDb = getWritableDatabase();
+
+ // Remove outdated repos
+ mDb.delete(TABLE_NAME, "minMagisk",
+ new String[] { String.valueOf(Const.MIN_MODULE_VER()) });
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ onUpgrade(db, 0, DATABASE_VER);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ try {
+ if (oldVersion < 3) {
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
+ db.execSQL(
+ "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " " +
+ "(id TEXT, name TEXT, version TEXT, versionCode INT, minMagisk INT, " +
+ "author TEXT, description TEXT, repo_name TEXT, last_update INT, " +
+ "PRIMARY KEY(id))");
+ mm.prefs.edit().remove(Const.Key.ETAG_KEY).apply();
+ oldVersion = 3;
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ // Reset database
+ onDowngrade(db, DATABASE_VER, 0);
+ }
+ }
+
+ @Override
+ public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ onUpgrade(db, 0, DATABASE_VER);
+ }
+
+ public void clearRepo() {
+ mDb.delete(TABLE_NAME, null, null);
+ }
+
+
+ public void removeRepo(String id) {
+ mDb.delete(TABLE_NAME, "id=?", new String[] { id });
+ }
+
+ public void removeRepo(Repo repo) {
+ mDb.delete(TABLE_NAME, "repo_name=?", new String[] { repo.getRepoName() });
+ }
+
+ public void removeRepo(Iterable list) {
+ for (String id : list) {
+ if (id == null) continue;
+ mDb.delete(TABLE_NAME, "id=?", new String[] { id });
+ }
+ }
+
+ public void addRepo(Repo repo) {
+ mDb.replace(TABLE_NAME, null, repo.getContentValues());
+ }
+
+ public Repo getRepo(String id) {
+ try (Cursor c = mDb.query(TABLE_NAME, null, "id=?", new String[] { id }, null, null, null)) {
+ if (c.moveToNext()) {
+ return new Repo(c);
+ }
+ }
+ return null;
+ }
+
+ public Cursor getRawCursor() {
+ return mDb.query(TABLE_NAME, null, null, null, null, null, null);
+ }
+
+ public Cursor getRepoCursor() {
+ String orderBy = null;
+ switch (mm.repoOrder) {
+ case Const.Value.ORDER_NAME:
+ orderBy = "name COLLATE NOCASE";
+ break;
+ case Const.Value.ORDER_DATE:
+ orderBy = "last_update DESC";
+ }
+ return mDb.query(TABLE_NAME, null, "minMagisk<=? AND minMagisk>=?",
+ new String[] { String.valueOf(mm.magiskVersionCode), String.valueOf(Const.MIN_MODULE_VER()) },
+ null, null, orderBy);
+ }
+
+ public Set getRepoIDSet() {
+ HashSet set = new HashSet<>(300);
+ try (Cursor c = mDb.query(TABLE_NAME, null, null, null, null, null, null)) {
+ while (c.moveToNext()) {
+ set.add(c.getString(c.getColumnIndex("id")));
+ }
+ }
+ return set;
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/receivers/BootReceiver.java b/app/src/full/java/com/topjohnwu/magisk/receivers/BootReceiver.java
new file mode 100644
index 000000000..6dd45497f
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/receivers/BootReceiver.java
@@ -0,0 +1,21 @@
+package com.topjohnwu.magisk.receivers;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+
+import com.topjohnwu.magisk.services.OnBootIntentService;
+
+public class BootReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ context.startForegroundService(new Intent(context, OnBootIntentService.class));
+ } else {
+ context.startService(new Intent(context, OnBootIntentService.class));
+ }
+ }
+
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/receivers/ManagerUpdate.java b/app/src/full/java/com/topjohnwu/magisk/receivers/ManagerUpdate.java
new file mode 100644
index 000000000..5dd8e520f
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/receivers/ManagerUpdate.java
@@ -0,0 +1,47 @@
+package com.topjohnwu.magisk.receivers;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.AsyncTask;
+
+import com.topjohnwu.magisk.utils.Const;
+import com.topjohnwu.magisk.utils.PatchAPK;
+import com.topjohnwu.magisk.utils.Utils;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+
+public class ManagerUpdate extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Utils.dlAndReceive(
+ context, new PatchedInstall(),
+ intent.getStringExtra(Const.Key.INTENT_SET_LINK),
+ intent.getStringExtra(Const.Key.INTENT_SET_FILENAME)
+ );
+ }
+
+ private static class PatchedInstall extends ManagerInstall {
+ @Override
+ public void onDownloadDone(Context context, Uri uri) {
+ if (!context.getPackageName().equals(Const.ORIG_PKG_NAME)) {
+ AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
+ String o = uri.getPath();
+ String p = o.substring(0, o.lastIndexOf('.')) + "-patched.apk";
+ try {
+ PatchAPK.patchPackageID(o, new BufferedOutputStream(new FileOutputStream(p)),
+ Const.ORIG_PKG_NAME, context.getPackageName());
+ } catch (FileNotFoundException ignored) { }
+ super.onDownloadDone(context, Uri.fromFile(new File(p)));
+ });
+ } else {
+ super.onDownloadDone(context, uri);
+ }
+ }
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/receivers/PackageReceiver.java b/app/src/full/java/com/topjohnwu/magisk/receivers/PackageReceiver.java
new file mode 100644
index 000000000..0fb4cfb01
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/receivers/PackageReceiver.java
@@ -0,0 +1,32 @@
+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.utils.Const;
+import com.topjohnwu.magisk.utils.Utils;
+import com.topjohnwu.superuser.Shell;
+
+public class PackageReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ MagiskManager mm = Utils.getMagiskManager(context);
+
+ String pkg = intent.getData().getEncodedSchemeSpecificPart();
+
+ switch (intent.getAction()) {
+ case Intent.ACTION_PACKAGE_REPLACED:
+ // This will only work pre-O
+ if (mm.prefs.getBoolean(Const.Key.SU_REAUTH, false)) {
+ mm.mDB.deletePolicy(pkg);
+ }
+ break;
+ case Intent.ACTION_PACKAGE_FULLY_REMOVED:
+ mm.mDB.deletePolicy(pkg);
+ Shell.Async.su("magiskhide --rm " + pkg);
+ break;
+ }
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/receivers/RebootReceiver.java b/app/src/full/java/com/topjohnwu/magisk/receivers/RebootReceiver.java
new file mode 100644
index 000000000..44794e461
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/receivers/RebootReceiver.java
@@ -0,0 +1,14 @@
+package com.topjohnwu.magisk.receivers;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import com.topjohnwu.superuser.Shell;
+
+public class RebootReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Shell.Async.su("/system/bin/reboot");
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/receivers/ShortcutReceiver.java b/app/src/full/java/com/topjohnwu/magisk/receivers/ShortcutReceiver.java
new file mode 100644
index 000000000..5ae2b2357
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/receivers/ShortcutReceiver.java
@@ -0,0 +1,87 @@
+package com.topjohnwu.magisk.receivers;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.graphics.drawable.Icon;
+import android.os.Build;
+import android.support.annotation.RequiresApi;
+import android.text.TextUtils;
+
+import com.topjohnwu.magisk.MagiskManager;
+import com.topjohnwu.magisk.R;
+import com.topjohnwu.magisk.SplashActivity;
+import com.topjohnwu.magisk.utils.Const;
+import com.topjohnwu.magisk.utils.Utils;
+import com.topjohnwu.superuser.Shell;
+
+import java.util.ArrayList;
+
+public class ShortcutReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
+ MagiskManager mm = Utils.getMagiskManager(context);
+ ShortcutManager manager = context.getSystemService(ShortcutManager.class);
+ if (TextUtils.equals(intent.getAction(), Intent.ACTION_LOCALE_CHANGED)) {
+ // It is triggered with locale change, manual load Magisk info
+ mm.loadMagiskInfo();
+ }
+ manager.setDynamicShortcuts(getShortCuts(mm));
+ }
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.N_MR1)
+ private ArrayList getShortCuts(MagiskManager mm) {
+ ArrayList shortCuts = new ArrayList<>();
+ if (Shell.rootAccess() &&
+ !(Const.USER_ID > 0 &&
+ mm.multiuserMode == Const.Value.MULTIUSER_MODE_OWNER_MANAGED)) {
+ shortCuts.add(new ShortcutInfo.Builder(mm, "superuser")
+ .setShortLabel(mm.getString(R.string.superuser))
+ .setIntent(new Intent(mm, SplashActivity.class)
+ .putExtra(Const.Key.OPEN_SECTION, "superuser")
+ .setAction(Intent.ACTION_VIEW)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
+ .setIcon(Icon.createWithResource(mm, R.drawable.sc_superuser))
+ .setRank(0)
+ .build());
+ }
+ if (Shell.rootAccess() && mm.magiskVersionCode >= Const.MAGISK_VER.UNIFIED
+ && mm.prefs.getBoolean(Const.Key.MAGISKHIDE, false)) {
+ shortCuts.add(new ShortcutInfo.Builder(mm, "magiskhide")
+ .setShortLabel(mm.getString(R.string.magiskhide))
+ .setIntent(new Intent(mm, SplashActivity.class)
+ .putExtra(Const.Key.OPEN_SECTION, "magiskhide")
+ .setAction(Intent.ACTION_VIEW)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
+ .setIcon(Icon.createWithResource(mm, R.drawable.sc_magiskhide))
+ .setRank(1)
+ .build());
+ }
+ if (!mm.prefs.getBoolean(Const.Key.COREONLY, false) &&
+ Shell.rootAccess() && mm.magiskVersionCode >= 0) {
+ shortCuts.add(new ShortcutInfo.Builder(mm, "modules")
+ .setShortLabel(mm.getString(R.string.modules))
+ .setIntent(new Intent(mm, SplashActivity.class)
+ .putExtra(Const.Key.OPEN_SECTION, "modules")
+ .setAction(Intent.ACTION_VIEW)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
+ .setIcon(Icon.createWithResource(mm, R.drawable.sc_extension))
+ .setRank(3)
+ .build());
+ shortCuts.add(new ShortcutInfo.Builder(mm, "downloads")
+ .setShortLabel(mm.getString(R.string.download))
+ .setIntent(new Intent(mm, SplashActivity.class)
+ .putExtra(Const.Key.OPEN_SECTION, "downloads")
+ .setAction(Intent.ACTION_VIEW)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
+ .setIcon(Icon.createWithResource(mm, R.drawable.sc_cloud_download))
+ .setRank(2)
+ .build());
+ }
+ return shortCuts;
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/services/OnBootIntentService.java b/app/src/full/java/com/topjohnwu/magisk/services/OnBootIntentService.java
new file mode 100644
index 000000000..a172736a5
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/services/OnBootIntentService.java
@@ -0,0 +1,44 @@
+package com.topjohnwu.magisk.services;
+
+import android.app.IntentService;
+import android.content.Intent;
+import android.os.Build;
+import android.support.v4.app.NotificationCompat;
+
+import com.topjohnwu.magisk.MagiskManager;
+import com.topjohnwu.magisk.R;
+import com.topjohnwu.magisk.utils.Const;
+import com.topjohnwu.magisk.utils.RootUtils;
+
+public class OnBootIntentService extends IntentService {
+
+ public OnBootIntentService() {
+ super("OnBootIntentService");
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ startForeground(Const.ID.ONBOOT_NOTIFICATION_ID,
+ new NotificationCompat.Builder(this, Const.ID.NOTIFICATION_CHANNEL)
+ .setSmallIcon(R.drawable.ic_magisk_outline)
+ .setContentTitle("Startup Operations")
+ .setContentText("Running startup operations...")
+ .build());
+ }
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ /* Pixel 2 (XL) devices will need to patch dtbo.img.
+ * However, that is not possible if Magisk is installed by
+ * patching boot image with Magisk Manager and fastboot flash
+ * the boot image, since at that time we do not have root.
+ * Check for dtbo status every boot time, and prompt user
+ * to reboot if dtbo wasn't patched and patched by Magisk Manager.
+ * */
+ MagiskManager.get().loadMagiskInfo();
+ RootUtils.patchDTBO();
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/services/UpdateCheckService.java b/app/src/full/java/com/topjohnwu/magisk/services/UpdateCheckService.java
new file mode 100644
index 000000000..89a958fc2
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/services/UpdateCheckService.java
@@ -0,0 +1,23 @@
+package com.topjohnwu.magisk.services;
+
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+
+import com.topjohnwu.magisk.asyncs.CheckUpdates;
+import com.topjohnwu.magisk.utils.Utils;
+
+public class UpdateCheckService extends JobService {
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ Utils.getMagiskManager(this).loadMagiskInfo();
+ new CheckUpdates(true)
+ .setCallBack(() -> jobFinished(params, false)).exec();
+ return true;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ return true;
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/superuser/RequestActivity.java b/app/src/full/java/com/topjohnwu/magisk/superuser/RequestActivity.java
new file mode 100644
index 000000000..10bb42c68
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/superuser/RequestActivity.java
@@ -0,0 +1,300 @@
+package com.topjohnwu.magisk.superuser;
+
+import android.content.ContentValues;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.hardware.fingerprint.FingerprintManager;
+import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
+import android.os.Bundle;
+import android.os.CountDownTimer;
+import android.os.FileObserver;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.Window;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import com.topjohnwu.magisk.MagiskManager;
+import com.topjohnwu.magisk.R;
+import com.topjohnwu.magisk.asyncs.ParallelTask;
+import com.topjohnwu.magisk.components.Activity;
+import com.topjohnwu.magisk.container.Policy;
+import com.topjohnwu.magisk.utils.Const;
+import com.topjohnwu.magisk.utils.FingerprintHelper;
+import com.topjohnwu.magisk.utils.Utils;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+
+public class RequestActivity extends Activity {
+
+ @BindView(R.id.su_popup) LinearLayout suPopup;
+ @BindView(R.id.timeout) Spinner timeout;
+ @BindView(R.id.app_icon) ImageView appIcon;
+ @BindView(R.id.app_name) TextView appNameView;
+ @BindView(R.id.package_name) TextView packageNameView;
+ @BindView(R.id.grant_btn) Button grant_btn;
+ @BindView(R.id.deny_btn) Button deny_btn;
+ @BindView(R.id.fingerprint) ImageView fingerprintImg;
+ @BindView(R.id.warning) TextView warning;
+
+ private String socketPath;
+ private LocalSocket socket;
+ private PackageManager pm;
+ private MagiskManager mm;
+
+ private boolean hasTimeout;
+ private Policy policy;
+ private CountDownTimer timer;
+ private FingerprintHelper fingerprintHelper;
+
+ @Override
+ public int getDarkTheme() {
+ return R.style.SuRequest_Dark;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
+
+ pm = getPackageManager();
+ mm = Utils.getMagiskManager(this);
+ mm.mDB.clearOutdated();
+
+ Intent intent = getIntent();
+ socketPath = intent.getStringExtra("socket");
+ hasTimeout = intent.getBooleanExtra("timeout", true);
+
+ new FileObserver(socketPath) {
+ @Override
+ public void onEvent(int fileEvent, String path) {
+ if (fileEvent == FileObserver.DELETE_SELF) {
+ finish();
+ }
+ }
+ }.startWatching();
+
+ new SocketManager(this).exec();
+ }
+
+ @Override
+ public void finish() {
+ if (timer != null)
+ timer.cancel();
+ if (fingerprintHelper != null)
+ fingerprintHelper.cancel();
+ super.finish();
+ }
+
+ private boolean cancelTimeout() {
+ timer.cancel();
+ deny_btn.setText(getString(R.string.deny));
+ return false;
+ }
+
+ private void showRequest() {
+ switch (mm.suResponseType) {
+ case Const.Value.SU_AUTO_DENY:
+ handleAction(Policy.DENY, 0);
+ return;
+ case Const.Value.SU_AUTO_ALLOW:
+ handleAction(Policy.ALLOW, 0);
+ return;
+ case Const.Value.SU_PROMPT:
+ default:
+ }
+
+ // If not interactive, response directly
+ if (policy.policy != Policy.INTERACTIVE) {
+ handleAction();
+ return;
+ }
+
+ setContentView(R.layout.activity_request);
+ ButterKnife.bind(this);
+
+ appIcon.setImageDrawable(policy.info.loadIcon(pm));
+ appNameView.setText(policy.appName);
+ packageNameView.setText(policy.packageName);
+
+ ArrayAdapter adapter = ArrayAdapter.createFromResource(this,
+ R.array.allow_timeout, android.R.layout.simple_spinner_item);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ timeout.setAdapter(adapter);
+
+ timer = new CountDownTimer(mm.suRequestTimeout * 1000, 1000) {
+ @Override
+ public void onTick(long millisUntilFinished) {
+ deny_btn.setText(getString(R.string.deny_with_str, "(" + millisUntilFinished / 1000 + ")"));
+ }
+ @Override
+ public void onFinish() {
+ deny_btn.setText(getString(R.string.deny_with_str, "(0)"));
+ handleAction(Policy.DENY);
+ }
+ };
+
+ boolean useFingerprint = mm.prefs.getBoolean(Const.Key.SU_FINGERPRINT, false) && FingerprintHelper.canUseFingerprint();
+
+ if (useFingerprint) {
+ try {
+ fingerprintHelper = new FingerprintHelper() {
+ @Override
+ public void onAuthenticationError(int errorCode, CharSequence errString) {
+ warning.setText(errString);
+ }
+
+ @Override
+ public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
+ warning.setText(helpString);
+ }
+
+ @Override
+ public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
+ handleAction(Policy.ALLOW);
+ }
+
+ @Override
+ public void onAuthenticationFailed() {
+ warning.setText(R.string.auth_fail);
+ }
+ };
+ fingerprintHelper.startAuth();
+ } catch (Exception e) {
+ e.printStackTrace();
+ useFingerprint = false;
+ }
+ }
+
+ if (!useFingerprint) {
+ grant_btn.setOnClickListener(v -> {
+ handleAction(Policy.ALLOW);
+ timer.cancel();
+ });
+ grant_btn.requestFocus();
+ }
+
+ grant_btn.setVisibility(useFingerprint ? View.GONE : View.VISIBLE);
+ fingerprintImg.setVisibility(useFingerprint ? View.VISIBLE : View.GONE);
+
+ deny_btn.setOnClickListener(v -> {
+ handleAction(Policy.DENY);
+ timer.cancel();
+ });
+ suPopup.setOnClickListener(v -> cancelTimeout());
+ timeout.setOnTouchListener((v, event) -> cancelTimeout());
+
+ if (hasTimeout) {
+ timer.start();
+ } else {
+ cancelTimeout();
+ }
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (policy != null) {
+ handleAction(Policy.DENY);
+ } else {
+ finish();
+ }
+ }
+
+ void handleAction() {
+ String response;
+ if (policy.policy == Policy.ALLOW) {
+ response = "socket:ALLOW";
+ } else {
+ response = "socket:DENY";
+ }
+ try {
+ socket.getOutputStream().write((response).getBytes());
+ socket.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ finish();
+ }
+
+ void handleAction(int action) {
+ handleAction(action, Const.Value.timeoutList[timeout.getSelectedItemPosition()]);
+ }
+
+ void handleAction(int action, int time) {
+ policy.policy = action;
+ if (time >= 0) {
+ policy.until = (time == 0) ? 0 : (System.currentTimeMillis() / 1000 + time * 60);
+ mm.mDB.addPolicy(policy);
+ }
+ handleAction();
+ }
+
+ private class SocketManager extends ParallelTask {
+
+ SocketManager(Activity context) {
+ super(context);
+ }
+
+ @Override
+ protected Boolean doInBackground(Void... params) {
+ try {
+ socket = new LocalSocket();
+ socket.connect(new LocalSocketAddress(socketPath, LocalSocketAddress.Namespace.FILESYSTEM));
+
+ DataInputStream is = new DataInputStream(socket.getInputStream());
+ ContentValues payload = new ContentValues();
+
+ while (true) {
+ int nameLen = is.readInt();
+ byte[] nameBytes = new byte[nameLen];
+ is.readFully(nameBytes);
+ String name = new String(nameBytes);
+ if (TextUtils.equals(name, "eof"))
+ break;
+
+ int dataLen = is.readInt();
+ byte[] dataBytes = new byte[dataLen];
+ is.readFully(dataBytes);
+ String data = new String(dataBytes);
+ payload.put(name, data);
+ }
+
+ if (payload.getAsInteger("uid") == null) {
+ return false;
+ }
+
+ int uid = payload.getAsInteger("uid");
+ policy = mm.mDB.getPolicy(uid);
+ if (policy == null) {
+ policy = new Policy(uid, pm);
+ }
+
+ /* Never allow com.topjohnwu.magisk (could be malware) */
+ if (TextUtils.equals(policy.packageName, Const.ORIG_PKG_NAME))
+ return false;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ if (result) {
+ showRequest();
+ } else {
+ finish();
+ }
+ }
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/superuser/SuReceiver.java b/app/src/full/java/com/topjohnwu/magisk/superuser/SuReceiver.java
new file mode 100644
index 000000000..d39d1f4be
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/superuser/SuReceiver.java
@@ -0,0 +1,90 @@
+package com.topjohnwu.magisk.superuser;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Process;
+import android.widget.Toast;
+
+import com.topjohnwu.magisk.MagiskManager;
+import com.topjohnwu.magisk.R;
+import com.topjohnwu.magisk.container.Policy;
+import com.topjohnwu.magisk.container.SuLogEntry;
+import com.topjohnwu.magisk.utils.Const;
+import com.topjohnwu.magisk.utils.Utils;
+
+import java.util.Date;
+
+public class SuReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ int fromUid, toUid, pid, mode;
+ String command, action;
+ Policy policy;
+
+ MagiskManager mm = Utils.getMagiskManager(context);
+
+ if (intent == null) return;
+
+ mode = intent.getIntExtra("mode", -1);
+ if (mode < 0) return;
+
+ if (mode == Const.Value.NOTIFY_USER_TO_OWNER) {
+ MagiskManager.toast(R.string.multiuser_hint_owner_request, Toast.LENGTH_LONG);
+ return;
+ }
+
+ fromUid = intent.getIntExtra("from.uid", -1);
+ if (fromUid < 0) return;
+ if (fromUid == Process.myUid()) return; // Don't show anything if it's Magisk Manager
+
+ action = intent.getStringExtra("action");
+ if (action == null) return;
+
+ policy = mm.mDB.getPolicy(fromUid);
+ if (policy == null) {
+ try {
+ policy = new Policy(fromUid, context.getPackageManager());
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ return;
+ }
+ }
+
+ SuLogEntry log = new SuLogEntry(policy);
+
+ String message;
+ switch (action) {
+ case "allow":
+ message = context.getString(R.string.su_allow_toast, policy.appName);
+ log.action = true;
+ break;
+ case "deny":
+ message = context.getString(R.string.su_deny_toast, policy.appName);
+ log.action = false;
+ break;
+ default:
+ return;
+ }
+
+ if (policy.notification && mm.suNotificationType == Const.Value.NOTIFICATION_TOAST) {
+ MagiskManager.toast(message, Toast.LENGTH_SHORT);
+ }
+
+ if (mode == Const.Value.NOTIFY_NORMAL_LOG && policy.logging) {
+ toUid = intent.getIntExtra("to.uid", -1);
+ if (toUid < 0) return;
+ pid = intent.getIntExtra("pid", -1);
+ if (pid < 0) return;
+ command = intent.getStringExtra("command");
+ if (command == null) return;
+ log.toUid = toUid;
+ log.fromPid = pid;
+ log.command = command;
+ log.date = new Date();
+ mm.mDB.addLog(log);
+ }
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/utils/BootSigner.java b/app/src/full/java/com/topjohnwu/magisk/utils/BootSigner.java
new file mode 100644
index 000000000..82204912d
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/utils/BootSigner.java
@@ -0,0 +1,47 @@
+package com.topjohnwu.magisk.utils;
+
+import android.support.annotation.Keep;
+
+import com.topjohnwu.utils.SignBoot;
+
+import java.io.FileInputStream;
+import java.io.InputStream;
+
+public class BootSigner {
+
+ @Keep
+ public static void main(String[] args) throws Exception {
+ if (args.length > 0 && "-verify".equals(args[0])) {
+ String certPath = "";
+ if (args.length >= 2) {
+ /* args[1] is the path to a public key certificate */
+ certPath = args[1];
+ }
+ boolean signed = SignBoot.verifySignature(System.in,
+ certPath.isEmpty() ? null : new FileInputStream(certPath));
+ System.exit(signed ? 0 : 1);
+ } else if (args.length > 0 && "-sign".equals(args[0])) {
+ InputStream cert = null;
+ InputStream key = null;
+
+ if (args.length >= 3) {
+ cert = new FileInputStream(args[1]);
+ key = new FileInputStream(args[2]);
+ }
+
+ boolean success = SignBoot.doSignature("/boot", System.in, System.out, cert, key);
+ System.exit(success ? 0 : 1);
+ } else {
+ System.err.println(
+ "BootSigner [args]\n" +
+ "Input from stdin, outputs to stdout\n" +
+ "\n" +
+ "Actions:\n" +
+ " -verify [x509.pem]\n" +
+ " verify image, cert is optional\n" +
+ " -sign [x509.pem] [pk8]\n" +
+ " sign image, cert and key pair is optional\n"
+ );
+ }
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/utils/FingerprintHelper.java b/app/src/full/java/com/topjohnwu/magisk/utils/FingerprintHelper.java
new file mode 100644
index 000000000..1808f06e5
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/utils/FingerprintHelper.java
@@ -0,0 +1,112 @@
+package com.topjohnwu.magisk.utils;
+
+import android.annotation.TargetApi;
+import android.app.KeyguardManager;
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.Build;
+import android.os.CancellationSignal;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyPermanentlyInvalidatedException;
+import android.security.keystore.KeyProperties;
+
+import com.topjohnwu.magisk.MagiskManager;
+
+import java.security.KeyStore;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+
+@TargetApi(Build.VERSION_CODES.M)
+public abstract class FingerprintHelper {
+
+ private FingerprintManager manager;
+ private Cipher cipher;
+ private CancellationSignal cancel;
+
+ public static boolean canUseFingerprint() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
+ return false;
+ MagiskManager mm = MagiskManager.get();
+ KeyguardManager km = mm.getSystemService(KeyguardManager.class);
+ FingerprintManager fm = mm.getSystemService(FingerprintManager.class);
+ return km.isKeyguardSecure() && fm != null && fm.isHardwareDetected() && fm.hasEnrolledFingerprints();
+ }
+
+ protected FingerprintHelper() throws Exception {
+ MagiskManager mm = MagiskManager.get();
+ KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+ manager = mm.getSystemService(FingerprintManager.class);
+ cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ + KeyProperties.BLOCK_MODE_CBC + "/"
+ + KeyProperties.ENCRYPTION_PADDING_PKCS7);
+ keyStore.load(null);
+ SecretKey key = (SecretKey) keyStore.getKey(Const.SU_KEYSTORE_KEY, null);
+ if (key == null) {
+ key = generateKey();
+ }
+ try {
+ cipher.init(Cipher.ENCRYPT_MODE, key);
+ } catch (KeyPermanentlyInvalidatedException e) {
+ // Only happens on Marshmallow
+ key = generateKey();
+ cipher.init(Cipher.ENCRYPT_MODE, key);
+ }
+ }
+
+ public abstract void onAuthenticationError(int errorCode, CharSequence errString);
+
+ public abstract void onAuthenticationHelp(int helpCode, CharSequence helpString);
+
+ public abstract void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result);
+
+ public abstract void onAuthenticationFailed();
+
+ public void startAuth() {
+ cancel = new CancellationSignal();
+ FingerprintManager.CryptoObject cryptoObject = new FingerprintManager.CryptoObject(cipher);
+ manager.authenticate(cryptoObject, cancel, 0, new FingerprintManager.AuthenticationCallback() {
+ @Override
+ public void onAuthenticationError(int errorCode, CharSequence errString) {
+ FingerprintHelper.this.onAuthenticationError(errorCode, errString);
+ }
+
+ @Override
+ public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
+ FingerprintHelper.this.onAuthenticationHelp(helpCode, helpString);
+ }
+
+ @Override
+ public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
+ FingerprintHelper.this.onAuthenticationSucceeded(result);
+ }
+
+ @Override
+ public void onAuthenticationFailed() {
+ FingerprintHelper.this.onAuthenticationFailed();
+ }
+ }, null);
+ }
+
+ public void cancel() {
+ if (cancel != null)
+ cancel.cancel();
+ }
+
+ private SecretKey generateKey() throws Exception {
+ KeyGenerator keygen = KeyGenerator
+ .getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
+ KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(
+ Const.SU_KEYSTORE_KEY,
+ KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
+ .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
+ .setUserAuthenticationRequired(true)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ builder.setInvalidatedByBiometricEnrollment(false);
+ }
+ keygen.init(builder.build());
+ return keygen.generateKey();
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/utils/ISafetyNetHelper.java b/app/src/full/java/com/topjohnwu/magisk/utils/ISafetyNetHelper.java
new file mode 100644
index 000000000..e4c329ec4
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/utils/ISafetyNetHelper.java
@@ -0,0 +1,22 @@
+package com.topjohnwu.magisk.utils;
+
+import android.support.annotation.Keep;
+
+public interface ISafetyNetHelper {
+
+ int CAUSE_SERVICE_DISCONNECTED = 0x01;
+ int CAUSE_NETWORK_LOST = 0x02;
+ int RESPONSE_ERR = 0x04;
+ int CONNECTION_FAIL = 0x08;
+
+ int BASIC_PASS = 0x10;
+ int CTS_PASS = 0x20;
+
+ void attest();
+ int getVersion();
+
+ interface Callback {
+ @Keep
+ void onResponse(int responseCode);
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/utils/Logger.java b/app/src/full/java/com/topjohnwu/magisk/utils/Logger.java
new file mode 100644
index 000000000..b3f05c111
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/utils/Logger.java
@@ -0,0 +1,27 @@
+package com.topjohnwu.magisk.utils;
+
+import android.util.Log;
+
+import com.topjohnwu.magisk.BuildConfig;
+
+import java.util.Locale;
+
+public class Logger {
+
+ public static void debug(String line) {
+ if (BuildConfig.DEBUG)
+ Log.d(Const.DEBUG_TAG, "DEBUG: " + line);
+ }
+
+ public static void debug(String fmt, Object... args) {
+ debug(Utils.fmt(fmt, args));
+ }
+
+ public static void error(String line) {
+ Log.e(Const.DEBUG_TAG, "ERROR: " + line);
+ }
+
+ public static void error(String fmt, Object... args) {
+ error(Utils.fmt(fmt, args));
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/utils/PatchAPK.java b/app/src/full/java/com/topjohnwu/magisk/utils/PatchAPK.java
new file mode 100644
index 000000000..516ed365b
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/utils/PatchAPK.java
@@ -0,0 +1,77 @@
+package com.topjohnwu.magisk.utils;
+
+import com.topjohnwu.utils.JarMap;
+
+import java.io.OutputStream;
+import java.util.jar.JarEntry;
+
+public class PatchAPK {
+
+ private static 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 static 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 static 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;
+ }
+
+ public static boolean patchPackageID(String fileName, OutputStream out, String from, String to) {
+ try {
+ JarMap apk = new JarMap(fileName);
+ JarEntry je = apk.getJarEntry(Const.ANDROID_MANIFEST);
+ byte xml[] = apk.getRawData(je);
+
+ if (!findAndPatch(xml, from, to))
+ return false;
+ if (!findAndPatch(xml, from + ".provider", to + ".provider"))
+ return false;
+
+ // Write in changes
+ apk.getOutputStream(je).write(xml);
+
+ // Sign the APK
+ ZipUtils.signZip(apk, out);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/utils/RootUtils.java b/app/src/full/java/com/topjohnwu/magisk/utils/RootUtils.java
new file mode 100644
index 000000000..00729a04d
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/utils/RootUtils.java
@@ -0,0 +1,38 @@
+package com.topjohnwu.magisk.utils;
+
+import com.topjohnwu.magisk.MagiskManager;
+import com.topjohnwu.superuser.Shell;
+import com.topjohnwu.superuser.ShellUtils;
+import com.topjohnwu.superuser.io.SuFile;
+
+public class RootUtils {
+
+ public static void init() {
+ if (Shell.rootAccess()) {
+ Const.MAGISK_DISABLE_FILE = new SuFile("/cache/.disable_magisk");
+ SuFile file = new SuFile("/sbin/.core/img");
+ if (file.exists()) {
+ Const.MAGISK_PATH = file;
+ } else if ((file = new SuFile("/dev/magisk/img")).exists()) {
+ Const.MAGISK_PATH = file;
+ } else {
+ Const.MAGISK_PATH = new SuFile("/magisk");
+ }
+ Const.MAGISK_HOST_FILE = new SuFile(Const.MAGISK_PATH + "/.core/hosts");
+ }
+ }
+
+ public static void uninstallPkg(String pkg) {
+ Shell.Sync.su("db_clean " + Const.USER_ID, "pm uninstall " + pkg);
+ }
+
+ public static void patchDTBO() {
+ if (Shell.rootAccess()) {
+ MagiskManager mm = MagiskManager.get();
+ if (mm.magiskVersionCode >= Const.MAGISK_VER.DTBO_SUPPORT) {
+ if (Boolean.parseBoolean(ShellUtils.fastCmd("mm_patch_dtbo")))
+ ShowUI.dtboPatchedNotification();
+ }
+ }
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/utils/ShellInitializer.java b/app/src/full/java/com/topjohnwu/magisk/utils/ShellInitializer.java
new file mode 100644
index 000000000..317e2aa14
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/utils/ShellInitializer.java
@@ -0,0 +1,36 @@
+package com.topjohnwu.magisk.utils;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+
+import com.topjohnwu.magisk.R;
+import com.topjohnwu.superuser.BusyBox;
+import com.topjohnwu.superuser.Shell;
+import com.topjohnwu.superuser.ShellUtils;
+
+import java.io.File;
+import java.io.InputStream;
+
+public class ShellInitializer extends Shell.Initializer {
+
+ static {
+ BusyBox.BB_PATH = new File(Const.BUSYBOX_PATH);
+ }
+
+ @Override
+ public boolean onRootShellInit(Context context, @NonNull Shell shell) throws Exception {
+ try (InputStream magiskUtils = context.getResources().openRawResource(R.raw.util_functions);
+ InputStream managerUtils = context.getResources().openRawResource(R.raw.utils)
+ ) {
+ shell.execTask((in, err, out) -> {
+ ShellUtils.pump(magiskUtils, in);
+ ShellUtils.pump(managerUtils, in);
+ });
+ }
+ shell.run(null, null,
+ "mount_partitions",
+ "get_flags",
+ "run_migrations");
+ return true;
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/utils/ShowUI.java b/app/src/full/java/com/topjohnwu/magisk/utils/ShowUI.java
new file mode 100644
index 000000000..3add96f34
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/utils/ShowUI.java
@@ -0,0 +1,273 @@
+package com.topjohnwu.magisk.utils;
+
+import android.Manifest;
+import android.app.Activity;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.support.v4.app.NotificationCompat;
+import android.support.v4.app.TaskStackBuilder;
+import android.support.v7.app.AlertDialog;
+import android.text.TextUtils;
+import android.widget.Toast;
+
+import com.topjohnwu.magisk.FlashActivity;
+import com.topjohnwu.magisk.MagiskManager;
+import com.topjohnwu.magisk.R;
+import com.topjohnwu.magisk.SplashActivity;
+import com.topjohnwu.magisk.asyncs.InstallMagisk;
+import com.topjohnwu.magisk.asyncs.MarkDownWindow;
+import com.topjohnwu.magisk.asyncs.RestoreImages;
+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 com.topjohnwu.magisk.receivers.RebootReceiver;
+import com.topjohnwu.superuser.Shell;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ShowUI {
+
+ public static void magiskUpdateNotification() {
+ MagiskManager mm = MagiskManager.get();
+
+ Intent intent = new Intent(mm, SplashActivity.class);
+ intent.putExtra(Const.Key.OPEN_SECTION, "magisk");
+ TaskStackBuilder stackBuilder = TaskStackBuilder.create(mm);
+ stackBuilder.addParentStack(SplashActivity.class);
+ stackBuilder.addNextIntent(intent);
+ PendingIntent pendingIntent = stackBuilder.getPendingIntent(Const.ID.MAGISK_UPDATE_NOTIFICATION_ID,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(mm, Const.ID.NOTIFICATION_CHANNEL);
+ builder.setSmallIcon(R.drawable.ic_magisk_outline)
+ .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)
+ .setContentIntent(pendingIntent);
+
+ NotificationManager notificationManager =
+ (NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.notify(Const.ID.MAGISK_UPDATE_NOTIFICATION_ID, builder.build());
+ }
+
+ public static void managerUpdateNotification() {
+ MagiskManager mm = MagiskManager.get();
+ String filename = Utils.fmt("MagiskManager-v%s(%d).apk",
+ mm.remoteManagerVersionString, mm.remoteManagerVersionCode);
+
+ Intent intent = new Intent(mm, ManagerUpdate.class);
+ intent.putExtra(Const.Key.INTENT_SET_LINK, mm.managerLink);
+ intent.putExtra(Const.Key.INTENT_SET_FILENAME, filename);
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(mm,
+ Const.ID.APK_UPDATE_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(mm, Const.ID.NOTIFICATION_CHANNEL);
+ builder.setSmallIcon(R.drawable.ic_magisk_outline)
+ .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)
+ .setContentIntent(pendingIntent);
+
+ NotificationManager notificationManager =
+ (NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.notify(Const.ID.APK_UPDATE_NOTIFICATION_ID, builder.build());
+ }
+
+ public static void dtboPatchedNotification() {
+ MagiskManager mm = MagiskManager.get();
+
+ Intent intent = new Intent(mm, RebootReceiver.class);
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(mm,
+ Const.ID.DTBO_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(mm, Const.ID.NOTIFICATION_CHANNEL);
+ builder.setSmallIcon(R.drawable.ic_magisk_outline)
+ .setContentTitle(mm.getString(R.string.dtbo_patched_title))
+ .setContentText(mm.getString(R.string.dtbo_patched_reboot))
+ .setVibrate(new long[]{0, 100, 100, 100})
+ .addAction(R.drawable.ic_refresh, mm.getString(R.string.reboot), pendingIntent);
+
+ NotificationManager notificationManager =
+ (NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.notify(Const.ID.DTBO_NOTIFICATION_ID, builder.build());
+ }
+
+ public static void envFixDialog(Activity activity) {
+ MagiskManager mm = Utils.getMagiskManager(activity);
+ String filename = Utils.fmt("Magisk-v%s(%d).zip",
+ mm.remoteMagiskVersionString, mm.remoteMagiskVersionCode);
+ new AlertDialogBuilder(activity)
+ .setTitle(R.string.env_fix_title)
+ .setMessage(R.string.env_fix_msg)
+ .setCancelable(true)
+ .setPositiveButton(R.string.yes, (d, i) -> {
+ Utils.dlAndReceive(activity, new DownloadReceiver() {
+ @Override
+ public void onDownloadDone(Context context, Uri uri) {
+ new InstallMagisk(activity, uri).exec();
+ }
+ }, mm.magiskLink, filename);
+ })
+ .setNegativeButton(R.string.no_thanks, null)
+ .show();
+ }
+
+ public static void magiskInstallDialog(Activity activity) {
+ MagiskManager mm = Utils.getMagiskManager(activity);
+ String filename = Utils.fmt("Magisk-v%s(%d).zip",
+ mm.remoteMagiskVersionString, mm.remoteMagiskVersionCode);
+ AlertDialog.Builder b = new AlertDialogBuilder(activity)
+ .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 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(activity)
+ .setTitle(R.string.select_method)
+ .setItems(
+ options.toArray(new String [0]),
+ (dialog, idx) -> {
+ DownloadReceiver receiver = null;
+ switch (idx) {
+ case 1:
+ if (mm.remoteMagiskVersionCode < 1400) {
+ MagiskManager.toast(R.string.no_boot_file_patch_support, Toast.LENGTH_LONG);
+ return;
+ }
+ MagiskManager.toast(R.string.boot_file_patch_msg, Toast.LENGTH_LONG);
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.setType("*/*");
+ ((com.topjohnwu.magisk.components.Activity) activity)
+ .startActivityForResult(intent, Const.ID.SELECT_BOOT,
+ (requestCode, resultCode, data) -> {
+ if (requestCode == Const.ID.SELECT_BOOT
+ && resultCode == Activity.RESULT_OK && data != null) {
+ Utils.dlAndReceive(
+ activity,
+ new DownloadReceiver() {
+ @Override
+ public void onDownloadDone(Context context, Uri uri) {
+ Intent intent = new Intent(mm, FlashActivity.class);
+ intent.setData(uri)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .putExtra(Const.Key.FLASH_SET_BOOT, data.getData())
+ .putExtra(Const.Key.FLASH_ACTION, Const.Value.PATCH_BOOT);
+ mm.startActivity(intent);
+ }
+ },
+ mm.magiskLink,
+ filename
+ );
+ }
+ });
+ return;
+ case 0:
+ receiver = new DownloadReceiver() {
+ @Override
+ public void onDownloadDone(Context context, Uri uri) {
+ SnackbarMaker.showUri(activity, uri);
+ }
+ };
+ break;
+ case 2:
+ receiver = new DownloadReceiver() {
+ @Override
+ public void onDownloadDone(Context context, Uri uri) {
+ Intent intent = new Intent(mm, FlashActivity.class);
+ intent.setData(uri).putExtra(Const.Key.FLASH_ACTION,
+ Const.Value.FLASH_MAGISK);
+ activity.startActivity(intent);
+ }
+ };
+ break;
+ case 3:
+ receiver = new DownloadReceiver() {
+ @Override
+ public void onDownloadDone(Context context, Uri uri) {
+ Intent intent = new Intent(mm, FlashActivity.class);
+ intent.setData(uri).putExtra(Const.Key.FLASH_ACTION,
+ Const.Value.FLASH_SECOND_SLOT);
+ activity.startActivity(intent);
+ }
+ };
+ default:
+ }
+ Utils.dlAndReceive(activity, receiver, mm.magiskLink, filename);
+ }
+ ).show();
+ })
+ .setNegativeButton(R.string.no_thanks, null);
+ if (!TextUtils.isEmpty(mm.magiskNoteLink)) {
+ b.setNeutralButton(R.string.release_notes, (d, i) -> {
+ if (mm.magiskNoteLink.contains("forum.xda-developers")) {
+ // Open forum links in browser
+ Intent openLink = new Intent(Intent.ACTION_VIEW, Uri.parse(mm.magiskNoteLink));
+ openLink.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mm.startActivity(openLink);
+ } else {
+ new MarkDownWindow(activity, null, mm.magiskNoteLink).exec();
+ }
+ });
+ }
+ b.show();
+ }
+
+ public static void managerInstallDialog(Activity activity) {
+ MagiskManager mm = Utils.getMagiskManager(activity);
+ String filename = Utils.fmt("MagiskManager-v%s(%d).apk",
+ mm.remoteManagerVersionString, mm.remoteManagerVersionCode);
+ AlertDialog.Builder b = 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, filename))
+ .setCancelable(true)
+ .setPositiveButton(R.string.install, (d, i) -> {
+ com.topjohnwu.magisk.components.Activity.runWithPermission(activity,
+ new String[] { 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, filename);
+ mm.sendBroadcast(intent);
+ });
+ })
+ .setNegativeButton(R.string.no_thanks, null);
+ if (!TextUtils.isEmpty(mm.managerNoteLink)) {
+ b.setNeutralButton(R.string.app_changelog, (d, i) ->
+ new MarkDownWindow(activity, null, mm.managerNoteLink).exec());
+ }
+ b.show();
+ }
+
+ public static void uninstallDialog(Activity activity) {
+ MagiskManager mm = Utils.getMagiskManager(activity);
+ AlertDialog.Builder b = new AlertDialogBuilder(activity)
+ .setTitle(R.string.uninstall_magisk_title)
+ .setMessage(R.string.uninstall_magisk_msg)
+ .setNeutralButton(R.string.restore_img, (d, i) -> new RestoreImages(activity).exec());
+ if (!TextUtils.isEmpty(mm.uninstallerLink)) {
+ b.setPositiveButton(R.string.complete_uninstall, (d, i) ->
+ Utils.dlAndReceive(activity, new DownloadReceiver() {
+ @Override
+ public void onDownloadDone(Context context, Uri uri) {
+ Intent intent = new Intent(context, FlashActivity.class)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .setData(uri)
+ .putExtra(Const.Key.FLASH_ACTION, Const.Value.UNINSTALL);
+ context.startActivity(intent);
+ }
+ }, mm.uninstallerLink, "magisk-uninstaller.zip"));
+ }
+ b.show();
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/utils/Topic.java b/app/src/full/java/com/topjohnwu/magisk/utils/Topic.java
new file mode 100644
index 000000000..d174c576a
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/utils/Topic.java
@@ -0,0 +1,99 @@
+package com.topjohnwu.magisk.utils;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+
+public class Topic {
+
+ private static final int NON_INIT = 0;
+ private static final int PENDING = 1;
+ private static final int PUBLISHED = 2;
+
+ private int state = NON_INIT;
+ private List> subscribers;
+ private Object[] results;
+
+ public Topic() {
+ subscribers = new SyncArrayList<>();
+ }
+
+ public synchronized void subscribe(Subscriber sub) {
+ subscribers.add(new WeakReference<>(sub));
+ }
+
+ public synchronized void unsubscribe() {
+ subscribers = new SyncArrayList<>();
+ }
+
+ public synchronized void unsubscribe(Subscriber sub) {
+ List> subs = subscribers;
+ subscribers = new ArrayList<>();
+ for (WeakReference subscriber : subs) {
+ if (subscriber.get() != null && subscriber.get() != sub)
+ subscribers.add(subscriber);
+ }
+ }
+
+ public void reset() {
+ state = NON_INIT;
+ results = null;
+ }
+
+ public boolean isPublished() {
+ return state == PUBLISHED;
+ }
+
+ public void publish() {
+ publish(true);
+ }
+
+ public void publish(boolean record, Object... results) {
+ if (record)
+ state = PUBLISHED;
+ this.results = results;
+ // Snapshot
+ List> subs = subscribers;
+ for (WeakReference subscriber : subs) {
+ if (subscriber != null && subscriber.get() != null)
+ subscriber.get().onTopicPublished(this);
+ }
+ }
+
+ public Object[] getResults() {
+ return results;
+ }
+
+ public boolean isPending() {
+ return state == PENDING;
+ }
+
+ public void setPending() {
+ state = PENDING;
+ }
+
+ public interface Subscriber {
+ default void subscribeTopics() {
+ for (Topic topic : getSubscription()) {
+ if (topic.isPublished()) {
+ onTopicPublished(topic);
+ }
+ topic.subscribe(this);
+ }
+ }
+ default void unsubscribeTopics() {
+ for (Topic event : getSubscription()) {
+ event.unsubscribe(this);
+ }
+ }
+ void onTopicPublished(Topic topic);
+ Topic[] getSubscription();
+ }
+
+ private static class SyncArrayList extends ArrayList {
+ @Override
+ public synchronized boolean add(E e) {
+ return super.add(e);
+ }
+ }
+}
diff --git a/app/src/full/java/com/topjohnwu/magisk/utils/ZipUtils.java b/app/src/full/java/com/topjohnwu/magisk/utils/ZipUtils.java
new file mode 100644
index 000000000..f18eb7017
--- /dev/null
+++ b/app/src/full/java/com/topjohnwu/magisk/utils/ZipUtils.java
@@ -0,0 +1,69 @@
+package com.topjohnwu.magisk.utils;
+
+import com.topjohnwu.superuser.ShellUtils;
+import com.topjohnwu.superuser.io.SuFile;
+import com.topjohnwu.superuser.io.SuFileOutputStream;
+import com.topjohnwu.utils.JarMap;
+import com.topjohnwu.utils.SignAPK;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+public class ZipUtils {
+
+ public static void unzip(File zip, File folder, String path, boolean junkPath) throws IOException {
+ 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 IOException {
+ try {
+ ZipInputStream zipfile = new ZipInputStream(zip);
+ ZipEntry entry;
+ while ((entry = zipfile.getNextEntry()) != 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();
+ }
+ File dest = new File(folder, name);
+ if (!dest.getParentFile().exists() && !dest.getParentFile().mkdirs()) {
+ dest = new SuFile(folder, name);
+ dest.getParentFile().mkdirs();
+ }
+ try (OutputStream out = new SuFileOutputStream(dest)) {
+ ShellUtils.pump(zipfile, out);
+ }
+ }
+ }
+ catch(IOException e) {
+ e.printStackTrace();
+ throw e;
+ }
+ }
+
+ public static void signZip(File input, File output) throws Exception {
+ try (JarMap map = new JarMap(input, false);
+ BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(output))) {
+ signZip(map, out);
+ }
+ }
+
+ public static void signZip(JarMap input, OutputStream output) throws Exception {
+ SignAPK.signZip(null, null, input, output);
+ }
+}
diff --git a/app/src/full/res/drawable-nodpi/logo.png b/app/src/full/res/drawable-nodpi/logo.png
new file mode 100644
index 000000000..94f74a0ca
Binary files /dev/null and b/app/src/full/res/drawable-nodpi/logo.png differ
diff --git a/app/src/full/res/drawable-v26/sc_cloud_download.xml b/app/src/full/res/drawable-v26/sc_cloud_download.xml
new file mode 100644
index 000000000..734f23865
--- /dev/null
+++ b/app/src/full/res/drawable-v26/sc_cloud_download.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/full/res/drawable-v26/sc_extension.xml b/app/src/full/res/drawable-v26/sc_extension.xml
new file mode 100644
index 000000000..a7a98ef76
--- /dev/null
+++ b/app/src/full/res/drawable-v26/sc_extension.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/full/res/drawable-v26/sc_magiskhide.xml b/app/src/full/res/drawable-v26/sc_magiskhide.xml
new file mode 100644
index 000000000..02a59ce4e
--- /dev/null
+++ b/app/src/full/res/drawable-v26/sc_magiskhide.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/full/res/drawable-v26/sc_superuser.xml b/app/src/full/res/drawable-v26/sc_superuser.xml
new file mode 100644
index 000000000..505088495
--- /dev/null
+++ b/app/src/full/res/drawable-v26/sc_superuser.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/full/res/drawable/ic_add.xml b/app/src/full/res/drawable/ic_add.xml
new file mode 100644
index 000000000..0258249cc
--- /dev/null
+++ b/app/src/full/res/drawable/ic_add.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/full/res/drawable/ic_archive.xml b/app/src/full/res/drawable/ic_archive.xml
new file mode 100644
index 000000000..8b18a9d56
--- /dev/null
+++ b/app/src/full/res/drawable/ic_archive.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/full/res/drawable/ic_arrow.xml b/app/src/full/res/drawable/ic_arrow.xml
new file mode 100644
index 000000000..46fd51124
--- /dev/null
+++ b/app/src/full/res/drawable/ic_arrow.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/full/res/drawable/ic_attach_money.xml b/app/src/full/res/drawable/ic_attach_money.xml
new file mode 100644
index 000000000..4fb28d7f4
--- /dev/null
+++ b/app/src/full/res/drawable/ic_attach_money.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/full/res/drawable/ic_bug_report.xml b/app/src/full/res/drawable/ic_bug_report.xml
new file mode 100644
index 000000000..0ac39ddc1
--- /dev/null
+++ b/app/src/full/res/drawable/ic_bug_report.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/full/res/drawable/ic_cancel.xml b/app/src/full/res/drawable/ic_cancel.xml
new file mode 100644
index 000000000..e6545bf8a
--- /dev/null
+++ b/app/src/full/res/drawable/ic_cancel.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/full/res/drawable/ic_check_circle.xml b/app/src/full/res/drawable/ic_check_circle.xml
new file mode 100644
index 000000000..45d1b3076
--- /dev/null
+++ b/app/src/full/res/drawable/ic_check_circle.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/full/res/drawable/ic_cloud_download.xml b/app/src/full/res/drawable/ic_cloud_download.xml
new file mode 100644
index 000000000..4aaf5ebc1
--- /dev/null
+++ b/app/src/full/res/drawable/ic_cloud_download.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/full/res/drawable/ic_delete.xml b/app/src/full/res/drawable/ic_delete.xml
new file mode 100644
index 000000000..9b7a8c7e5
--- /dev/null
+++ b/app/src/full/res/drawable/ic_delete.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/full/res/drawable/ic_device_information.xml b/app/src/full/res/drawable/ic_device_information.xml
new file mode 100644
index 000000000..2c4e5c7f4
--- /dev/null
+++ b/app/src/full/res/drawable/ic_device_information.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/full/res/drawable/ic_extension.xml b/app/src/full/res/drawable/ic_extension.xml
new file mode 100644
index 000000000..549fdce23
--- /dev/null
+++ b/app/src/full/res/drawable/ic_extension.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/full/res/drawable/ic_file_download_black.xml b/app/src/full/res/drawable/ic_file_download_black.xml
new file mode 100644
index 000000000..d05655222
--- /dev/null
+++ b/app/src/full/res/drawable/ic_file_download_black.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/full/res/drawable/ic_fingerprint.xml b/app/src/full/res/drawable/ic_fingerprint.xml
new file mode 100644
index 000000000..f650f7445
--- /dev/null
+++ b/app/src/full/res/drawable/ic_fingerprint.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/full/res/drawable/ic_github.xml b/app/src/full/res/drawable/ic_github.xml
new file mode 100644
index 000000000..83be6c04e
--- /dev/null
+++ b/app/src/full/res/drawable/ic_github.xml
@@ -0,0 +1,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/full/res/drawable/ic_help.xml b/app/src/full/res/drawable/ic_help.xml
new file mode 100644
index 000000000..98d384f8b
--- /dev/null
+++ b/app/src/full/res/drawable/ic_help.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/full/res/drawable/ic_history.xml b/app/src/full/res/drawable/ic_history.xml
new file mode 100644
index 000000000..6b9f91a37
--- /dev/null
+++ b/app/src/full/res/drawable/ic_history.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/full/res/drawable/ic_info_outline.xml b/app/src/full/res/drawable/ic_info_outline.xml
new file mode 100644
index 000000000..2d66b0bf6
--- /dev/null
+++ b/app/src/full/res/drawable/ic_info_outline.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/full/res/drawable/ic_language.xml b/app/src/full/res/drawable/ic_language.xml
new file mode 100644
index 000000000..85b37743a
--- /dev/null
+++ b/app/src/full/res/drawable/ic_language.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/full/res/drawable/ic_magisk_outline.xml b/app/src/full/res/drawable/ic_magisk_outline.xml
new file mode 100644
index 000000000..7bbc6e822
--- /dev/null
+++ b/app/src/full/res/drawable/ic_magisk_outline.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/full/res/drawable/ic_magiskhide.xml b/app/src/full/res/drawable/ic_magiskhide.xml
new file mode 100644
index 000000000..6a3ce181d
--- /dev/null
+++ b/app/src/full/res/drawable/ic_magiskhide.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/full/res/drawable/ic_menu_overflow_material.xml b/app/src/full/res/drawable/ic_menu_overflow_material.xml
new file mode 100644
index 000000000..4ab8f2f1d
--- /dev/null
+++ b/app/src/full/res/drawable/ic_menu_overflow_material.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/full/res/drawable/ic_more.xml b/app/src/full/res/drawable/ic_more.xml
new file mode 100644
index 000000000..da83afdb1
--- /dev/null
+++ b/app/src/full/res/drawable/ic_more.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/full/res/drawable/ic_notifications.xml b/app/src/full/res/drawable/ic_notifications.xml
new file mode 100644
index 000000000..be9f8368d
--- /dev/null
+++ b/app/src/full/res/drawable/ic_notifications.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/full/res/drawable/ic_person.xml b/app/src/full/res/drawable/ic_person.xml
new file mode 100644
index 000000000..234d73d8d
--- /dev/null
+++ b/app/src/full/res/drawable/ic_person.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/full/res/drawable/ic_refresh.xml b/app/src/full/res/drawable/ic_refresh.xml
new file mode 100644
index 000000000..635d77eb9
--- /dev/null
+++ b/app/src/full/res/drawable/ic_refresh.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/full/res/drawable/ic_safetynet.xml b/app/src/full/res/drawable/ic_safetynet.xml
new file mode 100644
index 000000000..a380b9bb4
--- /dev/null
+++ b/app/src/full/res/drawable/ic_safetynet.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/full/res/drawable/ic_save.xml b/app/src/full/res/drawable/ic_save.xml
new file mode 100644
index 000000000..194ffafd4
--- /dev/null
+++ b/app/src/full/res/drawable/ic_save.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/full/res/drawable/ic_settings.xml b/app/src/full/res/drawable/ic_settings.xml
new file mode 100644
index 000000000..ace746c40
--- /dev/null
+++ b/app/src/full/res/drawable/ic_settings.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/full/res/drawable/ic_sort.xml b/app/src/full/res/drawable/ic_sort.xml
new file mode 100644
index 000000000..0fd497923
--- /dev/null
+++ b/app/src/full/res/drawable/ic_sort.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/full/res/drawable/ic_splash_activity.xml b/app/src/full/res/drawable/ic_splash_activity.xml
new file mode 100644
index 000000000..7b0082a31
--- /dev/null
+++ b/app/src/full/res/drawable/ic_splash_activity.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+ -
+
+
+
+
\ No newline at end of file
diff --git a/app/src/full/res/drawable/ic_su_warning.xml b/app/src/full/res/drawable/ic_su_warning.xml
new file mode 100644
index 000000000..87e5f4ac4
--- /dev/null
+++ b/app/src/full/res/drawable/ic_su_warning.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/full/res/drawable/ic_superuser.xml b/app/src/full/res/drawable/ic_superuser.xml
new file mode 100644
index 000000000..01fb6a803
--- /dev/null
+++ b/app/src/full/res/drawable/ic_superuser.xml
@@ -0,0 +1,9 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/full/res/drawable/ic_undelete.xml b/app/src/full/res/drawable/ic_undelete.xml
new file mode 100644
index 000000000..8546fea72
--- /dev/null
+++ b/app/src/full/res/drawable/ic_undelete.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/full/res/drawable/ic_update.xml b/app/src/full/res/drawable/ic_update.xml
new file mode 100644
index 000000000..b2a2c14f0
--- /dev/null
+++ b/app/src/full/res/drawable/ic_update.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/full/res/drawable/ic_xda.xml b/app/src/full/res/drawable/ic_xda.xml
new file mode 100644
index 000000000..918a5497b
--- /dev/null
+++ b/app/src/full/res/drawable/ic_xda.xml
@@ -0,0 +1,9 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/full/res/drawable/sc_cloud_download.xml b/app/src/full/res/drawable/sc_cloud_download.xml
new file mode 100644
index 000000000..1a7dae048
--- /dev/null
+++ b/app/src/full/res/drawable/sc_cloud_download.xml
@@ -0,0 +1,13 @@
+
+
+ -
+
+
+
+
+ -
+
+
+
\ No newline at end of file
diff --git a/app/src/full/res/drawable/sc_extension.xml b/app/src/full/res/drawable/sc_extension.xml
new file mode 100644
index 000000000..21e823a39
--- /dev/null
+++ b/app/src/full/res/drawable/sc_extension.xml
@@ -0,0 +1,13 @@
+
+
+ -
+
+
+
+
+ -
+
+
+
\ No newline at end of file
diff --git a/app/src/full/res/drawable/sc_magiskhide.xml b/app/src/full/res/drawable/sc_magiskhide.xml
new file mode 100644
index 000000000..b388a2f3f
--- /dev/null
+++ b/app/src/full/res/drawable/sc_magiskhide.xml
@@ -0,0 +1,13 @@
+
+
+ -
+
+
+
+
+ -
+
+
+
\ No newline at end of file
diff --git a/app/src/full/res/drawable/sc_superuser.xml b/app/src/full/res/drawable/sc_superuser.xml
new file mode 100644
index 000000000..0e6c58937
--- /dev/null
+++ b/app/src/full/res/drawable/sc_superuser.xml
@@ -0,0 +1,13 @@
+
+
+ -
+
+
+
+
+ -
+
+
+
\ No newline at end of file
diff --git a/app/src/full/res/layout/activity_about.xml b/app/src/full/res/layout/activity_about.xml
new file mode 100644
index 000000000..2d77018df
--- /dev/null
+++ b/app/src/full/res/layout/activity_about.xml
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/full/res/layout/activity_flash.xml b/app/src/full/res/layout/activity_flash.xml
new file mode 100644
index 000000000..d33e6e726
--- /dev/null
+++ b/app/src/full/res/layout/activity_flash.xml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/full/res/layout/activity_main.xml b/app/src/full/res/layout/activity_main.xml
new file mode 100644
index 000000000..9ac55384f
--- /dev/null
+++ b/app/src/full/res/layout/activity_main.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/full/res/layout/activity_request.xml b/app/src/full/res/layout/activity_request.xml
new file mode 100644
index 000000000..9a37d32e6
--- /dev/null
+++ b/app/src/full/res/layout/activity_request.xml
@@ -0,0 +1,127 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/full/res/layout/activity_settings.xml b/app/src/full/res/layout/activity_settings.xml
new file mode 100644
index 000000000..10434c358
--- /dev/null
+++ b/app/src/full/res/layout/activity_settings.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/full/res/layout/alert_dialog.xml b/app/src/full/res/layout/alert_dialog.xml
new file mode 100644
index 000000000..91b8c4779
--- /dev/null
+++ b/app/src/full/res/layout/alert_dialog.xml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/full/res/layout/custom_channel_dialog.xml b/app/src/full/res/layout/custom_channel_dialog.xml
new file mode 100644
index 000000000..601a7144c
--- /dev/null
+++ b/app/src/full/res/layout/custom_channel_dialog.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/full/res/layout/fragment_log.xml b/app/src/full/res/layout/fragment_log.xml
new file mode 100644
index 000000000..370b3c64e
--- /dev/null
+++ b/app/src/full/res/layout/fragment_log.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/full/res/layout/fragment_magisk.xml b/app/src/full/res/layout/fragment_magisk.xml
new file mode 100644
index 000000000..6e86c2eed
--- /dev/null
+++ b/app/src/full/res/layout/fragment_magisk.xml
@@ -0,0 +1,363 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/full/res/layout/fragment_magisk_hide.xml b/app/src/full/res/layout/fragment_magisk_hide.xml
new file mode 100644
index 000000000..781a4ab06
--- /dev/null
+++ b/app/src/full/res/layout/fragment_magisk_hide.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/full/res/layout/fragment_magisk_log.xml b/app/src/full/res/layout/fragment_magisk_log.xml
new file mode 100644
index 000000000..290318956
--- /dev/null
+++ b/app/src/full/res/layout/fragment_magisk_log.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/full/res/layout/fragment_modules.xml b/app/src/full/res/layout/fragment_modules.xml
new file mode 100644
index 000000000..6bf2d68da
--- /dev/null
+++ b/app/src/full/res/layout/fragment_modules.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/full/res/layout/fragment_repos.xml b/app/src/full/res/layout/fragment_repos.xml
new file mode 100644
index 000000000..d10e3cf06
--- /dev/null
+++ b/app/src/full/res/layout/fragment_repos.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/full/res/layout/fragment_su_log.xml b/app/src/full/res/layout/fragment_su_log.xml
new file mode 100644
index 000000000..05c52db77
--- /dev/null
+++ b/app/src/full/res/layout/fragment_su_log.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
diff --git a/app/src/full/res/layout/fragment_superuser.xml b/app/src/full/res/layout/fragment_superuser.xml
new file mode 100644
index 000000000..55a4dd234
--- /dev/null
+++ b/app/src/full/res/layout/fragment_superuser.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
diff --git a/app/src/full/res/layout/info_item_row.xml b/app/src/full/res/layout/info_item_row.xml
new file mode 100644
index 000000000..ea51a6de1
--- /dev/null
+++ b/app/src/full/res/layout/info_item_row.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/full/res/layout/list_item_app.xml b/app/src/full/res/layout/list_item_app.xml
new file mode 100644
index 000000000..5e3709e86
--- /dev/null
+++ b/app/src/full/res/layout/list_item_app.xml
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/full/res/layout/list_item_module.xml b/app/src/full/res/layout/list_item_module.xml
new file mode 100644
index 000000000..2cffcca7b
--- /dev/null
+++ b/app/src/full/res/layout/list_item_module.xml
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/full/res/layout/list_item_policy.xml b/app/src/full/res/layout/list_item_policy.xml
new file mode 100644
index 000000000..6df19b83b
--- /dev/null
+++ b/app/src/full/res/layout/list_item_policy.xml
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/full/res/layout/list_item_repo.xml b/app/src/full/res/layout/list_item_repo.xml
new file mode 100644
index 000000000..6c19cb3af
--- /dev/null
+++ b/app/src/full/res/layout/list_item_repo.xml
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/full/res/layout/list_item_sulog.xml b/app/src/full/res/layout/list_item_sulog.xml
new file mode 100644
index 000000000..dae9ec209
--- /dev/null
+++ b/app/src/full/res/layout/list_item_sulog.xml
@@ -0,0 +1,145 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/full/res/layout/list_item_sulog_group.xml b/app/src/full/res/layout/list_item_sulog_group.xml
new file mode 100644
index 000000000..919dca942
--- /dev/null
+++ b/app/src/full/res/layout/list_item_sulog_group.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/full/res/layout/section.xml b/app/src/full/res/layout/section.xml
new file mode 100644
index 000000000..2340591a6
--- /dev/null
+++ b/app/src/full/res/layout/section.xml
@@ -0,0 +1,14 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/full/res/layout/toolbar.xml b/app/src/full/res/layout/toolbar.xml
new file mode 100644
index 000000000..f7311d812
--- /dev/null
+++ b/app/src/full/res/layout/toolbar.xml
@@ -0,0 +1,7 @@
+
+
diff --git a/app/src/full/res/menu/drawer.xml b/app/src/full/res/menu/drawer.xml
new file mode 100644
index 000000000..14603a710
--- /dev/null
+++ b/app/src/full/res/menu/drawer.xml
@@ -0,0 +1,63 @@
+
+
diff --git a/app/src/full/res/menu/menu_log.xml b/app/src/full/res/menu/menu_log.xml
new file mode 100644
index 000000000..7fc9e341b
--- /dev/null
+++ b/app/src/full/res/menu/menu_log.xml
@@ -0,0 +1,24 @@
+
+
\ No newline at end of file
diff --git a/app/src/full/res/menu/menu_magiskhide.xml b/app/src/full/res/menu/menu_magiskhide.xml
new file mode 100644
index 000000000..448057799
--- /dev/null
+++ b/app/src/full/res/menu/menu_magiskhide.xml
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/app/src/full/res/menu/menu_reboot.xml b/app/src/full/res/menu/menu_reboot.xml
new file mode 100644
index 000000000..5bf662aba
--- /dev/null
+++ b/app/src/full/res/menu/menu_reboot.xml
@@ -0,0 +1,25 @@
+
+
\ No newline at end of file
diff --git a/app/src/full/res/menu/menu_repo.xml b/app/src/full/res/menu/menu_repo.xml
new file mode 100644
index 000000000..9d26a596d
--- /dev/null
+++ b/app/src/full/res/menu/menu_repo.xml
@@ -0,0 +1,16 @@
+
+
\ No newline at end of file
diff --git a/app/src/full/res/raw/changelog.md b/app/src/full/res/raw/changelog.md
new file mode 100644
index 000000000..e490bb2e7
--- /dev/null
+++ b/app/src/full/res/raw/changelog.md
@@ -0,0 +1,11 @@
+### v5.8.1
+- Fix a bug that cause the root shell initializer not running in BusyBox.
+This is the cause of freezes on some older devices and DTBO being patched unconditionally.
+
+### Note
+If your device has a separate DTBO, and you do NOT want it patched but it was patched due to the bug,
+follow these instructions to fix it:
+
+1. Uninstall → Restore Images. Do **NOT** reboot afterwards!
+2. Check **Preserve AVB 2.0/dm-verity** in Advanced Settings
+3. Install → Install → Direct Install to install correctly with proper settings
diff --git a/app/src/full/res/raw/dark.css b/app/src/full/res/raw/dark.css
new file mode 100644
index 000000000..86314abf1
--- /dev/null
+++ b/app/src/full/res/raw/dark.css
@@ -0,0 +1,276 @@
+body {
+ font-family: Helvetica, arial, sans-serif;
+ font-size: 14px;
+ line-height: 1.6;
+ padding-top: 10px;
+ padding-bottom: 10px;
+ background-color: #424242;
+ color: white;
+ padding: 15px; }
+
+body > *:first-child {
+ margin-top: 0 !important; }
+body > *:last-child {
+ margin-bottom: 0 !important; }
+
+a {
+ color: #4183C4; }
+a.absent {
+ color: #cc0000; }
+a.anchor {
+ display: block;
+ padding-left: 30px;
+ margin-left: -30px;
+ cursor: pointer;
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0; }
+
+h1, h2, h3, h4, h5, h6 {
+ margin: 20px 0 10px;
+ padding: 0;
+ font-weight: bold;
+ -webkit-font-smoothing: antialiased;
+ cursor: text;
+ position: relative; }
+
+h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, h5:hover a.anchor, h6:hover a.anchor {
+ background: url("../../images/modules/styleguide/para.png") no-repeat 10px center;
+ text-decoration: none; }
+
+h1 tt, h1 code {
+ font-size: inherit; }
+
+h2 tt, h2 code {
+ font-size: inherit; }
+
+h3 tt, h3 code {
+ font-size: inherit; }
+
+h4 tt, h4 code {
+ font-size: inherit; }
+
+h5 tt, h5 code {
+ font-size: inherit; }
+
+h6 tt, h6 code {
+ font-size: inherit; }
+
+h1 {
+ font-size: 28px; }
+
+h2 {
+ font-size: 24px;
+ border-bottom: 1px solid #cccccc; }
+
+h3 {
+ font-size: 18px; }
+
+h4 {
+ font-size: 16px; }
+
+h5 {
+ font-size: 14px; }
+
+h6 {
+ color: #888888;
+ font-size: 14px; }
+
+p, blockquote, ul, ol, dl, li, table, pre {
+ margin: 15px 0; }
+
+hr {
+ background: transparent url("../../images/modules/pulls/dirty-shade.png") repeat-x 0 0;
+ border: 0 none;
+ color: #cccccc;
+ height: 4px;
+ padding: 0; }
+
+body > h2:first-child {
+ margin-top: 0;
+ padding-top: 0; }
+body > h1:first-child {
+ margin-top: 0;
+ padding-top: 0; }
+ body > h1:first-child + h2 {
+ margin-top: 0;
+ padding-top: 0; }
+body > h3:first-child, body > h4:first-child, body > h5:first-child, body > h6:first-child {
+ margin-top: 0;
+ padding-top: 0; }
+
+a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 {
+ margin-top: 0;
+ padding-top: 0; }
+
+h1 p, h2 p, h3 p, h4 p, h5 p, h6 p {
+ margin-top: 0; }
+
+li p.first {
+ display: inline-block; }
+
+ul, ol {
+ padding-left: 30px; }
+
+ul :first-child, ol :first-child {
+ margin-top: 0; }
+
+ul :last-child, ol :last-child {
+ margin-bottom: 0; }
+
+dl {
+ padding: 0; }
+ dl dt {
+ font-size: 14px;
+ font-weight: bold;
+ font-style: italic;
+ padding: 0;
+ margin: 15px 0 5px; }
+ dl dt:first-child {
+ padding: 0; }
+ dl dt > :first-child {
+ margin-top: 0; }
+ dl dt > :last-child {
+ margin-bottom: 0; }
+ dl dd {
+ margin: 0 0 15px;
+ padding: 0 15px; }
+ dl dd > :first-child {
+ margin-top: 0; }
+ dl dd > :last-child {
+ margin-bottom: 0; }
+
+blockquote {
+ border-left: 4px solid #404040;
+ padding: 0 15px;
+ color: #888888; }
+ blockquote > :first-child {
+ margin-top: 0; }
+ blockquote > :last-child {
+ margin-bottom: 0; }
+
+table {
+ padding: 0; }
+ table tr {
+ border-top: 1px solid #707070;
+ background-color: #303030;
+ margin: 0;
+ padding: 0; }
+ table tr:nth-child(2n) {
+ background-color: #505050; }
+ table tr th {
+ font-weight: bold;
+ border: 1px solid #707070;
+ text-align: left;
+ margin: 0;
+ padding: 6px 13px; }
+ table tr td {
+ border: 1px solid #707070;
+ text-align: left;
+ margin: 0;
+ padding: 6px 13px; }
+ table tr th :first-child, table tr td :first-child {
+ margin-top: 0; }
+ table tr th :last-child, table tr td :last-child {
+ margin-bottom: 0; }
+
+img {
+ max-width: 100%; }
+
+span.frame {
+ display: block;
+ overflow: hidden; }
+ span.frame > span {
+ border: 1px solid #dddddd;
+ display: block;
+ float: left;
+ overflow: hidden;
+ margin: 13px 0 0;
+ padding: 7px;
+ width: auto; }
+ span.frame span img {
+ display: block;
+ float: left; }
+ span.frame span span {
+ clear: both;
+ color: #cccccc;
+ display: block;
+ padding: 5px 0 0; }
+span.align-center {
+ display: block;
+ overflow: hidden;
+ clear: both; }
+ span.align-center > span {
+ display: block;
+ overflow: hidden;
+ margin: 13px auto 0;
+ text-align: center; }
+ span.align-center span img {
+ margin: 0 auto;
+ text-align: center; }
+span.align-right {
+ display: block;
+ overflow: hidden;
+ clear: both; }
+ span.align-right > span {
+ display: block;
+ overflow: hidden;
+ margin: 13px 0 0;
+ text-align: right; }
+ span.align-right span img {
+ margin: 0;
+ text-align: right; }
+span.float-left {
+ display: block;
+ margin-right: 13px;
+ overflow: hidden;
+ float: left; }
+ span.float-left span {
+ margin: 13px 0 0; }
+span.float-right {
+ display: block;
+ margin-left: 13px;
+ overflow: hidden;
+ float: right; }
+ span.float-right > span {
+ display: block;
+ overflow: hidden;
+ margin: 13px auto 0;
+ text-align: right; }
+
+code, tt {
+ margin: 0 2px;
+ padding: 0 5px;
+ white-space: nowrap;
+ border: 1px solid #707070;
+ background-color: #606060;
+ border-radius: 3px; }
+
+pre code {
+ margin: 0;
+ padding: 0;
+ white-space: pre;
+ border: none;
+ background: transparent; }
+
+.highlight pre {
+ background-color: #3f3f3f;
+ border: 1px solid #707070;
+ font-size: 13px;
+ line-height: 19px;
+ overflow: auto;
+ padding: 6px 10px;
+ border-radius: 3px; }
+
+pre {
+ background-color: #606060;
+ border: 1px solid #707070;
+ font-size: 13px;
+ line-height: 19px;
+ overflow: auto;
+ padding: 6px 10px;
+ border-radius: 3px; }
+ pre code, pre tt {
+ background-color: transparent;
+ border: none; }
\ No newline at end of file
diff --git a/app/src/full/res/raw/light.css b/app/src/full/res/raw/light.css
new file mode 100644
index 000000000..6895bb1a9
--- /dev/null
+++ b/app/src/full/res/raw/light.css
@@ -0,0 +1,277 @@
+body {
+ font-family: Helvetica, arial, sans-serif;
+ font-size: 14px;
+ line-height: 1.6;
+ padding-top: 10px;
+ padding-bottom: 10px;
+ background-color: white;
+ padding: 15px; }
+
+body > *:first-child {
+ margin-top: 0 !important; }
+body > *:last-child {
+ margin-bottom: 0 !important; }
+
+a {
+ color: #4183C4; }
+a.absent {
+ color: #cc0000; }
+a.anchor {
+ display: block;
+ padding-left: 30px;
+ margin-left: -30px;
+ cursor: pointer;
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0; }
+
+h1, h2, h3, h4, h5, h6 {
+ margin: 20px 0 10px;
+ padding: 0;
+ font-weight: bold;
+ -webkit-font-smoothing: antialiased;
+ cursor: text;
+ position: relative; }
+
+h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, h5:hover a.anchor, h6:hover a.anchor {
+ background: url("../../images/modules/styleguide/para.png") no-repeat 10px center;
+ text-decoration: none; }
+
+h1 tt, h1 code {
+ font-size: inherit; }
+
+h2 tt, h2 code {
+ font-size: inherit; }
+
+h3 tt, h3 code {
+ font-size: inherit; }
+
+h4 tt, h4 code {
+ font-size: inherit; }
+
+h5 tt, h5 code {
+ font-size: inherit; }
+
+h6 tt, h6 code {
+ font-size: inherit; }
+
+h1 {
+ font-size: 28px;
+ color: black; }
+
+h2 {
+ font-size: 24px;
+ border-bottom: 1px solid #cccccc;
+ color: black; }
+
+h3 {
+ font-size: 18px; }
+
+h4 {
+ font-size: 16px; }
+
+h5 {
+ font-size: 14px; }
+
+h6 {
+ color: #777777;
+ font-size: 14px; }
+
+p, blockquote, ul, ol, dl, li, table, pre {
+ margin: 15px 0; }
+
+hr {
+ background: transparent url("../../images/modules/pulls/dirty-shade.png") repeat-x 0 0;
+ border: 0 none;
+ color: #cccccc;
+ height: 4px;
+ padding: 0; }
+
+body > h2:first-child {
+ margin-top: 0;
+ padding-top: 0; }
+body > h1:first-child {
+ margin-top: 0;
+ padding-top: 0; }
+ body > h1:first-child + h2 {
+ margin-top: 0;
+ padding-top: 0; }
+body > h3:first-child, body > h4:first-child, body > h5:first-child, body > h6:first-child {
+ margin-top: 0;
+ padding-top: 0; }
+
+a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 {
+ margin-top: 0;
+ padding-top: 0; }
+
+h1 p, h2 p, h3 p, h4 p, h5 p, h6 p {
+ margin-top: 0; }
+
+li p.first {
+ display: inline-block; }
+
+ul, ol {
+ padding-left: 30px; }
+
+ul :first-child, ol :first-child {
+ margin-top: 0; }
+
+ul :last-child, ol :last-child {
+ margin-bottom: 0; }
+
+dl {
+ padding: 0; }
+ dl dt {
+ font-size: 14px;
+ font-weight: bold;
+ font-style: italic;
+ padding: 0;
+ margin: 15px 0 5px; }
+ dl dt:first-child {
+ padding: 0; }
+ dl dt > :first-child {
+ margin-top: 0; }
+ dl dt > :last-child {
+ margin-bottom: 0; }
+ dl dd {
+ margin: 0 0 15px;
+ padding: 0 15px; }
+ dl dd > :first-child {
+ margin-top: 0; }
+ dl dd > :last-child {
+ margin-bottom: 0; }
+
+blockquote {
+ border-left: 4px solid #dddddd;
+ padding: 0 15px;
+ color: #777777; }
+ blockquote > :first-child {
+ margin-top: 0; }
+ blockquote > :last-child {
+ margin-bottom: 0; }
+
+table {
+ padding: 0; }
+ table tr {
+ border-top: 1px solid #cccccc;
+ background-color: white;
+ margin: 0;
+ padding: 0; }
+ table tr:nth-child(2n) {
+ background-color: #f8f8f8; }
+ table tr th {
+ font-weight: bold;
+ border: 1px solid #cccccc;
+ text-align: left;
+ margin: 0;
+ padding: 6px 13px; }
+ table tr td {
+ border: 1px solid #cccccc;
+ text-align: left;
+ margin: 0;
+ padding: 6px 13px; }
+ table tr th :first-child, table tr td :first-child {
+ margin-top: 0; }
+ table tr th :last-child, table tr td :last-child {
+ margin-bottom: 0; }
+
+img {
+ max-width: 100%; }
+
+span.frame {
+ display: block;
+ overflow: hidden; }
+ span.frame > span {
+ border: 1px solid #dddddd;
+ display: block;
+ float: left;
+ overflow: hidden;
+ margin: 13px 0 0;
+ padding: 7px;
+ width: auto; }
+ span.frame span img {
+ display: block;
+ float: left; }
+ span.frame span span {
+ clear: both;
+ color: #333333;
+ display: block;
+ padding: 5px 0 0; }
+span.align-center {
+ display: block;
+ overflow: hidden;
+ clear: both; }
+ span.align-center > span {
+ display: block;
+ overflow: hidden;
+ margin: 13px auto 0;
+ text-align: center; }
+ span.align-center span img {
+ margin: 0 auto;
+ text-align: center; }
+span.align-right {
+ display: block;
+ overflow: hidden;
+ clear: both; }
+ span.align-right > span {
+ display: block;
+ overflow: hidden;
+ margin: 13px 0 0;
+ text-align: right; }
+ span.align-right span img {
+ margin: 0;
+ text-align: right; }
+span.float-left {
+ display: block;
+ margin-right: 13px;
+ overflow: hidden;
+ float: left; }
+ span.float-left span {
+ margin: 13px 0 0; }
+span.float-right {
+ display: block;
+ margin-left: 13px;
+ overflow: hidden;
+ float: right; }
+ span.float-right > span {
+ display: block;
+ overflow: hidden;
+ margin: 13px auto 0;
+ text-align: right; }
+
+code, tt {
+ margin: 0 2px;
+ padding: 0 5px;
+ white-space: nowrap;
+ border: 1px solid #eaeaea;
+ background-color: #f8f8f8;
+ border-radius: 3px; }
+
+pre code {
+ margin: 0;
+ padding: 0;
+ white-space: pre;
+ border: none;
+ background: transparent; }
+
+.highlight pre {
+ background-color: #f8f8f8;
+ border: 1px solid #cccccc;
+ font-size: 13px;
+ line-height: 19px;
+ overflow: auto;
+ padding: 6px 10px;
+ border-radius: 3px; }
+
+pre {
+ background-color: #f8f8f8;
+ border: 1px solid #cccccc;
+ font-size: 13px;
+ line-height: 19px;
+ overflow: auto;
+ padding: 6px 10px;
+ border-radius: 3px; }
+ pre code, pre tt {
+ background-color: transparent;
+ border: none; }
\ No newline at end of file
diff --git a/app/src/full/res/raw/utils.sh b/app/src/full/res/raw/utils.sh
new file mode 100644
index 000000000..e88ab9b03
--- /dev/null
+++ b/app/src/full/res/raw/utils.sh
@@ -0,0 +1,95 @@
+db_sepatch() {
+ magiskpolicy --live 'create magisk_file' 'attradd magisk_file mlstrustedobject' \
+ 'allow * magisk_file file *' 'allow * magisk_file dir *' \
+ 'allow magisk_file * filesystem associate'
+}
+
+db_clean() {
+ local USERID=$1
+ local DIR="/sbin/.core/db-${USERID}"
+ umount -l /data/user*/*/*/databases/su.db $DIR $DIR/*
+ rm -rf $DIR
+ [ "$USERID" = "*" ] && rm -fv /data/adb/magisk.db*
+}
+
+db_init() {
+ # Temporary let the folder rw by anyone
+ chcon u:object_r:magisk_file:s0 /data/adb
+ chmod 777 /data/adb
+}
+
+db_restore() {
+ chmod 700 /data/adb
+ magisk --restorecon
+}
+
+db_setup() {
+ local USER=$1
+ local USERID=$(($USER / 100000))
+ local DIR=/sbin/.core/db-${USERID}
+ mkdir -p $DIR
+ touch $DIR/magisk.db
+ mount -o bind /data/adb/magisk.db $DIR/magisk.db
+ rm -f /data/adb/magisk.db-*
+ chcon u:object_r:magisk_file:s0 $DIR $DIR/*
+ chmod 700 $DIR
+ chown $USER.$USER $DIR
+ chmod 666 $DIR/*
+}
+
+env_check() {
+ for file in busybox magisk magiskboot magiskinit util_functions.sh boot_patch.sh; do
+ [ -f /data/adb/magisk/$file ] || return 1
+ done
+ return 0
+}
+
+fix_env() {
+ cd /data/adb/magisk
+ sh update-binary extract
+ rm -f update-binary magisk.apk
+ cd /
+ rm -rf /sbin/.core/busybox/*
+ /sbin/.core/mirror/bin/busybox --install -s /sbin/.core/busybox
+}
+
+direct_install() {
+ flash_boot_image $1 $2
+ rm -f $1
+ rm -rf /data/adb/magisk/* 2>/dev/null
+ mkdir -p /data/adb/magisk 2>/dev/null
+ chmod 700 /data/adb
+ cp -rf $3/* /data/adb/magisk
+ rm -rf $3
+}
+
+mm_patch_dtbo() {
+ if $KEEPVERITY; then
+ echo false
+ else
+ find_dtbo_image
+ patch_dtbo_image >/dev/null 2>&1 && echo true || echo false
+ fi
+}
+
+restore_imgs() {
+ SHA1=`cat /.backup/.sha1`
+ [ -z $SHA1 ] && SHA1=`grep_prop #STOCKSHA1`
+ [ -z $SHA1 ] && return 1
+ STOCKBOOT=/data/stock_boot_${SHA1}.img.gz
+ STOCKDTBO=/data/stock_dtbo.img.gz
+ [ -f $STOCKBOOT ] || return 1
+
+ find_boot_image
+ find_dtbo_image
+
+ magisk --unlock-blocks 2>/dev/null
+ if [ -b "$DTBOIMAGE" -a -f $STOCKDTBO ]; then
+ gzip -d < $STOCKDTBO > $DTBOIMAGE
+ fi
+ if [ -b "$BOOTIMAGE" -a -f $STOCKBOOT ]; then
+ gzip -d < $STOCKBOOT | cat - /dev/zero > $BOOTIMAGE 2>/dev/null
+ return 0
+ fi
+ return 1
+}
diff --git a/app/src/full/res/values-ar/strings.xml b/app/src/full/res/values-ar/strings.xml
new file mode 100644
index 000000000..1719c0388
--- /dev/null
+++ b/app/src/full/res/values-ar/strings.xml
@@ -0,0 +1,148 @@
+
+
+
+
+ الإضافات
+ التنزيلات
+ Superuser
+ السجل
+ الإعدادات
+ التثبيت
+
+
+ Magisk غير مثبت
+
+ البحث عن تحديثات…
+ Magisk v%1$s متاح!
+ انقر لبدء فحص SafetyNet
+ التحقق من حالة SafetyNet…
+ نجح فحص SafetyNet
+ فقدان الاتصال بالشبكة
+ تم إنهاء الخدمة
+ الاستجابة غير صالحه
+
+
+ إعدادات متقدمة
+ الحفاظ علي قوه التشفير
+ إبقاء AVB 2.0/dm-verity
+ نسخة Magisk المثبته: %1$s
+ آخر نسخة Magisk: %1$s
+ إلغاء التثبيت
+ إلغاء تثبيت Magisk
+
+
+ (لم يتم توفير أي معلومات)
+ لم يعثر على الإضافات
+ سيتم تحديث الإضافة في إعادة التشغيل التالي
+ سيتم حذف الإضافة في إعادة التشغيل التالي
+ لن يتم حذف الإضافة في إعادة التشغيل التالي
+ سيتم تعطيل الإضافة في إعادة التشغيل التالي
+ سيتم تمكين الإضافة في إعادة التشغيل التالي
+ انشئ بواسطة %1$s
+
+
+ يتوفر تحديث
+ مثبت
+ غير مثبت
+
+
+ إعادة تحميل
+ حذف السجل الآن
+ تم حذف السجل بنجاح
+ السجل فارغ
+
+
+ حول
+ تغييرات التطبيق
+ xx6600xx ,silent_6600
+ إصدار التطبيق
+ الشفرة المصدرية
+ التبرع
+ مترجم التطبيق
+ منتدى الدعم
+
+
+ إغلاق
+ تثبيت %1$s
+ هل تريد تثبيت %1$s ?
+ التنزيل
+ إعادة التشغيل
+ معالجة الملف المضغوط …
+ تحديث Magisk جديد متوفر!
+ إعادة التشغيل لتطبيق الإعدادات
+ ملاحظات الإصدار
+ تم مسح الذاكرة المؤقته للمستودع
+ خطأ في العملية
+ يتم تخزين الملف المضغوط في:\n[التخزين الداخلي]%1$s
+ معالجة
+
+
+ عام
+ السمة الغامقة
+ تفعيل السمة الغامقة
+ حذف الذاكرة المؤقتة للمستودع
+ حذف المعلومات المخزنة مؤقتا للمستودع على الانترنت، يجبر التطبيق لتحديث عبر الانترنت
+
+ Magisk الوضع الأساسي فقط
+ تمكين الميزات الأساسية فقط، لن يتم تحميل جميع الإضافات. MagiskSU، MagiskHide، systemless hosts، و لا يزال ممكنا
+ إخفاء Magisk من مختلف الاكتشافات
+ تمكين المضيفين(الهوست) لـ systemless
+ Systemless يدعم تطبيقات حجب الإعلانات
+
+ التطبيقات و ADB
+ التطبيقات فقط
+ ADB فقط
+ معطل
+ 10 ثواني
+ 20 ثانية
+ 30 ثانية
+ 60 ثانية
+ Superuser صلاحيات
+ استجابة تلقائية
+ مهلة الطلب
+ Superuser إشعارات
+ %1$s ثانية
+
+ وضع تعدد المستخدمين
+ مالك الجهاز فقط
+ إدارة مالك الجهاز
+ مستخدم مستقل
+ المالك فقط لديه صلاحيات الروت
+ يمكن للمالك فقط إدارة صلاحيات الروت وتلقي مطالبات الطلب
+ كل مستخدم لديه قواعد روت منفصلة خاصة به
+ تم إرسال طلب إلى مالك الجهاز. يرجى التبديل إلى المالك ومنح الإذن
+
+
+ Superuser طلبات
+ رفض%1$s
+ رفض
+ طلب
+ سماح
+ منح حق الوصول الكامل إلى جهازك.\nرفض إذا كنت غير متأكد!
+ للابد
+ مره
+ 10 دقائق
+ 20 دقائق
+ 30 دقائق
+ 60 دقائق
+ %1$s يتم منح صلاحيات Superuser
+ %1$s يتم رفض صلاحيات Superuser
+ لا توجد تطبيقات
+ Superuser الصلاحيات لـ %1$s تم منحها
+ Superuser الصلاحيات لـ %1$s تم رفضها
+ الإشعارات لـ %1$s تم تفعيلها
+ الإشعارات لـ %1$s تم تعطيلها
+ السجلات لـ %1$s تم تفعيلها
+ السجلات لـ %1$s تم تعطيلها
+ %1$s الصلاحيات سحبت
+ سحب؟
+ تأكيد لسحب صلاحيات %1$s ?
+ نخب
+ بدون
+
+
+ PID:\u0020
+ الهدف UID:\u0020
+ الأمر:\u0020
+
+
diff --git a/app/src/full/res/values-bg/strings.xml b/app/src/full/res/values-bg/strings.xml
new file mode 100644
index 000000000..329c22514
--- /dev/null
+++ b/app/src/full/res/values-bg/strings.xml
@@ -0,0 +1,223 @@
+
+
+
+
+ Модули
+ Изтегляния
+ Superuser
+ Дневник
+ Настройки
+ Инсталиране
+
+
+ Magisk не е инсталиран
+ Проверяваме за актуализации…
+ Magisk версия %1$s е налице!
+ Невалиден канал за актуализации
+ Докоснете за стартиране на SafetyNet проверката
+ Проверяване статуса на SafetyNet…
+ SafetyNet проверката е успешна
+ Грешка в SafetyNet ППИ
+ Не е налична мрежова връзка
+ Услугата е прекъсната
+ Невалиден отговор
+
+
+ Допълнителни настройки
+ Запазване на наложеното криптиране
+ Запазване на AVB 2.0/dm-verity
+ Инсталирана версия: %1$s
+ Най-нова версия: %1$s
+ Деинсталиране
+ Деинсталиране на Magisk
+ Всички модули ще бъдат изключени/премахнати. Руут достъпът ще бъде премахнат и е възможно криптиране на данните Ви
+ Актуализация %1$s
+
+
+ (Не е представена информация)
+ Няма намерени модули
+ Модулът ще бъде обновен при следващото рестартиране
+ Модулът ще бъде премахнат при следващото рестартиране
+ Модулът няма да бъде премахнат при следващото рестартиране
+ Модулът ще бъде изключен при следващото рестартиране
+ Модулът ще бъде активиран при следващото рестартиране
+ Създаден от %1$s
+ Рестартиране в режима за възстановяване
+ Рестартиране в буутлоудъра
+ Рестартиране в даунлоуд режима
+
+
+ Налице е актуализация
+ Инсталиран
+ Не е инсталиран
+ Актуализиран на: %1$s
+ Сортиране
+ Сортиране по наименование
+ Сортиране по последно обновяване
+
+
+ Запазване на доклад
+ Презареждане
+ Изчистване на дневника
+ Успешно изчистване на дневника
+ Дневникът е празен
+
+
+ Относно
+ Списък с промени
+
+ Версия
+ Изходен код
+ Дарение
+ Преводачи
+ Страница за поддръжка
+
+
+ Затваряне
+ Инсталиране на %1$s
+ Желаете ли да инсталирате %1$s сега?
+ Изтегляне
+ Рестартиране
+ Налице е нова версия на Magisk!
+ Трябва да рестартирате за прилагане на настройките
+ Бележки
+ Кешът на хранилището е изчистен
+ Грешка при процеса
+ Архивът е записан във:\n[Вътрешната памет]%1$s
+ Изтегляне
+ Изтегляне на архив (%1$d%%)…
+ Обработване
+ Обработване на архива…
+ Налице е нова версия на Magisk Manager!
+ Докоснете за изтегляне и инсталиране
+ DTBO беше модифициран!
+ Magisk Manager модифицира dtbo.img, моля да рестартирате
+ Актуализации на Magisk
+ Инсталиране
+ Скриване на Magisk Manager…
+ Може да отнеме време…
+ Скриването на Magisk Manager е неуспешно…
+ Изтегляне само на архив
+ Модифициране на Boot образа
+ Директно инсталиране (Препоръчва се)
+ Инсталиране на втория слот (След OTA)
+ Избор на метод
+ Избраната версия на Magisk не поддържа модифициране на Boot образи
+ Изберете стоков Boot образ с формат .img или .img.tar
+ Пълно деинсталиране
+ Възстановяване на образи
+ Възстановяване…
+ Възстановяването е успешно!
+ Не е налице архив на стоковия образ!
+ Изтегляне на патентования код
+ Magisk Manager е FOSS и затова не включва частния код за SafetyNet ППИ на Google.\n\nПозволявате ли на Magisk Manager да изтегли добавката (включва GoogleApiClient) за SafetyNet проверки?
+ Базата данни за SU е повредена, ще създадем нов db файл
+ Не можем да проверим SafetyNet статуса
+ Поради промени в Услугите за Google Play, не можем да проверим SafetyNet статуса с модифициран Magisk Manager
+ Първоначалната настройка е готова
+ Първоначалната настройка е неуспешна
+ Изисква допълнително настройване
+ Вашето устройство се нуждае от допълнителни настройки за Magisk, за да работи перфектно. Ще бъде изтеглен архивът за настройка на Magisk. Желаете ли да продължите?
+ Допълнителни настройване
+ Настройването на средата е в ход…
+
+
+ Общи
+ Тъмна тема
+ Включване на тъмната тема
+ Изчистване кеша на хранилището
+ Изчистване на кешираната информация на онлайн хранилището за принудително обновяване
+ Скриване на Magisk Manager
+ Смяна пакетното наименование на Magisk Manager със случайно наименование
+ Възстановяване на Magisk Manager
+ Възстановяване на оригиналното пакетно наименование на Magisk Manager
+ Език
+ (Системен)
+ Настройки за актуализиране
+ Проверка за актуализации
+ Периодично проверяване за актуализации във фонов режим
+ Канал за актуализации
+ Стабилен
+ Бета
+ Потребителски
+ Въведете потребителски URL
+ Изходен формат за модифициран Boot образ
+ Избор на изходен формат за модифициран Boot образ.\nИзберете .img, за инсталиране чрез fastboot/download режим; изберете .img.tar за инсталиране чрез ODIN.
+ Режим Magisk Core Only
+ Работят само основни функции, като MagiskSU, MagiskHide и несистемни хостове, без модули.
+ Скриване на Magisk от различни детектори
+ Несистемни хостове
+ Поддръжка на несистемни хостове за използване на приложения, блокиращи реклами
+
+ Приложения и ADB
+ Само приложения
+ Само ADB
+ Изключен
+ 10 секунди
+ 20 секунди
+ 30 секунди
+ 60 секунди
+ Superuser достъп
+ Автоматичен отговор
+ Време за запитване
+ Superuser известие
+ %1$s секунди
+ Повторно запитване след ъпгрейд
+ Повторно запитване за Superuser достъп след ъпгрейд на приложение
+ Superuser права само с пръстов отпечатък
+ Използване на сензора за пръстови отпечатъци за разрешаване на Superuser досъп
+
+ Потребителски достъп
+ Само собственик
+ Управление от страна на собственика
+ Независими потребители
+ Само собственикът има руут достъп
+ Само собственикът може да управлява руут достъпа и да получава запитвания за достъп
+ Всеки потребител има собствени правила за руут достъп
+ Направено е запитване до собственика на устройството. Моля да преминете в режим на собственик и да дадете разрешение
+
+ Монтиране по именни пространства
+ Глобално
+ Наследено
+ Изолирано
+ Всички сесии с руут достъп използват глобалното именно пространство
+ Всички сесии с руут достъп наследяват именното пространство на запитващото приложение
+ Всички сесии с руут достъп имат собствени именни пространства
+ Не поддържа Android 8.0+
+ Не са добавени пръстови отпечатъци или устройството не поддържа тази функция
+
+
+ Запитване за Superuser достъп
+ Отказ%1$s
+ Отказ
+ Запитване
+ Разрешаване
+ Дава пълен достъп до устройството Ви.\Откажете, ако не Сте сигурен/на!
+ Завинаги
+ Веднъж
+ 10 мин
+ 20 мин
+ 30 мин
+ 60 мин
+ На %1$s бе разрешен Superuser достъп
+ На %1$s бе отказан Superuser достъп
+ Няма намерени приложения
+ На %1$s е предоставен Superuser достъп
+ На %1$s е отказан Superuser достъп
+ Извесията за %1$s са включени
+ Извесията за %1$s са изключени
+ Записването в дневника за %1$s е включено
+ Записването в дневника за %1$s е изключено
+ Настройките за достъп на %1$s са анулирани
+ Анулиране?
+ Потвърждавате ли анулирането на настройките за достъп на %1$s?
+ Toast
+ Без
+ Неуспешна заверка
+
+
+ PID:\u0020
+ Целеви UID:\u0020
+ Команда:\u0020
+
+
diff --git a/app/src/full/res/values-cs/strings.xml b/app/src/full/res/values-cs/strings.xml
new file mode 100644
index 000000000..22ff1a84a
--- /dev/null
+++ b/app/src/full/res/values-cs/strings.xml
@@ -0,0 +1,135 @@
+
+
+
+
+ Moduly
+ Stahování
+ Superuser
+ Log
+ Nastavení
+ Instalovat
+
+
+ Magisk není nainstalován
+
+ Kontrola aktualizací…
+ Magisk v%1$s je dostupný!
+ Kliknutím zahájíte SafetyNet kontrolu
+ Kontrola stavu SafetyNet…
+
+
+ Pokročilá Nastavení
+ Udržet "force encryption"
+ Udržet AVB 2.0/dm-verity
+ Nainstalovaná verze: %1$s
+ Poslední verze: %1$s
+ Odinstalovat
+ Odinstalovat Magisk
+
+
+ (Žádné info)
+ Žádný modul nenalezen
+ Modul bude aktualizován během příštího restartu
+ Modul bude smazán během příštího restartu
+ Modul nebude smazán během příštího restartu
+ Modul bude zakázán během příštího restartu
+ Modul bude povolen během příštího restartu
+ Vytvořeno %1$s
+
+
+ Dostupná Aktualizace
+ Nainstalováno
+ Nenainstalováno
+
+
+ Uložit log
+ Aktualizovat
+ Smazat Log
+ Log byl smazán
+ Log je prázdný
+
+
+ O aplikaci
+ Seznam změn
+
+ Verze aplikace
+ Zdrojový kód
+ Příspěvek
+ Překladatelé aplikace
+ Vlákno podpory na fóru
+
+
+
+ Zavřít
+ Instalovat %1$s
+ Chcete nainstalovat %1$s ?
+ Stáhnout
+ Restart
+ Zpracování zip souboru …
+ K dispozici je aktualizace Magisk!
+ Restartovat pro použití nastavení
+ Poznámky k vydání
+ Mezipaměť smazána
+ Chyba při Zpracování
+ Zip je uchován v:\n[Interním Úložišti]%1$s
+ Zpracování
+
+
+ Obecné
+ Tmavý Vzhled
+ Povolit tmavý vzhled
+ Smazat Uchovanou Mezipaměť
+ Smaže informace online použití v Mezipaměti, donutí aplikaci obnovit informace online
+
+ Skryje root (MagiskSU) před různými aplikacemi
+ Nesystémová "hosts" data
+ Podpora nesystémových dat "hosts" pro Adblock aplikace
+
+ Aplikace i ADB
+ Pouze aplikace
+ Pouze ADB
+ Zakázáno
+ 10 sekund
+ 20 sekund
+ 30 sekund
+ 60 sekund
+ Přístup Superuser
+ Automatická Reakce
+ Časový limit Požadavku
+ Oznámení Superuser
+ %1$s sekund
+
+
+ Požadavek Superuser
+ Zamítnout%1$s
+ Zamítnout
+ Dotaz
+ Povolit
+ Povolí plný přístup k vašemu zařízení.\nZamítněte pokud si nejste jisti!
+ Navždy
+ Jednou
+ 10 min
+ 20 min
+ 30 min
+ 60 min
+ Pro %1$s bylo oprávnění Superuser povoleno
+ Pro %1$s bylo oprávnění Superuser zamítnuto
+ Zatím zde není žádná aplikace
+ Superuser oprávnění pro %1$s je povoleno
+ Superuser oprávnění pro %1$s je zamítnuto
+ Oznámení pro %1$s je povoleno
+ Oznámení pro %1$s je zakázáno
+ Logování %1$s je povoleno
+ Logování %1$s je zakázáno
+ Záznamy oprávnění %1$s jsou smazány
+ Smazat?
+ Smazat záznam ohledně oprávnění pro %1$s?
+ Toast
+ Žádné
+
+
+ PID:\u0020
+ Cílové UID:\u0020
+ Příkaz:\u0020
+
+
diff --git a/app/src/full/res/values-de/strings.xml b/app/src/full/res/values-de/strings.xml
new file mode 100644
index 000000000..178cafc69
--- /dev/null
+++ b/app/src/full/res/values-de/strings.xml
@@ -0,0 +1,222 @@
+
+
+
+ Module
+ Downloads
+ Superuser
+ Log
+ Einstellungen
+ Installieren
+
+
+ Magisk ist nicht installiert
+ Suche nach Aktualisierungen…
+ Magisk %1$s ist verfügbar!
+ Nicht valider Aktualisierungskanal
+ SafetyNet-Status abfragen
+ Prüfe SafetyNet-Status…
+ SafetyNet-Test erfolgreich
+ SafetyNet API Fehler
+ Netzwerkverbindung verloren
+ Der Dienst wurde beendet
+ Die Antwort ist ungültig
+
+
+ Erweiterte Optionen
+ \"force encryption\" beibehalten
+ AVB 2.0/dm-verity beibehalten
+ Installierte Version: %1$s
+ Neueste Version: %1$s
+ Deinstallieren
+ Magisk deinstallieren
+ Alle Module werden deaktiviert/entfernt. Root wird entfernt und wenn die Daten nicht verschlüsselt sind, werden sie möglicherweise verschlüsselt.
+ Aktualisierung %1$s
+
+
+ (Nichts angegeben)
+ Keine Module gefunden
+ Modul wird beim nächsten Neustart aktualisiert
+ Modul wird beim nächsten Neustart entfernt
+ Modul wird beim nächsten Neustart nicht entfernt
+ Modul wird beim nächsten Neustart deaktiviert
+ Modul wird beim nächsten Neustart aktiviert
+ Erstellt von %1$s
+ Neustart in die Recovery
+ Neustart in den Bootloader
+ Neustart in den Download-Modus
+
+
+ Aktualisierung verfügbar
+ Installiert
+ Nicht installiert
+ Zuletzt aktualisiert: %1$s
+ Sortierung
+ Nach Namen sortiert
+ Nach Aktualisierung sortiert
+
+
+ Protokoll speichern
+ Log erneut laden
+ Log löschen
+ Log gelöscht
+ Log ist leer
+
+
+ Über
+ Änderungen
+ skalnet, c727, jenslody
+ Version
+ Quelltext
+ Spende
+ Übersetzer
+ Hilfeforum
+
+
+ Schließen
+ Installiere %1$s
+ Möchtest du %1$s installieren?
+ Herunterladen
+ Neustart
+ Neue Magisk-Aktualisierung verfügbar!
+ Neustarten, um die Änderungen anzuwenden
+ Änderungen
+ Repo-Cache gelöscht
+ Prozessfehler
+ Die zip-Datei ist gespeichert unter:\n[Interner Speicher]%1$s
+ Herunterladen
+ Lade Zip-Datei herunter (%1$d%%) …
+ Verarbeite
+ Verarbeite Zip-Datei…
+ Aktualisierung für Magisk Manager verfügbar!
+ Herunterladen und installieren
+ DTBO wurde gepatched!
+ Magisk Manager hat dtbo.img gepatched, bitte Neustart durchführen
+ Magisk Aktualisierungen
+ Flashing
+ Verberge Magisk Manager…
+ Dies könnte einige Zeit in Anspruch nehmen…
+ Verbergen von Magisk Manager fehlgeschlagen…
+ Zip-Datei nur herunterladen
+ Boot-Image-Datei patchen
+ Direkt installieren (empfohlen)
+ Installiere auf zweiten Slot (Nach OTA)
+ Methode auswählen
+ Magisk Zielversion unterstützt das Patchen des Boot-Images nicht
+ Selektiere das originale Boot image dump im Format .img oder .img.tar
+ Komplette Deinstallation
+ Images wiederherstellen
+ Wiederherstellen...
+ Wiederherstellung durchgeführt!
+ Kein Original Backup vorhanden!
+ Lade proprietären Code herunter
+ Magisk Manager ist FOSS und enthält keinen proprietären SafetyNet API Code von Google. Magisk Manager erlauben eine Erweiterung (enthält GoogleApiClient) für SafetyNet-Checks herunterzuladen?
+ Datenbank für SU ist fehlerhaft, es wird eine neue erstellt
+ Überprüfung von SafetyNet nicht möglich
+ Aufgrund von Änderungen in den Google Play-Diensten, ist es nicht möglich SafetyNet auf neu verpackten Magisk Manager Versionen zu überprüfen
+ Einrichtung abgeschlossen
+ Einrichtung fehlgeschlagen
+ Zusätzliche Einrichtung erforderlich
+ Um Magisk ordnungsgemäß zu nutzen, benötigt das Geräte eine zusätzliche Einrichtung. Magisk Einrichtungs-zip wird heruntergeladen, fortsetzen?
+ Zusätzliche Einrichtung
+ Umgebungseinrichtung läuft…
+
+
+ Allgemein
+ Dunkles Theme
+ Dunkles Theme aktivieren
+ Repo-Cache löschen
+ Löscht die zwischengespeicherten Informationen der Online-Repos. Erzwingt eine Online-Aktualisierung
+ Magisk Manager verbergen
+ Magisk Manager mit zufälligem Paketnamen neu packen
+ Stelle Magisk Manager wieder her
+ Stelle Magisk Manager mit ursprünglichem Paket wieder her
+ Sprache
+ (Systemstandard)
+ Aktualisierungs-Einstellungen
+ Prüfe nach Aktualisierungen
+ Prüfe regelmäßig im Hintergrund nach Aktualisierungen
+ Aktualisierungs-Kanal
+ Stabil
+ Beta
+ Benutzerdefiniert
+ Gebe eine benutzerdefinierte URL ein
+ Ausgabeformat des gepatchten Boot-Images
+ Wähle das Ausgabeformat des gepatchten Boot-Images.\nWähle .img, um mit \"fastboot/download mode\" zu flashen; wähle .img.tar zum Flashen mit ODIN.
+ Nur Kernfunktionen
+ Aktiviert lediglich die Kernfunktionen, Module werden nicht geladen. MagiskSU, Magisk Hide und Systemless hosts bleiben weiterhin aktiv
+ Versteckt Magisk vor diversen Entdeckungsmethoden
+ Systemlose hosts-Datei
+ Systemlose Unterstützung für Werbeblocker
+
+ Apps und ADB
+ Nur Apps
+ Nur ADB
+ Deaktiviert
+ 10 Sekunden
+ 20 Sekunden
+ 30 Sekunden
+ 60 Sekunden
+ Superuser-Zugriff
+ Automatisch beantworten
+ Zeitlimit für Anfrage
+ Superuser-Benachrichtigung
+ %1$s Sekunden
+ Nach Aktualisierung erneut authentifizieren
+ Superuser-Zugriff nach App-Aktualisierung erneut abfragen
+ Aktiviere Authentifizierung durch Fingerabdruck
+ Fingerabdrucksensor benutzen um Superuser-Anfragen zu erlauben
+
+ Mehrbenutzermodus
+ Nur der Gerätebesitzer
+ Durch Gerätebesitzer verwaltet
+ Benutzerunabhängig
+ Nur der Besitzer hat Root-Zugriff
+ Nur der Besitzer verwaltet den Root-Zugriff und erhält Zugriffs-Anfragen
+ Jeder Nutzer hat seine eingenen Root-Regeln
+ Eine Anfrage wurde an den Gerätebesitzer gesendet. Bitte wechsle zum Bersitzerkonto und gewähre die Rechte
+
+ Namensraum-Modus
+ Globaler Namensraum
+ Geerbter Namensraum
+ Isolierter Namensraum
+ Alle Root-Sitzungen benutzen den global angelegten Namensraum
+ Root-Sitzungen erben den Namensraum des Abfragenden
+ Jede Root-Sitzung hat ihren isolierten Namensraum
+ Android 8.0+ wird nicht unterstützt
+ Keine Fingerabdrücke gespeichert oder keine Geräteunterstützung
+
+
+ Superuser-Anfrage
+ Verweigern%1$s
+ Verweigern
+ Nachfragen
+ Gewähren
+ Erlaubt den vollen Zugriff auf das Gerät.\nVerweigere, wenn du dir unsicher bist!
+ Dauerhaft
+ Einmalig
+ 10 Min.
+ 20 Min.
+ 30 Min.
+ 60 Min.
+ Superuser-Rechte für %1$s gewährt
+ Superuser-Rechte für %1$s verweigert
+ Keine Apps gefunden
+ Superuser-Rechte werden für %1$s gewährt
+ Superuser-Rechte werden für %1$s verweigert
+ Benachrichtigungen sind für %1$s aktiviert
+ Benachrichtigungen sind für %1$s deaktiviert
+ Logging ist für %1$s aktiviert
+ Logging ist für %1$s deaktiviert
+ Die Rechte für %1$s wurden entzogen
+ Entziehen?
+ Möchtest du die Rechte für %1$s entziehen?
+ Popup
+ Keine
+ Authentifizierung fehlgeschlagen
+
+
+ PID:\u0020
+ Ziel-UID:\u0020
+ Befehl:\u0020
+
+
diff --git a/app/src/full/res/values-el/strings.xml b/app/src/full/res/values-el/strings.xml
new file mode 100644
index 000000000..2a1247bad
--- /dev/null
+++ b/app/src/full/res/values-el/strings.xml
@@ -0,0 +1,204 @@
+
+
+ Modules
+
+ Λήψεις
+ Υπερχρήστης
+ Αρχείο Καταγραφής
+ Ρυθμίσεις
+ Εγκατάσταση
+
+
+ Το Magisk δεν είναι εγκατεστημένο
+
+ Έλεγχος για ενημερώσεις…
+ Το Magisk v%1$s είναι διαθέσιμο!
+ Λανθασμένο κανάλι ενημέρωσης
+ Πατήστε για έλεγχο του SafetyNet
+ Έλεγχος κατάστασης SafetyNet…
+ Ο Έλεγχος του SafetyNet Ήταν Επιτυχής
+ Σφάλμα του SafetyNet API
+ Αδυναμία σύνδεσης στο δίκτυο
+ Η υπηρεσία τερματίστηκε
+ Η απόκριση είναι άκυρη
+
+
+ Προηγμένες ρυθμίσεις
+ Διατήρηση επιβεβλημένης κρυπτογράφησης
+ Διατήρηση dm-verity
+ Εγκατεστημένη έκδοση: %1$s
+ Τελευταία έκδοση: %1$s
+ Απεγκατάσταση
+ Απεγκατάσταση Magisk
+ Όλα τα modules θα απενεργοποιηθούν/αφαιρεθούν. Το root θα αφαιρεθεί και ενδέχεται να κρυπτογραφηθούν τα δεδομένα σας, εάν δεν είναι κρυπτογραφημένα
+ Ενημέρωση %1$s
+
+
+ (Δεν δόθηκαν πληροφορίες)
+ Δεν βρέθηκαν modules
+ Η ενότητα θα ενημερωθεί στην επόμενη επανεκκίνηση
+ Η ενότητα θα αφαιρεθεί στην επόμενη επανεκκίνηση
+ Η ενότητα δεν θα αφαιρεθεί στην επόμενη επανεκκίνηση
+ Η ενότητα θα απενεργοποιηθεί στην επόμενη επανεκκίνηση
+ Η ενότητα θα ενεργοποιηθεί στην επόμενη επανεκκίνηση
+ Δημιουργήθηκε από τον/την %1$s
+ Επανεκκίνηση στο Recovery
+ Επανεκκίνηση στο Bootloader
+ Επανεκκίνηση για λήψη
+
+
+ Διαθέσιμη Ενημέρωση
+ Εγκαταστάθηκε
+ Μη εγκατεστημένη
+ Αναβαθμίστηκε στις: %1$s
+ Ταξινόμηση κατά
+ Ταξινόμηση κατά όνομα
+ Ταξινόμηση κατά τελευταία αναβάθμιση
+
+
+ "Αποθήκευση καταγραφής "
+ Επαναφόρτωση
+ Εκκαθάριση αρχείου καταγραφής τώρα
+ Το αρχείο καταγραφής εκκαθαρίστηκε επιτυχώς
+ Το αρχείο καταγραφής είναι κενό
+
+
+ Περί
+ Καταγραφή αλλαγών εφαρμογής
+ GreatApo, JpegXguy
+ Έκδοση εφαρμογής
+ Πηγαίος κώδικας
+ Δωρεά
+ Μεταφραστές εφαρμογής
+ Σύνδεσμος υποστήριξης
+
+
+ Κλείσιμο
+ Εγκατάσταση %1$s
+ Θέλετε να εγκαταστήσετε το %1$s τώρα;
+ Λήψη
+ Επανεκκίνηση
+ Νέα Ενημέρωση Magisk Διαθέσιμη!
+ Επανεκκίνηση για εφαρμογή ρυθμίσεων
+ Σημειώσεις έκδοσης
+ Η Repo cache καθαρίστηκε
+ Σφάλμα διαδικασίας
+ Το zip είναι αποθηκευμένο σε:\n[Εσωτερική μνήμη]%1$s
+ Γίνεται λήψη
+ Λήψη αρχείου zip (%1$d%%) …
+ Γίνεται επεξεργασία
+ Επεξεργασία αρχείου zip …
+ Νέα Ενημέρωση Magisk Manager Διαθέσιμη!
+ Πιέστε για λήψη και εγκατάσταση
+ Έγινε patch στο DTBO!
+ Το Magisk Manager έκανε patch το dtbo.img, παρακαλώ κάντε επανεκκίνηση
+ Ενημερώσεις Magisk
+ Γίνεται flash
+ Κρύβοντας το Magisk Manager…
+ Αυτό μπορεί να πάρει λίγη ώρα…
+ Η απόκρυψη του Magisk Manager απέτυχε…
+ Λήψη Zip Μόνο
+ Εφαρμογή Patch στο Αρχείο Εικόνας Boot
+ Απευθείας Εγκατάσταση (Προτείνεται)
+ Εγκατάσταση σε Second Slot (Μετά από ΟΤΑ)
+ Επιλογή Μεθόδου
+ Αυτή η Magisk έκδοση δεν υποστηρίζει patch του boot image αρχείου
+ Επιλογή stock boot image dump σε μορφή .img ή .img.tar
+ Πλήρης απεγκατάσταση
+ Η ανάκτηση έγινε!
+ Δεν υπάρχει αντίγραφο ασφαλείας!
+ Λήψη Ιδιόκτητου Κώδικα
+ Το Magisk Manager είναι FOSS οπότε δεν περιέχει της Google τον ιδιόκτητο κώδικα του SafetyNet API.\n\nΕπιτρέπετε στο Magisk Manager να κατεβάσει μια επέκταση (περιέχει το GoogleApiClient) για ελέγχους του SafetyNet?
+ Η βάση δεδομένων SU είναι κατεστραμμένη, θα αναδημιουργηθεί νέα
+
+
+ Γενικά
+ Σκούρο θέμα
+ Ενεργοποίηση σκούρου θέματος
+ Εκκαθάριση προσωρινής μνήμης αποθετηρίων
+ Καθαρίζει τις κρυφές πληροφορίες για απευθείας συνδεδεμένα αποθετήρια, αναγκάζει την εφαρμογή να κάνει ανανέωση σε απευθείας σύνδεση
+ Απόκρυψη του Magisk Manager
+ Ανασυγκρότηση του Magisk Manager με τυχαίο όνομα πακέτου
+ Γλώσσα
+ (Προεπιλογή Συστήματος)
+ Ρυθμίσεις Ενημερώσεων
+ Κανάλι Ενημερώσεων
+ Σταθερό
+ Δοκιμαστικό
+ Custom
+ Εισαγωγή ενός custom URL
+ Μορφή Τροποποιημένης Εικόνας Boot
+ Επιλέξτε τη μορφή της εξαγόμενης εικόνας boot μετά το patch.\nΕπιλέξτε .img για flash μέσω λειτουργίας fastboot/download· επιλέξτε .img.tar για flash μέσω ODIN.
+ Magisk Λειτουργία Πυρήνα Μόνο
+ Ενεργοποίηση μόνο των λειτουργιών πυρήνα, καμία από τις ενότητες δεν θα ενεργοποιηθεί. Τα MagiskSU, MagiskHide, και systemless hosts θα παραμείνουν ενεργά
+ Κρύβει το Magisk από διάφορες ανιχνεύσεις
+ Systemless hosts
+ Υποστήριξη Systemless hosts για εφαρμογές Adblock
+
+ Εφαρμογές και ADB
+ Εφαρμογές μόνο
+ ADB μόνο
+ Απενεργοποιημένο
+ 10 δευτερόλεπτα
+ 20 δευτερόλεπτα
+ 30 δευτερόλεπτα
+ 60 δευτερόλεπτα
+ Πρόσβαση Υπερχρήστη
+ Αυτόματη Απόκριση
+ Χρονικό όριο Αιτήματος
+ Ειδοποίηση Υπερχρήστη
+ %1$s δευτερόλεπτα
+ Επαναπιστοποίηση μετά από αναβάθμιση
+ Επαναπιστοποίηση αδειών υπερχρήστη μετά την αναβάθμιση μίας εφαρμογής
+
+ Λειτουργία Πολλών Χρηστών
+ Μόνο Ιδιοκτήτης Συσκευής
+ Διαχειριζόμενη από τον Ιδιοκτήτη
+ Ανεξάρτητη από τον χρήστη
+ Μόνο ο ιδιοκτήτης έχει πρόσβαση root
+ Μόνο ο ιδιοκτήτης μπορεί να διαχειριστεί την πρόσβαση root και να δεχτεί προτροπές αίτημάτων
+ Κάθε χρήστης έχει τους δικούς του ξεχωριστούς κανόνες root
+ Ένα αίτημα έχει σταλεί στον ιδιοκτήτη της συσκευής. Παρακαλώ αλλάξτε σε ιδιοκτήτη και δώστε την άδεια
+
+ Λειτουργία προσάρτησης χώρου ονομάτων
+ Καθολικός Χώρος Ονομάτων
+ Κληρονόμησε Χώρο Ονομάτων
+ Απομονωμένος Χώρος Ονομάτων
+ Όλες οι συνεδρίες root χρησιμοποιούν τον καθολικό χώρο oνομάτων προσάρτησης
+ Οι συνεδρίες root θα κληρονομούν το χώρο ονομάτων του αιτούντα τους
+ Κάθε συνεδρία root θα έχει το δικό της απομονωμένο χώρο ονομάτων
+ Δεν υποστηρίζεται Android 8.0+
+
+
+ Αίτημα υπερχρήστη
+ Άρνηση%1$s
+ Άρνηση
+ Προτροπή
+ Αποδοχή
+ Δίνει πλήρη πρόσβαση στη συσκευή σας.\nΑρνηθείτε αν δεν είστε σίγουρος/η!
+ Πάντα
+ Μία φορά
+ 10 λεπτά
+ 20 λεπτά
+ 30 λεπτά
+ 60 λεπτά
+ Παραχωρήθηκαν δικαιώματα υπερχρήστη στο %1$s
+ Απορρίφθηκαν τα δικαιώματα υπερχρήστη του %1$s
+ Δεν βρέθηκαν εφαρμογές
+ Παραχορούνται δικαιώματα υπερχρήστη στο %1$s
+ Δεν παραχορούνται δικαιώματα υπερχρήστη στο %1$s
+ Οι ειδοποιήσεις του %1$s είναι ενεργοποιημένες
+ Οι ειδοποιήσεις του %1$s είναι απενεργοποιημένες
+ Η καταγραφή του %1$s είναι ενεργοποιημένη
+ Η καταγραφή του %1$s είναι απενεργοποιημένη
+ Τα δικαιώματα του %1$s ανακαλούνται
+ Ανάκληση;
+ Επιβεβαίωση για ανάκληση δικαιωμάτων %1$s;
+ Αναδυόμενο παράθυρο
+ Κανένα
+
+
+ PID:\u0020
+ UID Στόχος:\u0020
+ Εντολή:\u0020
+
diff --git a/app/src/full/res/values-es/strings.xml b/app/src/full/res/values-es/strings.xml
new file mode 100644
index 000000000..91b14e207
--- /dev/null
+++ b/app/src/full/res/values-es/strings.xml
@@ -0,0 +1,224 @@
+
+
+
+
+ Módulos
+ Descargas
+ Superusuario
+ Registro
+ Ajustes
+ Instalar
+
+
+ Magisk no está instalado
+ Comprobando Actualizaciones…
+ ¡Disponible Magisk v%1$s!
+ Canal de actualización inválido
+ Comprobar el estado de SafetyNet
+ Comprobando estado de SafetyNet…
+ La comprobación fue exitosa
+ Error en la API de SafetyNet
+ Red no Disponible
+ Se ha detenido el servicio
+ La respuesta no es válida
+
+
+ Ajustes avanzados
+ Mantener cifrado forzado
+ Mantener AVB 2.0/dm-verity
+ Versión instalada: %1$s
+ Última versión: %1$s
+ Desinstalar
+ Todos los módulos serán desactivados / eliminados. El acceso Root se eliminará y, posiblemente, cifrará los datos si los datos no están cifrados actualmente.
+ Desinstalar Magisk
+ Actualización %1$s
+
+
+ (No hay información)
+ No se encontraron módulos
+ El módulo se actualizará en el siguiente reinicio
+ El módulo se eliminará en el siguiente reinicio
+ El módulo no se eliminará en el siguiente reinicio
+ El módulo se desactivará en el siguiente reinicio
+ El módulo se activará en el siguiente reinicio
+ Creado por %1$s
+ Reiniciar en Modo Recovery
+ Reiniciar en Modo Bootloader
+ Reiniciar en Modo Download
+
+
+ Actualización Disponible
+ Instalado
+ No Instalado
+ Actualizado el: %1$s
+ Orden de Clasificación
+ Ordenar por nombre
+ Ordenar según la última actualización
+
+
+ Guardar registro
+ Recargar
+ Limpiar registro ahora
+ Registro limpiado correctamente
+ El registro está vacio
+
+
+ Acerca de
+ Registro de cambios
+ dark-basic, Gawenda, netizen, Deiki, Nosi
+ Versión
+ Código fuente
+ Donar
+ Traductores
+ Hilo de soporte
+
+
+ Cerrar
+ Instalar %1$s
+ ¿Quieres instalar %1$s ahora?
+ Descargar
+ Reiniciar
+ Procesando archivo zip…
+ ¡Nueva actualización de Magisk disponible!
+ Reinicia para aplicar los ajustes
+ Notas de lanzamiento
+ Caché del repositorio limpiada
+ Error de proceso
+ El zip es almacenado en:\n[Internal Storage]%1$s
+ Descargando
+ Descargando el archivo zip (%1$d%%)…
+ Procesando
+ Nueva actualización de Magisk Manager disponible!
+ Pulse para descargar e instalar
+ DTBO fue parchado!
+ Magisk Manager ha parcheado dtbo.img, por favor reinicia
+ Actualización de Magisk
+ Flasheando
+ Ocultando Magisk Manager…
+ Esto podría tomar un tiempo…
+ La Ocultación de Magisk Manager ha fallado…
+ Descargar sólo el archivo ZIP
+ Parcheo de la imagen boot
+ Instalación Directa (Recomendado)
+ Instalación en el segundo espacio (Después de OTA)
+ Seleccionar Método
+ La versión de Magisk no admite el parcheo de la imagen boot
+ Seleccione el volcado de la imagen boot en formato .img o .img.tar
+ Desinstalación completa
+ Restaurar imágenes
+ ¡Restauración Terminada!
+ Restaurando ...
+ ¡El respaldo de la imagen boot Stock no existe!
+ Descargar Código Propietario
+ Magisk Manager es un Software Libre por lo que no contiene el código API de SafetyNet (Código Propietario de Google).\n\n ¿Puede permitir que Magisk Manager descargue una extensión (contiene el GoogleApiClient) para la comprobación de SafetyNet?
+ La base de datos SU está dañada, se creará nueva base de datos
+ No es posible verificar SafetyNet!
+ Debido a algunos cambios en los Servicios de Google Play, no es posible verificar SafetyNet en la versión reempaquetada de Magisk Manager
+ Instalación Realizada
+ Instalación Fallida
+ Se Requiere una Instalación Adicional
+ Su dispositivo requiere una instalación adicional para que Magisk funcione correctamente. Se descargará el zip de instalación de Magisk, desea continuar ahora?
+ Configuración Adicional
+ Ejecutando Configuración de Entorno
+
+
+ General
+ Tema oscuro
+ Habilitar el tema oscuro
+ Limpiar caché del repositorio
+ Limpiar la información en caché para los repositorios en línea, fuerza a la aplicación a actualizar en línea
+ Ocultar Magisk Manager
+ Re-empaquetar Magisk Manager con un nombre de paquete al azar
+ Restaurar Magisk Manager
+ Restaura Magisk Manager con el paquete original
+ Idioma
+ (Idioma del sistema)
+ Ajustes de Actualización
+ Comprobar Actualizaciones
+ Comprobar periódicamente en segundo plano si existen actualizaciones
+ Canal de Actualización
+ Estable
+ Beta
+ Personalizado
+ Insertar una URL personalizada
+ Parchear imagen boot por tipo de formato
+ Seleccionar el formato de salida para parchear la imagen boot.\nEscoja .img para flashear mediante fastboot/download mode; escoja .img.tar para flashear con ODIN.
+
+ Habilitar sólo funciones principales, no se cargarán todos los módulos. MagiskSU, MagiskHide, y Systemless Hosts seguirán habilitados
+ Ocultar Magisk de varias detecciones
+ Systemless Hosts
+ Soporte para aplicaciones Adblock fuera de la partición system
+
+ Aplicaciones y ADB
+ Sólo aplicaciones
+ Sólo ADB
+ Deshabilitado
+ 10 segundos
+ 20 segundos
+ 30 segundos
+ 60 segundos
+ Acceso de superusuario
+ Respuesta automática
+ Tiempo de petición
+ Notificación de superusuario
+ %1$s segundos
+ Re-autenticación
+ Pedir permisos de superusuario nuevamente si una aplicación es actualizada o reinstalada
+ Autenticación por Huella Dactilar
+ Utilizar el sensor de Huella Dactilar para permitir las solicitudes de superusuario
+
+
+ Modo MultiUsuario
+ Sólo Administrador del Dispositivo
+ Administrador del Dispositivo
+ Usuario Independiente
+ Sólo el administrador tiene acceso root
+ Sólo el administrador puede supervisar el acceso root y recibir solicitudes de otros usuarios
+ Cada usuario tiene separadas sus propias reglas de root
+ Se ha enviado una solicitud al administrador del dispositivo. Por favor, cambie a la cuenta del administrador y conceda el permiso
+
+ Montar Namespace
+ Global Namespace
+ Heredar Namespace
+ Aislar Namespace
+ Todas las sesiones de root utilizan el soporte Global Namespace
+ Las sesiones de root heredarán las peticiones Namespace
+ Cada sesión root tendrá su propia Namespace
+ No es compatible con Android 8.0+
+ No se establecieron huellas dactilares o no existe soporte del dispositivo
+
+
+ Petición de superusuario
+ Denegar%1$s
+ Denegar
+ Preguntar
+ Permitir
+ Permite acceso total a tu dispositivo.\n¡Denegar si no está seguro!
+ Siempre
+ Una vez
+ 10 min
+ 20 min
+ 30 min
+ 60 min
+ Permitidos derechos de superusuario para %1$s
+ Denegados derechos de superusuario para %1$s
+ No se encontraron aplicaciones
+ Derechos de superusuario para %1$s permitidos
+ Derechos de superusuario para %1$s denegados
+ Notificaciones de %1$s habilitadas
+ Notificaciones de %1$s deshabilitadas
+ Registros de %1$s habilitados
+ Registros de %1$s deshabilitados
+ Anulados derechos de %1$s
+ ¿Revocar?
+ ¿Confirmar para revocar derechos de %1$s?
+ Aviso
+ Nada
+ Autenticación fallida
+
+
+ PID:\u0020
+ UID de objetivo:\u0020
+ Comando:\u0020
+
+
diff --git a/app/src/full/res/values-et/strings.xml b/app/src/full/res/values-et/strings.xml
new file mode 100644
index 000000000..a13a53aac
--- /dev/null
+++ b/app/src/full/res/values-et/strings.xml
@@ -0,0 +1,214 @@
+
+
+
+
+ Moodulid
+ Allalaadimised
+ Superkasutaja
+ Logi
+ Seaded
+ Installi
+
+
+ Magisk pole installitud
+ Kontrollin uuendusi…
+ Magisk v%1$s on saadaval!
+ Sobimatu uuenduste kanal
+ Koputa, et alustada SafetyNet\'i kontrolli
+ Kontrollin SafetyNet\'i olekut…
+ SafetyNet\'i kontroll edukas
+ SafetyNet\'i API viga
+ Võrguühendus puudub
+ Teenus on sundsuletud
+ Vastus on sobimatu
+
+
+ Täpsemad seaded
+ Säilita sunnitud krüpteering
+ Säilita AVB 2.0/dm-verity
+ Installitud versioon: %1$s
+ Viimane versioon: %1$s
+ Eemalda
+ Eemalda Magisk
+ Kõik moodulid keelatakse/eemaldatakse. Juurkasutaja eemaldatakse ning potensiaalselt krüptitakse su andmed, kui need ei ole hetkel krüpteeritud
+ Uuenda %1$s\'i
+
+
+ (Info puudub)
+ Mooduleid ei leitud
+ Moodul uuendatakse järgmisel taaskäivitusel
+ Moodul eemaldatakse järgmisel taaskäivitusel
+ Moodulit ei eemaldata järgmisel taaskäivitusel
+ Moodul keelatakse järgmisel taaskäivitusel
+ Moodul lubatakse järgmisel taaskäivitusel
+ Loodud %1$s poolt
+ Taaskäivita taastusesse
+ Taaskäivita käivitushaldurisse
+ Taaskäivita allalaadimisrežiimi
+
+
+ Uuendus saadaval
+ Installitud
+ Pole installitud
+ Uuendatud: %1$s
+ Sorteerimisjärjekord
+ Sorteeri nime järgi
+ Sorteeri viimase uuenduse järgi
+
+
+ Salvesta logi
+ Laadi uuesti
+ Tühjenda logi nüüd
+ Logi edukalt tühjendatud
+ Logi on tühi
+
+
+ Teave
+ Rakenduse muutuste logi
+
+ Rakenduse versioon
+ Lähtekood
+ Annetus
+ Rakenduse tõlkijad
+ Foorumi tugiteema
+
+
+ Sulge
+ Installi %1$s
+ Kas soovid kohe installida %1$s?
+ Allalaadimine
+ Taaskäivita
+ Magisk\'ile on uuendus saadaval!
+ Taaskäivita seadete rakendamiseks
+ Väljalaskemärkmed
+ Hoidla vahemälu tühjendatud
+ Protsessi viga
+ ZIP on salvestatud:\n
+ [Sisemälu]%1$s
+ Laadin alla
+ Laadin ZIP-faili alla (%1$d%%)…
+ Töötlen
+ Töötlen ZIP-faili…
+ Magisk Manager\'ile on uuendus saadaval!
+ Vajuta allalaadimiseks ja installimiseks
+ DTBO sai paigatud!
+ Magisk Manager on paiganud dtbo.img, palun taaskäivita
+ Magisk\'i uuendused
+ Välgutamine
+ Peidan Magisk Manager\'i…
+ See võib aega võtta…
+ Magisk Manager\'i peitmine ebaõnnestus…
+ Laadi ainult ZIP alla
+ Paika käivituspildi fail
+ Otsene install (soovitatud)
+ Installi teise lahtrisse (pärast üle õhu uuendust)
+ Vali meetod
+ Magisk\'i sihtversioon ei toeta käivituspildi faili paikamist
+ Vali originaalne käivituspildi väljastus .img või .img.tar vormingus
+ Täielik eemaldus
+ Taasta pildid
+ Taastus valmis!
+ Originaalne varundus puudub!
+ Laadi alla suletud koodi
+ Magisk Manager on vaba ja avatud lähtekoodiga, mis ei sisalda Google\'i suletud SafetyNet\'i API koodi.\n
+ \n
+ Kas lubad Magisk Manager\'il SafetyNet\'i kontrollide jaoks laadida alla laiendus (sisaldab GoogleApiClient\'i)?
+ Superkasutaja andmebaas on korrumpteerunud, loon uue andmebaasi
+
+
+ Üldine
+ Tume teema
+ Luba tume teema
+ Tühjenda hoidla vahemälu
+ Tühjenda võrgus olevate hoidlate vahemälus olev teave, sunnib rakendust võrgust värskendama
+ Peida Magisk Manager
+ Taaspaki Magisk Manager juhusliku nimega
+ Keel
+ (Süsteemi vaikesäte)
+ Uuenda seadeid
+ Uuenduste kanal
+ Stabiilne
+ Beeta
+ Kohandatud
+ Sisesta kohandatud URL
+ Paigatud käivitusväljundi vorming
+ Vali väljutatava paigatud käivituspildi vorming.\n
+ Vali .img, mida välgutada fastboot/allalaadimisrežiimi kaudu; vali .img.tar, mida välgutada ODIN\'i kaudu.
+ Magisk\'i ainult tuuma režiim
+ Luba ainult põhifunktsioonid. MagiskSU, MagiskHide ja süsteemivaba hosts siiski lubatakse, ent mooduleid ei laadita.
+ Peida Magisk erinevate tuvastuste eest
+ Süsteemivaba hosts
+ Süsteemivaba hosts-tugi reklaamiblokeerijatest rakendustele
+
+ Rakendused ja ADB
+ Ainult rakendused
+ Ainult ADB
+ Keelatud
+ 10 sekundit
+ 20 sekundit
+ 30 sekundit
+ 60 sekundit
+ Superkasutaja ligipääs
+ Automaatne vastus
+ Taotluse ajalõpp
+ Superkasutaja teade
+ %1$s sekundit
+ Taas-autendi peale uuendust
+ Taas-autendi superkasutaja õigused peale rakenduse uuendust
+ Luba sõrmejäljega autentimine
+ Kasuta sõrmejäljelugejat superkasutaja taotluste lubamiseks
+
+ Mitmikkasutaja režiim
+ Ainult seadme omanik
+ Seadme omaniku hallatud
+ Kasutajast sõltumatu
+ Ainult omanikul on juurkasutaja õigused
+ Ainult omanik saab hallata juurkasutaja ligipääsu ja saada taotlusküsimusi
+ Igal kasutajal on oma isiklikud juurkasutaja reeglid
+ Taotlus on saadetud seadme omanikule. Palun lülitu omanikule ja anna vajalikud load
+
+ Nimeruumi monteerimisrežiim
+ Globaalne nimeruum
+ Võta nimeruum üle
+ Isoleeritud nimeruum
+ Kõik juurkasutaja sessioonid kasutavad globaalset monteerimise nimeruumi
+ Juurkasutaja sessioonid võtavad üle selle taotleja nimeruumi
+ Iga juurkasutaja sessioon saab oma isoleeritud nimeruumi
+ Pole toetatud Androidi versioonis 8.0+
+
+
+ Superkasutaja taotlus
+ Keela %1$s
+ Keela
+ Küsi
+ Luba
+ Annab täieliku ligipääsu sinu seadmele.\n
+ Keela, kui sa pole kindel!
+ Igavesti
+ Üks kord
+ 10 min
+ 20 min
+ 30 min
+ 60 min
+ Rakendusele %1$s anti superkasutaja õigused
+ Rakendusel %1$s keelati superkasutaja õigused
+ Rakendusi ei leitud
+ Superkasutaja õigused antud rakendusele %1$s
+ Superkasutaja õigused keelatud rakendusele %1$s
+ Teated lubatud rakendusele %1$s
+ Teated keelatud rakendusele %1$s
+ Logimine lubatud rakendusele %1$s
+ Logimine keelatud rakendusele %1$s
+ Rakenduse %1$s õigused on eemaldatud
+ Eemaldad?
+ Kinnitad rakenduse %1$s õiguste eemaldamise?
+ Hüpik
+ Puudub
+ Autentimine ebaõnnestus
+
+
+ PID:\\u0020
+ Siht-UID:\\u0020
+ Käsklus:\\u0020
+
+
diff --git a/app/src/full/res/values-fr/strings.xml b/app/src/full/res/values-fr/strings.xml
new file mode 100644
index 000000000..0c48a79ca
--- /dev/null
+++ b/app/src/full/res/values-fr/strings.xml
@@ -0,0 +1,222 @@
+
+
+
+ Modules
+ Téléchargements
+ Superuser
+ Journal
+ Paramètres
+ Installer
+
+
+ Magisk n\'est pas installé
+ Vérification des mises à jours…
+ Magisk v%1$s disponible !
+ Canal de mise à jour invalide
+ Appuyer pour lancer le contrôle SafetyNet
+ Vérification de l\'état SafetyNet…
+ Contrôle SafetyNet passé avec succès
+ Erreur d\'API SafetyNet
+ Connexion réseau indisponible
+ Le service a été tué
+ La réponse est invalide
+
+
+ Paramètres avancés
+ Garder le chiffrement forcé
+ Garder AVB 2.0/dm-verity
+ Version installée : %1$s
+ Dernière Magisk : %1$s
+ Désinstaller
+ Désinstaller Magisk
+ Tous les modules seront désactivés/effacés. Le root sera enlevé et vos données seront potentiellement chiffrées si elles ne le sont pas actuellement
+ Mise à jour %1$s
+
+
+ (Aucune information transmise)
+ Aucun module trouvé
+ Le module va être mis à jour au prochain redémarrage
+ Le module va être supprimé au prochain redémarrage
+ Le module ne sera pas supprimé au prochain redémarrage
+ Le module va être désactivé au prochain redémarrage
+ Le module va être activé au prochain redémarrage
+ Créé par %1$s
+ Redémarrer en récupération
+ Redémarrer en chargement démarrage
+ Redémarrer en téléchargement
+
+
+ Mise à jour disponible
+ Installé
+ Non installé
+ Mis à jour le: %1$s
+ Mode de tri
+ Trier par nom
+ Trier par dernière mis à jour
+
+
+ Enregistrer journal
+ Actualiser
+ Effacer le journal maintenant
+ Journal effacé avec succès
+ Journal vide
+
+
+ À propos
+ Journal
+ Rom
+ Version
+ Code source
+ Dons
+ Traducteurs de l\'application
+ Fil d\'assistance
+
+
+ Fermer
+ Installer %1$s
+ Voulez-vous installer %1$s maintenant ?
+ Télécharger
+ Redémarrer
+ Nouvelle mis à jour Magisk disponible!
+ Redémarrer afin d\'appliquer les réglages
+ Notes de version
+ Cache du dépôt éffacé
+ Erreur du processus
+ Le zip est enregistré dans:\n[Stockage Interne]%1$s
+ Téléchargement
+ Téléchargement du fichier zip (%1$d%%) …
+ Tritement en cours
+ Traitement du fichier zip…
+ Nouvelle mise à jour du Gestionnaire Magisk disponible!
+ Appuyer pour télécharger et installer
+ DTBO a été patché!
+ Le Gestionnaire Magisk a vient de patcher dtbo.img, merci de redémarrer
+ Mises à jours Magisk
+ Flahage
+ Masquage du Gestionnaire Magisk…
+ Cela pourrait prendre un certain temps …
+ Masquage du Gestionnaire Magisk échoué…
+ Uniquement télécharger le zip
+ Patch Fichier Image Démarrage
+ Installation directe (Recommendée)
+ Installer dans le second Slot (Après OTA)
+ Sélectionner le méthode
+ La version cible de Magisk ne prend pas en charge la correction de fichier image de démarrage
+ Sélectionnez l\'image par défaut de démarrage stockée au format .img ou .img.tar
+ Désinstallation terminée
+ Restauration des images
+ Restauration…
+ Restauration terminée!
+ Le sauvegarde par défaut n\'existe pas!
+ Télécharger Code Propriétaire
+ Magisk Manager est Libre, il ne contient pas le code API SafetyNet propriétaire de Google. \ N \ nAutorisez vous le Gestionnaire Magisk à télécharger une extension (contenant GoogleApiClient) pour les contrôles SafetyNet?
+ La base de données SU est corrompue, une nouvelle base de donnée va être re-créé
+ Vérification SafetyNet impossible
+ En raison de certains changements dans les services Google Play, il n\'est pas possible de vérifier SafetyNet via Magisk Manager reconstruit
+ Installation terminée
+ Échec de l\'installation
+ Installation additionnelle requise
+ Votre appareil a besoin d\'une configuration supplémentaire pour que Magisk fonctionne correctement. Il téléchargera le zip d\'installation de Magisk, voulez-vous procéder maintenant ?
+ Installation Additionnel
+ Démarrer l\'installation de l\'environnement…
+
+
+ Général
+ Thème sombre
+ Activer le thème sombre
+ Effacer le cache du dépot
+ Effacer les informations de cache pour les dépots en ligne, Forcer l\'appli à se rafraichir en ligne
+ Masquer le Gestionnaire Magisk
+ Reconstruire le Gestionnaire Magisk avec un nom de paquet aléatoire
+ Restaurer le Gestionnaire Magisk
+ Restaurer le Gestionnaire Magisk avec le paquet originel
+ Language
+ (Système par Défaut)
+ Mis à jour des réglages
+ Vérification des mises à jours
+ Vérifier l\'éxistance de mise à jour en tâche de fond de façon périodique
+ Mis à jour du canal
+ Stable
+ Béta
+ Personalisé
+ Insérer une URL personalisée
+ Patcher Format Fichier Démarrage
+ Sélectioner le format de sortie de l\'image de boot.\nChoisir .img pour flasher à traver le mode démarrage rapide/téléchargement; choisir .img.tar pour flasher via ODIN.
+ Mode Magisk Core uniquement
+ Activer uniquement les fonctionnalités de base, tous les modules ne seront pas chargés. MagiskSU, MagiskHide et les hosts systemless restent activés
+ Masquer Magisk de diverses détections
+ Hosts systemless
+ Support hosts systemless pour les applications type Adblock
+
+ Applis et ADB
+ Applications uniquement
+ ADB uniquement
+ Désactivé
+ 10 secondes
+ 20 secondes
+ 30 secondes
+ 60 secondes
+ Accès Superuser
+ Réponse automatique
+ Délai de requête
+ Notification Superuser
+ %1$s secondes
+ Ré-authentifier après la mise à niveau
+ Réauthentifier les autorisations de superutilisateur après une mise à niveau d\'application
+ Activer l\'authentification par empreinte digitale
+ Utiliser un scanner d\'empreintes digitales pour autoriser les demandes superutilisateur
+
+ Mode Multi-utilisateurs
+ Propriétaire de l\'appareil uniquement
+ Propriétaire de l\'appareil géré
+ Utilisateur indépendant
+ Seul le propriétaire a un accès root
+ Seul le propriétaire peut gérer l\'accès root et recevoir des demandes de requêtes
+ Chaque utilisateur a ses propres règles de root séparées
+ Une requête a été envoyée au propriétaire du périphérique. Merci de basculer en propriétaire et d\'accepter les permissions requises
+
+ Mode Montage Espace de Noms
+ Espace de Nom Global
+ Hériter de l\'espace de noms
+ Espace de noms isolé
+ Toutes les sessions racines utilisent l\'espace de noms de montage global
+ Les sessions racines hériteront de l\'espace de noms de son demandeur
+ Chaque session racine aura son propre espace de noms isolé
+ Android 8.0+ n\'est pas supporté
+ Aucune empreinte digitale n\'a été définie ou aucun support de périphérique
+
+
+ Requête Superuser
+ Refuser%1$s
+ Refuser
+ Demander
+ Accepter
+ Accepter un accès complet à votre appareil.\nRefuser si vous n\'êtes pas sûr !
+ Toujours
+ Une fois
+ 10 min
+ 20 min
+ 30 min
+ 60 min
+ %1$s a obtenu les droits Superuser
+ %1$s n\'a pas obtenu les droits Superuser
+ Aucune application trouvée
+ Les droits Superuser de %1$s sont accordés
+ Les droits Superuser de %1$s sont refusés
+ Les notifications pour %1$s sont activées
+ Les notifications pour %1$s sont désactivées
+ La journalisation pour %1$s est activée
+ La journalisation pour %1$s est désactivée
+ Les droits de %1$s sont annulés
+ Annuler ?
+ Vous confirmez l\'annulation des droits pour %1$s ?
+ Toast
+ Aucun
+ Authentication Échouée
+
+
+ PID :\u0020
+ UID cible :\\u0020
+ Commande :\u0020
+
+
diff --git a/app/src/full/res/values-hr/strings.xml b/app/src/full/res/values-hr/strings.xml
new file mode 100644
index 000000000..99b7a8a93
--- /dev/null
+++ b/app/src/full/res/values-hr/strings.xml
@@ -0,0 +1,192 @@
+
+
+
+
+ Moduli
+ Preuzimanja
+ Superuser
+ Zapisnik događaja
+ Postavke
+ Instaliranje
+
+
+ Magisk nije instaliran
+
+ Provjera ažuriranja…
+ Dostupna je Maigsk inačica v%1$s!
+ Dodirni za SafetyNet provjeru
+ Provjera SafetyNet statusa
+ SafetyNet provjera uspješna
+ SafetyNet API greška
+ Mrežna veza nije dostupna
+ Usluga je ubijena
+ Odgovor je nevažeći
+
+
+ Napredne postavke
+ Zadrži prisilno šifirannje
+ Zadrži AVB 2.0/dm-verity
+ Instalirana inačica: %1$s
+ Najnovija inačica: %1$s
+ Deinstaliraj
+ Deinstaliraj Magisk
+ Svi moduli će biti onemogućeni/uklonjeni. Root će biti uklonjen i potencijalno šifrirati Vaše podatke, ukoliko Vaši podaci trenutačno nisu šifrirani
+ Ažuriraj %1$s
+
+
+ (Nema podataka)
+ Nijedan modul nije pronađen
+ Modul će se ažurirati pri sljedećem ponovnom pokretanju
+ Modul će biti uklonjen pri sljedećem ponovnom pokretanju
+ Modul neće biti uklonjen pri sljedećem ponovnom pokretanju
+ Modul će biti onemogućen pri sljedećem ponovnom pokretanju
+ Modul će biti omogućen pri sljedećem ponovnom pokretanju
+ Napravio %1$s
+
+
+ Ažuriranje dostupno
+ Instalirano
+ Nije instalirano
+
+
+ "Spremi zapisnik "
+ Ponovno učitaj
+ Očisti zapisnik sada
+ Zapisnik je uspješno izbrisan
+ Zapisnik je prazan
+
+
+ O aplikaciji
+ Popis izmjena aplikacije
+
+ Verzija aplikacije
+ Izvorni kod
+ Donacija
+ Prevoditelji aplikacije
+ Tema podrške
+
+
+ Zatvori
+ Instaliraj %1$s
+ Da li želite instalirati %1$s sada?
+ Preuzmi
+ Ponovno podizanje sustava
+ Dostupno je novo Magisk ažuriranje!
+ Ponovno pokrenite kako biste primjenili postavke
+ Bilješke o izdavanju aplikacije
+ Predmemorija repozitorija izbrisana
+ Pogreška u procesu
+ Zip je pohranjen u:\n[Unutarnja pohrana]%1$s
+ Preuzimanje
+ Preuzimanje zip datoteke (%1$d%%) …
+ Obrada
+ Obrada zip datoteke …
+ Dostupno je novo ažuriranje Magisk Manager aplikacije!
+ Pritisnite za preuzimanje i instalaciju
+ Magisk ažuriranja
+ Apliciranje
+ Skrivanje Magisk Manager aplikacije…
+ Skrivanje Magisk Manager nije uspjelo
+ Preuzmi samo zip
+ Zakrpa datoteke za podizanje sustava
+ Izravna instalacija (preporuča se)
+ Instalirajte na drugo mjesto (nakon OTA-e)
+ Odaberite metodu
+ Ciljana verzija Magiska ne podržava zakrpu datoteke za podizanje sustava
+ Odaberite standardnu datoteku za podizanje sustava u .img ili .img.tar formatu
+ Potpuna deinstalacija
+ Obnova je dovršena!
+ Stock backup does not exist!
+ Preuzmite vlasnički kod
+ Magisk Manager je FOSS aplikacija te ne sadrži Googleov SafetyNet API kod.\n\nDopuštate li Magisk Manager aplikaciji da preuzme proširenje (sadrži GoogleApiClient) za SafetyNet provjere?
+
+
+ Općenito
+ Tamna tema
+ Omogući tamnu temu
+ Izbriši predmemoriju repozitorija
+ Izbrišite predmemorirane informacije za online repozitorij, prisiljavajući aplikaciju da se osvježi online
+ Sakrij Magisk Manager
+ Privremeno sakrij Magisk Manager.\nTo će instalirati novu aplikaciju pod nazivom \"Otkrij Magisk Manager\"
+ Jezik
+ (Zadana postavka sustava)
+ Ažuriraj postavke
+ Kanal ažuriranja
+ Stabilno
+ Beta
+ Izlazni format datoteke za podizanje sustava nakon zakrpe
+ Odaberite izlazni format datoteke za podizanje sustava nakon zakrpe.\nOdaberite .img da biste aplicirali putem brzog pokretanja(fastboota)/preuzimanja(downloada); odaberite .img.tar da biste aplicirali putem ODIN-a.
+
+ Samo Magisk Core način rada
+ Omogućite samo osnovne značajke, svi se moduli neće učitati. MagiskSU, MagiskHide i systemless hostovi će i dalje biti omogućeni
+ Sakrij Magisk od raznih detekcija
+ Systemless hostovi
+ Systemless hostovi podržavaju Adblock aplikacije
+
+ Aplikacije i ADB
+ Samo aplikacije
+ Samo ADB
+ Onemogućeno
+ 10 sekundi
+ 20 sekundi
+ 30 sekundi
+ 60 sekundi
+ Superuser pristup
+ Automatski odgovor
+ Vremensko ograničenje zahtjeva
+ Superuser obavijest
+ %1$s sekundi
+ Ponovno provjerite autentičnost nakon ažuriranja
+ Ponovno provjerite autentičnost Superuser dopuštenja nakon ažuriranja aplikacije
+
+ Višekorisnički način rada
+ Samo vlasnik uređaja
+ Upravljano od strane vlasnika uređaja
+ Neovisno o korisniku
+ Samo vlasnik ima root pristup
+ Samo vlasnik može upravljati root pristupom i primati zahtjeve za root pristup
+ Svaki korisnik ima vlastita root pravila
+ Zahtjev je poslan vlasniku uređaja. Prebacite se na vlasnika i odobrite dopuštenje
+
+ Postavljanje imenskog prostora
+ Globalni imenski prostor
+ Naslijediti imenski prostor
+ Izolirani imenski prostor
+ Sve root sesije koriste globalni imenski prostor
+ Root sesije će naslijediti imenski prostor tražitelja
+ Svaka root sesija ima svoj vlastiti imenski prostor
+
+
+ Superuser zahtjev
+ Odbij%1$s
+ Odbij
+ Upitaj
+ Odobri
+ Omogućuje potpuni pristup Vašem uređaju.\nOdbijte ako niste sigurni!
+ Zauvijek
+ Jednom
+ 10 min
+ 20 min
+ 30 min
+ 60 min
+ %1$s su dodijeljena Superuser prava
+ %1$s su odbijena Superuser prava
+ Nisu pronađene aplikacije
+ Superuser prava su dodijeljena za %1$s
+ Superuser prava su odbijena za %1$s
+ Obavijesti za %1$s su odobrene
+ Obavijesti za %1$s su odbijene
+ Zapisnik događaja omogućen je za %1$s
+ Zapisnik događaja onemogućen je za %1$s
+ Opozvana su prava za %1$s
+ Opozvati?
+ Potvrdite da biste opozvali prava za %1$s?
+ Pop-up
+ Nijedan
+
+
+ PID:\u0020
+ Ciljani UID:\u0020
+ Naredba:\u0020
+
+
diff --git a/app/src/full/res/values-in/strings.xml b/app/src/full/res/values-in/strings.xml
new file mode 100644
index 000000000..b9570dc9e
--- /dev/null
+++ b/app/src/full/res/values-in/strings.xml
@@ -0,0 +1,221 @@
+
+
+
+
+ Modul
+ Unduhan
+ Superuser
+ Log
+ Pengaturan
+ Pasang
+
+
+ Magisk tidak terpasang
+ Memeriksa pembaruan…
+ Magisk v%1$s tersedia!
+ Kanal Pembaruan tidak valid!
+ Ketuk untuk memulai pemeriksaan SafetyNet
+ Memeriksa status SafetyNet…
+ Pemeriksaan SafetyNet Berhasil
+ Kesalahan pada API SafetyNet
+ Koneksi jaringan tidak tersedia
+ Layanan telah dimatikan
+ Tanggapan tidak valid
+
+
+ Pengaturan Lanjutan
+ Pertahankan enkripsi paksa
+ Pertahankan AVB 2.0/dm-verity
+ Versi yang Terpasang: %1$s
+ Versi Terbaru: %1$s
+ Copot
+ Copot Magisk
+ Semua modul akan dinonaktifkan/dihapus. Root akan dihapus, dan berpotensi mengenkripsi data Anda jika tidak terenkripsi saat ini.
+ Perbarui %1$s
+
+
+ (Tidak ada info tersedia)
+ Tidak ada modul ditemukan
+ Modul akan diperbarui pada reboot berikutnya
+ Modul akan dihapus pada reboot berikutnya
+ Modul tidak akan dihapus pada reboot berikutnya
+ Modul akan dinonaktifkan pada reboot berikutnya
+ Modul akan diaktifkan pada reboot berikutnya
+ Dibuat oleh %1$s
+ Reboot ke Recovery
+ Reboot ke Bootloader
+ Reboot ke Download
+
+
+ Pembaruan Tersedia
+ Terpasang
+ Tidak Terpasang
+ Diperbarui pada: %1$s
+ Urutkan Susunan
+ Urut berdasarkan nama
+ Urut berdasarkan pembaruan terakhir
+
+
+ Simpan log
+ Muat ulang
+ Bersihkan log sekarang
+ Log berhasil dibersihkan
+ Log kosong
+
+
+ Tentang
+ Log pembaruan
+ Albert I (krasCGQ)
+ Versi
+ Kode sumber
+ Donasi
+ Penerjemah
+ Thread dukungan
+
+
+ Tutup
+ Pasang %1$s
+ Apakah Anda ingin memasang %1$s sekarang?
+ Unduh
+ Reboot
+ Pembaruan Magisk Tersedia!
+ Reboot untuk menerapkan pengaturan
+ Catatan rilis
+ Cache repo dibersihkan
+ Kesalahan proses
+ Zip disimpan di:\n[Penyimpanan Internal]%1$s
+ Mengunduh
+ Mengunduh file zip (%1$d%%) …
+ Memproses
+ Memproses file zip …
+ Pembaruan Magisk Manager Tersedia!
+ Tekan untuk unduh dan pasang
+ DTBO telah ditambal!
+ Magisk Manager telah menambal dtbo.img, silahkan reboot
+ Pembaruan Magisk
+ Flashing
+ Menyembunyikan Magisk Manager…
+ Ini mungkin membutuhkan beberapa saat…
+ Kesalahan menyembunyikan Magisk Manager…
+ Unduh Zip Saja
+ Tambal File Boot Image
+ Pasang Langsung (Direkomendasikan)
+ Pasang ke Slot Kedua (Setelah OTA)
+ Pilih Metode
+ Versi target Magisk tidak mendukung penambalan file boot image
+ Pilih stock boot image dump dalam format .img atau .img.tar
+ Pulihkan Image
+ Copot Total
+ Memulihkan…
+ Pemulihan selesai!
+ Cadangan stock tidak ada!
+ Unduh Kode Proprieter
+ Magisk Manager adalah aplikasi FOSS, yang tidak menyertakan kode API proprieter Google SafetyNet.\n\nApakah Anda mengizinkan Magisk Manager untuk mengunduh sebuah ekstensi (berisi GoogleApiClient) untuk pemeriksaan SafetyNet?
+ Database SU rusak, akan membuat db baru
+ Tidak dapat memeriksa SafetyNet
+ Karena beberapa perubahan dalam Layanan Google Play, adalah tidak mungkin untuk memeriksa SafetyNet di Magisk Manager yang dipak ulang
+ Penyiapan selesai
+ Penyiapan gagal
+ Memerlukan Penyiapan Tambahan
+ Perangkat Anda memerlukan penyiapan tambahan untuk Magisk dapat bekerja dengan baik. Ia akan mengunduh zip penyiapan Magisk, apakah Anda ingin melanjutkan sekarang?
+ Penyiapan Tambahan
+ Menjalankan penyiapan lingkungan…
+
+
+ Umum
+ Tema Gelap
+ Aktifkan tema gelap
+ Bersihkan Cache Repo
+ Bersihkan informasi ter-cache untuk repo online, memaksa apl untuk menyegarkan online
+ Sembunyikan Magisk Manager
+ Pak ulang Magisk Manager dengan nama paket acak
+ Pulihkan Magisk Manager
+ Pulihkan Magisk Manager dengan paket asli
+ Bahasa
+ (Default Sistem)
+ Pengaturan Pembaruan
+ Kanal Pembaruan
+ Stabil
+ Beta
+ Kustom
+ Masukkan sebuah URL kustom
+ Format Keluaran Boot yang Ditambal
+ Pilih format keluaran boot image yang ditambal.\nPilih .img untuk flash melalui mode recovery/download; pilih .img.tar untuk flash melalui ODIN.
+ Magisk Mode Inti Saja
+ Aktifkan fitur inti saja. MagiskSU, MagiskHide, dan host tanpa sistem akan tetap diaktifkan
+ Sembunyikan Magisk dari berbagai pendeteksian
+ Host tanpa sistem
+ Dukungan host tanpa sistem untuk apl pemblokir iklan
+
+ Apl dan ADB
+ Apl saja
+ ADB saja
+ Nonaktif
+ 10 detik
+ 20 detik
+ 30 detik
+ 60 detik
+ Akses Superuser
+ Tanggapan Otomatis
+ Batas Waktu Permintaan
+ Notifikasi Superuser
+ %1$s detik
+ Otentikasi ulang setelah pembaruan
+ Otentikasi ulang izin superuser setelah pembaruan sebuah aplikasi
+ Aktifkan Otentikasi Sidik Jari
+ Gunakan pemindai sidik jari untuk mengizinkan permintaan superuser
+
+ Mode Multipengguna
+ Pemilik Perangkat Saja
+ Pemilik Perangkat Mengelola
+ Pengguna Independen
+ Hanya pemilik yang memiliki akses root
+ Hanya pemilik yang dapat mengelola akses root dan menerima permintaan
+ Setiap pengguna memiliki aturan root tersendiri
+ Permintaan telah dikirim kepada pemilik perangkat. Silakan beralih ke pemilik dan berikan izin yang diperlukan
+
+ Mode Mount Ruang Nama
+ Ruang Nama Global
+ Ruang Nama Warisan
+ Ruang Nama Terisolasi
+ Semua sesi root menggunakan mount ruang nama global
+ Sesi root akan mewarisi ruang nama pemintanya
+ Setiap sesi root akan memiliki ruang nama tersendiri
+ Tidak mendukung Android 8.0+
+ Tidak ada sidik jari diatur atau tidak ada dukungan perangkat
+
+
+ Permintaan Superuser
+ Tolak%1$s
+ Tolak
+ Beritahu
+ Izinkan
+ Mengizinkan akses penuh ke perangkat Anda.\nTolak jika Anda tidak yakin!
+ Selamanya
+ Sekali
+ 10 mnt
+ 20 mnt
+ 30 mnt
+ 60 mnt
+ %1$s diizinkan akses Superuser
+ %1$s ditolak akses Superuser
+ Tidak ada apl ditemukan
+ Akses Superuser dari %1$s diizinkan
+ Akses Superuser dari %1$s ditolak
+ Notifikasi dari %1$s diaktifkan
+ Notifikasi dari %1$s dinonaktifkan
+ Logging dari %1$s diaktifkan
+ Logging dari %1$s dinonaktifkan
+ Akses %1$s dicabut
+ Cabut?
+ Konfirmasi untuk mencabut akses %1$s?
+ Toast
+ Tidak ada
+ Otentikasi Gagal
+
+
+ PID:\u0020
+ UID target:\u0020
+ Perintah:\u0020
+
+
diff --git a/app/src/full/res/values-it/strings.xml b/app/src/full/res/values-it/strings.xml
new file mode 100644
index 000000000..f42521b7f
--- /dev/null
+++ b/app/src/full/res/values-it/strings.xml
@@ -0,0 +1,224 @@
+
+
+
+
+ Moduli
+ Download
+ Superuser
+ Registro eventi
+ Impostazioni
+ Installa
+
+
+ Magisk non è installato
+ Controllo aggiornamenti…
+ È disponibile Magisk v%1$s!
+ Canale di aggiornamento non valido
+ Tocca per controllare SafetyNet
+ Controllo stato SafetyNet…
+ Controllo SafetyNet OK
+ Errore API SafetyNet
+ Connessione di rete persa
+ Il servizio è stato terminato
+ La risposta non è valida
+
+
+ Impostazioni avanzate
+ Mantieni crittografia forzata
+ Mantieni AVB 2.0/dm-verity
+ Versione installata: %1$s
+ Ultima versione disponibile: %1$s
+ Disinstalla
+ Disinstalla Magisk
+ Tutti i moduli verranno disabilitati/rimossi. Il root verrà rimosso e se il dispositivo non è crittografato è possibile che tutti i dati vengano crittografati
+ Aggiorna %1$s
+
+
+ (Nessuna informazione)
+ Nessun modulo trovato
+ Il modulo sarà aggiornato al prossimo riavvio
+ Il modulo sarà rimosso al prossimo riavvio
+ Il modulo non sarà rimosso al prossimo riavvio
+ Il modulo sarà disabilitato al prossimo riavvio
+ Il modulo sarà abilitato al prossimo riavvio
+ Creato da: %1$s
+ Riavvia in Recovery
+ Riavvia in Bootloader
+ Riavvia in Download Mode
+
+
+ Aggiornamento disponibile
+ Installato
+ Non installato
+ Aggiornato il: %1$s
+ Ordinamento
+ Ordina per nome
+ Ordina per ultimo aggiornamento
+
+
+ Salva registro eventi
+ Ricarica
+ Svuota il registro eventi
+ Registro eventi svuotato correttamente
+ Il registro eventi è vuoto
+
+
+ Informazioni
+ Novità
+ Auanasgheps | Fabb2303 | bovirus
+ Versione
+ Codice sorgente
+ Dona
+ Traduttori
+ Thread di supporto
+
+
+ Chiudi
+ Installazione di %1$s
+ Vuoi installare %1$s?
+ Download
+ Riavvia
+ È disponibile un nuovo aggiornamento di Magisk!
+ Riavvia per applicare
+ Note di rilascio
+ La cache delle repository è stata svuotata
+ Errore di elaborazione
+ Il file zip si trova in:\n[Memoria Interna]%1$s
+ Download in corso
+ Download del file zip (%1$d%%) …
+ Elaborazione
+ Elaborazione del file zip…
+ È disponibile un nuovo aggiornamento di Magisk Manager!
+ Premere per scaricare e installare
+ DTBO è stato aggiornato!
+ Magisk Manager ha aggiornato dtbo.img, riavvia per completare
+ Aggiornamenti di Magisk
+ Flash in corso…
+ Nascondendo Magisk Manager…
+ Potrebbe volerci un po\'…
+ Non è stato possibile nascondere Magisk Manager
+ Scarica solo il file zip
+ Aggiorna l\'immagine di boot
+ Installazione diretta (raccomandata)
+ Installa nel secondo slot (dopo OTA)
+ Seleziona un metodo
+ La versione Magisk di destinazione non supporta l\'aggiornamento dell\'immagine di boot
+ Seleziona l\'immagine originale di boot in formato .img o img.tar
+ Disinstallazione completa
+ Ripristina Immagini
+ Ripristino…
+ Ripristino completato!
+ Non esiste un\'immagine originale di boot!
+ Scarica codice proprietario
+ Magisk Manager è FOSS, quindi non contiene codice proprietario delle API Google SafetyNet.\n\nVuoi permettere il download di un\'estensione (che contiene GoogleApiClient) per controllare lo stato di SafetyNet?
+ Il database SU è corrotto, un nuovo DB verrà ricreato
+ Impossibile controllare SafetyNet
+ In seguito a dei cambiamenti in Google Play Services, non è possibile controllare SafetyNet quando Magisk Manager è nascosto
+ Configurazione completata
+ Configurazione fallita
+ Richiedi configurazione aggiuntiva
+ Il tuo dispositivo necessita di una configurazione aggiuntiva per far funzionare Magisk correttamente. Verrà scaricato il file zip di Magisk, vuoi procedere ora?
+ Configurazione aggiuntiva
+ Configurazione dell\'ambiente in corso…
+
+
+
+ Generale
+ Tema scuro
+ Abilita il tema scuro
+ Svuota cache repository
+ Svuota la cache delle repository e forza l\'aggiornamento online dell\'app
+ Nascondi Magisk Manager
+ Reinstalla Magisk Manager con un nome pacchetto casuale
+ Ripristina Magisk Manager
+ Ripristina Magisk Manager con il nome pacchetto originale
+ Lingua
+ (Sistema)
+ Impostazioni aggiornamento
+ Controlla aggiornamenti
+ Controlla automaticamente gli aggiornamenti in background
+ Canale di aggiornamento
+ Stabile
+ Beta
+ Personalizzato
+ Inserisci un URL personalizzato
+ Formato dell\'immagine di boot aggiornata
+ Seleziona il formato nel quale l\'immagine di boot verrà salvata.\nSeleziona .img per il flash in Fastboot/Download Mode; Seleziona .img.tar per il flash con Odin.
+ Modalità Magisk Core
+ Abilita solo le funzioni principali. Nessun modulo verrà caricato. MagiskSU, MagiskHide e host systemless rimarranno abilitati
+ Nasconde Magisk da numerose rilevazioni
+ Host systemless
+ Supporto a host systemless per le app che bloccano le pubblicità
+
+ App e ADB
+ Solo app
+ Solo ADB
+ Disabilitato
+ 10 secondi
+ 20 secondi
+ 30 secondi
+ 60 secondi
+ Accesso Superuser
+ Accesso predefinito
+ Timeout richiesta
+ Notifica Superuser
+ %1$s secondi
+ Ri-autentica dopo aggiornamento
+ Ri-autentica i permessi Superuser dopo un aggiornamento dell\'app
+ Abilita autenticazione impronta
+ Utilizza il sensore di impronte per accettare le richieste Superuser
+
+ Modalità multiutente
+ Solo proprietario del dispositivo
+ Gestito dal proprietario utente
+ Utente indipendente
+ Solo il proprietario ha i permessi di root
+ Solo il proprietario può gestire accesso root e ricevere richieste
+ Ogni utente ha le sue regole di root separate
+ Una richiesta è stata inviata al proprietario del dispositivo. Accedi come proprietario dispositivo e concedi i permessi.
+
+ Modalità mount namespace
+ Namespace globale
+ Namespace ereditato
+ Namespace isolato
+ Tutte le sessioni di root erediteranno il namespace globale
+ Le sessioni di root erediteranno il namespace del loro richiedente
+ Ogni sessione di root avrà il suo namespace isolato
+ Non è supportato da Android 8.0+
+ Non è presente alcuna impronta o il dispositivo non è supportato
+
+
+ Richiesta Superuser
+ Nega %1$s
+ Nega
+ Chiedi
+ Concedi
+ Concede il pieno accesso al dispositivo.\nNega se non sei sicuro
+ Sempre
+ Una volta
+ 10 minuti
+ 20 minuti
+ 30 minuti
+ 60 minuti
+ %1$s ha ottenuto i permessi Superuser
+ %1$s non ha ottenuto i permessi Superuser
+ Nessuna app trovata
+ %1$s ha ottenuto i permessi Superuser
+ %1$s non ha ottenuto i permessi Superuser
+ Notifiche per %1$s abilitate
+ Notifiche per %1$s disabilitate
+ Registro eventi abilitato per %1$s
+ Registro eventi non abilitato per %1$s
+ I diritti di %1$s sono stati revocati
+ Vuoi revocare?
+ Confermi la revoca dei diritti di %1$s?
+ Toast
+ Nessuno
+ Autenticatione fallita
+
+
+ PID:\u0020
+ UID destinazione:\u0020
+ Comando:\u0020
+
+
diff --git a/app/src/full/res/values-ja/strings.xml b/app/src/full/res/values-ja/strings.xml
new file mode 100644
index 000000000..90256c041
--- /dev/null
+++ b/app/src/full/res/values-ja/strings.xml
@@ -0,0 +1,209 @@
+
+
+
+
+ モジュール
+ ダウンロード
+ スーパーユーザー
+ ログ
+ 設定
+ インストール
+
+
+ Magiskがインストールされていません
+ 更新を確認中…
+ Magisk v%1$s が利用可能です!
+ 不正な更新チャンネルです
+ タップしてSafetyNetチェックを開始
+ SafetyNetの状態をチェック中…
+ SafetyNetチェック成功
+ SafetyNet APIエラー
+ ネットワーク接続がありません
+ サービスが終了されました
+ 応答が不正です
+
+
+ 高度な設定
+ 強制的な暗号化を維持する
+ AVB 2.0/dm-verityを維持する
+ インストール済: %1$s
+ 最新: %1$s
+ アンインストール
+ Magiskのアンインストール
+ すべてのモジュールが無効化/削除されます。Rootも無効化され、ストレージが暗号化されていない場合、暗号化される場合があります
+ %1$sの更新
+
+
+ (情報がありません)
+ モジュールが見つかりません
+ 次の再起動時にモジュールを更新する
+ 次の再起動時にモジュールを削除する
+ 次の再起動時にモジュールを削除しない
+ 次の再起動時にモジュールを無効にする
+ 次の再起動時にモジュールを有効にする
+ 開発者: %1$s
+ リカバリへ再起動
+ Bootloaderへ再起動
+ Downloadへ再起動
+
+
+ 更新あり
+ インストール済
+ 未インストール
+ 更新日: %1$s
+ 並び順
+ 名前順
+ 最終更新日順
+
+
+ ログの保存
+ 再読み込み
+ ログの消去
+ ログを消去しました
+ ログは空です
+
+
+ このアプリについて
+ アプリの更新履歴
+ 神楽坂桜(Sakura_Sa233#Twitter)/ hota (@lindwurm) / AndroPlus (@AndroPlus_org)
+ アプリのバージョン
+ ソースコード
+ 寄付
+ アプリの翻訳者
+ サポートスレッド
+
+
+ 閉じる
+ %1$s をインストール
+ %1$s をインストールしますか?
+ ダウンロード
+ 再起動
+ 新しいMagiskの更新が利用可能です!
+ 再起動して設定を適用する
+ 更新履歴
+ リポジトリキャッシュを消去しました
+ プロセスエラー
+ ZIPは\n[内部ストレージ]%1$sに保存されます
+ ダウンロード中
+ ZIPファイルのダウンロード中 (%1$d%%) …
+ 処理
+ ZIPファイルの処理中…
+ Magisk Managerの更新があります!
+ タップでダウンロードしてインストールします
+ DTBOをパッチしました!
+ Magisk Managerはdtbo.imgをパッチしました。再起動してください
+ Magiskの更新
+ 書き込み中
+ Magisk Managerを隠しています…
+ しばらくお待ちください…
+ Magisk Managerを隠せませんでした…
+ ZIPのみダウンロード
+ Bootイメージのパッチ
+ 直接インストール (推奨)
+ 第2スロットへインストール (OTA後)
+ 方法の選択
+ 指定されたMagiskバージョンはBootイメージのパッチに対応していません
+ StockのBootイメージ (.img または .img.tar形式) を選択してください
+ 完全にアンインストール
+ イメージのリストア
+ リストア完了!
+ Stockのバックアップがありません!
+ プロプライエタリコードのダウンロード
+ Magisk ManagerはFOSSのため、GoogleのプロプライエタリなSafetyNet APIコードを含んでいません。\n\nMagisk ManagerがSafetyNetチェックのための拡張機能 (GoogleApiClientを含む) をダウンロードすることを許可しますか?
+ SUデータベースが壊れています。DBを再生成します
+
+
+ 一般
+ ダークテーマ
+ ダークテーマを有効化します
+ キャッシュを消去
+ オンラインリポジトリのキャッシュされた情報を消去し、アプリをオンラインで更新します
+ Magisk Managerを隠す
+ Magisk Managerをランダムなパッケージ名で再パックします
+ Language
+ (システム標準)
+ 更新設定
+ 更新チャンネル
+ Stable
+ Beta
+ カスタム
+ カスタムURLを入力
+ パッチしたBootの出力形式
+ パッチしたBootイメージの出力形式を選択してください。\nfastboot/download modeでインストールするには .img を、ODINでインストールするには .img.tar を選択してください
+ Magisk コアモード
+ コア機能のみを有効にします。すべてのモジュールが読み込まれなくなります。 MagiskSU、MagiskHide、systemless hostsは引き続き有効になります
+ さまざまな検出からMagiskを隠します
+ Systemless hosts
+ 広告ブロックアプリのためのSystemless hostsサポートを有効化します
+
+ アプリとADB
+ アプリのみ
+ ADBのみ
+ 無効
+ 10秒
+ 20秒
+ 30秒
+ 60秒
+ スーパーユーザーアクセス
+ 自動応答
+ リクエストタイムアウト
+ スーパーユーザー通知
+ %1$s秒
+ アップグレード後の再認証
+ アプリのアップグレード後にスーパーユーザー権限を再認証します
+ 指紋認証の有効化
+ スーパーユーザー権限のリクエストの許可に指紋認証を使います
+
+ マルチユーザーモード
+ 端末の管理者のみ
+ 端末の管理者が管理
+ ユーザー毎
+ 端末の管理者のみスーパーユーザー権限を利用できます
+ 端末の管理者のみがスーパーユーザー権限を管理し、リクエストを受け付けられます
+ ユーザー毎にそれぞれスーパーユーザー権限を設定できます
+ 端末の管理者にリクエストを送信しました。管理者に切り替えて権限を許可してください
+
+ 名前空間のマウントモード
+ グローバル名前空間
+ 継承された名前空間
+ 分離された名前空間
+ すべてのrootセッションがグローバル名前空間を使用します
+ rootセッションはリクエスト者の名前空間を継承します
+ rootセッション毎に分離された名前空間を使用します
+ Android 8.0以降では対応していません
+
+
+ スーパーユーザーリクエスト
+ 拒否 %1$s
+ 拒否
+ プロンプト
+ 許可
+ 端末への完全なアクセスを許可します。\nもし確信が持てなければ拒否してください!
+ 今後も
+ 一度だけ
+ 10分
+ 20分
+ 30分
+ 60分
+ %1$s はスーパーユーザー権限を許可されました
+ %1$s はスーパーユーザー権限を拒否されました
+ アプリが見つかりません
+ %1$s のスーパーユーザー権限が許可されました
+ %1$s のスーパーユーザー権限は拒否されました
+ %1$s の通知は有効です
+ %1$s の通知は無効です
+ %1$s のログは有効です
+ %1$s のログは無効です
+ %1$s の権限は取り消されました
+ 確認
+ %1$s の権限を取り消しますか?
+ トースト通知
+ なし
+ 認証に失敗しました
+
+
+ PID:\u0020
+ ターゲット UID:\u0020
+ コマンド:\u0020
+
+
diff --git a/app/src/full/res/values-ko/strings.xml b/app/src/full/res/values-ko/strings.xml
new file mode 100644
index 000000000..a97be20a0
--- /dev/null
+++ b/app/src/full/res/values-ko/strings.xml
@@ -0,0 +1,136 @@
+
+
+
+
+ 모듈
+ 다운로드
+ 슈퍼유저
+ 로그
+ 설정
+ 설치
+
+
+ Magisk가 설치되지 않음
+
+ 업데이트 확인 중…
+ Magisk v%1$s 사용 가능!
+ SafetyNet 체크를 시작하려면 누르기
+ SafetyNet 상태 확인 중…
+
+
+ 고급 설정
+ 강제 암호화 유지
+ AVB 2.0/dm-verity 유지
+ 설치된버전: %1$s
+ 최신버전: %1$s
+ 제거
+ Magisk 제거
+
+
+ (제공된 정보 없음)
+ 검색된 모듈 없음
+ 모듈이 다음 다시 시작 시 업데이트됨
+ 모듈이 다음 다시 시작 시 제거됨
+ 모듈이 다음 다시 시작 시 제거되지 않음
+ 모듈이 다음 다시 시작 시 비활성화됨
+ 모듈이 다음 다시 시작 시 활성화됨
+ 제작: %1$s
+
+
+ 업데이트 있음
+ 설치됨
+ 설치되지 않음
+
+
+ 저장 로그
+ 다시 불러오기
+ 지금 로그 비우기
+ 로그가 성공적으로 비워짐
+ 로그가 비어 있음
+
+
+ 정보
+ 앱 변경 사항
+ lilymaniac
+ 앱 버전
+ 소스 코드
+ 기부
+ 앱 번역자
+ 지원 스레드
+
+
+ 닫기
+ %1$s 설치
+ 정말 %1$s을(를) 설치하시겠습니까?
+ 다시 시작
+ zip 파일 처리 중…
+ 새 버전의 Magisk를 사용할 수 있습니다!
+ 기기를 다시 시작하면 설정이 적용됩니다.
+ 릴리즈 노트
+ 저장소 캐시 비워짐
+ 처리 오류
+ zip 파일이 다음 위치에 저장됨:\n[내부 저장소]%1$s
+ 처리 중
+
+
+ 일반
+ 어두운 테마
+ 어두운 테마 사용
+ 저장소 캐시 비우기
+ 온라인 저장소에 대해 캐시된 정보를 지우고, 온라인에서 정보를 강제로 새로 고칩니다.
+
+ Magisk 핵심 기능 모드
+ 핵심 기능만 사용합니다. 모든 모듈은 로드하지 않습니다. MagiskSU, MagiskHide 및 systemless hosts 는 계속 사용할 수 있습니다.
+ 다양한 감지로부터 Magisk를 숨깁니다.
+ systemless hosts
+ 광고 차단 앱에서 사용하는 systemless hosts를 지원합니다.
+
+ 앱 및 ADB
+ 앱만
+ ADB만
+ 사용 안 함
+ 10초
+ 20초
+ 30초
+ 60초
+ 슈퍼유저 액세스
+ 자동 응답
+ 요청 시간 제한
+ 슈퍼유저 알림
+ %1$s초
+
+
+ 슈퍼유저 요청
+ 거부%1$s
+ 거부
+ 메시지
+ 허용
+ 기기에 대한 전체 액세스 권한을 부여합니다.\n확실하지 않은 경우 거부하세요!
+ 영구적으로
+ 한 번만
+ 10분
+ 20분
+ 30분
+ 60분
+ %1$s에 슈퍼유저 권한이 허용됨
+ %1$s에 슈퍼유저 권한이 거부됨
+ 검색된 앱 없음
+ %1$s의 슈퍼유저 권한이 허용됨
+ %1$s의 슈퍼유저 권한이 거부됨
+ %1$s의 알림이 활성화됨
+ %1$s의 알림이 비활성화됨
+ %1$s의 로깅이 활성화됨
+ %1$s의 로깅이 비활성화됨
+ %1$s의 권한이 취소됨
+ 취소하시겠습니까?
+ 정말 %1$s의 권한을 취소하시겠습니까?
+ 토스트
+ 없음
+
+
+ PID:\u0020
+ 대상 UID:\u0020
+ 명령:\u0020
+ 다운로드
+
+
diff --git a/app/src/full/res/values-lt/strings.xml b/app/src/full/res/values-lt/strings.xml
new file mode 100644
index 000000000..bd78ab148
--- /dev/null
+++ b/app/src/full/res/values-lt/strings.xml
@@ -0,0 +1,222 @@
+
+
+
+ Papildiniai
+ Papildinių parduotuvė
+ Supervartotojas
+ Įvykių sąrašas
+ Nustatymai
+ Instaliuoti
+
+
+ Magisk yra neinstaliuotas
+ Ieškoma atnaujinimų…
+ Magisk v%1$s yra galima!
+ Neteisingas atnaujinimų nustatymas
+ Paspauskite SafetyNet paieškai
+ Ieškomas SafetyNet statusas…
+ SafetyNet Paieška sėkminga
+ SafetyNet API Klaida
+ Nėra prieigos prie interneto
+ Paslauga atjungta
+ Gautas neteisingas atsakymas
+
+
+ Išplėstiniai nustatymai
+ Palikti priverstinį šifravimą
+ Palikti dm-verity
+ Instaliuota versija: %1$s
+ Naujausia versija: %1$s
+ Pašalinti
+ Pašalinti Magisk
+ Visi papildiniai bus išjungti/pašalinti. Root bus panaikintas. Yra galimybė, kad duomenys bus užšifruoti…
+ Atnaujinti %1$s
+
+
+ (Nėra informacijos)
+ Papildinių nesurasta
+ Perkrovus sistemą papildinys bus atnaujintas
+ Perkrovus sistemą papildinys bus panaikintas
+ Perkrovus sistemą papildinys nebus panaikintas
+ Perkrovus sistemą papildinys bus išjungtas, tačiau nepanaikintas
+ Perkrovus sistemą papildinys bus įjungtas
+ Sukūrė %1$s
+ Perkrauti į Recovery
+ Perkrauti į Bootloader
+ Perkrauti į Download režimą
+
+
+ Galimas atnaujinimas
+ Instaliuota
+ Neinstaliuota
+ Atnaujinta: %1$s
+ Išdėliojimo tvarka
+ Išdėlioti pagal pavadinimą (A-Z)
+ Išdėlioti pagal atnaujinimo datą (Naujausi-Seniausi)
+
+
+ Išsaugoti įvykių sąrašą
+ Įvykių sąrašo atnaujinmas
+ Išvalyti įvykių sąrašą
+ Įvykių sąrašas pašalintas
+ Įvykių sąrašas tuščias
+
+
+ Apie
+ Pakeitimų sąrašas
+ Vv2233Bb
+ Versija
+ Prisidėkite
+ Paaukoti
+ Vertėjai
+ Mūsų XDA puslapis
+
+
+ Uždaryti
+ Instaliuoti %1$s
+ Ar jūs norite instaliuoti %1$s?
+ Atsisiųsti
+ Perkrauti
+ Atsirado nauja Magisk versija!
+ Nustatymų įgalinimui prašome perkrauti telefoną
+ Pakeitimai
+ Repo failai išvalyti
+ Proceso klaida
+ Zip failas yra saugomas:\n[Internal Storage]%1$s
+ Atsisiunčiama
+ Atsiunčiamas zip failas(%1$d%%)…
+ Vyksta procesas
+ Zip failas apdorojamas…
+ Atsirado naujas Magisk Manager atnaujinimas!
+ Paspauskite, kad atsisiųstumėte ir instaliuotumėte
+ DTBO buvo ištaisytas!
+ Magisk Manager ištaisė dtbo.img, prašome perkrauti telefoną
+ Magisk Atnaujinimai
+ Instaliuojama
+ Magisk Manager paslėpiamas…
+ Tai užtruks sekundėlę…
+ Magisk Manager paslėpimas žlugo…
+ Atsisiųsti zip failą
+ Ištaisyti boot failą
+ Tiesioginis instaliavimas (Rekomenduojamas)
+ Instaliuoti į antrą vietą (Po OTA)
+ Pasirinkite metodą
+ Pasirinkta Magisk versija nepalaiko pakeitimų boot faile
+ Pasirinkti boot failą .img ar .img.tar formate
+ Pilnas pašalinimas
+ Atstatyti boot failą
+ Atstatome…
+ Atstatymas įvykdytas!
+ Gamyklinis atstatymo failas neegzistuoja!
+ Atsisiųsti patentuotą kodą
+ Magisk Manager yra FOSS todėl neturi Google patentuoto SafetyNet API kodo.\n\nAr jūs leidžiate Magisk Manager atsisiųsti papildinį (turintį GoogleApiClient) SafetyNet paieškai?
+ Supervartotojo duomenų bazė yra sugadinta, perkursime naują duomenų bazę
+ Negalime surasti SafetyNet
+ Dėl Google Play Services pakeitimų, SafetyNet suradimas perpakuotame Magisk Manager yra neįmanomas
+ Pasiruošimas baigtas
+ Pasiruošimas nesėkmingas
+ Reikalingas papildomas pasiruošimas
+ Kad Magisk veiktų tinkamai, jūsų įrenginiui reikia papildomo paruošimo. Tai atsisiųs papildomą Magisk zip failą, ar norite tęsti?
+ Papildomas pasiruošimas
+ Paruošiama aplinka…
+
+
+ Pagrininiai
+ Tamsi tema
+ Įjungti tamsią temą
+ Išvalyti nereikalingus saugyklos failus
+ Išvalyti patalpintą informaciją talpykloms internete, priverčia perkrauti interneto jungtį
+ Paslėpti Magisk Manager
+ Perpakuoti Magisk Manager su atsitiktiniu pakuotės pavadinimu
+ Grąžinti Magisk Manager
+ Grąžinti Magisk Manager su orginalia pakuote
+ Kalba
+ (Sistemos)
+ Atnaujinimų nustatymai
+ Automatiškas atnaujinimų ieškojimas
+ Automatiškai ieškoti Magisk ir Magisk Manager atnaujinimų
+ Atnaujinimų tipai
+ Stabilūs
+ Beta
+ Pasirinktiniai
+ Įvesti pasirinktinį URL
+ Boot failo formatas
+ Pasirinkti boot failo formatą.\n.img naudojamas įdiegimui per fastboot/download; .img.tar naudojamas įdiegimui per ODIN
+ Pagrindinis Magisk režimas
+ Įgalinti tik pagrindines funkcijas, išjungti visus papildinius. MagiskSU, Magisk Hide ir Sistemos pedejėjai liks įgalinti
+ Paslėpti Magisk nuo įvairių susekimų
+ Sistemos padejėjai
+ Įgalinti sistemos padejėjus Adblock programėlėms
+
+ Programėlėms ir ADB
+ Tik programėlėms
+ Tik ADB
+ Išjungta
+ 10 sekundžių
+ 20 sekundžių
+ 30 sekundžių
+ 60 sekundžių
+ Supervartotojo prieiga
+ Automatinis atsakymas
+ Prašymo laikas baigiasi po
+ Supervartotojo pranešimai
+ %1$s sekundžių
+ Pakartotinai patvirtinti po atnaujinimo
+ Pakartotinai patvirtinti supervartotojo leidimus po programėlės atnaujinimo
+ Įgalinti patvirtinimą piršto antspaudu
+ Naudoti piršto antspaudą supervartotojo leidimo prašymų atsakymui
+
+ Daugialypio vartotojo režimas
+ Tik įrenginio savininkas
+ Įrenginio savininko valdomas
+ Visų
+ Tik įrenginio savininkas turi root prieigą
+ Tik įrenginio savinkas gali tvarkyti root prieigą ir gauti lenteles prašančias root prieigos
+ Kiekvienas vartotojas turi savo atskiras root taisykles
+ Prašymas buvo nusiųstas savininkui. Prašome tapti savininku ir suteikite leidimą
+
+ Root sesijos vardų srities režimas
+ Globali vardų sritis
+ Paveldima vardų sritis
+ Izoliuota vardų sritis
+ Visos root sesijos naudoja globalią vardų sritį
+ Root sesijos paveldi jos išprašytojo/s vardų sritį
+ Kiekviena root sesija turi savo izoliuotą vardų sritį
+ Įrenginiai su Android 8.0+ nepalaiko šio nustatymo
+ Jūsų įrenginyje nebuvo surasta pirštų antspaudų arba jūsų įrenginys neturi pirštų antspaudų skaitytuvo
+
+
+ Supervartotojo prašymas
+ Atmesti%1$s
+ Atmesti
+ Klausti
+ Suteikti(a)
+ Supervartotojo prieiga suteikia pilną prieigą prie jūsų įrenginio\nAtmeskite jei neesate tikri dėl programėlės patikimumo!
+ Visados
+ Vieną kartą
+ 10 min
+ 20 min
+ 30 min
+ 60 min
+ %1$s gavo supervartotojo teises
+ %1$s negavo supervartotojo teisių
+ Nėra surastų programėlių
+ %1$s supervartotojo teisių prašymas buvo atsakytas teigiamai
+ %1$s supervartotojo teisių prašymas buvo atmestas
+ Buvo įjungta %1$s pranešimai
+ Buvo išjungta %1$s pranešimai
+ %1$s įvykių surašymas yra įgalintas
+ %1$s įvykių surašymas yra išjungtas
+ %1$s supervartotojo teisės atimtos
+ Neleisti?
+ Neleisti %1$s naudotis supervartotojo teisėmis?
+ Išmesti
+ Nėra
+ Patvirtinimas žlugo
+
+
+ PID:\u0020
+ Target UID:\u0020
+ Komanda:\u0020
+
+
diff --git a/app/src/full/res/values-nl/strings.xml b/app/src/full/res/values-nl/strings.xml
new file mode 100644
index 000000000..b184ca3b7
--- /dev/null
+++ b/app/src/full/res/values-nl/strings.xml
@@ -0,0 +1,216 @@
+
+
+
+
+ Modules
+ Downloads
+ Superuser
+ Log
+ Instellingen
+ Installeren
+
+
+ Magisk niet geïnstalleerd
+ Controleren op updates…
+ Magisk v%1$s beschikbaar!
+ Ongeldig bijwerkkanaal
+ Tik om SafetyNet controle te starten
+ SafetyNet status controleren…
+ SafetyNet controle succesvol
+ SafetyNet API fout
+ Netwerkverbinding verloren
+ Service is beëindigd
+ Reactie is ongeldig
+
+
+ Geavanceerde instellingen
+ Behoud afgedwongen versleuteling
+ Behoud AVB 2.0/dm-verity
+ Geïnstalleerde versie: %1$s
+ Recentste versie: %1$s
+ Deïnstalleren
+ Magisk deïnstalleren
+ Alle modules worden uitgeschakeld/verwijderd. Root wordt verwijderd, en je data wordt mogelijk versleuteld als deze dat momenteel niet is
+ Bijwerken %1$s
+
+
+ (Geen info verstrekt)
+ Geen modules gevonden
+ Module wordt bijgewerkt bij volgende herstart
+ Module wordt verwijderd bij volgende herstart
+ Module wordt niet verwijderd bij volgende herstart
+ Module wordt uitgeschakeld bij volgende herstart
+ Module wordt ingeschakeld bij volgende herstart
+ Gemaakt door %1$s
+ Herstart naar Recovery
+ Herstart naar Bootloader
+ Herstart naar Download
+
+
+ Update beschikbaar
+ Geïnstalleerd
+ Niet geïnstalleerd
+ Bijgewerkt op: %1$s
+ Sorteervolgorde
+ Sorteren op naam
+ Sorteren op laatste update
+
+
+ Opslaan log
+ Herladen
+ Log nu wissen
+ Log succesvol gewist
+ Log is leeg
+
+
+ Over
+ App\'s changelog
+
+ App\'s versie
+ Broncode
+ Donatie
+ App\'s vertalers
+ Hulp thread
+
+
+ Sluiten
+ %1$s installeren
+ Zeker weten %1$s installeren?
+ Downloaden
+ Herstarten
+ Nieuwe Magisk update beschikbaar!
+ Herstarten om instellingen toe te passen
+ Publicatie notities
+ Opslagcache gewist
+ Verwerkingsfout
+ De zip is opgeslagen in:\n[Interne opslag]%1$s
+ Downloaden
+ Zip-bestand downloaden (%1$d%%) …
+ Verwerken
+ Zip-bestand verwerken…
+ Nieuwe Magisk Manager update beschikbaar!
+ Tik om te downloaden en installeren
+ DTBO is gepatched!
+ Magisk Manager heeft dtbo.img gepatched, herstarten a.u.b.
+ Magisk updates
+ Flashen
+ Magisk Manager verbergen…
+ Dit kan even duren…
+ Magisk Manager verbergen mislukt…
+ Alleen zip downloaden
+ Boot image-bestand patchen
+ Direct installeren (aangeraden)
+ Op tweede positie installeren (na OTA)
+ Methode kiezen
+ Magisk versie ondersteund geen boot image-bestand patchen
+ Kies originele boot image-dump in .img- of .img.tar-formaat
+ Compleet deïnstalleren
+ Images herstellen
+ Herstel voltooid!
+ Originele back-up bestaat niet!
+ Google\'s code downloaden
+ Magisk Manager is FOSS, dus bevat geen SafetyNet API code van Google.\n\nSta je Magisk Manager toe om een extensie te downloaden (bevat GoogleApiClient) voor SafetyNet controles?
+ SU database is corrupt, nieuwe db maken…
+ Kan SafetyNet niet controleren
+ Door wijzigingen in Google Play Services is het niet mogelijk om SafetyNet te controleren met een opnieuw ingepakte Magisk Manager
+
+
+ Algemeen
+ Donker thema
+ Donker thema inschakelen
+ Opslagcache wissen
+ Wis de gecachte informatie voor online opslagplaatsen. Dit dwingt de app om online te verversen
+ Magisk Manager verbergen
+ Magisk Manager opnieuw inpakken met een willekeurige pakketnaam
+ Magisk Manager herstellen
+ Magisk Manager met oorspronkelijk pakket herstellen
+ Taal
+ (Systeem standaard)
+ Instellingen bijwerken
+ Updates controleren
+ Periodiek op updates controleren in de achtergrond
+ Update-kanaal
+ Stabiel
+ Beta
+ Aangepast
+ Aangepaste URL invoeren
+ Gepatchte boot uitvoerformaat
+ Kies het formaat van de boot image uitvoer.\nKies .img om via fastboot/downloadmodus te flashen; kies .img.tar om via ODIN te flashen.
+ Magisk basismodus
+ Alleen kernfuncties inschakelen. Alle modules worden niet geladen. MagiskSU, MagiskHide, en systeemloze hosts blijven ingeschakeld
+ Magisk van verschillende detecties verbergen
+ Systeemloze hosts
+ Systemloze hosts ondersteuning voor Adblock apps
+
+ Apps en ADB
+ Alleen apps
+ Alleen ADB
+ Uitgeschakeld
+ 10 seconden
+ 20 seconden
+ 30 seconden
+ 60 seconden
+ Superuser toegang
+ Automatisch antwoord
+ Verzoek time-out
+ Superuser melding
+ %1$s seconden
+ Opnieuw verzoeken na bijwerken
+ Superuser rechten opnieuw opvragen na bijwerken applicatie
+ Vingerafdruk authenticatie inschakelen
+ Vingerafdruk gebruiken om superuser verzoeken toe te staan
+
+ Multi-gebruiker modus
+ Alleen apparaateigenaar
+ Beheerd door apparaateigenaar
+ Gebruiker onafhankelijk
+ Alleen eigenaar heeft roottoegang
+ Alleen eigenaar kan roottoegang beheren en prompt verzoeken ontvangen
+ Iedere gebruiker heeft eigen afzonderlijke rootregels
+ Een verzoek is naar de apparaateigenaar verstuurd. Wissel van gebruiker en sta de rechten toe
+
+ Naamruimte-mount modus
+ Globale naamruimte
+ Verkrijg naamruimte
+ Geïsoleerde naamruimte
+ Alle rootsessies gebruiken de globale naamruimte
+ Rootsessies verkrijgen de verzoeker\'s naamruimte
+ Iedere rootsessie heeft een eigen geïsoleerde naamruimte
+ Ondersteunt geen Android 8.0+
+ Geen vingerafdrukken ingesteld, of geen apparaatondersteuning
+
+
+ Superuser verzoek
+ Weigeren %1$s
+ Weigeren
+ Prompt
+ Toestaan
+ Verleent volledige toegang tot je apparaat.\nWeigeren als je het niet zeker weet!
+ Altijd
+ Eénmalig
+ 10 min
+ 20 min
+ 30 min
+ 60 min
+ Superuser rechten voor %1$s toegestaan
+ Superuser rechten voor %1$s geweigerd
+ Geen apps gevonden
+ Superuser rechten van %1$s toegestaan
+ Superuser rechten van %1$s geweigerd
+ Meldingen van %1$s ingeschakeld
+ Meldingen van %1$s uitgeschakeld
+ Loggen van %1$s ingeschakeld
+ Loggen of %1$s uitgeschakeld
+ %1$s rechten zijn ingetrokken
+ Intrekken?
+ De rechten van %1$s intrekken?
+ Toast
+ Geen
+ Authenticatie mislukt
+
+
+ PID:\u0020
+ Doel UID:\u0020
+ Opdracht:\u0020
+
+
diff --git a/app/src/full/res/values-pl/strings.xml b/app/src/full/res/values-pl/strings.xml
new file mode 100644
index 000000000..7d05f3293
--- /dev/null
+++ b/app/src/full/res/values-pl/strings.xml
@@ -0,0 +1,219 @@
+
+
+
+ Moduły
+ Pobieranie
+ Superuser
+ Log
+ Ustawienia
+ Instalacja
+
+
+ Magisk nie jest zainstalowany
+ Sprawdzanie aktualizacji…
+ Magisk v%1$s dostępny!
+ Nieprawidłowy Kanał Aktualizacji
+ Dotknij aby sprawdzić SafetyNet
+ Sprawdzanie statusu SafetyNet…
+ SafetyNet Poprawny
+ SafetyNet Błędny
+ Brak połączenia z internetem
+ Usługa została zamknięta
+ Usługa nie odpowiada
+
+
+ Zaawansowane Ustawienia
+ Zachowaj force encryption
+ Zachowaj AVB 2.0/dm-verity
+ Zainstalowana Wersja: %1$s
+ Ostatnia Wersja: %1$s
+ Odinstaluj
+ Odinstaluj Magisk
+ Wszystkie moduły będą wyłączone/usunięte. Root zostanie usunięty i przywrócone szyfrowanie danych, jeśli nie są te dane obecnie szyfrowane
+ Aktualizacja %1$s
+
+
+ (Nie umieszczono informacji)
+ Nie znaleziono modułów
+ Moduł zostanie zaktualizowany przy następnym restarcie
+ Moduł zostanie usunięty przy następnym uruchomieniu
+ Moduł nie zostanie usunięty podczas następnego restartu
+ Moduł zostanie wyłączony przy następnym restarcie
+ Moduł zostanie włączony przy następnym restarcie
+ Autor: %1$s
+ Restart do Recovery
+ Restart do Bootloadera
+ Restart do Download
+
+
+ Aktualizacja jest dostępna
+ Zainstalowany
+ Nie zainstalowany
+ Zaktualizowano: %1$s
+ Kolejność Sortowania
+ Sortuj po nazwie
+ Sortuj po ostatniej aktualizacji
+
+
+ Zapisz log
+ Załaduj ponownie
+ Wyczyść Log
+ Log wyczyszczony
+ Log jest pusty
+
+
+ O Aplikacji
+ Zmiany w Aplikacji
+
+ Wersja Aplikacji
+ Kod Źródłowy
+ Dotacja
+ Tłumacze Aplikacji
+ Strona Wsparcia
+
+
+ Zamknij
+ Zainstaluj %1$s
+ Czy chcesz zainstalować %1$s ?
+ Pobierz
+ Restart
+ Nowa Wersja Magisk Dostępna!
+ Uruchom ponownie, aby zastosować ustawienia
+ Zmiany
+ Cache repozytorium wyczyszczone
+ Błąd procesu
+ Zip jest przechowywany w:\n[Pamięć Wewnętrzna]%1$s
+ Pobieranie
+ Pobieranie pliku zip (%1$d%%) …
+ Przetwarzanie
+ Przetwarzanie pliku zip …
+ Nowa Wersja Magisk Manager Jest Dostępna!
+ Naciśnij aby pobrać i zainstalować
+ DTBO został wgrany!
+ Magisk Manager wgrał dtbo.img, uruchom ponownie
+ Aktualizacja Magisk
+ Flashowanie
+ Ukryj Magisk Manager…
+ To może chwilę potrwać…
+ Błąd Ukrycia Magisk Managera
+ Pobierz Tylko Zip
+ Patchowanie Pliku Boot Image
+ Bezpośrednia instalacja (Zalecane)
+ Zainstaluj na Drugim Slocie (Po OTA)
+ Wybierz Metodę
+ Wersja docelowa programu Magisk nie obsługuje ładowania pliku boot image
+ Wybierz stock boot image w formacie .img lub .img.tar
+ Odinstalowywanie Zakończone
+ Przywróć Obraz
+ Przywracanie zakończone!
+ Stock backup nie istnieje!
+ Pobierz Kod
+ Magisk Manager to FOSS, więc nie zawiera zastrzeżonego kodu API SafetyNet. \n\nCzy zezwolić Magisk Managerowi na pobranie rozszerzenia (zawierającego GoogleApiClient) do sprawdzenia SafetyNet?
+ Baza danych SU jest uszkodzona, odtworzy nową bazę danych
+ Nie mogę sprawdzić SafetyNet
+ Z powodu pewnych zmian w Usługach Google Play nie jest możliwe sprawdzenie SafetyNet w przepakowanym Magisk Managerze
+ Konfiguracja gotowa
+ Konfiguracja nieudana
+ Wymaga Dodatkowej Konfiguracji
+ Twoje urządzenie potrzebuje dodatkowej konfiguracji, aby Magisk działał prawidłowo. Spowoduje to pobranie pliku instalacyjnego Magisk, czy chcesz kontynuować?
+
+
+ Ogólne
+ Ciemny Motyw
+ Włącz ciemny motyw
+ Wyczyść Pamięć Repozytorium
+ Wymusza na aplikacji odświeżenie online repozytorium
+ Ukryj Magisk Manager
+ Przepakowanie Magisk Manager z losową nazwą pakietu
+ Przywróć Magisk Manager
+ Przywróć oryginalną paczkę Magisk Manager
+ Język
+ (Domyślny Systemu)
+ Aktualizacja Ustawień
+ Sprawdź Aktualizację
+ Regularnie sprawdzaj aktualizacje w tle
+ Kanał Aktualizacji
+ Stabilny
+ Beta
+ Własny
+ Wstaw własny URL
+ Poprawny format pliku rozruchowego
+ Wybierz format pliku boot image.\nWybierz .img dla wgrywania poprzez Fastboot/Download Mode. Wybierz .img.tar dla ODINA.
+ Tylko Podstawowy Tryb Magisk
+ Włącz tylko podstawowe funkcje, wszystkie moduły nie zostaną załadowane. MagiskSU, MagiskHide i systemless hosts nadal będą włączone
+ Włącz Magisk Hide dla wykrytych aplikacji
+ Włącz systemless hosts
+ Wsparcie systemless dla aplikacji Adblock
+
+ Aplikacje i ADB
+ Tylko Aplikacje
+ Tylko ADB
+ Wyłączone
+ 10 sekund
+ 20 sekund
+ 30 sekund
+ 60 sekund
+ Dostęp Superuser
+ Automatyczna Odpowiedź
+ Czas na decyzję
+ Powiadomienia Superusera
+ %1$s sekund
+ Ponowienie uwierzytelnienia po aktualizacji
+ Ponowne uwierzytelnianie uprawnienia superużytkownika po aktualizacji aplikacji
+ Włącz Uwierzytelnienie Odciskiem Palca
+ Użyj skanera linii papilarnych, aby zezwolić na żądania supersu
+
+ Tryb Multiusera
+ Tylko Właściciel Urządzenia
+ Zarządzanie Właścicielami Urządzenia
+ Niezależny Użytkownik
+ Tylko właściciel ma dostęp do roota
+ Tylko właściciel może zarządzać prawami dostępu użytkownika do roota iwyświetlać żądania dostępu
+ Każdy użytkownik ma swoje własne odrębne ustawienia dostępu
+ Żądanie zostało wysłane do właściciela urządzenia. Proszę przejść do właściciela i udzielić pozwolenia
+
+ Montuj Tryb Odstępu Nazw
+ Globalna Nazwa
+ Dziedziczenie Nazw
+ Izolacja Nazw
+ Wszystkie sesje root za pomocą globalnej przestrzeni montowań nazw
+ Sesje Root będzie dziedziczyć prośby i nazwy
+ W każdej sesji root będzie miał własną odosobnioną nazwę
+ Brak wsparcia dla Androida 8.0+
+ Nie ustawiono żadnych odcisków palców lub brak obsługi urządzenia
+
+
+ Prośba o dostęp Superusera
+ Odmów%1$s
+ Odmów
+ Zapytaj
+ Przyznaj
+ Udziela pełnego dostępu do urządzenia.\nOdmów jeśli nie jesteś pewien!
+ Zawsze
+ Raz
+ 10 min
+ 20 min
+ 30 min
+ 60 min
+ %1$s ma przyznane uprawnienia Superusera
+ %1$s ma odmówione uprawnienia Superusera
+ Nie znaleziono aplikacji
+ Przyznano uprawnienia Superuser dla %1$s
+ Odmówiono uprawnień Superuser dla %1$s
+ Powiadomienia dla %1$s są włączone
+ Powiadomienia dla %1$s są wyłączone
+ Logowanie dla %1$s jest włączone
+ Logowanie dla %1$s jest wyłączone
+ %1$s uprawnienia są odwołane
+ Odwołać?
+ Potwierdzasz odwołanie uprawnień %1$s?
+ Powiadomienie
+ Brak
+ Uwierzytelnienie Nieudane
+
+
+ PID:\u0020
+ Identyfikator UID:\u0020
+ Komenda:\u0020
+
+
diff --git a/app/src/full/res/values-pt-rBR/strings.xml b/app/src/full/res/values-pt-rBR/strings.xml
new file mode 100644
index 000000000..722b008fa
--- /dev/null
+++ b/app/src/full/res/values-pt-rBR/strings.xml
@@ -0,0 +1,214 @@
+
+
+
+
+ Módulos
+ Downloads
+ Superusuário
+ Registro
+ Definições
+ Instalar
+
+
+ Magisk não está instalado
+ Verificando por atualizações…
+ Magisk v%1$s está disponível!
+ Canal de Atualização Inválido
+ Verificar SafetyNet
+ Verificando status de SafetyNet…
+ SafetyNet Verificado Com Sucesso
+ Erro de SafetyNet API
+ Conexão de rede indisponível
+ Serviço foi morto
+ A resposta é inválida
+
+
+ Definições Avançadas
+ Preservar criptografia forçada
+ Preservar AVB 2.0/dm-verity
+ Versão Instalada: %1$s
+ Última Versão: %1$s
+ Desinstalar
+ Desinstalar Magisk
+ Todos os módulos serão desativados/removidos. O root será removido, e potencialmente criptografará seus dados se seus dados não estiverem atualmente criptografados
+ Atualizar %1$s
+
+
+ (Nenhuma informação fornecida)
+ Nenhum módulo encontrado
+ Módulo será atualizado na próxima reinicialização
+ Módulo será removido na próxima reinicialização
+ Módulo não será removido na próxima reinicialização
+ Módulo será desativado na próxima reinicialização
+ Módulo será ativado na próxima reinicialização
+ Criado por %1$s
+ Reiniciar na Recuperação
+ Reiniciar no Bootloader
+ Reiniciar no Download
+
+
+ Atualização Disponível
+ Instalado
+ Não Instalado
+ Atualizado em: %1$s
+ Ordenar
+ Ordenar por nome
+ Ordenar por última atualização
+
+
+ Salvar registro
+ Recarregar
+ Limpar registro agora
+ Registro limpo com sucesso
+ Registro está vazio
+
+
+ Sobre
+ Registro de mudanças
+
+ Versão
+ Código fonte
+ Doação
+ Tradutores
+ Tópico de suporte
+
+
+ Fechar
+ Instalar %1$s
+ Instalar %1$s agora?
+ Baixar
+ Reiniciar
+ Nova Atualização do Magisk Disponível!
+ Reinicie para aplicar as definições
+ Notas de lançamento
+ Cache de repositório limpado
+ Erro de processo
+ O zip está armazenado em:\n[Armazenamento Interno]%1$s
+ Baixando
+ Baixando arquivo zip (%1$d%%) …
+ Processando
+ Processando arquivo zip …
+ Nova Atualização do Magisk Manager Disponível!
+ Toque para baixar e instalar
+ DTBO foi emendado!
+ Magisk Manager emendou dtbo.img, reinicie
+ Atualizações do Magisk
+ Gravando
+ Ocultando Magisk Manager…
+ Isto pode demorar um pouco…
+ Falha ao ocultar Magisk Manager…
+ Baixar Zip Apenas
+ Emendar Arquivo de Imagem de Inicialização
+ Instalação Direta (Recomendado)
+ Instalar no Segundo Slot (Após OTA)
+ Selecionar Método
+ A versão alvo do Magisk não suporta emendamento de imagem de inicialização
+ Selecionar depósito de imagem de inicialização de fábrica no formato .img ou .img.tar
+ Completar Desinstalação
+ Restaurar Imagens
+ Restauração concluída!
+ Backup de fábrica não existe!
+ Baixar Código de Propriedade
+ Magisk Manager é FOSS, que não contém o código de propriedade de SafetyNet API da Google.\n\nPermitir ao Magisk Manager baixar uma extensão (que contém a GoogleApiClient) para verificação de SafetyNet?
+ Banco de dados do SU está corrompido, será recriado novo db
+
+
+ Geral
+ Tema Escuro
+ Ativar tema escuro
+ Limpar Cache de Repositório
+ Limpar as informações no cache para repositórios onlines, força o app a reatualizar online
+ Ocultar Magisk Manager
+ Reempacotar Magisk Manager com nome de pacote aleatório
+ Restaurar Magisk Manager
+ Restaurar Magisk Manager com pacote original
+ Idioma
+ (Padrão do Sistema)
+ Definições de Atualizações
+ Verificar Atualizações
+ Verificar atualizações em segundo plano periodicamente
+ Canal de Atualização
+ Estável
+ Beta
+ Personalizado
+ Inserir uma URL personalizada
+ Formato de Saída de Inicialização Emendada
+ Selecionar formato de saída de imagem de inicialização emendada.\nEscolha .img para gravar através do modo fastboot/download; escolha .img.tar para gravar com ODIN.
+ Modo Magisk de Núcleo Apenas
+ Ativar apenas recursos de núcleo. MagiskSU, MagiskHide e hosts sem sistema ainda serão ativados, mas nenhum módulo será carregado.
+ Ocultar Magisk de várias detecções
+ Hosts sem sistema
+ Suporte de hosts sem sistema para apps de Adblock
+
+ Apps e ADB
+ Apps apenas
+ ADB apenas
+ Desativado
+ 10 segundos
+ 20 segundos
+ 30 segundos
+ 60 segundos
+ Acesso de Superusuário
+ Resposta Automática
+ Tempo Limite de Solicitação
+ Notificação de Superusuário
+ %1$s segundos
+ Reautenticar após atualizar
+ Reautenticar permissões de superusuário após um app atualizar
+ Ativar Autenticação de Impressão Digital
+ Usar escaneador de impressão digital para permitir solicitações de superusuário
+
+ Modo de Multiusuário
+ Proprietário do Dispositivo Apenas
+ Proprietário do Dispositivo Gerenciado
+ Usuário Independente
+ Apenas proprietário tem acesso root
+ Apenas proprietário pode gerenciar acesso root e receber expedições de solicitações
+ Cada usuário tem suas próprias regras root separadas
+ Uma solicitação foi enviada ao proprietário do dispositivo. Mude para proprietário e conceda as permissões requeridas
+
+ Montar Modo de Espaço de Nome
+ Espaço de Nome Global
+ Espaço de Nome Herdado
+ Espaço de Nome Isolado
+ Todas as sessões root usam montagem de espaço de nome global
+ As sessões root herdarão espaço de nome de seu solicitante
+ Cada sessão root terá seu próprio espaço de nome isolado
+ Não suporta Android 8.0+
+ Nenhuma impressão digital foi definida ou dispostivo sem suporte
+
+
+ Solicitação de Superusuário
+ Negar%1$s
+ Negar
+ Solicitar
+ Conceder
+ Concede acesso total ao seu dispositivo.\nNegue se você não tem certeza!
+ Sempre
+ Uma Vez
+ 10 mins
+ 20 mins
+ 30 mins
+ 60 mins
+ %1$s teve concedido direitos de Superusuário
+ %1$s teve negado direitos de Superusuário
+ Nenhum app encontrado
+ Direitos de Superusuário de %1$s estão concedidos
+ Direitos de Superusuário de %1$s estão negados
+ Notificações de %1$s estão ativadas
+ Notificações de %1$s estão desativadas
+ Registração de %1$s está ativada
+ Registração de %1$s está desativada
+ Direitos de %1$s estão revogados
+ Revogar?
+ Revogar os direitos de %1$s?
+ Torrada
+ Nenhuma
+ Falha de Autenticação
+
+
+ PID:\u0020
+ Alvo UID:\u0020
+ Comando:\u0020
+
+
diff --git a/app/src/full/res/values-pt-rPT/strings.xml b/app/src/full/res/values-pt-rPT/strings.xml
new file mode 100644
index 000000000..7157aacb3
--- /dev/null
+++ b/app/src/full/res/values-pt-rPT/strings.xml
@@ -0,0 +1,179 @@
+
+
+
+
+ Módulos
+ Transferir
+ root
+ Registo
+ Definições
+ Instalar
+
+
+ Magisk não instalado
+ A procurar por atualizações…
+ Magisk v%1$s disponível!
+ Pressione para verificar SafetyNet
+ A verificar o estado do SafetyNet…
+ SafetyNet verificado com sucesso
+ Erro na API SafetyNet
+ Perda de ligação
+ Serviço foi interrompido
+ A resposta é inválida
+
+
+ Definições avançadas
+ Manter encriptação forçada
+ Manter AVB 2.0/dm-verity
+ Versão instalada: %1$s
+ Última versão: %1$s
+ Desinstalar
+ Desinstalar Magisk
+ Atualizar %1$s
+
+
+ (Nenhuma informação fornecida)
+ Nenhum módulo encontrado
+ Módulo será atualizado depois de reiniciar
+ Módulo será removido depois de reiniciar
+ Módulo não será removido ao reiniciar
+ Módulo será desativado depois de reiniciar
+ Módulo será ativado depois de reiniciar
+ Criado por %1$s
+
+
+ Atualização disponível
+ Instalado
+ Não Instalado
+
+
+ Gravar registo
+ Recarregar
+ Limpar registo agora
+ Registo limpo com sucesso
+ Registo está vazio
+
+
+ Sobre
+ Lista de alterações da aplicação
+ Versão do aplicação
+ Código fonte
+ Doar
+ Tradutores da Aplicação
+ Suporte
+
+
+ Fechar
+ Instalar %1$s
+ Deseja instalar%1$s agora?
+ Transferir
+ Reiniciar
+ Nova atualização do Magisk disponível!
+ Reinicie para aplicar definições
+ Notas da atualização
+ Cache do repositório apagado
+ Erro no processo
+ O zip foi guardado em:
+\n[Armazenamento interno]%1$s
+ A transferir ficheiro zip (%1$d%%) …
+ A processar
+ A processar ficheiro zip …
+ Nova atualização do Magisk Manager disponível!
+ Pressione para transferir e instalar
+ Atualizações do Magisk
+ A instalar
+ A esconder Magisk Manager…
+ Isto pode demorar algum tempo...
+ Falha ao esconder Magisk Manager…
+ Transferir Apenas Ficheiro Zip
+ Patch a Imagem de Arranque
+ Instalar Diretamente (Recomendado)
+ Selecionar Método
+ Desinstalação Completa
+ Restauração feita!
+
+
+ Geral
+ Tema escuro
+ Ativa o tema escuro
+ Apagar Cache de Repositório
+ Apaga a informação cache de repositórios online. online, forçando a aplicação a atualizar online
+ Esconder Magisk Manager
+ Língua
+ (Padrão do Sistema)
+ Estável
+ Beta
+ Magisk somente em Modo Core
+ Ativar somente funcionalidades principais, todos os módulos não serão carregados. MagiskSU, MagiskHide, e systemless hosts ainda estará ativado
+ Oculta Magisk de várias deteções
+ Ativar systemless hosts
+ Suporte de systemless para aplicações Adblock
+
+ Aplicações e ADB
+ Somente Aplicações
+ Somente ADB
+ Desativado
+ 10 segundos
+ 20 segundos
+ 30 segundos
+ 60 segundos
+ Acesso de root
+ Resposta Automática
+ Tempo limite de solicitação
+ Notificação de root
+ %1$s segundos
+ Autenticar novamente após atualizar
+ Autenticar novamente permissões de root após atualizar aplicações
+
+ Modo Multi-Utilizador
+ Apenas Proprietário de Dispositivo
+ Gerido Proprietário do Dispositivo
+ Independente de Utilizadores
+ Apenas o proprietário tem acesso a root
+ Apenas o proprietário pode gerir acesso root e receber pedidos
+ Cada utilizador tem suas próprias regras de root separadas
+ Um pedido foi enviado ao proprietário do dispositivo. Mude para o proprietário e conceda a permissão
+
+ Cada sessão root terá sua própria identificação isolada
+ Identificação Global
+ Herdar Identificação
+ Identificação Isolada
+ Todas as sessões root usam a identificação de montagem global
+ As sessões de root herdarão a identificação do seu solicitante
+ Cada sessão root terá sua própria identificação isolada
+
+
+ Pedido de root
+ Negar%1$s
+ Negar
+ Perguntar
+ Permitir
+ Concede acesso total ao seu dispositivo.
+\nNegue se não tiver certeza!
+ Sempre
+ Uma vez
+ 10 minutos
+ 20 minutos
+ 30 minutos
+ 60 minutos
+ %1$s foi permitido o acesso de root
+ %1$s foi negado o acesso de root
+ Não foram encontrados aplicações
+ Acesso de root a %1$s foi permitido
+ Acesso de root a %1$s foi negado
+ Notificações de %1$s está ativado
+ Notificações da %1$s está desativado
+ Registo de %1$s está ativado
+ Registo de %1$s está desativado
+ %1$s direitos foram revogados
+ Revogar?
+ Revogar os diretos do %1$s, Confirmar?
+ Notificação(pop-up)
+ Nenhum
+
+
+ PID:\u0020
+ Alvo UID:\u0020
+ Comando:\u0020
+
+
diff --git a/app/src/full/res/values-ro/strings.xml b/app/src/full/res/values-ro/strings.xml
new file mode 100644
index 000000000..7d20171c2
--- /dev/null
+++ b/app/src/full/res/values-ro/strings.xml
@@ -0,0 +1,207 @@
+
+
+ Module
+ Descărcări
+ Superuser
+ Jurnal
+ Setări
+ Instalare
+
+
+ Magisk nu este instalat
+ Verificare actualizări…
+ Magisk v%1$s disponibil!
+ Canal de actualizare nevalid
+ Verificare SafetyNet
+ Verificarea stării SafetyNet…
+ Verificare SafetyNet cu succes
+ Eroare API SafetyNet
+ Conexiunea la rețea nu este disponibilă
+ Serviciul a fost oprit
+
+
+ Setări avansate
+ Păstrare criptare forţată
+ Păstrare AVB 2.0/dm-verity
+ Versiune instalată: %1$s
+ Ultima versiune: %1$s
+ Dezinstalare
+ Dezinstalare Magisk
+ Toate modulele vor fi dezactivate/eliminate. Accesul la Root va fi eliminat și potențial se vor cripta datele dacă nu sunt în prezent criptate
+ Actualizare %1$s
+
+
+
+ (Nu sunt furnizate informații)
+ Niciun modul
+ Modulul va fi actualizat la următoarea repornire
+ Modulul va fi eliminat la următoarea repornire
+ Modulul nu va fi eliminat la următoarea repornire
+ Modulul va fi dezactivat la următoarea repornire
+ Modulul va fi activat la următoarea repornire
+ Creat de %1$s
+ Repornire în Recovery
+ Repornire în Bootloader
+ Repornire în Download
+
+
+ Actualizare disponibilă
+ Instalat
+ Nu este instalat
+
+
+ Salvare jurnal
+ Reîncărcare
+ Șterge jurnal acum
+ Jurnal şters
+ Jurnal gol
+
+
+ Despre
+ Lista modificărilor
+
+ Versiune
+ Cod sursă
+ Donaţie
+ Traducători
+ Pagina de suport
+
+
+ Închide
+ Instalare %1$s
+ Instalaţi %1$s?
+ Descărcare
+ Repornire
+ O nouă actualizare Magisk este disponibilă!
+ Reporniți pentru a aplica setările
+ Note de lansare
+ Arhive cache eliminate
+ Eroare de proces
+ Zip stocat în:\n[Stocare internă]%1$s
+ Descărcare
+ Descărcare fişier zip (%1$d%%)…
+ Procesare
+ Procesare fişier zip…
+ O nouă actualizare pentru Magisk Manager este disponibilă!
+ Apăsați pentru a descărca și instala
+ DTBO a fost modificat!
+ Magisk Manager a modificat dtbo.img, reporniţi
+ Actualizări Magisk
+ Flashing
+ Ascundere Magisk Manager…
+ Ar putea dura ceva timp…
+ Ascunderea Magisk Manager a eşuat
+ Doar descărcare fişier Zip
+ Modificare fişier imagine Boot
+ Instalare directă (Recomandat)
+ Instalare în slotul secund (După OTA)
+ Selectaţi metoda
+ Versiunea Magisk nu acceptă modificarea imaginii de Boot
+ Selectaţi formatul .img sau .img.tar pentru imaginea de Boot stoc
+ Dezinstalare totală
+ Restabilire finalizată!
+ Backup stoc nu există!
+ Descărcare cod proprietar
+ Magisk Manager este FOSS așa că nu conține codul API SafetyNet de la Google.\n\nPermiteți ca Magisk Manager să descarce o extensie (conține GoogleApiClient) pentru verificările SafetyNet?
+ Baza de date SU este defectă, va fi creată o bază nouă
+
+
+ General
+ Temă întunecată
+ Activare temă întunecată
+ Eliminare cache
+ Ștergeți informațiile memorate în cache, forțează actualizarea aplicației online
+ Ascundeţi Magisk Manager
+ Reîmpachetare Magisk Manager cu nume de pachet aleatoriu
+ Restabilire Magisk Manager
+ Restabilire Magisk Manager cu pachetul original
+ Limbă
+ (Implicit)
+ Setări actualizare
+ Verificare actualizări
+ Se verifică periodic în fundal dacă există actualizări
+ Canal de actualizare
+ Stabil
+ Beta
+ Personalizat
+ Introduceți un URL personalizat
+ Formatul Boot-ului modificat
+ Selectați formatul fișierului imaginii de Boot modificată.\nAlegeţi .img pentru flash prin modul fastboot/download; alegeţi .img.tar pentru flash prin ODIN.
+ Mod de bază
+ Se activează numai caracteristicile principale, toate modulele nu vor fi încărcate. MagiskSU, MagiskHide şi systemless hosts vor fi în continuare activate
+ Ascundeţi Magisk de la diferite detectări
+ Systemless hosts
+ Systemless hosts suport pentru aplicațiile Adblock
+
+ Aplicaţii şi ADB
+ Doar aplicaţii
+ Doar ADB
+ Dezactivat
+ 10 secunde
+ 20 de secunde
+ 30 de secunde
+ 60 de secunde
+ Acces Superuser
+ Răspuns automat
+ Expirare
+ Notificare Superuser
+ %1$s secunde
+ Reautentificare după actualizare
+ Reautentificare permisiuni pentru superuser după o actualizare a aplicației
+ Activați autentificarea cu amprenta digitală
+ Utilizați scanerul de amprentă pentru a permite solicitările superuser
+
+ Mod Multiutilizator
+ Numai proprietarul dispozitivului
+ Gestionat de proprietarul dispozitivului
+ Utilizator independent
+ Numai proprietarul are acces la root
+ Numai proprietarul poate gestiona accesul la root și să primească solicitări
+ Fiecare utilizator are propriile reguli de acces la root
+ A fost trimisă o solicitare proprietarului dispozitivului. Comutaţi la proprietar și acordați permisiunea
+
+ Mod montare spaţiu de nume
+ Spaţiu de nume global
+ Spaţiu de nume moştenit
+ Spațiu de nume izolat
+ Toate sesiunile de root utilizează spațiul de nume global
+ Sesiunile de root vor moșteni spațiul de nume al solicitantului
+ Fiecare sesiune de root va avea propriul spațiu de nume izolat
+ Nu se acceptă pe Android 8.0+
+ Nu au fost setate amprente sau scanerul de apmrentă lipseşte
+
+
+ Solicitare Superuser
+ Refuzaţi %1$s
+ Refuzaţi
+ Solicitare
+ Permiteţi
+ Acordă acces complet la dispozitivul dvs..\nRefuzaţi dacă nu sunteţi sigur!
+ Pentru totdeauna
+ O singura dată
+ 10 min
+ 20 min
+ 30 min
+ 60 min
+ %1$s i-au fost acordate drepturi Superuser
+ %1$s i-au fost refuzate drepturi Superuser
+ Nicio aplicaţie
+ Drepturile Superuser pentru %1$s au fost acordate
+ Drepturile Superuser pentru %1$s au fost refuzate
+ Notificările pentru %1$s au fost acordate
+ Notificările pentru %1$s au fost refuzate
+ Logarea pentru %1$s a fost activată
+ Logarea pentru %1$s a fost dezactivată
+ Drepturile pentru %1$s sunt revocate
+ Revocaţi?
+ Confirmați revocarea drepturilor pentru %1$s?
+ Mesaj
+ Nimic
+ Autentificare eşuată
+
+
+ PID:\u0020
+ UID:\u0020
+ Comandă:\u0020
+
+
diff --git a/app/src/full/res/values-ru/strings.xml b/app/src/full/res/values-ru/strings.xml
new file mode 100644
index 000000000..312b9fa2b
--- /dev/null
+++ b/app/src/full/res/values-ru/strings.xml
@@ -0,0 +1,222 @@
+
+
+
+ Модули
+ Репозиторий
+ Суперпользователь
+ Логи
+ Настройки
+ Установка
+
+
+ Magisk не установлен
+ Проверка обновлений…
+ Доступен Magisk v%1$s!
+ Некорректный канал обновлений
+ Проверить статус SafetyNet
+ Проверка статуса SafetyNet…
+ Результат проверки SafetyNet
+ Ошибка SafetyNet API
+ Нет подключения к сети
+ Служба была остановлена
+ Некорректный ответ
+
+
+ Расширенные опции
+ Сохранить шифрование
+ Сохранить AVB 2.0/dm-verity
+ Установлена версия: %1$s
+ Последняя версия: %1$s
+ Удаление
+ Удаление Magisk
+ Все модули будут отключены/удалены. Root-права будут удалены. Шифрование будет активировано.
+ Обновить %1$s
+
+
+ (Нет информации)
+ Модули не найдены
+ Модуль будет обновлен после перезагрузки
+ Модуль будет удален после перезагрузки
+ Модуль не будет удален после перезагрузки
+ Модуль будет отключен после перезагрузки
+ Модуль будет активирован после перезагрузки
+ Автор: %1$s
+ Перезагрузка в Recovery
+ Перезагрузка в Bootloader
+ Перезагрузка в Download
+
+
+ Доступно обновление
+ Установлены
+ Не установлены
+ Обновлено: %1$s
+ Сортировка
+ По имени
+ По дате обновления
+
+
+ Сохранить лог
+ Обновить
+ Очистить лог
+ Лог успешно очищен
+ Лог пуст
+
+
+ О приложении
+ Список изменений
+ Displax [4PDA]
+ Версия
+ Исходный код
+ Поддержать проект
+ Переводчики
+ Домашняя страница
+
+
+ Закрыть
+ Установка %1$s
+ Установить %1$s ?
+ Скачать
+ Перезагрузка
+ Доступно обновление Magisk!
+ Для применения настроек перезагрузите устройство
+ О версии
+ Кэш репозитория очищен
+ Ошибка обработки
+ Расположение архива:\n[Внутреннее Хранилище]%1$s
+ Загрузка
+ Загрузка архива (%1$d%%) …
+ Обработка
+ Обработка архива…
+ Доступно обновление Magisk Manager!
+ Нажмите для установки
+ DTBO пропатчен!
+ Magisk Manager пропатчил dtbo.img. Перезагрузите устройство.
+ Обновления Magisk
+ Прошивка…
+ Скрытие Magisk Manager…
+ Может занять некоторое время…
+ Скрытие Magisk Manager неудачно!
+ Загрузка установочного ZIP
+ Пропатчить образ ядра (boot.img)
+ Прямая установка (Рекомендуется)
+ Установка во Второй Слот (После OTA)
+ Выбор способа
+ Целевая версия Magisk не поддерживает патчинг boot-образов
+ Выберите файл ядра (boot) - *.img или *.img.tar
+ Полное удаление
+ Восстановить разделы
+ Восстановление…
+ Восстановление завершено!
+ Резервная копия отсутствует!
+ Загрузка SafetyNet
+ Magisk Manager — свободно распространяемый продукт, он не содержит собственный код SafetyNet API от Google.\n\nРазрешить Magisk Manager загрузить расширение для проверки SafetyNet? (содержит GoogleApiClient)
+ База данных SU повреждена, будет создана новая
+ Невозможно проверить SafetyNet
+ В связи с изменениями в сервисах Google Play, невозможно выполнить проверку статуса SafetyNet с пересобранным Magisk Manager
+ Установка завершена
+ Ошибка установки
+ Требуется дополнительная установка
+ Вашему устройству требуется дополнительная установка Magisk для корректной работы. Будет загружен установочный ZIP Magisk, продолжить?
+ Дополнительная установка
+ Настройка рабочей среды…
+
+
+ Основные
+ Тёмная тема
+ Включить темное оформление
+ Очистка кэша репозитория
+ Очистить сохранённую информацию о репозитории. Будет обновлено принудительно
+ Скрытие Magisk Manager
+ Пересобрать Magisk Manager со случайным именем пакета
+ Восстановление Magisk Manager
+ Восстановить Magisk Manager с исходным именем пакета
+ Язык
+ По умолчанию (Системный)
+ Настройки обновлений
+ Проверка обновлений
+ Периодически проверять обновления в фоне
+ Источник обновлений
+ Стабильный канал
+ Beta канал
+ Сторонний канал
+ Укажите ссылку
+ Тип образа ядра
+ Выберите тип патченого образа ядра:\n*.img - для прошивки через fastboot/download режимы\n*.img.tar - для прошивки через ODIN (Samsung)
+ Magisk Core режим
+ Включить только основные (Core) возможности. Модули не будут загружены. MagiskSU, MagiskHide и внесистемные хосты останутся активными
+ Скрывать Magisk от различных проверок
+ Внесистемные хосты
+ Поддержка внесистемных хостов для приложений, блокирующих рекламу
+
+ Приложения и ADB
+ Только приложения
+ Только ADB
+ Отключен
+ 10 секунд
+ 20 секунд
+ 30 секунд
+ 60 секунд
+ Уровень доступа
+ Автоматический ответ
+ Ожидание ответа
+ Уведомления Суперпользователя
+ %1$s секунд
+ Повторная аутентификация
+ Повторный запрос прав Суперпользователя после обновления приложений
+ Биометрическая аутентификация
+ Использовать дактилоскопический сканер для подтверждения запросов Суперпользователя
+
+ Многопользовательский режим
+ Только владелец
+ Регулировка владельцем
+ Правила пользователей
+ Только владелец имеет Root-доступ
+ Только владелец управляет Root-доступом и обрабатывает запросы
+ Каждый пользователь имеет свои собственные правила Root-доступа
+ Запрос отправлен владельцу устройства. Переключитесь в профиль владельца для обработки запроса
+
+ Настройка именных пространств
+ Общее именное пространство
+ Наследуемое именное пространство
+ Изолированное именное пространство
+ Сессии Суперпользователя используют общее именное пространство
+ Сессии Суперпользователя наследуют именное пространство запрашивающего
+ Сессии Суперпользователя используют изолированные именные пространства
+ Не поддерживается в Android 8.0+
+ Не поддерживается устройством или не заданы отпечатки
+
+
+ Запрос прав Суперпользователя
+ Отказать %1$s
+ Отказать
+ Запрос
+ Предоставить
+ Предоставить полный доступ к устройству.\nЕсли не уверены, что желаете продолжить, отклоните данное действие!
+ Навсегда
+ Сейчас
+ 10 мин.
+ 20 мин.
+ 30 мин.
+ 60 мин.
+ %1$s предоставлены права Суперпользователя
+ %1$s отказано в правах Суперпользователя
+ Приложения не обнаружены
+ %1$s предоставлены права Суперпользователя
+ %1$s отказано в правах Суперпользователя
+ Уведомления для %1$s включены
+ Уведомления для %1$s отключены
+ Логирование для %1$s включено
+ Логирование для %1$s отключено
+ Настройки для %1$s сброшены
+ Сброс настроек
+ Сбросить настройки для %1$s?
+ Всплывающие уведомления
+ Нет
+ Ошибка аутентификации
+
+
+ PID:\u0020
+ UID:\u0020
+ Команда:\u0020
+
+
diff --git a/app/src/full/res/values-sr/strings.xml b/app/src/full/res/values-sr/strings.xml
new file mode 100644
index 000000000..c4f61d851
--- /dev/null
+++ b/app/src/full/res/values-sr/strings.xml
@@ -0,0 +1,197 @@
+
+
+
+
+ Модули
+ Преузимања
+ Супер-корисник
+ Дневник
+ Подешавања
+ Инсталација
+
+
+ Магиск није инсталиран
+ Проверавам Ажурирања…
+ Магиск v%1$s је доступан!
+ Непостојећи Канал Ажурирања
+ Притисни да започнеш СигурнаМрежа проверу
+ Проверавам СигурнаМрежа статус…
+ СигурнаМрежа Провера Успешна
+ СигурнаМрежа АПИ Грешка
+ Интернет конекција недоступна
+ Сервис је елеминисан
+ Одговор није валидан
+
+
+ Напредна Подешавања
+ Задржи форсирану енкрипцију
+ Задржи AVB 2.0/dm-verity
+ Инсталирана верзија: %1$s
+ Најновија верзија: %1$s
+ Унинсталирај
+ Унинсталирај Магиск
+ Сви модули ће бити онеспособљени/уклоњени. Корен ће бити уклоњен, и потенцијално енкриптовати твоје податке уколико већ нису енкриптовани
+ Ажурирање %1$s
+
+
+ (Без информација)
+ Нема пронађених модула
+ Модул ће бити ажуриран после рестарта
+ Модул ће бити уклоњен после рестарта
+ Модул неће бити уклоњен после рестарта
+ Модул ће бити онеспособљен после рестарта
+ Модул ће бити оспособљен после рестарта
+ Креирао %1$s
+
+
+ Ажурирање доступно
+ Инсталирано
+ Није инсталирано
+
+
+ Сачувај дневник
+ Поново учитај
+ Обриши дневник
+ Дневник успешно креиран
+ Дневник је празан
+
+
+ О апликацији
+ Дневник промена апликације
+
+ Верзија апликације
+ изворни код
+ Донације
+ Преводиоци апликације
+ Подршка
+
+
+ Затвори
+ Инсталирај %1$s
+ Да ли желите да инсталирате %1$s?
+ Преузми
+ Рестартуј
+ Нови Адбејт Магиска Доступан!
+ Рестартуј да примениш подешавања
+ Белешке обљављивања
+ Кеш спремишта обрисан
+ Грешка у обради
+ Зип је сачуван у:\n[Интерно складиште]%1$s
+ Преузимање
+ Преузимање зип фајла (%1$d%%) …
+ Обрада
+ Обрада зип фајла …
+ Ново Ажурирање Магиск Менаџера Доступно!
+ Притисни да преузмеш и инсталираш
+ DTBO је закрпљен!
+ Магиск Менаџер је закрпио dtbo.img, рестартујте телефон
+ Магиск Ажурирање
+ Флешовање
+ Сакривам Магиск Менаџер…
+ Ово може потрајати…
+ Скривање Магиск Менаџера неуспешно…
+ Преузми само Зип
+ Закрпи фајл слике покретања
+ Директна Инсталација (Препоручено)
+ Инсталирај у Други Слот (После ОТА)
+ Изабери Методу
+ Циљана Магиск верзија не подржава закрпу фајла слике покретања
+ Изабери испис фабричког фајла слике покретања у .img или img.tar формату
+ Комплетна Унинсталација
+ Повратак успешан!
+ Фабрички бекап не постоји!
+ Преузми Власнички Код
+ Магиск Менаџер је \'FOSS\' што значи да не садржи Гуглов власнички код од СигурнаМрежа АПИ.\n\nДа ли дозвољавате да Магиск Менаџер преузме додатак (садржи GoogleApiClient) за СигурнаМрежа провере?
+ СК база података оштећена, креирам нову
+
+
+ Генерално
+ Црна тема
+ Омогући црну тему
+ Обриши кеш спремишта
+ Очисти кеширану имформацију за онлајн спремишта, форсира апликацију да освежи онлајн
+ Сакриј Магиск Менаџер
+ Препакуј Магиск Менаџер са насумичним именом пакета
+ Језик
+ (Фабрички Система)
+ Подешавања Ажурирања
+ Канал Ажурирања
+ Стабилан
+ Бета
+ По наруџби
+ Унеси наруџбени УРЛ
+ Закрпљено Покретање Излазни Формат
+ Изабери формат излазних закрпљених слика покретања.\nИзаберите .img ако флешујете преко fastboot/download режима; Изаберите .img.tar ако флешујете уз ОДИН.
+ Магиск Основни Режим
+ Омогућава само основне карактеристике, сви модули неће бити учитани. МагискСК, МагискСакриј, и без-системски домаћини ће бити омогућени
+ Сакриј Магиск од разних детекција
+ Без-системски домаћини (hosts)
+ Подршка без-системских домаћина за апликације за блокирање реклама
+
+ Апликације и АДБ
+ Само Апликације
+ Само АДБ
+ Онемогућено
+ 10 секунди
+ 20 секунди
+ 30 секунди
+ 60 секунди
+ Приступ Супер-кориснику
+ Аутоматски одговор
+ Истек захтева
+ Нотификације Супер-корисника
+ %1$s секунди
+ Поново одобри после ажурирања
+ Поново одобри дозволу Супер-корисника после ажурирања апликације
+
+ Више-кориснички режим
+ Власник уређаја само
+ Одређено од стране власника
+ Независно од корисника
+ Само власник има приступ корену
+ Само власник може да приступа корену и да прими захтеве за њега
+ Сваки корисник има своја правила корена
+ Захтев је послат власнику уређаја. Молимо вас да на власниковом налогу прихватите захтев
+
+ Постоље режима имена простора
+ Глобално име простора
+ Наслеђено име простора
+ Ограђено име простора
+ Све коренске сесије користе глобално име простора
+ Коренске сесије ће наследити свој простор именовања
+ Свака коренска сесија ће имати свој изоловани простор именовања
+
+
+ Супер-кориснички захтев
+ Забрани%1$s
+ Забрани
+ Захтев
+ Дозволи
+ Пружа потпун приступ вашем уређају.\nЗабраните ако нисте сигурни!
+ Заувек
+ Једном
+ 10 мин
+ 20 мин
+ 30 мин
+ 60 мин
+ %1$s је добио права на Супер-корисника
+ %1$s није добио права на Супер-корисника
+ Нема пронађених апликација
+ Супер-корисничка права од %1$s су пружена
+ Супер-корисничка права од %1$s су одбијена
+ Нотификације од %1$s су омогућене
+ Нотификације од %1$s су онемогућене
+ Записивање у дневник за %1$s је омогућено
+ Записивање у дневник за %1$s је онемогућено
+ %1$s права су опозвана
+ Опозови?
+ Потврди да опозовеш права од %1$s?
+ Тост
+ Ниједан
+
+
+ ПИД:\u0020
+ Циљани УИД:\u0020
+ Команда:\u0020
+
+
diff --git a/app/src/full/res/values-sv/strings.xml b/app/src/full/res/values-sv/strings.xml
new file mode 100644
index 000000000..fa6f302f6
--- /dev/null
+++ b/app/src/full/res/values-sv/strings.xml
@@ -0,0 +1,163 @@
+
+
+
+
+ Moduler
+ Nerladdningar
+ Superuser
+ Logg
+ Inställningar
+ Install
+
+
+ Magisk är inte installerad
+
+ Söker efter uppdateringar…
+ Magisk v%1$s tillgänglig!
+ Tryck för att starta SafetyNet-kontroll
+ Kontrollerar SafetyNet-status…
+ SafetyNet-kontroll lyckades
+ Tappade nätverksanslutningen
+ Tjänsten har dödats
+ Svaret är ogiltigt
+
+
+ Advancerade inställningar
+ Fortsätt tvinga kryptering
+ Behåll AVB 2.0/dm-verity
+ Installerad: %1$s
+ Senaste: %1$s
+ Avinstallera
+ Avinstallera Magisk
+ Uppdatera %1$s
+
+
+ (Ingen information tillhandahållen)
+ Hittade inga moduler
+ Modulen kommer att uppdateras vid nästa omstart
+ Modulen kommer att tas bort vid nästa omstart
+ Modulen kommer att inte att tas bort vid nästa omstart
+ Modulen kommer att inaktiveras vid nästa omstart
+ Modulen kommer att aktiveras vid nästa omstart
+ Skapad av %1$s
+
+
+ Uppdatering tillgänglig
+ Installerad
+ Inte installerad
+
+
+ Spara loggen
+ Uppdatera
+ Rensa loggen
+ Loggen rensad
+ Loggen är tom
+
+
+ Om
+ Ändringslogg
+
+ Version
+ Källkod
+ Donation
+ Översättare
+ Supporttråd
+
+
+ Stäng
+ Installera %1$s
+ Vill du installera %1$s ?
+ Ladda ner
+ Starta om
+ Bearbetar ZIP-filen …
+ En uppdatering av Magisk finns tillgänglig!
+ Starta om för att tillämpa inställningar
+ Release notes
+ Repo-cache rensad
+ Processfel
+ ZIP-filen är sparad i:\n[Internal Storage]%1$s
+ Arbetar
+ En uppdatering av Magisk maneger finns tillgänglig!
+ Tryck för att ladda ner och installera
+ Magiska uppdateringar
+
+
+ Allmän
+ Mörkt tema
+ Aktivera mörkt tema
+ Rensa repo-cache
+ Rensa den lagrade information för online-repos, tvingar appen att uppdatera online
+
+ Endast Magisk kärnläge
+ Aktiverar endast kärnfunktioner, alla moduler laddas inte. MagiskSU, MagiskHide och systemless hosts kommer fortfarande att vara aktiverade
+ Dölj Magisk från att bli upptäckt
+ Systemless hosts
+ Systemless hosts-stöd för Adblock-appar
+
+ Apps och ADB
+ Endast appar
+ Endast ADB
+ Inaktiverad
+ 10 sekunder
+ 20 sekunder
+ 30 sekunder
+ 60 sekunder
+ Superuser-tillgång
+ Automatiskt svar
+ Förfrågnings-timeout
+ Superuser-avisering
+ %1$s sekunder
+ Återautentisera efter uppdatering
+ Återautentisera superuser-behörigheter efter en applikationsuppdatering
+
+ Multianvändarläge
+ Endast ägaren av enheten
+ Enhetsägarhantering
+ Användaroberoende
+ Endast ägaren har root-tillgång
+ Endast ägaren kan hantera root-åtkomst och få begärda förfrågningar
+ Varje användare har sina egna separata root-regler
+ En förfrågan har skickats till enhetens ägaren. Byt till ägaren och bevilja behörigheten
+
+ Montera namnrymdsläge
+ Global namnrymd
+ Ärv namnrymden
+ Isolerad namnrymd
+ Alla root-sessioner använder den globala monteringsnamnrymden
+ Root-sessioner kommer att ärva den sökandes namnrymd
+ Varje root-session kommer att ha sin egen isolerade namnrymd
+
+
+ Superuser-förfrågan
+ Neka%1$s
+ Neka
+ Fråga
+ Bevilja
+ Beviljar full tillgång till din enhet.\nNeka om du är osäker!
+ För alltid
+ En gång
+ 10 min
+ 20 min
+ 30 min
+ 60 min
+ %1$s är beviljad Superuser-rättigheter
+ %1$s är nekad Superuser-rättigheter
+ Hittade inga appar
+ Superuser-rättigheter av %1$s är beviljad
+ Superuser-rättigheter av %1$s är nekad
+ Aviseringar från %1$s är aktiverad
+ Aviseringar från %1$s är inaktiverade
+ Loggning av %1$s är aktiverad
+ Loggning av %1$s är inaktiverad
+ %1$s rättigheter är upphävda
+ Upphäv?
+ Bekräfta upphävning av %1$s rättigheter?
+ Toast-meddelande
+ Inga
+
+
+ PID:\u0020
+ Mål-UID:\u0020
+ Kommando:\u0020
+
+
diff --git a/app/src/full/res/values-sw600dp/bools.xml b/app/src/full/res/values-sw600dp/bools.xml
new file mode 100644
index 000000000..d3a0e92c9
--- /dev/null
+++ b/app/src/full/res/values-sw600dp/bools.xml
@@ -0,0 +1,4 @@
+
+
+ true
+
\ No newline at end of file
diff --git a/app/src/full/res/values-sw600dp/dimens.xml b/app/src/full/res/values-sw600dp/dimens.xml
new file mode 100644
index 000000000..90ae789ca
--- /dev/null
+++ b/app/src/full/res/values-sw600dp/dimens.xml
@@ -0,0 +1,4 @@
+
+
+ 24dp
+
\ No newline at end of file
diff --git a/app/src/full/res/values-sw600dp/styles.xml b/app/src/full/res/values-sw600dp/styles.xml
new file mode 100644
index 000000000..73325c6af
--- /dev/null
+++ b/app/src/full/res/values-sw600dp/styles.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/full/res/values-tr/strings.xml b/app/src/full/res/values-tr/strings.xml
new file mode 100644
index 000000000..55a4d6c00
--- /dev/null
+++ b/app/src/full/res/values-tr/strings.xml
@@ -0,0 +1,219 @@
+
+
+
+ Modüller
+ İndir
+ Yetkili kullanıcı
+ Günlük
+ Ayarlar
+ Yükle
+
+
+ Magisk yüklü değil
+ Güncelleştirmeler denetleniyor…
+ Magisk v%1$s mevcut!
+ Geçersiz Güncelleme Kanalı
+ SafetyNet kontrolünü başlatmak için dokun
+ SafetyNet durumu kontrol ediliyor…
+ SafetyNet Kontrolü Başarılı
+ SafetyNet API Hatası
+ Ağ bağlantısı kullanılamıyor
+ Hizmet sonlandırıldı
+ Yanıt geçersiz
+
+
+ Gelişmiş Ayarlar
+ Şifrelemeyi zorlamayı sürdür
+ AVB 2.0/dm-verity\'yi koru
+ Yüklü Sürüm: %1$s
+ Yeni Sürüm: %1$s
+ Kaldır
+ "Magisk\'i kaldır"
+ Tüm modüller devre dışı bırakılacak/kaldırılacaktır. Kök erişimi kaldırılacak ve verileriniz şu anda şifrelenmemişse potansiyel olarak verileriniz şifrelenecek
+ Güncelle %1$s
+
+
+ (Hiçbir açıklama sağlanmadı)
+ Modül yok
+ Modül sonraki yeniden başlatmada güncellenecek
+ Modül sonraki yeniden başlatmada kaldırılacak
+ Modül sonraki yeniden başlatmada kaldırılmayacak
+ Modül sonraki yeniden başlatmada devre dışı bırakılacak
+ Modül sonraki yeniden başlatmada etkinleştirilecek
+ Yapımcı: %1$s
+ Kurtarma modunda yeniden başlat
+ Önyükleyici modunda yeniden başlat
+ Yükleme modunda yeniden başlat
+
+
+ Güncelleme Mevcut
+ Yüklenmiş
+ Yüklenmemiş
+ Güncelleme: %1$s
+ Sıralama Düzeni
+ İsme göre sırala
+ Son güncellemeye göre sırala
+
+
+ Günlüğü kaydet
+ Yenile
+ Günlüğü temizle
+ Günlük başarıyla temizlendi
+ Günlük boş
+
+
+ Hakkında
+ Uygulama değişiklikleri
+ Fatih FIRINCI - Mevlüt TOPÇU
+ Uygulama sürümü
+ Kaynak kodu
+ Bağış Yapın
+ Çevirmenler
+ Destek konusu
+
+
+ Kapat
+ %1$s yükle
+ %1$s yüklensin mi?
+ İndir
+ Yeniden başlat
+ Yeni Magisk Güncellemesi Mevcut!
+ Ayarları uygulamak için yeniden başlatın
+ Sürüm notları
+ Repo önbelleği temizlendi
+ İşlem hatası
+ Zip şuraya depolandı:\n[Dahili Hafıza]%1$s
+ İndiriliyor
+ Zip dosyası indiriliyor (%1$d%%) …
+ İşleniyor
+ Zip dosyası işleniyor …
+ Yeni Magisk Manager Güncellemesi Mevcut!
+ İndirmek ve yüklemek için dokunun
+ DTBO yamalandı!
+ Magisk Manager dtbo.img\'yi yamaladı, lütfen yeniden başlatın
+ Magisk Güncellemeleri
+ Yükleniyor
+ Magisk Manager Gizleniyor…
+ Bu biraz zaman alabilir…
+ Magisk Manager\'ı Gizleme başarısız oldu…
+ Yalnızca Zip Dosyasını İndir
+ Önyükleme İmaj Dosyasını Yamala
+ Doğrudan Yükle (Önerilen)
+ İkinci Yuvaya Yükle (OTA\'dan sonra)
+ Yöntem Seçin
+ Hedef Magisk sürümü önyükleme imaj dosyasını yamalamayı desteklemiyor
+ .img veya .img.tar formatında stok önyükleme imajını seçin
+ Tamamen Kaldır
+ Önyükleme İmajını Geri Yükle
+ Yenileme tamamlandı!
+ Stok önyükleme yedeği yok!
+ Tescil Kodunu İndirin
+ Magisk Yöneticisi, FOSS olduğundan Google\'ın tescilli olduğu SafetyNet API kodunu içermez.\n\nMagisk Manager\'ın SafetyNet kontrolü için bir uzantıyı (GoogleApiClient içeriyor) indirmesine izin veriyor musunuz?
+ SU veritabanı bozuk, yeni db oluşturacak
+ SafetyNet denetlenemiyor
+ Google Play Hizmetleri\'ndeki bazı değişiklikler nedeniyle, yeniden paketlenmiş Magisk Manager\'da SafetyNet\'i denetlemek mümkün değildir
+ Kurulum tamamlandı
+ Kurulum başarısız
+ Ek Kurulum İste
+ Cihazınızın Magisk\'in düzgün çalışması için ek kuruluma ihtiyacı var. Bu Magisk kurulum zip dosyasını indirecektir, şimdi devam etmek istiyor musunuz?
+
+
+ Genel
+ Karanlık Tema
+ Karanlık temayı etkinleştir
+ Repo Önbelleğini Temizle
+ Çevrimiçi repolar için önbellek bilgilerini temizle, uygulamayı çevrimiçi yenilemeye zorla
+ Magisk Manager\'ı Gizle
+ Rastgele paket adı ile Magisk Manager\'ı yeniden paketle
+ Magisk Manager\'ı Geri Yükle
+ Orijinal paket adı ile Magisk Manager\'ı geri yükle
+ Dil
+ (Sistem Varsayılanı)
+ Güncelleme Ayarları
+ Güncellemeleri denetle
+ Düzenli aralıklarla arka planda güncellemeleri denetle
+ Güncelleme Kanalı
+ Kararlı
+ Beta
+ Özel
+ Özel bir URL ekleyin
+ Yamalı Önyükleme Formatı
+ Yamalı önyükleme imaj dosyasının formatını seçin\nFastboot/download modunda yüklemek için .img seçeneğini seçin; ODIN ile yüklemek için .img.tar\'ı seçin.
+ Magisk Sadece Çekirdek Modu
+ Sadece temel özellikleri etkinleştirin, tüm modüller yüklenmez. MagiskSU, MagiskHide ve host yine de etkinleştirilecektir
+ "Magisk\'i çeşitli algılamalardan gizle"
+ Sistemsiz host
+ Reklam engelleme uygulamaları için sistemsiz host desteği
+
+ Uygulamalar ve ADB
+ Sadece uygulamalar
+ Sadece ADB
+ Devre dışı
+ 10 saniye
+ 20 saniye
+ 30 saniye
+ 60 saniye
+ Yetkili Kullanıcı Erişimi
+ Otomatik Yanıt
+ İstek Zaman Aşımı
+ Yetkili Kullanıcı Bildirimi
+ %1$s saniye
+ Yükseltmeden sonra yeniden kimlik doğrula
+ Uygulama yükseltmeleri sonrasında yetkili kullanıcı izinlerini yeniden doğrula
+ Parmak İzi Kimlik Doğrulamayı Etkinleştir
+ Yetkili kullanıcı isteklerine izin vermek için parmak izi tarayıcısını kullan
+
+ Çok Kullanıcılı Mod
+ Yalnızca Cihaz Sahibi
+ Cihaz Sahibi Tarafından Yönetilen
+ Bağımsız Kullanıcı
+ Yalnızca kök erişimi olan kullanıcı
+ Yalnızca cihaz sahibi kök erişimini yönetebilir ve izin isteklerini alabilir
+ Her kullanıcının kendi ayrı kök erişimi kuralları vardır
+ Cihaz sahibine bir istek gönderilmiştir. Lütfen cihaz sahibi hesabına geçin ve izin verin
+
+ Ad Alanı Modunu Doldur
+ Global Ad Alanı
+ Inherit Ad Alanı
+ Isolated Ad Alanı
+ Tüm kök oturumları genel bağlama ad alanını kullanır
+ Kök oturumları, istekte bulunanın ad alanını devralır
+ Her bir kök oturumunun kendi izole ad alanı olacaktır
+ Android 8.0 ve üzerinde desteklenmiyor
+ Parmak izi ayarlanmadı veya cihaz desteği yok
+
+
+ Yetkili Kullanıcı İsteği
+ Reddet%1$s
+ Reddet
+ Sor
+ İzin ver
+ Cihazınıza tam erişim izni verir.\nEmin değilseniz, reddedin!
+ Daima
+ Bir kere
+ 10 dakika
+ 20 dakika
+ 30 dakika
+ 60 dakika
+ %1$s için yetkili kullanıcı hakları verildi
+ %1$s için yetkili kullanıcı hakları reddedildi
+ Hiçbir uygulama bulunamadı
+ %1$s için yetkili kullanıcı hakları verildi
+ %1$s için yetkili kullanıcı hakları reddedildi
+ %1$s için bildirimler etkin
+ %1$s için bildirimler devre dışı
+ %1$s için günlük etkin
+ %1$s için günlük devre dışı
+ %1$s hakları geri alındı
+ Geri alınsın mı?
+ %1$s hakları geri alınsın mı?
+ Pencere
+ Hiçbiri
+ Kimlik Doğrulama Başarısız
+
+
+ PID:\u0020
+ Hedef UID:\u0020
+ Komut:\u0020
+
+
diff --git a/app/src/full/res/values-uk/strings.xml b/app/src/full/res/values-uk/strings.xml
new file mode 100644
index 000000000..7bbb01e58
--- /dev/null
+++ b/app/src/full/res/values-uk/strings.xml
@@ -0,0 +1,222 @@
+
+
+
+ Модулі
+ Завантаження
+ Суперкористувач
+ Логи
+ Налаштування
+ Встановлення
+
+
+ Magisk не встановлено
+ Перевірка оновлень…
+ Доступно Magisk v%1$s!
+ Неправильний канал оновлень
+ Перевірити статус SafetyNet
+ Перевірка статусу SafetyNet…
+ Результат перевірки SafetyNet
+ Помилка в API SafetyNet
+ Підключення до інтернету втрачено
+ Службу зупинено
+ Невірна відповідь
+
+
+ Розширені налаштування
+ Залишити примусове шифрування
+ Залишити AVB 2.0/dm-verity
+ Поточна версія: %1$s
+ Найновіша версія: %1$s
+ Видалити
+ Видалити Magisk
+ Ця дія призведе до видалення всіх модулів, MagiskSU, і може зашифрувати дані, якщо вони не зашифровані.\nВпевнені, що бажаєте продовжити?
+ Оновити %1$s
+
+
+ (Немає наданої інформації)
+ Модулів не знайдено
+ Модуль оновиться при перезавантаженні
+ Модуль видалиться при перезавантаженні
+ Модуль не видалиться при перезавантаженні
+ Модуль вимкнеться при перезавантаженні
+ Модуль увімкнеться при перезавантаженні
+ Автор: %1$s
+ Перезавантаження в Recovery
+ Перезавантаження в Bootloader
+ Перезавантаження в Download
+
+
+ Доступне оновлення
+ Встановлено
+ Не встановлено
+ Оновлено: %1$s
+ Порядок сортування
+ Сортувати за назвою
+ Сортувати за оновленням
+
+
+ Зберегти логи
+ Оновити
+ Очистити логи
+ Логи очищено
+ Логи пусті
+
+
+ Про програму
+ Перелік змін
+
+ Версія
+ Вихідний код
+ Підтримати проект
+ Перекладачі
+ Сторінка підтримки
+
+
+ Закрити
+ Встановити %1$s
+ Бажаєте встановити %1$s ?
+ Завантажити
+ Перезавантаження
+ Доступне оновлення Magisk!
+ Для застосування змін перезавантажте пристрій
+ Особливості версії
+ Кеш репозиторію очищено
+ Помилка опрацювання
+ Архів розташований:\n[Внутрішнє Сховище]%1$s
+ Завантаження
+ Завантаження zip файлу (%1$d%%) …
+ Опрацювання
+ Опрацювання архіву …
+ Доступне оновлення Magisk Manager!
+ Натисніть, щоб завантажити і встановити
+ DTBO пропатчено!
+ Magisk Manager пропатчив dtbo.img, будь ласка, перезавантажте пристрій
+ Оновлення Magisk
+ Прошивання
+ Приховування Magisk Manager…
+ Це може зайняти деякий час…
+ Не вдалося приховати Magisk Manager…
+ Тільки завантажити
+ Пропатчити boot образ
+ Пряме встановлення (рекомендовано)
+ Встановити в Другий Слот (після OTA)
+ Виберіть спосіб
+ Цільова версія Magisk не підтримує пропатчування boot образу
+ Виберіть оригінальний дамп boot образу в форматі .img чи .img.tar
+ Видалення виконано
+ Відновити образи
+ Відновлення…
+ Відновлення завершено!
+ Немає резервної копії оригінального boot образу
+ Завантажити пропрієтарний код
+ Magisk Manager — це безкоштовний додаток з відкритим вихідним кодом, тому він не містить пропрієтарний код API SafetyNet від компанії Google.\n\nДозволити Magisk Manager завантажити розширення (яке містить GoogleApiClient) для перевірки SafetyNet?
+ База даних SU пошкоджена, буде створено нову БД
+ Неможливо перевірити SafetyNet
+ Через деякі зміни в Сервісах Google Play, неможливо перевірити статус SafetyNet на перепакованому Magisk Manager
+ Налаштування завершено
+ Не вдалося налаштувати
+ Потрібне додаткове налаштування
+ Для правильної роботи Magisk, потрібне додаткове налаштування вашого пристрою. Буде завантажено zip-архів. Розпочати негайно?
+ Додаткове налаштування
+ Налаштування робочого середовища…
+
+
+ Основні
+ Темна тема
+ Увімкнути темне оформлення
+ Очищення кешу
+ Очистити збережену інформацію про мережеві репозиторії, змушуючи програму примусово оновлюватися через Інтернет
+ Приховати Magisk Manager
+ Перезібрати Magisk Manager з випадковим іменем пакету
+ Відновити Magisk Manager
+ Відновити Magisk Manager з оригінального пакету
+ Мова
+ Стандартна (системна)
+ Налаштування оновлень
+ Перевіряти оновлення
+ Періодично перевіряти оновлення у фоновому режимі
+ Канал оновлення
+ Стабільний реліз
+ Бета реліз
+ Власний
+ Вставте власний URL
+ Формат пропатченого образу
+ Виберіть формат вихідного пропатченого boot образу.\n.img - для прошивання через fastboot/download режим;\n.img.tar - для прошивання через ODIN.
+ Режим ядра Magisk
+ Увімкнути тільки можливості ядра, всі модулі не будуть активними. MagiskSU, MagiskHide і позасистемні хости залишуться увімкненими
+ Приховати Magisk від різних перевірок
+ Позасистемні хости
+ Підтримка позасистемних хостів для програм блокування реклами
+
+ Програми і ADB
+ Програми
+ ADB
+ Вимкнено
+ 10 секунд
+ 20 секунд
+ 30 секунд
+ 60 секунд
+ Доступ суперкористувача
+ Автоматична відповідь
+ Час запиту
+ Сповіщення суперкористувача
+ %1$s сек.
+ Повторна автентифікація
+ Перевидача прав суперкористувача після оновлення програм
+ Автентифікація за відбитком
+ Використовувати сканер відбитків пальців, щоб надавати дозвіл суперкористувача
+
+ Багатокористувацький режим
+ Тільки власник
+ Регулювання власником
+ Незалежний користувач
+ Тільки власник має root-доступ
+ Тільки власник може керувати root-доступом і опрацьовувати запити на надання доступу
+ Кожен користувач має власні правила root-доступу
+ Запит відправлено власнику. Будь ласка, перейдіть на профіль власника і надайте дозвіл
+
+ Режим монтування простору імен
+ Глобальний простір імен
+ Наслідуваний простір імен
+ Ізольований простір імен
+ Всі сеанси Суперкористувача використовують глобальний простір імен
+ Сеанси Суперкористувача наслідують простір імен запитувача
+ Кожнен сеанс Суперкористувача має власний ізольований простір імен
+ Не підтримує Android 8.0+
+ Немає відбитків пальців або пристрій не підтримується
+
+
+ Запит прав Суперкористувача
+ Відмовити %1$s
+ Відмовити
+ Запитувати
+ Надати
+ Надати повний доступ до пристрою.\nЯкщо не впевнені, що бажаєте продовжити, відхиліть дану дію.
+ Назавжди
+ Одноразово
+ 10 хв.
+ 20 хв.
+ 30 хв.
+ 60 хв.
+ %1$s надані права Суперкористувача
+ %1$s відмовлено в правах Суперкористувача
+ Програм не виявлено
+ %1$s надані права Суперкористувача
+ %1$s відмовлено в правах Суперкористувача
+ Сповіщення для %1$s увімкнено
+ Сповіщення для %1$s вимкнено
+ Логи подій для %1$s увімкнено
+ Логи подій для %1$s вимкнено
+ Права для %1$s відкликані
+ Відкликати?
+ Підтвердити відкликання прав для %1$s?
+ Спливаюче сповіщення
+ Ні
+ Помилка автентифікації
+
+
+ PID:\u0020
+ Цільовий UID:\u0020
+ Команда:\u0020
+
+
diff --git a/app/src/full/res/values-vi/strings.xml b/app/src/full/res/values-vi/strings.xml
new file mode 100644
index 000000000..838d91c69
--- /dev/null
+++ b/app/src/full/res/values-vi/strings.xml
@@ -0,0 +1,134 @@
+
+
+
+
+ Mô-đun
+ Tải xuống
+ Superuser
+ Nhật ký
+ Thiết lập
+ Cài đặt
+
+
+ Magisk chưa được cài đặt
+
+ Đang kiểm tra cập nhật…
+ Magisk v%1$s available!
+ Chạm để bắt đầu kiểm tra SafetyNet
+ Đang kiểm tra trạng thái SafetyNet…
+
+
+ Thiết lập nâng cao
+ Giữ bắt buộc mã hoá
+ Giữ AVB 2.0/dm-verity
+ Đã cài đặt phiên bản: %1$s
+ Phiên bản mới nhất: %1$s
+ Gỡ bỏ
+ Gỡ bỏ Magisk
+
+
+ (Không có thông tin được cung cấp)
+ Không tìm thấy mô-đun
+ Mô-đun sẽ được cập nhật ở lần khởi động lại kế tiếp
+ Mô-đun sẽ được xoá bỏ ở lần khởi động lại kế tiếp
+ Mô-đun sẽ không được xoá bỏ ở lần khởi động lại kế tiếp
+ Mô-đun sẽ bị vô hiệu ở lần khởi động lại kế tiếp
+ Mô-đun sẽ được kích hoạt ở lần khởi động lại kế tiếp
+ Tạo bởi %1$s
+
+
+ Có cập nhật mới
+ Đã được cài đặt
+ Chưa được cài đặt
+
+
+ Lưu nhật ký
+ Tải lại
+ Xoá nhật ký ngay
+ Đã xoá nhật ký thành công
+ Nhật ký trống
+
+
+ Thông tin
+ Nhật ký thay đổi của ứng dụng
+ thanhtai2009@tekcafe.vn]]>
+ Phiên bản ứng dụng
+ Mã nguồn
+ Ủng hộ
+ Người dịch ứng dụng
+ Chủ đề hỗ trợ
+
+
+ Đóng
+ Cài đặt %1$s
+ Bạn muốn cài đặt %1$s ?
+ Tải xuống
+ Khởi động lại
+ Đang xử lý tập tin zip …
+ Có cập nhật Magisk mới!
+ Khởi động lại để áp dụng thiết lập
+ Ghi chú phát hành
+ Đã xoá bộ đệm kho
+ Lỗi xử lý
+ Tập tin zip được lưu vào:\n[Bộ nhớ trong]%1$s
+ Đang xử lý
+
+
+ Chung
+ Chủ đề tối
+ Dùng chủ đề tối
+ Xoá bộ đệm kho
+ Xoá thông tin truy cập nhật về các kho mô-đun, buộc ứng dụng làm mới trực tuyến
+
+ Ẩn Magisk khỏi nhiều phương thức phát hiện
+ Systemless hosts
+ Systemless hosts hỗ trợ các ứng dụng chặn quảng cáo
+
+ Ứng dụng và ADB
+ Chỉ ứng dụng
+ Chỉ ADB
+ Đã vô hiệu
+ 10 giây
+ 20 giây
+ 30 giây
+ 60 giây
+ Truy nhập Superuser
+ Tự phản hồi
+ Thời gian chờ yêu cầu
+ Thông báo Superuser
+ %1$s giây
+
+
+ Yêu cầu Superuser
+ Từ chối%1$s
+ Từ chối
+ Nhắc nhở
+ Cấp phép
+ Cấp toàn quyền truy cập thiết bị.\nTừ chối nếu bạn không chắc chắn!
+ Mãi mãi
+ Một lần
+ 10 phút
+ 20 phút
+ 30 phút
+ 60 phút
+ %1$s đã được cấp quyền Superuser
+ Đã từ chối cấp quyền Superuser cho %1$s
+ Không tìm thấy ứng dụng
+ Quyền Superuser của %1$s đã được cấp
+ Quyền Superuser của %1$s đã bị từ chối
+ Thông báo của %1$s đã được kích hoạt
+ Thông báo của %1$s đã bị vô hiệu
+ Ghi nhận của %1$s đã được kích hoạt
+ Ghi nhận của %1$s đã bị vô hiệu
+ Quyền của %1$s đã được thu hồi
+ Thu hồi?
+ Xác nhận thu hồi quyền của %1$s?
+ Thông báo ngắn
+ Không có
+
+
+ PID:\u0020
+ Target UID:\u0020
+ Command:\u0020
+
+
diff --git a/app/src/full/res/values-zh-rCN/strings.xml b/app/src/full/res/values-zh-rCN/strings.xml
new file mode 100644
index 000000000..d9ed1b663
--- /dev/null
+++ b/app/src/full/res/values-zh-rCN/strings.xml
@@ -0,0 +1,222 @@
+
+
+
+ 模块
+ 下载
+ 超级用户
+ 日志
+ 设置
+ 安装
+
+
+ 未安装 Magisk
+ 正在检查更新…
+ Magisk 可更新到 v%1$s!
+ 无效的更新通道
+ 点按启动 SafetyNet 检查
+ 正在检查 SafetyNet 状态…
+ SafetyNet 检查成功
+ SafetyNet API 错误
+ 网络连接不可用
+ 服务已被取消
+ 回传值无效
+
+
+ 安装选项
+ 保持强制加密
+ 保留 AVB 2.0/dm-verity
+ 已安装版本:%1$s
+ 最新的版本:%1$s
+ 卸载
+ 卸载 Magisk
+ 所有模块将停用或删除,Root 会被移除。未加密的设备重启时可能会被进行加密。
+ 更新 %1$s
+
+
+ (未提供信息)
+ 未找到模块
+ 模块将在下次重启后更新
+ 模块将在下次重启后移除
+ 模块将不会在下次重启后移除
+ 模块将在下次重启后禁用
+ 模块将在下次重启后启用
+ 作者:%1$s
+ 重启到 Recovery
+ 重启到 Bootloader
+ 重启到 Download
+
+
+ 可更新
+ 已安装
+ 未安装
+ 更新于: %1$s
+ 排序方式
+ 按名称排序
+ 按更新时间排序
+
+
+ 保存日志
+ 刷新
+ 清除日志
+ 日志已清除
+ 日志为空
+
+
+ 关于
+ 应用更新日志
+ gh2923, vvb2060
+ 应用版本
+ 源代码
+ 捐赠
+ 翻译人员
+ 支持主题
+
+
+ 关闭
+ 安装 %1$s
+ 是否立即安装 %1$s ?
+ 下载
+ 重启
+ Magisk 可更新!
+ 重启以应用设置
+ 发布说明
+ 仓库缓存已清除
+ 处理失败
+ Zip 已被储存到:\n[内部存储空间]%1$s
+ 正在下载
+ 正在下载 Zip 文件(%1$d%%)…
+ 正在处理
+ 正在处理 zip 文件…
+ Magisk Manager 有更新!
+ 点按以下载并安装
+ 已修补 DTBO!
+ Magisk Manager 为 dtbo 分区进行了修补,请立即重新启动
+ Magisk 更新
+ 正在刷入
+ 正在隐藏 Magisk Manager…
+ 这可能需要一点时间…
+ 隐藏 Magisk Manager 失败…
+ 仅下载 Zip
+ 修补 Boot 镜像文件
+ 直接安装(推荐)
+ 安装到第二分区(安装完OTA后)
+ 选择安装方法
+ 目标安装版本不支持修补 Boot 镜像文件
+ 选择原厂 Boot 镜像备份;支持 .img 以及 .img.tar 格式
+ 完全卸载
+ 还原原厂镜像
+ 还原中…
+ 还原完成!
+ 原厂 Boot 镜像备份不存在!
+ 下载专有代码
+ Magisk Manager 是一个 100% 开源的应用,因此不会包含 Google 专有的 SafetyNet API 代码。\n\n允许 Magisk Manager 下载一个扩展(包含 GoogleApiClient)用于 SafetyNet 检查吗?
+ SU 数据库已损坏,将重新创建数据库
+ 无法检查 SafetyNet
+ 由于一些 Google Play 服务的改变,隐藏后的 Magisk Manager 无法进行 SafetyNet 检查。
+ 安装完成
+ 安装失败
+ 需要额外安装
+ 您的设备需要额外的安装才能使 Magisk 正常工作。 将下载 Magisk 安装包,是否继续?
+ 额外安装
+ 运行环境安装中…
+
+
+ 常规
+ 暗色主题
+ 使用暗色主题
+ 清除仓库缓存
+ 清除已缓存的在线仓库信息,强制刷新在线数据
+ 隐藏 Magisk Manager
+ 用随机包名重新安装 Magisk Manager
+ 还原 Magisk Manager
+ 用原安装包还原 Magisk Manager
+ 语言
+ (系统默认)
+ 更新设定
+ 检查更新
+ 在后台定期检查更新
+ 更新通道
+ 稳定版
+ 测试版
+ 自定义
+ 请输入自定义网址
+ 已修补的 Boot 镜像输出格式
+ 选择已修补的 Boot 镜像文件输出格式\n若要通过 fastboot/download 模式刷入,请选择 .img 格式;若要通过 Odin 刷入,则选择 .img.tar\n
+ Magisk 核心功能模式
+ 仅启用核心功能,所有模块将不会被载入。MagiskSU、MagiskHide 和 systemless hosts 仍会持续运作
+ 隐藏 Magisk 使其不被多种方法检测到
+ Systemless hosts
+ 为广告屏蔽应用提供 Systemless hosts 支持
+
+ 应用和 ADB
+ 仅应用
+ 仅 ADB
+ 已禁用
+ 10 秒
+ 20 秒
+ 30 秒
+ 60 秒
+ 超级用户访问权限
+ 自动响应
+ 请求超时
+ 超级用户通知
+ %1$s 秒
+ 更新后重新认证
+ 应用更新后重新认证超级用户权限
+ 启用指纹验证
+ 使用指纹识别来允许超级用户请求
+
+ 多用户模式
+ 仅设备所有者
+ 由设备所有者管理
+ 各用户独立
+ 仅设备所有者账户有超级用户权限
+ 仅设备所有者账户能管理超级用户并接收权限请求提示
+ 每个用户有独立的权限规则
+ 已发送权限请求到设备所有者账户中。请切换到所有者账户授予权限
+
+ 挂载命名空间 模式
+ 全局命名空间
+ 继承命名空间
+ 独立命名空间
+ 所有的 ROOT 会话使用全局挂载命名空间
+ ROOT 会话继承原程序的命名空间
+ 每一个 ROOT 会话使用自己独立的命名空间
+ 不支持 Android 8.0+
+ 没有设置指纹或设备不支持
+
+
+ 超级用户请求
+ 拒绝 %1$s
+ 拒绝
+ 提示
+ 允许
+ 将授予对该设备的最高权限。\n如果不确定,请拒绝!
+ 永久
+ 一次
+ 10 分钟
+ 20 分钟
+ 30 分钟
+ 60 分钟
+ %1$s 已被授予超级用户权限
+ %1$s 已被拒绝超级用户权限
+ 未找到应用
+ 已授予 %1$s 超级用户权限
+ 已拒绝 %1$s 超级用户权限
+ 已启用 %1$s 的通知
+ 已禁用 %1$s 的通知
+ 已启用对 %1$s 的日志记录
+ 已禁用对 %1$s 的日志记录
+ 已撤销 %1$s 的权限
+ 撤销
+ 确认撤销 %1$s 的权限?
+ 消息提示
+ 无
+ 验证失败
+
+
+ PID:\u0020
+ 目标 UID:\u0020
+ 命令:\u0020
+
+
diff --git a/app/src/full/res/values-zh-rTW/strings.xml b/app/src/full/res/values-zh-rTW/strings.xml
new file mode 100644
index 000000000..051cf9315
--- /dev/null
+++ b/app/src/full/res/values-zh-rTW/strings.xml
@@ -0,0 +1,222 @@
+
+
+
+
+ 模組
+ 下載
+ 超級用戶
+ 日誌
+ 設置
+ 安裝
+
+
+ 未安裝 Magisk
+
+ 正在檢查更新…
+ Magisk 可更新到 v%1$s!
+ 點擊啟動 SafetyNet 檢查
+ 正在檢查 SafetyNet 狀態…
+
+
+ 高級設置
+ 保持強制加密
+ 保留 AVB 2.0/dm-verity
+ 已安裝版本:%1$s
+ 最新的版本:%1$s
+ 解除安裝
+
+
+ (未提供資訊)
+ 未找到模組
+ 模組將在下次重啟後更新
+ 模組將在下次重啟後移除
+ 模組將不會在下次重啟後移除
+ 模組將在下次重啟後禁用
+ 模組將在下次重啟後啟用
+ 作者:%1$s
+
+
+ 可更新
+ 已安裝
+ 未安裝
+
+
+ 保存日誌
+ 重載
+ 清除日誌
+ 日誌已成功清除
+ 日誌為空
+
+
+ 關於
+ 應用更新日誌
+ topjohnwu
+ 應用版本
+ 原始碼
+ 捐贈
+ 翻譯者
+ 討論串
+
+
+ 安裝 %1$s
+ 你想要安裝 %1$s 嗎?
+ 重啟
+ 正在處理 zip 文件 …
+ Magisk 可更新!
+ 重啟以完成設定
+ 發布說明
+ 資源庫暫存已清除
+
+
+ 常規
+ 深色主題
+ 使用深色主題
+ 清除資源庫快取
+ 清除已暫存的在線資源庫快取,強制刷新在線數據
+ Magisk 核心功能模式
+ 隱藏 Magisk 使其不被多種方法檢測到
+ Systemless hosts
+ 為廣告屏蔽應用提供 Systemless hosts 支持
+
+ 應用和 ADB
+ 僅應用
+ 僅 ADB
+ 已禁用
+ 10 秒
+ 20 秒
+ 30 秒
+ 60 秒
+ 超級用戶訪問權限
+ 自動回應
+ 請求逾時
+ 超級用戶通知
+ %1$s 秒
+
+
+ 超級用戶請求
+ 拒絕 %1$s
+ 拒絕
+ 提示
+ 允許
+ 將授予對你設備的最高權限。\n如果你不確定,請拒絕!
+ 永久
+ 一次
+ 10 分鐘
+ 20 分鐘
+ 30 分鐘
+ 60 分鐘
+ %1$s 已被授予超級用戶權限
+ %1$s 已被拒絕超級用戶權限
+ 未找到應用
+ 已授予 %1$s 超級用戶權限
+ 已拒絕 %1$s 超級用戶權限
+ 已啟用 %1$s 的通知
+ 已禁用 %1$s 的通知
+ 已啟用對 %1$s 的日誌記錄
+ 已禁用對 %1$s 的日誌記錄
+ 已撤銷 %1$s 的權限
+ 撤銷
+ 確認撤銷 %1$s 的權限?
+ 消息提示
+ 無
+
+
+ PID:\u0020
+ 目標 UID:\u0020
+ "指令: "
+ 關閉
+ Zip 已被儲存到:\n[內部儲存空間]%1$s
+ 處理失敗
+ 下載
+ 處理中
+ 解除安裝 Magisk
+ 所有模組將會被停用 / 刪除。root會被移除,並有可能在目前資料未加密的情況下被加密
+ 僅啟用核心功能,所有模組將不會被載入。MagiskSU、MagiskHide 和 systemless hosts 仍會持續運作
+ SafetyNet 檢查成功
+ 網路斷線
+ 回傳值無效
+ 服務已被取消
+ 已發送權限請求到擁有者帳戶中。請切換到擁有者帳戶進行准許
+ 多使用者設定
+ 僅裝置擁有者帳戶能管理超級用戶權限並接收權限請求通知
+ 僅裝置擁有者帳戶有超級用戶權限
+ 每一位使用者有獨立的權限規則
+ 各使用者獨立
+ 僅裝置擁有者
+ 由裝置擁有者管理
+ 點擊以下載並安裝
+ 有 Magisk Manager 更新!
+ 應用程式更新後,重新認證root權限
+ 更新後重新認證
+ 所有 root 工作使用全域 mount namespace
+ 每一 root 工作有自己獨立的 namespace
+ 掛載命名空間(Namespace)模式
+ Root 工作繼承原程式之 namespace
+ 全域 Namespace
+ 獨立 Namespace
+ 繼承 Namespace
+ 更新 %1$s
+ Magisk 更新
+ (系統預設)
+ 語言
+ 正在刷入
+ 正在隱藏 Magisk Manager…
+ 隱藏 Magisk Manager 失敗
+ 隱藏 Magisk Manager
+ 更新頻道
+ 穩定版
+ 測試版
+ 僅下載 Zip
+ 補丁 Boot 映像文件檔
+ 直接安裝 (推薦)
+ 選擇安裝方法
+ 目標安裝版本不支援補丁 Boot 映像文件
+ 更新設定
+ 已補丁 Boot 映像輸出格式
+ 正在下載
+ 正在下載 Zip 文件 (%1$d%%) …
+ 選擇已補丁 Boot 映像文件輸出格式\n若要透過 fastboot/download 模式刷入,請選擇 .img 格式;若要透過 ODIN 刷入,則選擇 .img.tar\n
+ 完全解除安裝
+ 還原完成!
+ 原廠 boot 映像備份不存在!
+ 選擇原廠 boot 映像備份;支援 .img 以及 .img.tar 格式
+ 安裝到第二分區 (安裝完OTA後)
+ SafetyNet API 錯誤
+ 下載非開源程式
+ Magisk Manager 是一個 100% 開源的程式,因此不會包含 Google 私有所有權的 SafetyNet API 程式碼。\n\n你允許 Magisk Manager 下載一個擴充包 (包含 GoogleApiClient) 以執行 SafetyNet 檢查嗎?
+ DTBO 已被打上補丁!
+ Magisk Manager 已為 dtbo.img 打上補丁,請立即重新啟動
+ 這可能會花上一段時間…
+ 將 Magisk Manager 以隨機套件名稱重新打包,以達成隱藏效果
+ 不合法的更新頻道
+ 自訂
+ 請輸入自訂網址
+ SU 資料庫已毀損,將會重新建置
+ 重啟到 Recovery
+ 重啟到 Bootloader
+ 重啟到 Download
+ 不支援 Android 8.0+
+ 還原原廠映像檔
+ 更新於:%1$s
+ 排序方式
+ 按名稱排序
+ 按更新時間排序
+ 啟用指紋認證
+ 用指紋辨識來允許超級用戶權限
+ 認證失敗
+ 還原 Magisk Manager
+ 以原安裝包還原 Magisk Manager
+ 檢查更新
+ 在背景定期檢查更新
+ 沒有指紋加入或是裝置不支援
+ 無法檢查 SafetyNet
+ 由於一些 Google Play Service 的改變,重新包裝過的 Magisk Manager 無法進行 SafetyNet 檢查。
+ 安裝完成
+ 安裝失敗
+ 需要額外的安裝
+ 您的設備需要额外的安装才能正常使用 Magisk。將下載 Magisk 安装包,是否繼續?
+ 額外安裝
+ 安裝環境進行中…
+ 還原中…
+
+
diff --git a/app/src/full/res/values/arrays.xml b/app/src/full/res/values/arrays.xml
new file mode 100644
index 000000000..c89750d81
--- /dev/null
+++ b/app/src/full/res/values/arrays.xml
@@ -0,0 +1,91 @@
+
+
+
+ - @string/forever
+ - @string/once
+ - @string/tenmin
+ - @string/twentymin
+ - @string/thirtymin
+ - @string/sixtymin
+
+
+
+ - @string/settings_su_disable
+ - @string/settings_su_app
+ - @string/settings_su_adb
+ - @string/settings_su_app_adb
+
+
+
+ - 0
+ - 1
+ - 2
+ - 3
+
+
+
+ - @string/settings_su_request_10
+ - @string/settings_su_request_20
+ - @string/settings_su_request_30
+ - @string/settings_su_request_60
+
+
+
+ - 10
+ - 20
+ - 30
+ - 60
+
+
+
+ - @string/prompt
+ - @string/deny
+ - @string/grant
+
+
+
+ - @string/none
+ - @string/toast
+
+
+
+ - @string/settings_owner_only
+ - @string/settings_owner_manage
+ - @string/settings_user_independent
+
+
+
+ - @string/owner_only_summary
+ - @string/owner_manage_summary
+ - @string/user_indepenent_summary
+
+
+
+ - @string/settings_ns_global
+ - @string/settings_ns_requester
+ - @string/settings_ns_isolate
+
+
+
+ - @string/global_summary
+ - @string/requester_summary
+ - @string/isolate_summary
+
+
+
+ - @string/settings_update_stable
+ - @string/settings_update_beta
+ - @string/settings_update_custom
+
+
+
+ - .img
+ - .img.tar
+
+
+
+ - @string/sort_by_name
+ - @string/sort_by_update
+
+
+
\ No newline at end of file
diff --git a/app/src/full/res/values/attrs.xml b/app/src/full/res/values/attrs.xml
new file mode 100644
index 000000000..4b67ac85d
--- /dev/null
+++ b/app/src/full/res/values/attrs.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/full/res/values/bools.xml b/app/src/full/res/values/bools.xml
new file mode 100644
index 000000000..f855de6a2
--- /dev/null
+++ b/app/src/full/res/values/bools.xml
@@ -0,0 +1,4 @@
+
+
+ false
+
\ No newline at end of file
diff --git a/app/src/full/res/values/dimens.xml b/app/src/full/res/values/dimens.xml
new file mode 100644
index 000000000..95137b41d
--- /dev/null
+++ b/app/src/full/res/values/dimens.xml
@@ -0,0 +1,14 @@
+
+
+ 650dp
+ 500dp
+ 5sp
+ 8dip
+ 3dip
+ 2dp
+ 2dp
+ 10dp
+ 300dip
+ 3dp
+ 16dp
+
\ No newline at end of file
diff --git a/app/src/full/res/values/strings.xml b/app/src/full/res/values/strings.xml
new file mode 100644
index 000000000..53d387b5e
--- /dev/null
+++ b/app/src/full/res/values/strings.xml
@@ -0,0 +1,222 @@
+
+
+
+ Modules
+ Downloads
+ Superuser
+ Log
+ Settings
+ Install
+
+
+ Magisk is not installed
+ Checking for updates…
+ Magisk v%1$s is available!
+ Invalid Update Channel
+ Tap to start SafetyNet check
+ Checking SafetyNet status…
+ SafetyNet Check Success
+ SafetyNet API Error
+ Network connection unavailable
+ Service has been killed
+ The response is invalid
+
+
+ Advanced Settings
+ Preserve force encryption
+ Preserve AVB 2.0/dm-verity
+ Installed Version: %1$s
+ Latest Version: %1$s
+ Uninstall
+ Uninstall Magisk
+ All modules will be disabled/removed. Root will be removed, and potentially encrypt your data if your data is not currently encrypted
+ Update %1$s
+
+
+ (No info provided)
+ No modules found
+ Module will be updated at next reboot
+ Module will be removed at next reboot
+ Module will not be removed at next reboot
+ Module will be disabled at next reboot
+ Module will be enabled at next reboot
+ Created by %1$s
+ Reboot to Recovery
+ Reboot to Bootloader
+ Reboot to Download
+
+
+ Update Available
+ Installed
+ Not Installed
+ Updated on: %1$s
+ Sorting Order
+ Sort by name
+ Sort by last update
+
+
+ Save log
+ Reload
+ Clear log now
+ Log successfully cleared
+ Log is empty
+
+
+ About
+ Changelog
+
+ Version
+ Source code
+ Donation
+ Translators
+ Support thread
+
+
+ Close
+ Install %1$s
+ Do you want to install %1$s now?
+ Download
+ Reboot
+ New Magisk Update Available!
+ Reboot to apply settings
+ Release notes
+ Repo cache cleared
+ Process error
+ The zip is stored in:\n[Internal Storage]%1$s
+ Downloading
+ Downloading zip file (%1$d%%) …
+ Processing
+ Processing zip file …
+ New Magisk Manager Update Available!
+ Press to download and install
+ DTBO was patched!
+ Magisk Manager has patched dtbo.img, please reboot
+ Magisk Updates
+ Flashing
+ Hiding Magisk Manager…
+ This might take a while…
+ Hide Magisk Manager failed…
+ Download Zip Only
+ Patch Boot Image File
+ Direct Install (Recommended)
+ Install to Second Slot (After OTA)
+ Select Method
+ Target Magisk version doesn\'t support boot image file patching
+ Select stock boot image dump in .img or .img.tar format
+ Complete Uninstall
+ Restore Images
+ Restoring…
+ Restoration done!
+ Stock backup does not exist!
+ Download Proprietary Code
+ Magisk Manager is FOSS, which doesn\'t contain Google\'s proprietary SafetyNet API code.\n\nDo you allow Magisk Manager to download an extension (contains GoogleApiClient) for SafetyNet checks?
+ SU database is corrupted, will recreate new db
+ Cannot check SafetyNet
+ Due to some changes in Google Play Services, it is not possible to check SafetyNet on repackaged Magisk Manager
+ Setup done
+ Setup failed
+ Requires Additional Setup
+ Your device needs additional setup for Magisk to work properly. It will download the Magisk setup zip, do you want to proceed now?
+ Additional Setup
+ Running environment setup…
+
+
+ General
+ Dark Theme
+ Enable dark theme
+ Clear Repo Cache
+ Clear the cached information for online repos, forces the app to refresh online
+ Hide Magisk Manager
+ Repackage Magisk Manager with random package name
+ Restore Magisk Manager
+ Restore Magisk Manager with original package
+ Language
+ (System Default)
+ Update Settings
+ Check Updates
+ Check updates in the background periodically
+ Update Channel
+ Stable
+ Beta
+ Custom
+ Insert a custom URL
+ Patched Boot Output Format
+ Select the format of the output patched boot image.\nChoose .img to flash through fastboot/download mode; choose .img.tar to flash with ODIN.
+ Magisk Core Only Mode
+ Enable only core features. MagiskSU, MagiskHide and systemless hosts will still be enabled, but no modules will be loaded.
+ Hide Magisk from various detections
+ Systemless hosts
+ Systemless hosts support for Adblock apps
+
+ Apps and ADB
+ Apps only
+ ADB only
+ Disabled
+ 10 seconds
+ 20 seconds
+ 30 seconds
+ 60 seconds
+ Superuser Access
+ Automatic Response
+ Request Timeout
+ Superuser Notification
+ %1$s seconds
+ Re-authenticate after upgrade
+ Re-authenticate superuser permissions after an application upgrades
+ Enable Fingerprint Authentication
+ Use fingerprint scanner to allow superuser requests
+
+ Multiuser Mode
+ Device Owner Only
+ Device Owner Managed
+ User Independent
+ Only owner has root access
+ Only owner can manage root access and receive request prompts
+ Each user has its own separate root rules
+ A request has been sent to the device owner. Please switch to the owner and grant the permissions required
+
+ Mount Namespace Mode
+ Global Namespace
+ Inherit Namespace
+ Isolated Namespace
+ All root sessions use the global mount namespace
+ Root sessions will inherit its requester\'s namespace
+ Each root session will have its own isolated namespace
+ Does not support Android 8.0+
+ No fingerprints were set or no device support
+
+
+ Superuser Request
+ Deny%1$s
+ Deny
+ Prompt
+ Grant
+ Grants full access to your device.\nDeny if you\'re not sure!
+ Forever
+ Once
+ 10 min
+ 20 min
+ 30 min
+ 60 min
+ %1$s was granted Superuser rights
+ %1$s was denied Superuser rights
+ No apps found
+ Superuser rights of %1$s are granted
+ Superuser rights of %1$s are denied
+ Notifications of %1$s are enabled
+ Notifications of %1$s are disabled
+ Logging of %1$s is enabled
+ Logging of %1$s is disabled
+ %1$s rights are revoked
+ Revoke?
+ Confirm to revoke %1$s rights?
+ Toast
+ None
+ Authentication Failed
+
+
+ PID:\u0020
+ Target UID:\u0020
+ Command:\u0020
+
+
diff --git a/app/src/full/res/values/styles.xml b/app/src/full/res/values/styles.xml
new file mode 100644
index 000000000..fab2c35e6
--- /dev/null
+++ b/app/src/full/res/values/styles.xml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/full/res/xml/app_settings.xml b/app/src/full/res/xml/app_settings.xml
new file mode 100644
index 000000000..4a969d99c
--- /dev/null
+++ b/app/src/full/res/xml/app_settings.xml
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..f7ef05e55
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/topjohnwu/magisk/components/Activity.java b/app/src/main/java/com/topjohnwu/magisk/components/Activity.java
new file mode 100644
index 000000000..9c0e75c44
--- /dev/null
+++ b/app/src/main/java/com/topjohnwu/magisk/components/Activity.java
@@ -0,0 +1,94 @@
+package com.topjohnwu.magisk.components;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.support.annotation.NonNull;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
+import android.widget.Toast;
+
+import com.topjohnwu.magisk.NoUIActivity;
+import com.topjohnwu.magisk.R;
+import com.topjohnwu.magisk.utils.Const;
+
+public abstract class Activity extends FlavorActivity {
+
+ protected static Runnable permissionGrantCallback;
+
+ private ActivityResultListener activityResultListener;
+
+ public Activity() {
+ super();
+ Configuration configuration = new Configuration();
+ configuration.setLocale(Application.locale);
+ applyOverrideConfiguration(configuration);
+ }
+
+ public static void runWithPermission(Context context, String[] permissions, Runnable callback) {
+ boolean granted = true;
+ for (String perm : permissions) {
+ if (ContextCompat.checkSelfPermission(context, perm) != PackageManager.PERMISSION_GRANTED)
+ granted = false;
+ }
+ if (granted) {
+ callback.run();
+ } else {
+ // Passed in context should be an activity if not granted, need to show dialog!
+ permissionGrantCallback = callback;
+ if (!(context instanceof Activity)) {
+ // Start activity to show dialog
+ Intent intent = new Intent(context, NoUIActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(Const.Key.INTENT_PERM, permissions);
+ context.startActivity(intent);
+ } else {
+ ActivityCompat.requestPermissions((Activity) context, permissions, 0);
+ }
+ }
+ }
+
+ public void runWithPermission(String[] permissions, Runnable callback) {
+ runWithPermission(this, permissions, callback);
+ }
+
+ @Override
+ public Application getApplicationContext() {
+ return (Application) super.getApplicationContext();
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ boolean grant = true;
+ for (int result : grantResults) {
+ if (result != PackageManager.PERMISSION_GRANTED)
+ grant = false;
+ }
+ if (grant) {
+ if (permissionGrantCallback != null) {
+ permissionGrantCallback.run();
+ }
+ } else {
+ Application.toast(R.string.no_rw_storage, Toast.LENGTH_LONG);
+ }
+ permissionGrantCallback = null;
+ }
+
+ @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);
+ }
+
+ public interface ActivityResultListener {
+ void onActivityResult(int requestCode, int resultCode, Intent data);
+ }
+
+}
diff --git a/app/src/main/java/com/topjohnwu/magisk/components/Application.java b/app/src/main/java/com/topjohnwu/magisk/components/Application.java
new file mode 100644
index 000000000..3e47fdb14
--- /dev/null
+++ b/app/src/main/java/com/topjohnwu/magisk/components/Application.java
@@ -0,0 +1,34 @@
+package com.topjohnwu.magisk.components;
+
+import android.os.Handler;
+import android.widget.Toast;
+
+import java.lang.ref.WeakReference;
+import java.util.Locale;
+
+public abstract class Application extends android.app.Application {
+
+ public static WeakReference weakSelf;
+ public static Locale locale;
+ public static Locale defaultLocale;
+
+ private static Handler mHandler = new Handler();
+
+ public Application() {
+ weakSelf = new WeakReference<>(this);
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ locale = defaultLocale = Locale.getDefault();
+ }
+
+ 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());
+ }
+}
diff --git a/app/src/main/java/com/topjohnwu/magisk/receivers/DownloadReceiver.java b/app/src/main/java/com/topjohnwu/magisk/receivers/DownloadReceiver.java
new file mode 100644
index 000000000..72aa7654f
--- /dev/null
+++ b/app/src/main/java/com/topjohnwu/magisk/receivers/DownloadReceiver.java
@@ -0,0 +1,59 @@
+package com.topjohnwu.magisk.receivers;
+
+import android.app.DownloadManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.widget.Toast;
+
+import com.topjohnwu.magisk.MagiskManager;
+import com.topjohnwu.magisk.R;
+import com.topjohnwu.magisk.utils.Utils;
+
+import java.io.File;
+
+public abstract class DownloadReceiver extends BroadcastReceiver {
+ protected File mFile;
+ private long downloadID;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
+ String action = intent.getAction();
+ if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
+ DownloadManager.Query query = new DownloadManager.Query();
+ query.setFilterById(downloadID);
+ Cursor c = downloadManager.query(query);
+ if (c.moveToFirst()) {
+ int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS);
+ int status = c.getInt(columnIndex);
+ switch (status) {
+ case DownloadManager.STATUS_SUCCESSFUL:
+ Uri uri = Uri.parse(c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)));
+ onDownloadDone(context, uri);
+ break;
+ default:
+ MagiskManager.toast(R.string.download_file_error, Toast.LENGTH_LONG);
+ break;
+ }
+ context.unregisterReceiver(this);
+ }
+ c.close();
+ }
+ Utils.isDownloading = false;
+ }
+
+ public DownloadReceiver setDownloadID(long id) {
+ downloadID = id;
+ return this;
+ }
+
+ public DownloadReceiver setFile(File file) {
+ mFile = file;
+ return this;
+ }
+
+ public abstract void onDownloadDone(Context context, Uri uri);
+}
diff --git a/app/src/main/java/com/topjohnwu/magisk/receivers/ManagerInstall.java b/app/src/main/java/com/topjohnwu/magisk/receivers/ManagerInstall.java
new file mode 100644
index 000000000..87138a77f
--- /dev/null
+++ b/app/src/main/java/com/topjohnwu/magisk/receivers/ManagerInstall.java
@@ -0,0 +1,29 @@
+package com.topjohnwu.magisk.receivers;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.support.v4.content.FileProvider;
+
+import java.io.File;
+
+public class ManagerInstall extends DownloadReceiver {
+ @Override
+ public void onDownloadDone(Context context, Uri uri) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ Intent install = new Intent(Intent.ACTION_INSTALL_PACKAGE);
+ install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ Uri content = FileProvider.getUriForFile(context,
+ context.getPackageName() + ".provider", new File(uri.getPath()));
+ install.setData(content);
+ context.startActivity(install);
+ } else {
+ Intent install = new Intent(Intent.ACTION_VIEW);
+ install.setDataAndType(uri, "application/vnd.android.package-archive");
+ install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(install);
+ }
+ }
+}
diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/Const.java b/app/src/main/java/com/topjohnwu/magisk/utils/Const.java
new file mode 100644
index 000000000..9d7fe22a5
--- /dev/null
+++ b/app/src/main/java/com/topjohnwu/magisk/utils/Const.java
@@ -0,0 +1,165 @@
+package com.topjohnwu.magisk.utils;
+
+import android.os.Environment;
+import android.os.Process;
+
+import com.topjohnwu.magisk.BuildConfig;
+import com.topjohnwu.magisk.MagiskManager;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+
+public class Const {
+
+ public static final String DEBUG_TAG = "MagiskManager";
+ public static final String ORIG_PKG_NAME = BuildConfig.APPLICATION_ID;
+ public static final String MAGISKHIDE_PROP = "persist.magisk.hide";
+
+ // APK content
+ public static final String ANDROID_MANIFEST = "AndroidManifest.xml";
+
+ public static final String SU_KEYSTORE_KEY = "su_key";
+
+ // Paths
+ public static File MAGISK_PATH;
+ public static File MAGISK_DISABLE_FILE;
+ public static File MAGISK_HOST_FILE;
+
+ static {
+ /* Prevent crashing on unrooted devices */
+ MAGISK_PATH = MAGISK_DISABLE_FILE = MAGISK_HOST_FILE = new File("xxx");
+ }
+
+ public static final String BUSYBOX_PATH = "/sbin/.core/busybox";
+ public static final String TMP_FOLDER_PATH = "/dev/tmp";
+ public static final String MAGISK_LOG = "/cache/magisk.log";
+ public static final File EXTERNAL_PATH = new File(Environment.getExternalStorageDirectory(), "MagiskManager");
+ public static final String MANAGER_CONFIGS = ".tmp.magisk.config";
+
+ // Versions
+ public static final int UPDATE_SERVICE_VER = 1;
+ public static final int SNET_VER = 8;
+
+ public static int MIN_MODULE_VER() {
+ return MagiskManager.get().magiskVersionCode >= MAGISK_VER.REMOVE_LEGACY_LINK ? 1500 : 1400;
+ }
+
+ /* A list of apps that should not be shown as hide-able */
+ public static final List HIDE_BLACKLIST = Arrays.asList(
+ "android",
+ MagiskManager.get().getPackageName(),
+ "com.google.android.gms"
+ );
+
+ public static final int USER_ID = Process.myUid() / 100000;
+
+ public static final class MAGISK_VER {
+ public static final int UNIFIED = 1300;
+ public static final int FBE_AWARE = 1410;
+ public static final int RESETPROP_PERSIST = 1436;
+ public static final int MANAGER_HIDE = 1440;
+ public static final int DTBO_SUPPORT = 1446;
+ public static final int HIDDEN_PATH = 1460;
+ public static final int REMOVE_LEGACY_LINK = 1630;
+ public static final int SEPOL_REFACTOR = 1640;
+ public static final int FIX_ENV = 1650;
+ }
+
+ public static class ID {
+ public static final int UPDATE_SERVICE_ID = 1;
+ public static final int FETCH_ZIP = 2;
+ public static final int SELECT_BOOT = 3;
+
+ // notifications
+ public static final int MAGISK_UPDATE_NOTIFICATION_ID = 4;
+ public static final int APK_UPDATE_NOTIFICATION_ID = 5;
+ public static final int ONBOOT_NOTIFICATION_ID = 6;
+ public static final int DTBO_NOTIFICATION_ID = 7;
+ public static final String NOTIFICATION_CHANNEL = "magisk_notification";
+ }
+
+ public static class Url {
+ public static final String STABLE_URL = "https://raw.githubusercontent.com/topjohnwu/MagiskManager/update/stable.json";
+ public static final String BETA_URL = "https://raw.githubusercontent.com/topjohnwu/MagiskManager/update/beta.json";
+ public static final String SNET_URL = "https://github.com/topjohnwu/MagiskManager/raw/788f01f499714471949613820d43bc364786e85d/snet.apk";
+ public static final String REPO_URL = "https://api.github.com/users/Magisk-Modules-Repo/repos?per_page=100&page=%d";
+ public static final String FILE_URL = "https://raw.githubusercontent.com/Magisk-Modules-Repo/%s/master/%s";
+ public static final String ZIP_URL = "https://github.com/Magisk-Modules-Repo/%s/archive/master.zip";
+ public static final String DONATION_URL = "https://www.paypal.me/topjohnwu";
+ public static final String XDA_THREAD = "http://forum.xda-developers.com/showthread.php?t=3432382";
+ public static final String SOURCE_CODE_URL = "https://github.com/topjohnwu/MagiskManager";
+ }
+
+
+ public static class Key {
+ // su
+ public static final String ROOT_ACCESS = "root_access";
+ public static final String SU_MULTIUSER_MODE = "multiuser_mode";
+ public static final String SU_MNT_NS = "mnt_ns";
+ public static final String SU_MANAGER = "requester";
+ public static final String SU_REQUEST_TIMEOUT = "su_request_timeout";
+ public static final String SU_AUTO_RESPONSE = "su_auto_response";
+ public static final String SU_NOTIFICATION = "su_notification";
+ public static final String SU_REAUTH = "su_reauth";
+ public static final String SU_FINGERPRINT = "su_fingerprint";
+
+ // intents
+ public static final String OPEN_SECTION = "section";
+ public static final String INTENT_SET_FILENAME = "filename";
+ public static final String INTENT_SET_LINK = "link";
+ public static final String INTENT_PERM = "perm_dialog";
+ public static final String FLASH_ACTION = "action";
+ public static final String FLASH_SET_BOOT = "boot";
+
+ // others
+ public static final String CHECK_UPDATES = "check_update";
+ public static final String UPDATE_CHANNEL = "update_channel";
+ public static final String CUSTOM_CHANNEL = "custom_channel";
+ public static final String BOOT_FORMAT = "boot_format";
+ public static final String UPDATE_SERVICE_VER = "update_service_version";
+ public static final String APP_VER = "app_version";
+ public static final String MAGISKHIDE = "magiskhide";
+ public static final String HOSTS = "hosts";
+ public static final String COREONLY = "disable";
+ public static final String LOCALE = "locale";
+ public static final String DARK_THEME = "dark_theme";
+ public static final String ETAG_KEY = "ETag";
+ public static final String LINK_KEY = "Link";
+ public static final String IF_NONE_MATCH = "If-None-Match";
+ public static final String REPO_ORDER = "repo_order";
+ }
+
+
+ public static class Value {
+ public static final int STABLE_CHANNEL = 0;
+ public static final int BETA_CHANNEL = 1;
+ public static final int CUSTOM_CHANNEL = 2;
+ 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 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 int NAMESPACE_MODE_GLOBAL = 0;
+ public static final int NAMESPACE_MODE_REQUESTER = 1;
+ public static final int NAMESPACE_MODE_ISOLATE = 2;
+ public static final int NO_NOTIFICATION = 0;
+ public static final int NOTIFICATION_TOAST = 1;
+ public static final int NOTIFY_NORMAL_LOG = 0;
+ public static final int NOTIFY_USER_TOASTS = 1;
+ public static final int NOTIFY_USER_TO_OWNER = 2;
+ public static final int SU_PROMPT = 0;
+ public static final int SU_AUTO_DENY = 1;
+ public static final int SU_AUTO_ALLOW = 2;
+ public static final String FLASH_ZIP = "flash";
+ public static final String PATCH_BOOT = "patch";
+ public static final String FLASH_MAGISK = "magisk";
+ public static final String FLASH_SECOND_SLOT = "slot";
+ public static final String UNINSTALL = "uninstall";
+ public static final int[] timeoutList = {0, -1, 10, 20, 30, 60};
+ public static final int ORDER_NAME = 0;
+ public static final int ORDER_DATE = 1;
+ }
+}
diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/Utils.java b/app/src/main/java/com/topjohnwu/magisk/utils/Utils.java
new file mode 100644
index 000000000..9f2fd6677
--- /dev/null
+++ b/app/src/main/java/com/topjohnwu/magisk/utils/Utils.java
@@ -0,0 +1,160 @@
+package com.topjohnwu.magisk.utils;
+
+import android.Manifest;
+import android.app.DownloadManager;
+import android.content.Context;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.database.Cursor;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.Uri;
+import android.provider.OpenableColumns;
+import android.support.annotation.StringRes;
+import android.widget.Toast;
+
+import com.topjohnwu.magisk.MagiskManager;
+import com.topjohnwu.magisk.R;
+import com.topjohnwu.magisk.components.Activity;
+import com.topjohnwu.magisk.receivers.DownloadReceiver;
+
+import java.io.File;
+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 boolean isDownloading = false;
+
+ public static void dlAndReceive(Context context, DownloadReceiver receiver, String link, String filename) {
+ if (isDownloading)
+ return;
+
+ Activity.runWithPermission(context,
+ new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, () -> {
+ File file = new File(Const.EXTERNAL_PATH, getLegalFilename(filename));
+
+ if ((!file.getParentFile().exists() && !file.getParentFile().mkdirs())
+ || (file.exists() && !file.delete())) {
+ return;
+ }
+
+ MagiskManager.toast(context.getString(R.string.downloading_toast, filename), Toast.LENGTH_LONG);
+ isDownloading = true;
+
+ DownloadManager.Request request = new DownloadManager
+ .Request(Uri.parse(link))
+ .setDestinationUri(Uri.fromFile(file));
+
+ DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
+ receiver.setDownloadID(dm.enqueue(request)).setFile(file);
+ 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 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 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 boolean checkNetworkStatus() {
+ ConnectivityManager manager = (ConnectivityManager)
+ MagiskManager.get().getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo networkInfo = manager.getActiveNetworkInfo();
+ return networkInfo != null && networkInfo.isConnected();
+ }
+
+ public static String getLocaleString(Locale locale, @StringRes int id) {
+ Context context = MagiskManager.get();
+ Configuration config = context.getResources().getConfiguration();
+ config.setLocale(locale);
+ Context localizedContext = context.createConfigurationContext(config);
+ return localizedContext.getString(id);
+ }
+
+ public static List getAvailableLocale() {
+ List locales = new ArrayList<>();
+ HashSet set = new HashSet<>();
+ Locale locale;
+
+ @StringRes int compareId = R.string.download_file_error;
+
+ // Add default locale
+ locales.add(Locale.ENGLISH);
+ set.add(getLocaleString(Locale.ENGLISH, compareId));
+
+ // Add some special locales
+ locales.add(Locale.TAIWAN);
+ set.add(getLocaleString(Locale.TAIWAN, compareId));
+ locale = new Locale("pt", "BR");
+ locales.add(locale);
+ set.add(getLocaleString(locale, compareId));
+
+ // Other locales
+ for (String s : MagiskManager.get().getAssets().getLocales()) {
+ locale = Locale.forLanguageTag(s);
+ if (set.add(getLocaleString(locale, compareId))) {
+ locales.add(locale);
+ }
+ }
+
+ Collections.sort(locales, (l1, l2) -> l1.getDisplayName(l1).compareTo(l2.getDisplayName(l2)));
+
+ return locales;
+ }
+
+ public static int dpInPx(int dp) {
+ Context context = MagiskManager.get();
+ float scale = context.getResources().getDisplayMetrics().density;
+ return (int) (dp * scale + 0.5);
+ }
+
+ public static String fmt(String fmt, Object... args) {
+ return String.format(Locale.US, fmt, args);
+ }
+
+ public static String dos2unix(String s) {
+ String newString = s.replace("\r\n", "\n");
+ if(!newString.endsWith("\n")) {
+ return newString + "\n";
+ } else {
+ return newString;
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/WebService.java b/app/src/main/java/com/topjohnwu/magisk/utils/WebService.java
new file mode 100644
index 000000000..df578b0e6
--- /dev/null
+++ b/app/src/main/java/com/topjohnwu/magisk/utils/WebService.java
@@ -0,0 +1,72 @@
+package com.topjohnwu.magisk.utils;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.List;
+import java.util.Map;
+
+public class WebService {
+
+ public static String getString(String url) {
+ return getString(url, null);
+ }
+
+ public static String getString(String url, Map header) {
+ try {
+ HttpURLConnection conn = request(url, header);
+ return getString(conn);
+ } catch (IOException e) {
+ e.printStackTrace();
+ return "";
+ }
+ }
+
+ public static String getString(HttpURLConnection conn) {
+ try {
+ StringBuilder builder = new StringBuilder();
+ if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
+ try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()))) {
+ int len;
+ char buf[] = new char[4096];
+ while ((len = br.read(buf)) != -1) {
+ builder.append(buf, 0, len);
+ }
+ }
+ }
+ conn.disconnect();
+ return builder.toString();
+ } catch (IOException e) {
+ e.printStackTrace();
+ return "";
+ }
+ }
+
+ public static HttpURLConnection request(String address, Map header) throws IOException {
+ URL url = new URL(address);
+
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ conn.setReadTimeout(15000);
+ conn.setConnectTimeout(15000);
+
+ if (header != null) {
+ for (Map.Entry entry : header.entrySet()) {
+ conn.setRequestProperty(entry.getKey(), entry.getValue());
+ }
+ }
+
+ conn.connect();
+
+ if (header != null) {
+ header.clear();
+ for (Map.Entry> entry : conn.getHeaderFields().entrySet()) {
+ List l = entry.getValue();
+ header.put(entry.getKey(), l.get(l.size() - 1));
+ }
+ }
+
+ return conn;
+ }
+}
diff --git a/app/src/main/res/drawable-v26/ic_launcher.xml b/app/src/main/res/drawable-v26/ic_launcher.xml
new file mode 100644
index 000000000..29421e133
--- /dev/null
+++ b/app/src/main/res/drawable-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_logo.xml b/app/src/main/res/drawable/ic_logo.xml
new file mode 100644
index 000000000..42a1f7989
--- /dev/null
+++ b/app/src/main/res/drawable/ic_logo.xml
@@ -0,0 +1,11 @@
+
+
+ -
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_magisk.xml b/app/src/main/res/drawable/ic_magisk.xml
new file mode 100644
index 000000000..2725dd470
--- /dev/null
+++ b/app/src/main/res/drawable/ic_magisk.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_magisk_padded.xml b/app/src/main/res/drawable/ic_magisk_padded.xml
new file mode 100644
index 000000000..47d089a6e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_magisk_padded.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
new file mode 100644
index 000000000..3f178f4ac
--- /dev/null
+++ b/app/src/main/res/values-ar/strings.xml
@@ -0,0 +1,8 @@
+
+ جاري التنزيل %1$s
+ خطأ تنزيل الملف
+ أن هذه الميزة لا تعمل دون الحصول على إذن الكتابة على التخزين الخارجي.
+ لا شكراً
+ نعم
+ موافق
+
diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml
new file mode 100644
index 000000000..b9301e449
--- /dev/null
+++ b/app/src/main/res/values-bg/strings.xml
@@ -0,0 +1,8 @@
+
+ Изтегляне на %1$s
+ Грешка при изтеглянето на файла
+ Тази функция няма да работи без разрешение за запис във външната памет.
+ Не, благодаря
+ Да
+ OK
+
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
new file mode 100644
index 000000000..c5e43ccdb
--- /dev/null
+++ b/app/src/main/res/values-cs/strings.xml
@@ -0,0 +1,8 @@
+
+ Chyba při Stahování souboru
+ Stahování %1$s
+ Tato funkce nebude fungovat bez povolení k zápisu na externí úložiště.
+ Ne, díky
+ Ano
+ OK
+
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
new file mode 100644
index 000000000..6b9ad07fa
--- /dev/null
+++ b/app/src/main/res/values-de/strings.xml
@@ -0,0 +1,8 @@
+
+ Fehler beim Herunterladen der Datei
+ Herunterladen von %1$s
+ Diese Funktion benötigt Rechte zum Schreiben auf den externen Speicher.
+ Nein danke
+ Ja
+ OK
+
diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml
new file mode 100644
index 000000000..22b9805fa
--- /dev/null
+++ b/app/src/main/res/values-el/strings.xml
@@ -0,0 +1,8 @@
+
+ Σφάλμα στη λήψη του αρχείου
+ Κατέβασμα %1$s
+ Η λειτουργία αυτή δεν θα δουλέψει χωρίς την άδεια εγγραφής στον εξωτερικό χώρο αποθηκεύσης.
+ Όχι ευχαριστώ
+ Ναι
+ OK
+
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
new file mode 100644
index 000000000..cd2cc5206
--- /dev/null
+++ b/app/src/main/res/values-es/strings.xml
@@ -0,0 +1,8 @@
+
+ Error descargando archivo
+ Descargando %1$s
+ Esta opción no funcionará sin permiso de escritura en la memoria externa.
+ No gracias
+ Sí
+ Aceptar
+
diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml
new file mode 100644
index 000000000..14488efc3
--- /dev/null
+++ b/app/src/main/res/values-et/strings.xml
@@ -0,0 +1,8 @@
+
+ Faili allalaadimisel esines viga
+ Laadin %1$s alla
+ See funktsioon ei tööta ilma välismälule kirjutamise õiguseta.
+ Tänan ei
+ Jah
+ OK
+
\ No newline at end of file
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
new file mode 100644
index 000000000..0b0e1ff4e
--- /dev/null
+++ b/app/src/main/res/values-fr/strings.xml
@@ -0,0 +1,10 @@
+
+
+
+ Erreur de téléchargement du fichier
+ Téléchargement à %1$s
+ Cette fonctionnalité ne marchera pas sans la permission d\'écriture sur le stockage externe.
+ Non merci
+ Oui
+ OK
+
diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml
new file mode 100644
index 000000000..17f27ce12
--- /dev/null
+++ b/app/src/main/res/values-hr/strings.xml
@@ -0,0 +1,8 @@
+
+ Pogreška prilikom preuzimanja datoteke
+ Preuzimanje %1$s
+ Ova značajka neće raditi bez dopuštenja za korištenje vanjske pohrane.
+ Ne hvala
+ Da
+ OK
+
\ No newline at end of file
diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml
new file mode 100644
index 000000000..3a54f47f5
--- /dev/null
+++ b/app/src/main/res/values-in/strings.xml
@@ -0,0 +1,8 @@
+
+ Kesalahan mengunduh file
+ Mengunduh %1$s
+ Fitur ini tidak akan bekerja tanpa izin untuk menulis ke penyimpanan eksternal.
+ Tidak, terima kasih
+ Ya
+ OK
+
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
new file mode 100644
index 000000000..90309086d
--- /dev/null
+++ b/app/src/main/res/values-it/strings.xml
@@ -0,0 +1,8 @@
+
+ Errore nel download del file
+ Download di %1$s
+ Questa funzione non sarà attiva senza l\'autorizzazione di scrittura nella memoria di archiviazione esterna
+ No, grazie
+ Sì
+ OK
+
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
new file mode 100644
index 000000000..02dd2c484
--- /dev/null
+++ b/app/src/main/res/values-ja/strings.xml
@@ -0,0 +1,8 @@
+
+ ダウンロード中にエラーが発生しました
+ %1$s をダウンロード中
+ この機能は外部ストレージへの書き込み権限がないと動作しません
+ いいえ
+ はい
+ OK
+
\ No newline at end of file
diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml
new file mode 100644
index 000000000..e43577a64
--- /dev/null
+++ b/app/src/main/res/values-ko/strings.xml
@@ -0,0 +1,8 @@
+
+ 파일 다운로드 오류
+ %1$s 다운로드 중
+ 이 기능은 외부 저장소 쓰기 권한 없이는 작동하지 않습니다.
+ 아니오, 괜찮습니다
+ 예
+ 확인
+
diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml
new file mode 100644
index 000000000..d5eaef534
--- /dev/null
+++ b/app/src/main/res/values-lt/strings.xml
@@ -0,0 +1,8 @@
+
+ Atsisiunčiant failą įvyko klaida
+ Atsisiunčiamas %1$s
+ Ši funkija neveiks be prieigos prie saugyklos
+ Ačiū, nereikia
+ Taip
+ Gerai
+
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
new file mode 100644
index 000000000..3fc43244f
--- /dev/null
+++ b/app/src/main/res/values-nl/strings.xml
@@ -0,0 +1,8 @@
+
+ Fout tijdens downloaden
+ %1$s downloaden
+ Deze functie werkt niet zonder schrijfpermissie voor externe opslag.
+ Nee bedankt
+ Ja
+ Oké
+
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
new file mode 100644
index 000000000..8950be046
--- /dev/null
+++ b/app/src/main/res/values-pl/strings.xml
@@ -0,0 +1,8 @@
+
+ Błąd pobierania pliku
+ Pobieranie %1$s
+ Ta funkcja nie będzie działać bez uprawnień do zapisu na pamięci zewnętrznej.
+ Nie dziękuję
+ Tak
+ OK
+
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
new file mode 100644
index 000000000..b99b18c3a
--- /dev/null
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -0,0 +1,8 @@
+
+ Erro ao baixar arquivo
+ Baixando %1$s
+ Este recurso não funcionará sem permissão para gravar no armazenamento externo.
+ Não
+ Sim
+ OK
+
diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml
new file mode 100644
index 000000000..f1a37db89
--- /dev/null
+++ b/app/src/main/res/values-pt-rPT/strings.xml
@@ -0,0 +1,8 @@
+
+ Erro ao transferir ficheiro
+ A transferir %1$s
+ Esta funcionalidade não funcionará sem permissão de escrita do armazenamento externo.
+ Não, Obrigado
+ Sim
+ OK
+
diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml
new file mode 100644
index 000000000..4a2f02a92
--- /dev/null
+++ b/app/src/main/res/values-ro/strings.xml
@@ -0,0 +1,8 @@
+
+ Eroare la descărcarea fișierului
+ Descărcare %1$s
+ Această caracteristică nu va funcționa fără permisiunea de a scrie pe memoria externă.
+ Nu, mulţumesc
+ Da
+ OK
+
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
new file mode 100644
index 000000000..86464acbb
--- /dev/null
+++ b/app/src/main/res/values-ru/strings.xml
@@ -0,0 +1,8 @@
+
+ Ошибка загрузки файла
+ Загрузка %1$s
+ Требуется разрешение на запись во внешнее хранилище.
+ Нет
+ Да
+ OK
+
diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml
new file mode 100644
index 000000000..d5f89cb60
--- /dev/null
+++ b/app/src/main/res/values-sr/strings.xml
@@ -0,0 +1,8 @@
+
+ Грешка при преузимању фајла
+ Преузимање %1$s
+ Ово својство неће радити без дозволе приступу екстерном складишту.
+ Не хвала
+ Да
+ ОК
+
diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml
new file mode 100644
index 000000000..e7524cb1c
--- /dev/null
+++ b/app/src/main/res/values-sv/strings.xml
@@ -0,0 +1,8 @@
+
+ Fel vid nerladdning av fil
+ Laddar ner %1$s
+ Denna funktionen måste ha behörighet att skriva till externt lagringsutrymme.
+ Nej tack
+ Ja
+ OK
+
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
new file mode 100644
index 000000000..646eacc60
--- /dev/null
+++ b/app/src/main/res/values-tr/strings.xml
@@ -0,0 +1,8 @@
+
+ Dosya indirme hatası
+ %1$s indiriliyor
+ Bu özellik harici depolamaya yazma izni olmadan çalışmaz.
+ Hayır teşekkürler
+ Evet
+ Tamam
+
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
new file mode 100644
index 000000000..78cf2cba2
--- /dev/null
+++ b/app/src/main/res/values-uk/strings.xml
@@ -0,0 +1,8 @@
+
+ Помилка завантаження файлу
+ Завантаження %1$s
+ Ця функція не працюватиме без дозволу на запис у Внутрішнє сховище
+ Ні, дякую
+ Так
+ OK
+
diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml
new file mode 100644
index 000000000..f1d717ae5
--- /dev/null
+++ b/app/src/main/res/values-vi/strings.xml
@@ -0,0 +1,8 @@
+
+ Lỗi tải tập tin
+ Đang tải xuống %1$s
+ Tính năng này không hoạt động nếu thiếu quyền ghi vào bộ nhớ ngoài.
+ Không, cảm ơn
+ Có
+ OK
+
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
new file mode 100644
index 000000000..8c88b7fd6
--- /dev/null
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -0,0 +1,8 @@
+
+ 下载文件时出错
+ 正在下载 %1$s
+ 未授予写入内置存储权限,此功能无法正常工作。
+ 不,谢谢
+ 是
+ 好
+
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
new file mode 100644
index 000000000..f044dd1a8
--- /dev/null
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -0,0 +1,8 @@
+
+ 下載文件時出錯
+ 正在下載 %1$s
+ 未授予寫入外部存儲權限,此功能無法正常工作。
+ 不,謝謝
+ 是
+ 好
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 000000000..633134384
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,23 @@
+
+
+
+ #009688
+ #00796B
+ #FFC107
+ #FBC02D
+ #757575
+
+ #F44336
+ #4CAF50
+ #9E9E9E
+ #2196f3
+ #FFC107
+
+ #dedede
+ #e0e0e0
+
+
+ @android:color/black
+
+ #00AF9C
+
diff --git a/app/src/main/res/values/drawables.xml b/app/src/main/res/values/drawables.xml
new file mode 100644
index 000000000..6a7707251
--- /dev/null
+++ b/app/src/main/res/values/drawables.xml
@@ -0,0 +1,4 @@
+
+
+ @drawable/ic_logo
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 000000000..71c93db17
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,17 @@
+
+
+
+
+ Magisk Manager
+ Magisk
+ Magisk Hide
+
+
+ Error downloading file
+ Downloading %1$s
+ This feature will not work without permission to write external storage.
+ No thanks
+ Yes
+ OK
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/file_paths.xml b/app/src/main/res/xml/file_paths.xml
new file mode 100644
index 000000000..4495c28c8
--- /dev/null
+++ b/app/src/main/res/xml/file_paths.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/stub/AndroidManifest.xml b/app/src/stub/AndroidManifest.xml
new file mode 100644
index 000000000..5f01dafef
--- /dev/null
+++ b/app/src/stub/AndroidManifest.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/stub/java/com/topjohnwu/magisk/MagiskManager.java b/app/src/stub/java/com/topjohnwu/magisk/MagiskManager.java
new file mode 100644
index 000000000..9a112f1ce
--- /dev/null
+++ b/app/src/stub/java/com/topjohnwu/magisk/MagiskManager.java
@@ -0,0 +1,14 @@
+package com.topjohnwu.magisk;
+
+import com.topjohnwu.magisk.components.Application;
+
+public class MagiskManager extends Application {
+
+ public int magiskVersionCode = -1;
+
+ public static MagiskManager get() {
+ return (MagiskManager) weakSelf.get();
+ }
+
+ public void dumpPrefs() {/* NOP */}
+}
diff --git a/app/src/stub/java/com/topjohnwu/magisk/NoUIActivity.java b/app/src/stub/java/com/topjohnwu/magisk/NoUIActivity.java
new file mode 100644
index 000000000..4caa96115
--- /dev/null
+++ b/app/src/stub/java/com/topjohnwu/magisk/NoUIActivity.java
@@ -0,0 +1,65 @@
+package com.topjohnwu.magisk;
+
+import android.Manifest;
+import android.app.AlertDialog;
+import android.os.AsyncTask;
+import android.os.Bundle;
+
+import com.topjohnwu.magisk.components.Activity;
+import com.topjohnwu.magisk.receivers.ManagerInstall;
+import com.topjohnwu.magisk.utils.Const;
+import com.topjohnwu.magisk.utils.Utils;
+import com.topjohnwu.magisk.utils.WebService;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class NoUIActivity extends Activity {
+
+ private String apkLink;
+ private String version;
+ private int versionCode;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (Utils.checkNetworkStatus()) {
+ AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
+ String str = WebService.getString(Const.Url.STABLE_URL);
+ try {
+ JSONObject json = new JSONObject(str);
+ JSONObject manager = json.getJSONObject("app");
+ version = manager.getString("version");
+ versionCode = manager.getInt("versionCode");
+ apkLink = manager.getString("link");
+ } catch (JSONException e) {
+ e.printStackTrace();
+ finish();
+ return;
+ }
+ runOnUiThread(() -> {
+ String filename = Utils.fmt("MagiskManager-v%s(%d).apk", version, versionCode);
+ new AlertDialog.Builder(this, AlertDialog.THEME_DEVICE_DEFAULT_LIGHT)
+ .setCancelable(false)
+ .setTitle(R.string.app_name)
+ .setMessage(R.string.upgrade_msg)
+ .setPositiveButton(R.string.yes, (d, w) -> runWithPermission(new String[]
+ { Manifest.permission.WRITE_EXTERNAL_STORAGE }, () -> {
+ Utils.dlAndReceive(this, new ManagerInstall(), apkLink, filename);
+ finish();
+ }))
+ .setNegativeButton(R.string.no_thanks, (d, w) -> finish())
+ .show();
+ });
+ });
+ } else {
+ new AlertDialog.Builder(this, AlertDialog.THEME_DEVICE_DEFAULT_LIGHT)
+ .setCancelable(false)
+ .setTitle(R.string.app_name)
+ .setMessage(R.string.no_internet_msg)
+ .setNegativeButton(R.string.ok, (d, w) -> finish())
+ .show();
+ }
+
+ }
+}
diff --git a/app/src/stub/java/com/topjohnwu/magisk/components/FlavorActivity.java b/app/src/stub/java/com/topjohnwu/magisk/components/FlavorActivity.java
new file mode 100644
index 000000000..eda11a52e
--- /dev/null
+++ b/app/src/stub/java/com/topjohnwu/magisk/components/FlavorActivity.java
@@ -0,0 +1,5 @@
+package com.topjohnwu.magisk.components;
+
+import android.app.Activity;
+
+public abstract class FlavorActivity extends Activity {}
diff --git a/app/src/stub/java/com/topjohnwu/magisk/receivers/BootLauncher.java b/app/src/stub/java/com/topjohnwu/magisk/receivers/BootLauncher.java
new file mode 100644
index 000000000..8f7060bea
--- /dev/null
+++ b/app/src/stub/java/com/topjohnwu/magisk/receivers/BootLauncher.java
@@ -0,0 +1,16 @@
+package com.topjohnwu.magisk.receivers;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import com.topjohnwu.magisk.NoUIActivity;
+
+public class BootLauncher extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Intent i = new Intent(context, NoUIActivity.class);
+ i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(i);
+ }
+}
diff --git a/app/src/stub/res/values-bg/strings.xml b/app/src/stub/res/values-bg/strings.xml
new file mode 100644
index 000000000..2b3e84751
--- /dev/null
+++ b/app/src/stub/res/values-bg/strings.xml
@@ -0,0 +1,5 @@
+
+
+ Надградете до пълната версия на Magisk Manager, за да довършите първоначалната настройка. Изтегляне и инсталиране сега?
+ Моля да се свържете към работеща интернет мрежа, защото надграждането до пълната версия на Magisk Manager е задължително.
+
diff --git a/app/src/stub/res/values-de/strings.xml b/app/src/stub/res/values-de/strings.xml
new file mode 100644
index 000000000..17e6bff6c
--- /dev/null
+++ b/app/src/stub/res/values-de/strings.xml
@@ -0,0 +1,5 @@
+
+
+ Upgrade zu vollständigem Magisk Manager, um das Setup zu beenden. Herunterladen und installieren?
+ Bitte mit dem Internet verbinden! Upgrade zu volsltändigem Magisk Manager ist erforderlich.
+
diff --git a/app/src/stub/res/values-es/strings.xml b/app/src/stub/res/values-es/strings.xml
new file mode 100644
index 000000000..acd1b12f1
--- /dev/null
+++ b/app/src/stub/res/values-es/strings.xml
@@ -0,0 +1,5 @@
+
+
+ Actualizar a la Ver. Completa de Magisk Manager para finalizar la instalación. Descargar e instalar?
+ ¡Por favor conectarse a Internet! Se requiere actualizar a la Ver. Completa de Magisk Manager
+
diff --git a/app/src/stub/res/values-fr/strings.xml b/app/src/stub/res/values-fr/strings.xml
new file mode 100644
index 000000000..bcb5563c8
--- /dev/null
+++ b/app/src/stub/res/values-fr/strings.xml
@@ -0,0 +1,5 @@
+
+
+ Mettre à jour vers Magisk Manager version complête pour finir l\'installation. Télécharger et installer?
+ Veuillez vous connecter à Internet! Mise à niveau de Magisk Manager requis.
+
diff --git a/app/src/stub/res/values-in/strings.xml b/app/src/stub/res/values-in/strings.xml
new file mode 100644
index 000000000..0cec7e72a
--- /dev/null
+++ b/app/src/stub/res/values-in/strings.xml
@@ -0,0 +1,5 @@
+
+
+ Tingkatkan ke Magisk Manager versi penuh untuk menyelesaikan penyiapan. Unduh dan pasang?
+ Harap menyambungkan ke Internet! Peningkatan ke Magisk Manager versi penuh diperlukan.
+
diff --git a/app/src/stub/res/values-it/strings.xml b/app/src/stub/res/values-it/strings.xml
new file mode 100644
index 000000000..0f5d3ffbc
--- /dev/null
+++ b/app/src/stub/res/values-it/strings.xml
@@ -0,0 +1,5 @@
+
+
+ Aggiorna al Magisk Manager completo per completare l\'installazione. Vuoi procedere al download e all\'installazione?
+ Controlla la connessione a Internet! È necessaria per l\'aggiornamento al Magisk Manager completo.
+
diff --git a/app/src/stub/res/values-lt/strings.xml b/app/src/stub/res/values-lt/strings.xml
new file mode 100644
index 000000000..a0df2e8ea
--- /dev/null
+++ b/app/src/stub/res/values-lt/strings.xml
@@ -0,0 +1,5 @@
+
+
+ Atsinaujinkite į pilną Magisk Manager versiją, kad baigtumėte pasiruošimą. Atsisiųsti ir instaliuoti?
+ Prašome prisijungti prie interneto! Atsinaujinimas į pilną Magisk Manager versiją yra privalomas.
+
\ No newline at end of file
diff --git a/app/src/stub/res/values-pl/strings.xml b/app/src/stub/res/values-pl/strings.xml
new file mode 100644
index 000000000..e4f02e80f
--- /dev/null
+++ b/app/src/stub/res/values-pl/strings.xml
@@ -0,0 +1,5 @@
+
+
+ Przejdź na pełną wersję programu Magisk Manager, aby ukończyć konfigurację. Ściągnąć i zainstalować?
+ Połącz się z Internetem! Wymagana jest aktualizacja do pełnego programu Magisk Manager.
+
diff --git a/app/src/stub/res/values-ro/strings.xml b/app/src/stub/res/values-ro/strings.xml
new file mode 100644
index 000000000..675b9c888
--- /dev/null
+++ b/app/src/stub/res/values-ro/strings.xml
@@ -0,0 +1,5 @@
+
+
+ Treceți la versiunea completă a Magic Manager pentru a finaliza configurarea. Descărcaţi şi instalaţi?
+ Conectați-vă la internet! Este necesară actualizarea la Magisk Manager complet.
+
diff --git a/app/src/stub/res/values-ru/strings.xml b/app/src/stub/res/values-ru/strings.xml
new file mode 100644
index 000000000..4e288d2c7
--- /dev/null
+++ b/app/src/stub/res/values-ru/strings.xml
@@ -0,0 +1,5 @@
+
+
+ Обновите Magisk Manager для завершения установки. Загрузить и установить?
+ Пожалуйста, подключитесь к интернету! Требуется обновление Magisk Manager.
+
diff --git a/app/src/stub/res/values-tr/strings.xml b/app/src/stub/res/values-tr/strings.xml
new file mode 100644
index 000000000..a7af6d6fe
--- /dev/null
+++ b/app/src/stub/res/values-tr/strings.xml
@@ -0,0 +1,5 @@
+
+
+ Kurulumu tamamlamak için tam Magisk Manager\'a yükseltin. İndirip yüklensin mi?
+ Lütfen internete bağlanın! Tam Magisk Manager\'a yükseltmek gerekiyor.
+
diff --git a/app/src/stub/res/values-zh-rCN/strings.xml b/app/src/stub/res/values-zh-rCN/strings.xml
new file mode 100644
index 000000000..c4045136a
--- /dev/null
+++ b/app/src/stub/res/values-zh-rCN/strings.xml
@@ -0,0 +1,5 @@
+
+
+ 需要升级到完整的 Magisk Manager 来完成安装。 现在下载?
+ 请连接到互联网! 这是升级到完整 Magisk Manager 所必需的。
+
diff --git a/app/src/stub/res/values-zh-rTW/strings.xml b/app/src/stub/res/values-zh-rTW/strings.xml
new file mode 100644
index 000000000..73d202e02
--- /dev/null
+++ b/app/src/stub/res/values-zh-rTW/strings.xml
@@ -0,0 +1,5 @@
+
+
+ 需要升級到完整版 Magisk Manager。是否下載並安裝?
+ 請連上網路!升級到完整版 Magisk Manager 是必須的。
+
diff --git a/app/src/stub/res/values/strings.xml b/app/src/stub/res/values/strings.xml
new file mode 100644
index 000000000..120d99fb6
--- /dev/null
+++ b/app/src/stub/res/values/strings.xml
@@ -0,0 +1,5 @@
+
+
+ Upgrade to full Magisk Manager to finish the setup. Download and install?
+ Please connect to the Internet! Upgrading to full Magisk Manager is required.
+