Compare commits

..

21 Commits

Author SHA1 Message Date
topjohnwu
55ecc41d06 Bump version 2017-07-20 03:20:17 +08:00
#DarkBasic - BasicHD
28fcdf2cbb Update strings.xml
Delele Translate "Magisk Modo Sólo Núcleo". (After several hours (Days :v ). I thought it was best left in its original form .Magisk Hide, should also be translated if it were the case, it was better to leave it that way so as not to confuse the users.)
Fix translation error
Translations Updates and added new line
2017-07-20 03:19:58 +08:00
topjohnwu
24087679a8 Update uninstaller 2017-07-20 02:56:36 +08:00
topjohnwu
5ac6a8cb4a Small minor updates 2017-07-20 02:54:34 +08:00
topjohnwu
668d85d14e Improve notification support 2017-07-20 01:44:32 +08:00
topjohnwu
c11a3dc95c Fix Magisk Manager freezing issue 2017-07-20 00:51:30 +08:00
topjohnwu
56f57c20a2 Update AsyncTasks to prevent memory leak 2017-07-19 18:01:22 +08:00
topjohnwu
240d14779a Minor cleanup in check updates 2017-07-19 16:10:17 +08:00
topjohnwu
3550d1e61c Bump version 2017-07-19 00:38:25 +08:00
topjohnwu
6513ad249c Fix string.xml 2017-07-19 00:36:54 +08:00
killer7mod
50297b1880 update strings.xml portuguese brazil 2017-07-19 00:25:36 +08:00
#DarkBasic - BasicHD
f189b78b9e #DBC01 - Translation update 2017-07-19 00:25:23 +08:00
zertyuiop
5c0250f495 Fix too long string
checking_safetyNet_status string is too long.
2017-07-19 00:24:47 +08:00
pavlaras
2093f726e9 Update strings.xml
corrected Greek translation
2017-07-19 00:24:36 +08:00
topjohnwu
10efe3859d Update repo fragment and adapter 2017-07-18 23:18:57 +08:00
topjohnwu
6933bcf7bb Merge shells 2017-07-18 17:14:42 +08:00
topjohnwu
2ea046cd80 Add flashing screen 2017-07-18 17:14:42 +08:00
topjohnwu
f4097a372b Root shell with no outputs 2017-07-18 01:06:05 +08:00
topjohnwu
87ea2a2bef Rewrite root shell 2017-07-16 03:00:01 +08:00
JpegXguy
cc14a1c361 Fix untranslated strings 2017-07-15 01:23:59 +08:00
topjohnwu
bcdface60d Fix crashing when installing modules 2017-07-15 01:22:00 +08:00
45 changed files with 1037 additions and 1106 deletions

View File

@@ -8,8 +8,8 @@ android {
applicationId "com.topjohnwu.magisk"
minSdkVersion 21
targetSdkVersion 26
versionCode 45
versionName "5.0.5"
versionCode 51
versionName "5.1.1"
ndk {
moduleName 'zipadjust'
abiFilters 'x86', 'armeabi-v7a'

View File

@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
package="com.topjohnwu.magisk"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.topjohnwu.magisk">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
@@ -22,8 +21,7 @@
<activity
android:name=".MainActivity"
android:configChanges="orientation|screenSize"
android:exported="true"/>
android:exported="true" />
<activity
android:name=".SplashActivity"
android:configChanges="orientation|screenSize"
@@ -31,16 +29,22 @@
android:theme="@style/SplashTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".AboutActivity"
android:theme="@style/AppTheme.Transparent"/>
android:theme="@style/AppTheme.Transparent" />
<activity
android:name=".SettingsActivity"
android:theme="@style/AppTheme.Transparent" />
<activity
android:name=".FlashActivity"
android:screenOrientation="nosensor"
android:configChanges="keyboardHidden|orientation|screenSize"
android:theme="@style/AppTheme.Transparent" />
<activity
android:name=".superuser.RequestActivity"
android:excludeFromRecents="true"
@@ -54,29 +58,26 @@
android:theme="@style/SuRequest" />
<receiver android:name=".superuser.SuReceiver" />
<receiver android:name=".receivers.BootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver android:name=".receivers.PackageReceiver">
<intent-filter>
<action android:name="android.intent.action.PACKAGE_REPLACED" />
<action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
<data android:scheme="package" />
</intent-filter>
</receiver>
<receiver android:name=".receivers.ManagerUpdate" />
<service android:name=".services.OnBootIntentService" />
<service
android:name=".services.UpdateCheckService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="true" />
android:exported="true"
android:permission="android.permission.BIND_JOB_SERVICE" />
<provider
android:name="android.support.v4.content.FileProvider"
@@ -94,4 +95,4 @@
</application>
</manifest>
</manifest>

View File

@@ -65,7 +65,22 @@ cd $MAGISKBIN
ui_print_wrap "- Unpacking boot image"
./magiskboot --unpack "$BOOTIMAGE"
[ $? -ne 0 ] && abort_wrap "! Unable to unpack boot image"
CHROMEOS=false
case $? in
1 )
abort_wrap "! Unable to unpack boot image"
;;
2 )
CHROMEOS=true
;;
3 )
ui_print_wrap "! Sony ELF32 format detected"
abort_wrap "! Please use BootBridge from @AdrianDC to flash Magisk"
;;
4 )
ui_print_wrap "! Sony ELF64 format detected"
abort_wrap "! Stock kernel cannot be patched, please use a custom kernel"
esac
# Update our previous backup to new format if exists
if [ -f /data/stock_boot.img ]; then
@@ -109,11 +124,11 @@ case $? in
esac
# Sign chromeos boot
if [ -f chromeos ]; then
if $CHROMEOS; then
echo > empty
LD_LIBRARY_PATH=$SYSTEMLIB $CHROMEDIR/futility vbutil_kernel --pack stock_boot.img.signed \
--keyblock $CHROMEDIR/kernel.keyblock --signprivate $CHROMEDIR/kernel_data_key.vbprivk \
./chromeos/futility vbutil_kernel --pack stock_boot.img.signed \
--keyblock ./chromeos/kernel.keyblock --signprivate ./chromeos/kernel_data_key.vbprivk \
--version 1 --vmlinuz stock_boot.img --config empty --arch arm --bootloader empty --flags 0x1
rm -f empty stock_boot.img
@@ -132,6 +147,7 @@ ui_print_wrap "- Removing Magisk files"
rm -rf /cache/magisk.log /cache/last_magisk.log /cache/magiskhide.log /cache/.disable_magisk \
/cache/magisk /cache/magisk_merge /cache/magisk_mount /cache/unblock /cache/magisk_uninstaller.sh \
/data/Magisk.apk /data/magisk.apk /data/magisk.img /data/magisk_merge.img /data/magisk_debug.log \
/data/busybox /data/magisk /data/custom_ramdisk_patch.sh 2>/dev/null
/data/busybox /data/magisk /data/custom_ramdisk_patch.sh /data/property/*magisk* \
/data/app/com.topjohnwu.magisk* /data/user/*/com.topjohnwu.magisk 2>/dev/null
$BOOTMODE && reboot

View File

@@ -0,0 +1,98 @@
package com.topjohnwu.magisk;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.topjohnwu.magisk.asyncs.FlashZip;
import com.topjohnwu.magisk.components.Activity;
import com.topjohnwu.magisk.utils.AdaptiveList;
import com.topjohnwu.magisk.utils.Shell;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
public class FlashActivity extends Activity {
@BindView(R.id.toolbar) Toolbar toolbar;
@BindView(R.id.flash_logs) RecyclerView flashLogs;
@BindView(R.id.button_panel) LinearLayout buttonPanel;
private AdaptiveList<String> rootShellOutput;
@OnClick(R.id.no_thanks)
public void dismiss() {
finish();
}
@OnClick(R.id.reboot)
public void reboot() {
Shell.getShell(this).su_raw("reboot");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_flash);
ButterKnife.bind(this);
rootShellOutput = new AdaptiveList<>(flashLogs);
setSupportActionBar(toolbar);
ActionBar ab = getSupportActionBar();
if (ab != null) {
ab.setTitle(R.string.flashing);
}
setFloating();
flashLogs.setAdapter(new FlashLogAdapter());
// We must receive a Uri of the target zip
Uri uri = getIntent().getData();
new FlashZip(this, uri, rootShellOutput)
.setCallBack(() -> buttonPanel.setVisibility(View.VISIBLE))
.exec();
}
@Override
public void onBackPressed() {
// Prevent user accidentally press back button
}
private class FlashLogAdapter extends RecyclerView.Adapter<ViewHolder> {
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_item_flashlog, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.text.setText(rootShellOutput.get(position));
}
@Override
public int getItemCount() {
return rootShellOutput.size();
}
}
public class ViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.textView) TextView text;
public ViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
}
}

View File

@@ -2,7 +2,9 @@ package com.topjohnwu.magisk;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.app.NotificationManager;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
@@ -25,7 +27,6 @@ import android.widget.Spinner;
import android.widget.TextView;
import com.topjohnwu.magisk.asyncs.CheckUpdates;
import com.topjohnwu.magisk.asyncs.ProcessMagiskZip;
import com.topjohnwu.magisk.components.AlertDialogBuilder;
import com.topjohnwu.magisk.components.Fragment;
import com.topjohnwu.magisk.components.SnackbarMaker;
@@ -50,7 +51,8 @@ import butterknife.Unbinder;
public class MagiskFragment extends Fragment
implements CallbackEvent.Listener<Void>, SwipeRefreshLayout.OnRefreshListener {
private static boolean noDialog = false;
public static final String SHOW_DIALOG = "dialog";
private static int expandHeight = 0;
private static boolean mExpanded = false;
@@ -104,25 +106,18 @@ public class MagiskFragment extends Fragment
collapse();
}
@OnClick(R.id.detect_bootimage)
public void toAutoDetect() {
if (magiskManager.bootBlock != null) {
spinner.setSelection(0);
}
}
@OnClick(R.id.install_button)
public void install() {
String bootImage = null;
if (magiskManager.blockList != null) {
int idx = spinner.getSelectedItemPosition();
if (Shell.rootAccess()) {
if (magiskManager.bootBlock != null) {
bootImage = magiskManager.bootBlock;
} else {
int idx = spinner.getSelectedItemPosition();
if (idx > 0) {
bootImage = magiskManager.blockList.get(idx - 1);
} else {
SnackbarMaker.make(getActivity(), R.string.manual_boot_image, Snackbar.LENGTH_LONG);
SnackbarMaker.make(getActivity(), R.string.manual_boot_image, Snackbar.LENGTH_LONG).show();
return;
}
}
@@ -134,21 +129,36 @@ public class MagiskFragment extends Fragment
.setMessage(getString(R.string.repo_install_msg, filename))
.setCancelable(true)
.setPositiveButton(Shell.rootAccess() ? R.string.install : R.string.download,
(dialogInterface, i) -> Utils.dlAndReceive(
getActivity(),
new DownloadReceiver() {
private String boot = finalBootImage;
private boolean enc = keepEncChkbox.isChecked();
private boolean verity = keepVerityChkbox.isChecked();
(d, i) -> {
((NotificationManager) getActivity()
.getSystemService(Context.NOTIFICATION_SERVICE)).cancelAll();
Utils.dlAndReceive(
getActivity(),
new DownloadReceiver() {
private String boot = finalBootImage;
private boolean enc = keepEncChkbox.isChecked();
private boolean verity = keepVerityChkbox.isChecked();
@Override
public void onDownloadDone(Uri uri) {
new ProcessMagiskZip(getActivity(), uri, boot, enc, verity).exec();
@Override
public void onDownloadDone(Uri uri) {
if (Shell.rootAccess()) {
magiskManager.shell.su_raw(
"rm -f /dev/.magisk",
"echo \"BOOTIMAGE=" + boot + "\" >> /dev/.magisk",
"echo \"KEEPFORCEENCRYPT=" + String.valueOf(enc) + "\" >> /dev/.magisk",
"echo \"KEEPVERITY=" + String.valueOf(verity) + "\" >> /dev/.magisk"
);
startActivity(new Intent(getActivity(), FlashActivity.class).setData(uri));
} else {
Utils.showUriSnack(getActivity(), uri);
}
},
magiskManager.magiskLink,
Utils.getLegalFilename(filename)))
.setNeutralButton(R.string.release_notes, (dialog, which) -> {
}
},
magiskManager.magiskLink,
Utils.getLegalFilename(filename));
}
)
.setNeutralButton(R.string.release_notes, (d, i) -> {
if (magiskManager.releaseNoteLink != null) {
Intent openReleaseNoteLink = new Intent(Intent.ACTION_VIEW, Uri.parse(magiskManager.releaseNoteLink));
openReleaseNoteLink.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -196,7 +206,7 @@ public class MagiskFragment extends Fragment
@Override
public void onFinish() {
progress.setMessage(getString(R.string.reboot_countdown, 0));
Shell.su(true,
magiskManager.shell.su_raw(
"mv -f " + uninstaller + " /cache/" + MagiskManager.UNINSTALLER,
"mv -f " + utils + " /data/magisk/" + MagiskManager.UTIL_FUNCTIONS,
"reboot"
@@ -238,19 +248,11 @@ public class MagiskFragment extends Fragment
mSwipeRefreshLayout.setOnRefreshListener(this);
if (magiskManager.magiskVersionCode < 0 && Shell.rootAccess() && !noDialog) {
noDialog = true;
new AlertDialogBuilder(getActivity())
.setTitle(R.string.no_magisk_title)
.setMessage(R.string.no_magisk_msg)
.setCancelable(true)
.setPositiveButton(R.string.goto_install, (d, i) -> {})
.setNegativeButton(R.string.no_thanks, null)
.show();
}
updateUI();
if (getArguments() != null && getArguments().getBoolean(SHOW_DIALOG))
install();
return v;
}
@@ -269,7 +271,6 @@ public class MagiskFragment extends Fragment
magiskManager.remoteMagiskVersionString = null;
magiskManager.remoteMagiskVersionCode = -1;
collapse();
noDialog = false;
// Trigger state check
if (Utils.checkNetworkStatus(magiskManager)) {
@@ -285,8 +286,6 @@ public class MagiskFragment extends Fragment
updateCheckUI();
} else if (event == magiskManager.safetyNetDone) {
updateSafetyNetUI();
} else if (event == magiskManager.blockDetectionDone) {
updateInstallUI();
}
}
@@ -298,11 +297,8 @@ public class MagiskFragment extends Fragment
updateCheckUI();
if (magiskManager.safetyNetDone.isTriggered)
updateSafetyNetUI();
if (magiskManager.blockDetectionDone.isTriggered || !Shell.rootAccess())
updateInstallUI();
magiskManager.updateCheckDone.register(this);
magiskManager.safetyNetDone.register(this);
magiskManager.blockDetectionDone.register(this);
getActivity().setTitle(R.string.magisk);
}
@@ -310,7 +306,6 @@ public class MagiskFragment extends Fragment
public void onStop() {
magiskManager.updateCheckDone.unRegister(this);
magiskManager.safetyNetDone.unRegister(this);
magiskManager.blockDetectionDone.unRegister(this);
super.onStop();
}
@@ -322,6 +317,8 @@ public class MagiskFragment extends Fragment
private void updateUI() {
((MainActivity) getActivity()).checkHideSection();
magiskManager.updateMagiskInfo();
final int ROOT = 0x1, NETWORK = 0x2, UPTODATE = 0x4;
int status = 0;
status |= Shell.rootAccess() ? ROOT : 0;
@@ -333,14 +330,9 @@ public class MagiskFragment extends Fragment
installOptionCard.setVisibility(Utils.checkBits(status, NETWORK, ROOT) ? View.VISIBLE : View.GONE);
installButton.setVisibility(Utils.checkBits(status, NETWORK) ? View.VISIBLE : View.GONE);
uninstallButton.setVisibility(Utils.checkBits(status, UPTODATE, ROOT) ? View.VISIBLE : View.GONE);
updateVersionUI();
}
private void updateVersionUI() {
int image, color;
magiskManager.updateMagiskInfo();
if (magiskManager.magiskVersionCode < 0) {
color = colorBad;
image = R.drawable.ic_cancel;
@@ -376,6 +368,29 @@ public class MagiskFragment extends Fragment
rootStatusIcon.setImageResource(image);
rootStatusIcon.setColorFilter(color);
if (!Shell.rootAccess()) {
installText.setText(R.string.download);
} else {
if (magiskManager.remoteMagiskVersionCode > magiskManager.magiskVersionCode) {
installText.setText(R.string.update);
} else {
installText.setText(R.string.reinstall);
}
List<String> items = new ArrayList<>();
if (magiskManager.bootBlock != null) {
items.add(getString(R.string.auto_detect, magiskManager.bootBlock));
spinner.setEnabled(false);
} else {
items.add(getString(R.string.cannot_auto_detect));
items.addAll(magiskManager.blockList);
}
ArrayAdapter<String> adapter = new ArrayAdapter<>(getActivity(),
android.R.layout.simple_spinner_item, items);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
}
}
private void updateCheckUI() {
@@ -397,28 +412,9 @@ public class MagiskFragment extends Fragment
magiskUpdateProgress.setVisibility(View.GONE);
mSwipeRefreshLayout.setRefreshing(false);
}
private void updateInstallUI() {
if (!Shell.rootAccess()) {
installText.setText(R.string.download);
} else {
installText.setText(R.string.download_install);
List<String> items = new ArrayList<>();
if (magiskManager.bootBlock != null) {
items.add(getString(R.string.auto_detect, magiskManager.bootBlock));
spinner.setEnabled(false);
} else {
items.add(getString(R.string.cannot_auto_detect));
items.addAll(magiskManager.blockList);
}
ArrayAdapter<String> adapter = new ArrayAdapter<>(getActivity(),
android.R.layout.simple_spinner_item, items);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
toAutoDetect();
}
if (magiskManager.remoteMagiskVersionCode > magiskManager.magiskVersionCode)
install();
}
private void updateSafetyNetUI() {

View File

@@ -1,16 +1,13 @@
package com.topjohnwu.magisk;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -24,17 +21,15 @@ import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import com.topjohnwu.magisk.asyncs.RootTask;
import com.topjohnwu.magisk.asyncs.ParallelTask;
import com.topjohnwu.magisk.components.Fragment;
import com.topjohnwu.magisk.components.SnackbarMaker;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Utils;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Calendar;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
@@ -127,45 +122,28 @@ public class MagiskLogFragment extends Fragment {
}
}
private class LogManager extends RootTask<Object, Void, Object> {
private class LogManager extends ParallelTask<Object, Void, Object> {
int mode;
File targetFile;
private int mode;
private File targetFile;
@SuppressLint("DefaultLocale")
@Override
protected Object doInRoot(Object... params) {
protected Object doInBackground(Object... params) {
MagiskManager magiskManager = MagiskLogFragment.this.getApplication();
mode = (int) params[0];
switch (mode) {
case 0:
List<String> logList = Utils.readFile(MAGISK_LOG);
if (Utils.isValidShellResponse(logList)) {
StringBuilder llog = new StringBuilder(15 * 10 * 1024);
for (String s : logList) {
llog.append(s).append("\n");
}
return llog.toString();
}
return "";
StringBuildingList logList = new StringBuildingList();
magiskManager.shell.su(logList, "cat " + MAGISK_LOG);
return logList.toString();
case 1:
Shell.su("echo > " + MAGISK_LOG);
magiskManager.shell.su_raw("echo > " + MAGISK_LOG);
SnackbarMaker.make(txtLog, R.string.logs_cleared, Snackbar.LENGTH_SHORT).show();
return "";
case 2:
if (ActivityCompat.checkSelfPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
}
return false;
}
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
return false;
}
Calendar now = Calendar.getInstance();
String filename = String.format(
"magisk_%s_%04d%02d%02d_%02d%02d%02d.log", "error",
@@ -180,19 +158,14 @@ public class MagiskLogFragment extends Fragment {
return false;
}
List<String> in = Utils.readFile(MAGISK_LOG);
if (Utils.isValidShellResponse(in)) {
try (FileWriter out = new FileWriter(targetFile)) {
for (String line : in)
out.write(line + "\n");
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
try (FileWriter out = new FileWriter(targetFile)) {
FileWritingList fileWritingList = new FileWritingList(out);
magiskManager.shell.su(fileWritingList, "cat " + MAGISK_LOG);
} catch (IOException e) {
e.printStackTrace();
return false;
}
return false;
return true;
}
return null;
}
@@ -200,12 +173,10 @@ public class MagiskLogFragment extends Fragment {
@Override
protected void onPostExecute(Object o) {
if (o == null) return;
boolean bool;
String llog;
switch (mode) {
case 0:
case 1:
llog = (String) o;
String llog = (String) o;
progressBar.setVisibility(View.GONE);
if (TextUtils.isEmpty(llog))
txtLog.setText(R.string.log_is_empty);
@@ -215,27 +186,64 @@ public class MagiskLogFragment extends Fragment {
hsvLog.post(() -> hsvLog.scrollTo(0, 0));
break;
case 2:
bool = (boolean) o;
boolean bool = (boolean) o;
if (bool) {
Toast.makeText(getActivity(), targetFile.toString(), Toast.LENGTH_LONG).show();
MagiskLogFragment.this.getApplication().toast(targetFile.toString(), Toast.LENGTH_LONG);
} else {
Toast.makeText(getActivity(), getString(R.string.logs_save_failed), Toast.LENGTH_LONG).show();
MagiskLogFragment.this.getApplication().toast(R.string.logs_save_failed, Toast.LENGTH_LONG);
}
break;
}
}
public void read() {
void read() {
exec(0);
}
public void clear() {
void clear() {
exec(1);
}
public void save() {
void save() {
exec(2);
}
}
private static class StringBuildingList extends Shell.AbstractList<String> {
StringBuilder builder;
StringBuildingList() {
builder = new StringBuilder();
}
@Override
public boolean add(String s) {
builder.append(s).append("\n");
return true;
}
@Override
public String toString() {
return builder.toString();
}
}
private static class FileWritingList extends Shell.AbstractList<String> {
private FileWriter writer;
FileWritingList(FileWriter out) {
writer = out;
}
@Override
public boolean add(String s) {
try {
writer.write(s + "\n");
} catch (IOException ignored) {}
return true;
}
}
}

View File

@@ -32,13 +32,14 @@ public class MagiskManager extends Application {
public static final String UNINSTALLER = "magisk_uninstaller.sh";
public static final String UTIL_FUNCTIONS= "util_functions.sh";
public static final String INTENT_SECTION = "section";
public static final String INTENT_VERSION = "version";
public static final String INTENT_LINK = "link";
public static final String BUSYBOX_VERSION = "1.26.2";
public static final String MAGISKHIDE_PROP = "persist.magisk.hide";
public static final String DISABLE_INDICATION_PROP = "ro.magisk.disable";
public static final String NOTIFICATION_CHANNEL = "magisk_update_notice";
// Events
public final CallbackEvent<Void> blockDetectionDone = new CallbackEvent<>();
public final CallbackEvent<Void> magiskHideDone = new CallbackEvent<>();
public final CallbackEvent<Void> reloadMainActivity = new CallbackEvent<>();
public final CallbackEvent<Void> moduleLoadDone = new CallbackEvent<>();
@@ -88,13 +89,16 @@ public class MagiskManager extends Application {
// Global resources
public SharedPreferences prefs;
public SuDatabaseHelper suDB;
public Shell shell;
private static Handler mHandler = new Handler();
@Override
public void onCreate() {
super.onCreate();
new File(getApplicationInfo().dataDir).mkdirs(); /* Create the app data directory */
prefs = PreferenceManager.getDefaultSharedPreferences(this);
shell = Shell.getShell();
}
public void toast(String msg, int duration) {
@@ -115,16 +119,14 @@ public class MagiskManager extends Application {
shellLogging = false;
}
magiskHide = prefs.getBoolean("magiskhide", true);
updateNotification = prefs.getBoolean("notification", true);
initSU();
// Always start a new root shell manually, just for safety
Shell.init();
updateMagiskInfo();
updateBlockInfo();
// Initialize busybox
File busybox = new File(getApplicationInfo().dataDir + "/busybox/busybox");
if (!busybox.exists() || !TextUtils.equals(prefs.getString("busybox_version", ""), BUSYBOX_VERSION)) {
busybox.getParentFile().mkdirs();
Shell.su(
shell.su_raw(
"cp -f " + new File(getApplicationInfo().nativeLibraryDir, "libbusybox.so") + " " + busybox,
"chmod -R 755 " + busybox.getParent(),
busybox + " --install -s " + busybox.getParent()
@@ -136,7 +138,7 @@ public class MagiskManager extends Application {
.putBoolean("magiskhide", magiskHide)
.putBoolean("notification", updateNotification)
.putBoolean("hosts", new File("/magisk/.core/hosts").exists())
.putBoolean("disable", Utils.itemExist(MAGISK_DISABLE_FILE))
.putBoolean("disable", Utils.itemExist(shell, MAGISK_DISABLE_FILE))
.putBoolean("su_reauth", suReauth)
.putString("su_request_timeout", String.valueOf(suRequestTimeout))
.putString("su_auto_response", String.valueOf(suResponseType))
@@ -147,7 +149,7 @@ public class MagiskManager extends Application {
.putString("busybox_version", BUSYBOX_VERSION)
.apply();
// Add busybox to PATH
Shell.su("PATH=$PATH:" + busybox.getParent());
shell.su_raw("PATH=$PATH:" + busybox.getParent());
// Create notification channel on Android O
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@@ -167,12 +169,9 @@ public class MagiskManager extends Application {
}
public void initSU() {
// Create the app data directory, so su binary can work properly
new File(getApplicationInfo().dataDir).mkdirs();
initSUConfig();
List<String> ret = Shell.sh("su -v");
List<String> ret = shell.sh("su -v");
if (Utils.isValidShellResponse(ret)) {
suVersion = ret.get(0);
isSuClient = suVersion.toUpperCase().contains("MAGISK");
@@ -185,10 +184,11 @@ public class MagiskManager extends Application {
}
public void updateMagiskInfo() {
updateNotification = prefs.getBoolean("notification", true);
List<String> ret;
ret = Shell.sh("magisk -v");
ret = shell.sh("magisk -v");
if (!Utils.isValidShellResponse(ret)) {
ret = Shell.sh("getprop magisk.version");
ret = shell.sh("getprop magisk.version");
if (Utils.isValidShellResponse(ret)) {
try {
magiskVersionString = ret.get(0);
@@ -197,24 +197,39 @@ public class MagiskManager extends Application {
}
} else {
magiskVersionString = ret.get(0).split(":")[0];
ret = Shell.sh("magisk -V");
ret = shell.sh("magisk -V");
try {
magiskVersionCode = Integer.parseInt(ret.get(0));
} catch (NumberFormatException ignored) {}
}
ret = Shell.sh("getprop " + DISABLE_INDICATION_PROP);
ret = shell.sh("getprop " + DISABLE_INDICATION_PROP);
try {
disabled = Utils.isValidShellResponse(ret) && Integer.parseInt(ret.get(0)) != 0;
} catch (NumberFormatException e) {
disabled = false;
}
ret = Shell.sh("getprop " + MAGISKHIDE_PROP);
ret = shell.sh("getprop " + MAGISKHIDE_PROP);
try {
magiskHide = !Utils.isValidShellResponse(ret) || Integer.parseInt(ret.get(0)) != 0;
} catch (NumberFormatException e) {
magiskHide = true;
}
}
public void updateBlockInfo() {
List<String> res = shell.su(
"for BLOCK in boot_a BOOT_A kern-a KERN-A android_boot ANDROID_BOOT kernel KERNEL boot BOOT lnx LNX; do",
"BOOTIMAGE=`ls /dev/block/by-name/$BLOCK || ls /dev/block/platform/*/by-name/$BLOCK || ls /dev/block/platform/*/*/by-name/$BLOCK` 2>/dev/null",
"[ ! -z \"$BOOTIMAGE\" ] && break",
"done",
"[ ! -z \"$BOOTIMAGE\" -a -L \"$BOOTIMAGE\" ] && BOOTIMAGE=`readlink $BOOTIMAGE`",
"echo \"$BOOTIMAGE\""
);
if (Utils.isValidShellResponse(res)) {
bootBlock = res.get(0);
} else {
blockList = shell.su("ls -d /dev/block/mmc* /dev/block/sd* 2>/dev/null");
}
}
}

View File

@@ -140,9 +140,11 @@ public class MainActivity extends Activity
if (item != null) {
switch (item) {
case "magisk":
case "install":
itemId = R.id.magisk;
break;
case "install":
itemId = -1;
break;
case "superuser":
itemId = R.id.superuser;
break;
@@ -174,6 +176,13 @@ public class MainActivity extends Activity
mDrawerItem = itemId;
navigationView.setCheckedItem(itemId);
switch (itemId) {
case -1:
Bundle args = new Bundle();
args.putBoolean(MagiskFragment.SHOW_DIALOG, true);
Fragment frag = new MagiskFragment();
frag.setArguments(args);
displayFragment(frag, "magisk", true);
break;
case R.id.magisk:
displayFragment(new MagiskFragment(), "magisk", true);
break;

View File

@@ -2,7 +2,6 @@ package com.topjohnwu.magisk;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
@@ -14,7 +13,6 @@ import android.view.ViewGroup;
import android.widget.TextView;
import com.topjohnwu.magisk.adapters.ModulesAdapter;
import com.topjohnwu.magisk.asyncs.FlashZip;
import com.topjohnwu.magisk.asyncs.LoadModules;
import com.topjohnwu.magisk.components.Fragment;
import com.topjohnwu.magisk.module.Module;
@@ -86,8 +84,9 @@ public class ModulesFragment extends Fragment implements CallbackEvent.Listener<
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == FETCH_ZIP_CODE && resultCode == Activity.RESULT_OK && data != null) {
// Get the URI of the selected file
final Uri uri = data.getData();
new FlashZip(getActivity(), uri).exec();
Intent intent = new Intent(getActivity(), FlashActivity.class);
intent.setData(data.getData()).putExtra("ACTION", "flash");
startActivity(intent);
}
}

View File

@@ -2,7 +2,6 @@ package com.topjohnwu.magisk;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
@@ -14,18 +13,11 @@ import android.widget.SearchView;
import android.widget.TextView;
import com.topjohnwu.magisk.adapters.ReposAdapter;
import com.topjohnwu.magisk.adapters.SimpleSectionedRecyclerViewAdapter;
import com.topjohnwu.magisk.asyncs.LoadRepos;
import com.topjohnwu.magisk.asyncs.ParallelTask;
import com.topjohnwu.magisk.components.Fragment;
import com.topjohnwu.magisk.module.Module;
import com.topjohnwu.magisk.module.Repo;
import com.topjohnwu.magisk.utils.CallbackEvent;
import com.topjohnwu.magisk.utils.Logger;
import java.util.ArrayList;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.Unbinder;
@@ -37,16 +29,7 @@ public class ReposFragment extends Fragment implements CallbackEvent.Listener<Vo
@BindView(R.id.empty_rv) TextView emptyRv;
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
private List<Repo> mUpdateRepos = new ArrayList<>();
private List<Repo> mInstalledRepos = new ArrayList<>();
private List<Repo> mOthersRepos = new ArrayList<>();
private List<Repo> fUpdateRepos = new ArrayList<>();
private List<Repo> fInstalledRepos = new ArrayList<>();
private List<Repo> fOthersRepos = new ArrayList<>();
private SimpleSectionedRecyclerViewAdapter mSectionedAdapter;
private SearchView.OnQueryTextListener searchListener;
private ReposAdapter adapter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
@@ -60,10 +43,9 @@ public class ReposFragment extends Fragment implements CallbackEvent.Listener<Vo
View view = inflater.inflate(R.layout.fragment_repos, container, false);
unbinder = ButterKnife.bind(this, view);
mSectionedAdapter = new SimpleSectionedRecyclerViewAdapter(R.layout.section,
R.id.section_text, new ReposAdapter(fUpdateRepos, fInstalledRepos, fOthersRepos));
adapter = new ReposAdapter(getApplication().repoMap);
recyclerView.setAdapter(mSectionedAdapter);
recyclerView.setAdapter(adapter);
mSwipeRefreshLayout.setRefreshing(true);
@@ -73,38 +55,42 @@ public class ReposFragment extends Fragment implements CallbackEvent.Listener<Vo
});
if (getApplication().repoLoadDone.isTriggered) {
reloadRepos();
updateUI();
onTrigger(null);
}
searchListener = new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
new FilterApps().exec(newText);
return false;
}
};
return view;
}
@Override
public void onTrigger(CallbackEvent<Void> event) {
Logger.dev("ReposFragment: UI refresh triggered");
reloadRepos();
updateUI();
if (getApplication().repoMap.isEmpty()) {
recyclerView.setVisibility(View.GONE);
emptyRv.setVisibility(View.VISIBLE);
} else {
adapter.filter(getApplication().moduleMap, "");
recyclerView.setVisibility(View.VISIBLE);
emptyRv.setVisibility(View.GONE);
mSwipeRefreshLayout.setRefreshing(false);
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.menu_repo, menu);
SearchView search = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.repo_search));
search.setOnQueryTextListener(searchListener);
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(getApplication().moduleMap, newText);
return false;
}
});
}
@Override
@@ -125,92 +111,4 @@ public class ReposFragment extends Fragment implements CallbackEvent.Listener<Vo
super.onDestroyView();
unbinder.unbind();
}
private void reloadRepos() {
mUpdateRepos.clear();
mInstalledRepos.clear();
mOthersRepos.clear();
for (Repo repo : getApplication().repoMap.values()) {
Module module = getApplication().moduleMap.get(repo.getId());
if (module != null) {
if (repo.getVersionCode() > module.getVersionCode()) {
mUpdateRepos.add(repo);
} else {
mInstalledRepos.add(repo);
}
} else {
mOthersRepos.add(repo);
}
}
fUpdateRepos.clear();
fInstalledRepos.clear();
fOthersRepos.clear();
fUpdateRepos.addAll(mUpdateRepos);
fInstalledRepos.addAll(mInstalledRepos);
fOthersRepos.addAll(mOthersRepos);
}
private void updateUI() {
if (fUpdateRepos.size() + fInstalledRepos.size() + fOthersRepos.size() == 0) {
emptyRv.setVisibility(View.VISIBLE);
recyclerView.setVisibility(View.GONE);
} else {
List<SimpleSectionedRecyclerViewAdapter.Section> sections = new ArrayList<>();
if (!fUpdateRepos.isEmpty()) {
sections.add(new SimpleSectionedRecyclerViewAdapter.Section(0, getString(R.string.update_available)));
}
if (!fInstalledRepos.isEmpty()) {
sections.add(new SimpleSectionedRecyclerViewAdapter.Section(fUpdateRepos.size(), getString(R.string.installed)));
}
if (!fOthersRepos.isEmpty()) {
sections.add(new SimpleSectionedRecyclerViewAdapter.Section(fUpdateRepos.size() + fInstalledRepos.size(), getString(R.string.not_installed)));
}
SimpleSectionedRecyclerViewAdapter.Section[] array = sections.toArray(new SimpleSectionedRecyclerViewAdapter.Section[sections.size()]);
mSectionedAdapter.setSections(array);
emptyRv.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
}
mSwipeRefreshLayout.setRefreshing(false);
}
private class FilterApps extends ParallelTask<String, Void, Void> {
@Override
protected Void doInBackground(String... strings) {
String newText = strings[0];
fUpdateRepos.clear();
fInstalledRepos.clear();
fOthersRepos.clear();
for (Repo repo: mUpdateRepos) {
if (repo.getName().toLowerCase().contains(newText.toLowerCase())
|| repo.getAuthor().toLowerCase().contains(newText.toLowerCase())
|| repo.getDescription().toLowerCase().contains(newText.toLowerCase())
) {
fUpdateRepos.add(repo);
}
}
for (Repo repo: mInstalledRepos) {
if (repo.getName().toLowerCase().contains(newText.toLowerCase())
|| repo.getAuthor().toLowerCase().contains(newText.toLowerCase())
|| repo.getDescription().toLowerCase().contains(newText.toLowerCase())
) {
fInstalledRepos.add(repo);
}
}
for (Repo repo: mOthersRepos) {
if (repo.getName().toLowerCase().contains(newText.toLowerCase())
|| repo.getAuthor().toLowerCase().contains(newText.toLowerCase())
|| repo.getDescription().toLowerCase().contains(newText.toLowerCase())
) {
fOthersRepos.add(repo);
}
}
return null;
}
@Override
protected void onPostExecute(Void v) {
updateUI();
}
}
}

View File

@@ -149,9 +149,9 @@ public class SettingsActivity extends Activity {
case "disable":
enabled = prefs.getBoolean("disable", false);
if (enabled) {
Utils.createFile(MagiskManager.MAGISK_DISABLE_FILE);
Utils.createFile(magiskManager.shell, MagiskManager.MAGISK_DISABLE_FILE);
} else {
Utils.removeItem(MagiskManager.MAGISK_DISABLE_FILE);
Utils.removeItem(magiskManager.shell, MagiskManager.MAGISK_DISABLE_FILE);
}
Toast.makeText(getActivity(), R.string.settings_reboot_toast, Toast.LENGTH_LONG).show();
break;
@@ -162,24 +162,24 @@ public class SettingsActivity extends Activity {
new AlertDialogBuilder(getActivity())
.setTitle(R.string.no_magisksu_title)
.setMessage(R.string.no_magisksu_msg)
.setPositiveButton(R.string.understand, (dialog, which) -> new MagiskHide().enable())
.setPositiveButton(R.string.understand, (dialog, which) -> new MagiskHide(getActivity()).enable())
.setCancelable(false)
.show();
} else {
new MagiskHide().enable();
new MagiskHide(getActivity()).enable();
}
} else {
new MagiskHide().disable();
new MagiskHide(getActivity()).disable();
}
break;
case "hosts":
enabled = prefs.getBoolean("hosts", false);
if (enabled) {
Shell.su_async(null,
magiskManager.shell.su_raw(
"cp -af /system/etc/hosts /magisk/.core/hosts",
"mount -o bind /magisk/.core/hosts /system/etc/hosts");
} else {
Shell.su_async(null,
magiskManager.shell.su_raw(
"umount -l /system/etc/hosts",
"rm -f /magisk/.core/hosts");
}

View File

@@ -8,7 +8,6 @@ import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import com.topjohnwu.magisk.asyncs.GetBootBlocks;
import com.topjohnwu.magisk.asyncs.LoadApps;
import com.topjohnwu.magisk.asyncs.LoadModules;
import com.topjohnwu.magisk.asyncs.LoadRepos;
@@ -25,13 +24,17 @@ public class SplashActivity extends Activity{
super.onCreate(savedInstanceState);
// Init the info and configs and root shell
getApplicationContext().init();
MagiskManager magiskManager = getApplicationContext();
// Now fire all async tasks
new GetBootBlocks(this).exec();
new LoadModules(this).setCallBack(() -> new LoadRepos(this).exec()).exec();
new LoadApps(this).exec();
// Init the info and configs and root sh
magiskManager.init();
// Get possible additional info from intent
magiskManager.remoteMagiskVersionString = getIntent().getStringExtra(MagiskManager.INTENT_VERSION);
magiskManager.magiskLink = getIntent().getStringExtra(MagiskManager.INTENT_LINK);
LoadModules loadModuleTask = new LoadModules(this);
if (Utils.checkNetworkStatus(this)) {
// Initialize the update check service, notify every 8 hours
@@ -45,8 +48,13 @@ public class SplashActivity extends Activity{
JobScheduler scheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
scheduler.schedule(jobInfo);
}
loadModuleTask.setCallBack(() -> new LoadRepos(this).exec());
}
// Now fire all async tasks
loadModuleTask.exec();
new LoadApps(this).exec();
Intent intent = new Intent(this, MainActivity.class);
String section = getIntent().getStringExtra(MagiskManager.INTENT_SECTION);
if (section != null) {

View File

@@ -1,5 +1,6 @@
package com.topjohnwu.magisk.adapters;
import android.app.Activity;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.support.design.widget.Snackbar;
@@ -86,10 +87,10 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
holder.checkBox.setChecked(mHideList.contains(info.packageName));
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> {
if (isChecked) {
new MagiskHide().add(info.packageName);
new MagiskHide((Activity) holder.itemView.getContext()).add(info.packageName);
mHideList.add(info.packageName);
} else {
new MagiskHide().rm(info.packageName);
new MagiskHide((Activity) holder.itemView.getContext()).rm(info.packageName);
mHideList.remove(info.packageName);
}
});

View File

@@ -38,6 +38,7 @@ public class ModulesAdapter extends RecyclerView.Adapter<ModulesAdapter.ViewHold
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
Context context = holder.itemView.getContext();
Shell rootShell = Shell.getShell(context);
final Module module = mList.get(position);
String version = module.getVersion();
@@ -55,10 +56,10 @@ public class ModulesAdapter extends RecyclerView.Adapter<ModulesAdapter.ViewHold
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> {
int snack;
if (isChecked) {
module.removeDisableFile();
module.removeDisableFile(rootShell);
snack = R.string.disable_file_removed;
} else {
module.createDisableFile();
module.createDisableFile(rootShell);
snack = R.string.disable_file_created;
}
SnackbarMaker.make(holder.itemView, snack, Snackbar.LENGTH_SHORT).show();
@@ -68,10 +69,10 @@ public class ModulesAdapter extends RecyclerView.Adapter<ModulesAdapter.ViewHold
boolean removed = module.willBeRemoved();
int snack;
if (removed) {
module.deleteRemoveFile();
module.deleteRemoveFile(rootShell);
snack = R.string.remove_file_deleted;
} else {
module.createRemoveFile();
module.createRemoveFile(rootShell);
snack = R.string.remove_file_created;
}
SnackbarMaker.make(holder.itemView, snack, Snackbar.LENGTH_SHORT).show();

View File

@@ -16,98 +16,184 @@ import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.asyncs.ProcessRepoZip;
import com.topjohnwu.magisk.components.AlertDialogBuilder;
import com.topjohnwu.magisk.components.MarkDownWindow;
import com.topjohnwu.magisk.module.Module;
import com.topjohnwu.magisk.module.Repo;
import com.topjohnwu.magisk.receivers.DownloadReceiver;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.magisk.utils.ValueSortedMap;
import java.util.ArrayList;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
public class ReposAdapter extends RecyclerView.Adapter<ReposAdapter.ViewHolder> {
public class ReposAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int SECTION_TYPE = 0;
private static final int REPO_TYPE = 1;
private List<Repo> mUpdateRepos, mInstalledRepos, mOthersRepos;
private Context mContext;
private int[] sectionList;
private int size;
private ValueSortedMap<String, Repo> repoMap;
public ReposAdapter(List<Repo> update, List<Repo> installed, List<Repo> others) {
mUpdateRepos = update;
mInstalledRepos = installed;
mOthersRepos = others;
public ReposAdapter(ValueSortedMap<String, Repo> map) {
repoMap = map;
mUpdateRepos = new ArrayList<>();
mInstalledRepos = new ArrayList<>();
mOthersRepos = new ArrayList<>();
sectionList = new int[3];
size = 0;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
mContext = parent.getContext();
View v = LayoutInflater.from(mContext).inflate(R.layout.list_item_repo, parent, false);
return new ViewHolder(v);
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Context context = parent.getContext();
View v;
RecyclerView.ViewHolder holder = null;
switch (viewType) {
case SECTION_TYPE:
v = LayoutInflater.from(context).inflate(R.layout.section, parent, false);
holder = new SectionHolder(v);
break;
case REPO_TYPE:
v = LayoutInflater.from(context).inflate(R.layout.list_item_repo, parent, false);
holder = new RepoHolder(v);
break;
}
return holder;
}
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
Repo repo = getItem(position);
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
Context context = holder.itemView.getContext();
switch (getItemViewType(position)) {
case SECTION_TYPE:
SectionHolder section = (SectionHolder) holder;
if (position == sectionList[0]) {
section.sectionText.setText(context.getString(R.string.update_available));
} else if (position == sectionList[1]) {
section.sectionText.setText(context.getString(R.string.installed));
} else {
section.sectionText.setText(context.getString(R.string.not_installed));
}
break;
case REPO_TYPE:
RepoHolder repoHolder = (RepoHolder) holder;
Repo repo = getRepo(position);
repoHolder.title.setText(repo.getName());
repoHolder.versionName.setText(repo.getVersion());
String author = repo.getAuthor();
repoHolder.author.setText(TextUtils.isEmpty(author) ? null : context.getString(R.string.author, author));
repoHolder.description.setText(repo.getDescription());
holder.title.setText(repo.getName());
holder.versionName.setText(repo.getVersion());
String author = repo.getAuthor();
holder.author.setText(TextUtils.isEmpty(author) ? null : mContext.getString(R.string.author, author));
holder.description.setText(repo.getDescription());
repoHolder.infoLayout.setOnClickListener(v -> new MarkDownWindow(null, repo.getDetailUrl(), context));
holder.infoLayout.setOnClickListener(v -> new MarkDownWindow(null, repo.getDetailUrl(), mContext));
repoHolder.downloadImage.setOnClickListener(v -> {
String filename = repo.getName() + "-" + repo.getVersion() + ".zip";
new AlertDialogBuilder(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) -> Utils.dlAndReceive(
context,
new DownloadReceiver() {
@Override
public void onDownloadDone(Uri uri) {
new ProcessRepoZip((Activity) context, uri, true).exec();
}
},
repo.getZipUrl(),
Utils.getLegalFilename(filename)))
.setNeutralButton(R.string.download, (d, i) -> Utils.dlAndReceive(
context,
new DownloadReceiver() {
@Override
public void onDownloadDone(Uri uri) {
new ProcessRepoZip((Activity) context, uri, false).exec();
}
},
repo.getZipUrl(),
Utils.getLegalFilename(filename)))
.setNegativeButton(R.string.no_thanks, null)
.show();
});
break;
}
}
holder.downloadImage.setOnClickListener(v -> {
String filename = repo.getName() + "-" + repo.getVersion() + ".zip";
new AlertDialogBuilder(mContext)
.setTitle(mContext.getString(R.string.repo_install_title, repo.getName()))
.setMessage(mContext.getString(R.string.repo_install_msg, filename))
.setCancelable(true)
.setPositiveButton(R.string.install, (d, i) -> Utils.dlAndReceive(
mContext,
new DownloadReceiver() {
@Override
public void onDownloadDone(Uri uri) {
Activity activity = (Activity) mContext;
new ProcessRepoZip(activity, uri, true).exec();
}
},
repo.getZipUrl(),
Utils.getLegalFilename(filename)))
.setNeutralButton(R.string.download, (d, i) -> Utils.dlAndReceive(
mContext,
new DownloadReceiver() {
@Override
public void onDownloadDone(Uri uri) {
Activity activity = (Activity) mContext;
new ProcessRepoZip(activity, uri, false).exec();
}
},
repo.getZipUrl(),
Utils.getLegalFilename(filename)))
.setNegativeButton(R.string.no_thanks, null)
.show();
});
@Override
public int getItemViewType(int position) {
for (int i : sectionList) {
if (position == i)
return SECTION_TYPE;
}
return REPO_TYPE;
}
@Override
public int getItemCount() {
return mUpdateRepos.size() + mInstalledRepos.size() + mOthersRepos.size();
return size;
}
private Repo getItem(int position) {
if (position >= mUpdateRepos.size()) {
position -= mUpdateRepos.size();
if (position >= mInstalledRepos.size()) {
position -= mInstalledRepos.size();
return mOthersRepos.get(position);
} else {
return mInstalledRepos.get(position);
public void filter(ValueSortedMap<String, Module> moduleMap, String s) {
mUpdateRepos.clear();
mInstalledRepos.clear();
mOthersRepos.clear();
sectionList[0] = sectionList[1] = sectionList[2] = 0;
for (Repo repo : repoMap.values()) {
if (repo.getName().toLowerCase().contains(s.toLowerCase())
|| repo.getAuthor().toLowerCase().contains(s.toLowerCase())
|| repo.getDescription().toLowerCase().contains(s.toLowerCase())
) {
// Passed the filter
Module module = moduleMap.get(repo.getId());
if (module != null) {
if (repo.getVersionCode() > module.getVersionCode()) {
// Updates
mUpdateRepos.add(repo);
} else {
mInstalledRepos.add(repo);
}
} else {
mOthersRepos.add(repo);
}
}
} else {
return mUpdateRepos.get(position);
}
sectionList[0] = mUpdateRepos.isEmpty() ? -1 : 0;
size = mUpdateRepos.isEmpty() ? 0 : mUpdateRepos.size() + 1;
sectionList[1] = mInstalledRepos.isEmpty() ? -1 : size;
size += mInstalledRepos.isEmpty() ? 0 : mInstalledRepos.size() + 1;
sectionList[2] = mOthersRepos.isEmpty() ? -1 : size;
size += mOthersRepos.isEmpty() ? 0 : mOthersRepos.size() + 1;
notifyDataSetChanged();
}
private Repo getRepo(int position) {
if (!mUpdateRepos.isEmpty()) position -= 1;
if (position < mUpdateRepos.size()) return mUpdateRepos.get(position);
position -= mUpdateRepos.size();
if (!mInstalledRepos.isEmpty()) position -= 1;
if (position < mInstalledRepos.size()) return mInstalledRepos.get(position);
position -= mInstalledRepos.size();
if (!mOthersRepos.isEmpty()) position -= 1;
return mOthersRepos.get(position);
}
static class SectionHolder extends RecyclerView.ViewHolder {
@BindView(R.id.section_text) TextView sectionText;
SectionHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
}
static class ViewHolder extends RecyclerView.ViewHolder {
static class RepoHolder extends RecyclerView.ViewHolder {
@BindView(R.id.title) TextView title;
@BindView(R.id.version_name) TextView versionName;
@@ -116,7 +202,7 @@ public class ReposAdapter extends RecyclerView.Adapter<ReposAdapter.ViewHolder>
@BindView(R.id.info_layout) LinearLayout infoLayout;
@BindView(R.id.download) ImageView downloadImage;
ViewHolder(View itemView) {
RepoHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}

View File

@@ -1,178 +0,0 @@
package com.topjohnwu.magisk.adapters;
import android.support.v7.widget.RecyclerView;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.Arrays;
import java.util.Comparator;
public class SimpleSectionedRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int SECTION_TYPE = 0;
private boolean mValid = true;
private int mSectionResourceId;
private int mTextResourceId;
private RecyclerView.Adapter mBaseAdapter;
private SparseArray<Section> mSections = new SparseArray<Section>();
public SimpleSectionedRecyclerViewAdapter(int sectionResourceId, int textResourceId,
RecyclerView.Adapter baseAdapter) {
mSectionResourceId = sectionResourceId;
mTextResourceId = textResourceId;
mBaseAdapter = baseAdapter;
mBaseAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
mValid = mBaseAdapter.getItemCount()>0;
notifyDataSetChanged();
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount) {
mValid = mBaseAdapter.getItemCount()>0;
notifyItemRangeChanged(positionStart, itemCount);
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
mValid = mBaseAdapter.getItemCount()>0;
notifyItemRangeInserted(positionStart, itemCount);
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
mValid = mBaseAdapter.getItemCount()>0;
notifyItemRangeRemoved(positionStart, itemCount);
}
});
}
public static class SectionViewHolder extends RecyclerView.ViewHolder {
public TextView title;
public SectionViewHolder(View view, int mTextResourceid) {
super(view);
title = (TextView) view.findViewById(mTextResourceid);
}
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int typeView) {
if (typeView == SECTION_TYPE) {
View view = LayoutInflater.from(parent.getContext()).inflate(mSectionResourceId, parent, false);
return new SectionViewHolder(view,mTextResourceId);
}else{
return mBaseAdapter.onCreateViewHolder(parent, typeView -1);
}
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder sectionViewHolder, int position) {
if (isSectionHeaderPosition(position)) {
((SectionViewHolder)sectionViewHolder).title.setText(mSections.get(position).title);
}else{
mBaseAdapter.onBindViewHolder(sectionViewHolder,sectionedPositionToPosition(position));
}
}
@Override
public int getItemViewType(int position) {
return isSectionHeaderPosition(position)
? SECTION_TYPE
: mBaseAdapter.getItemViewType(sectionedPositionToPosition(position)) +1 ;
}
public static class Section {
int firstPosition;
int sectionedPosition;
CharSequence title;
public Section(int firstPosition, CharSequence title) {
this.firstPosition = firstPosition;
this.title = title;
}
public CharSequence getTitle() {
return title;
}
}
public void setSections(Section[] sections) {
mSections.clear();
Arrays.sort(sections, new Comparator<Section>() {
@Override
public int compare(Section o, Section o1) {
return (o.firstPosition == o1.firstPosition)
? 0
: ((o.firstPosition < o1.firstPosition) ? -1 : 1);
}
});
int offset = 0; // offset positions for the headers we're adding
for (Section section : sections) {
section.sectionedPosition = section.firstPosition + offset;
mSections.append(section.sectionedPosition, section);
++offset;
}
notifyDataSetChanged();
}
public int positionToSectionedPosition(int position) {
int offset = 0;
for (int i = 0; i < mSections.size(); i++) {
if (mSections.valueAt(i).firstPosition > position) {
break;
}
++offset;
}
return position + offset;
}
public int sectionedPositionToPosition(int sectionedPosition) {
if (isSectionHeaderPosition(sectionedPosition)) {
return RecyclerView.NO_POSITION;
}
int offset = 0;
for (int i = 0; i < mSections.size(); i++) {
if (mSections.valueAt(i).sectionedPosition > sectionedPosition) {
break;
}
--offset;
}
return sectionedPosition + offset;
}
public boolean isSectionHeaderPosition(int position) {
return mSections.get(position) != null;
}
@Override
public long getItemId(int position) {
return isSectionHeaderPosition(position)
? Integer.MAX_VALUE - mSections.indexOfKey(position)
: mBaseAdapter.getItemId(sectionedPositionToPosition(position));
}
@Override
public int getItemCount() {
return (mValid ? mBaseAdapter.getItemCount() + mSections.size() : 0);
}
}

View File

@@ -3,6 +3,7 @@ package com.topjohnwu.magisk.asyncs;
import android.content.Context;
import com.topjohnwu.magisk.BuildConfig;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.magisk.utils.WebService;
@@ -15,17 +16,19 @@ public class CheckUpdates extends ParallelTask<Void, Void, Void> {
private boolean showNotification = false;
public CheckUpdates(Context context, boolean b) {
this(context);
showNotification = b;
public CheckUpdates(Context context) {
super(context);
}
public CheckUpdates(Context context) {
magiskManager = Utils.getMagiskManager(context);
public CheckUpdates(Context context, boolean b) {
super(context);
showNotification = b;
}
@Override
protected Void doInBackground(Void... voids) {
MagiskManager magiskManager = getMagiskManager();
if (magiskManager == null) return null;
String jsonStr = WebService.request(UPDATE_JSON, WebService.GET);
try {
JSONObject json = new JSONObject(jsonStr);
@@ -44,6 +47,8 @@ public class CheckUpdates extends ParallelTask<Void, Void, Void> {
@Override
protected void onPostExecute(Void v) {
MagiskManager magiskManager = getMagiskManager();
if (magiskManager == null) return;
if (showNotification && magiskManager.updateNotification) {
if (BuildConfig.VERSION_CODE < magiskManager.remoteManagerVersionCode) {
Utils.showManagerUpdate(magiskManager);

View File

@@ -1,15 +1,12 @@
package com.topjohnwu.magisk.asyncs;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.net.Uri;
import android.widget.Toast;
import android.text.TextUtils;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.components.AlertDialogBuilder;
import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.AdaptiveList;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.magisk.utils.ZipUtils;
@@ -21,134 +18,104 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
public class FlashZip extends RootTask<Void, String, Integer> {
public class FlashZip extends ParallelTask<Void, String, Integer> {
private Uri mUri;
private File mCachedFile, mScriptFile, mCheckFile;
private String mFilename;
private ProgressDialog progress;
private AdaptiveList<String> mList;
public FlashZip(Activity context, Uri uri) {
public FlashZip(Context context, Uri uri, AdaptiveList<String> list) {
super(context);
mUri = uri;
mList = list;
mCachedFile = new File(magiskManager.getCacheDir(), "install.zip");
mScriptFile = new File(magiskManager.getCacheDir(), "/META-INF/com/google/android/update-binary");
mCachedFile = new File(context.getCacheDir(), "install.zip");
mScriptFile = new File(context.getCacheDir(), "/META-INF/com/google/android/update-binary");
mCheckFile = new File(mScriptFile.getParent(), "updater-script");
// Try to get the filename ourselves
mFilename = Utils.getNameFromUri(magiskManager, mUri);
}
private void copyToCache() throws Throwable {
publishProgress(magiskManager.getString(R.string.copying_msg));
if (mCachedFile.exists() && !mCachedFile.delete()) {
Logger.error("FlashZip: Error while deleting already existing file");
throw new IOException();
}
try (
InputStream in = magiskManager.getContentResolver().openInputStream(mUri);
OutputStream outputStream = new FileOutputStream(mCachedFile)
) {
byte buffer[] = new byte[1024];
int length;
if (in == null) throw new FileNotFoundException();
while ((length = in.read(buffer)) > 0)
outputStream.write(buffer, 0, length);
Logger.dev("FlashZip: File created successfully - " + mCachedFile.getPath());
} catch (FileNotFoundException e) {
Logger.error("FlashZip: Invalid Uri");
throw e;
} catch (IOException e) {
Logger.error("FlashZip: Error in creating file");
throw e;
}
mFilename = Utils.getNameFromUri(context, mUri);
}
private boolean unzipAndCheck() throws Exception {
ZipUtils.unzip(mCachedFile, mCachedFile.getParentFile(), "META-INF/com/google/android");
List<String> ret;
ret = Utils.readFile(mCheckFile.getPath());
List<String> ret = Utils.readFile(getShell(), mCheckFile.getPath());
return Utils.isValidShellResponse(ret) && ret.get(0).contains("#MAGISK");
}
private int cleanup(int ret) {
Shell.su(
"rm -rf " + mCachedFile.getParent() + "/*",
"rm -rf " + MagiskManager.TMP_FOLDER_PATH
);
return ret;
}
@Override
protected void onPreExecute() {
progress = new ProgressDialog(activity);
progress.setTitle(R.string.zip_install_progress_title);
progress.show();
// UI updates must run in the UI thread
mList.setCallback(this::publishProgress);
}
@Override
protected void onProgressUpdate(String... values) {
progress.setMessage(values[0]);
mList.updateView();
}
@Override
protected Integer doInRoot(Void... voids) {
Logger.dev("FlashZip Running... " + mFilename);
List<String> ret;
protected Integer doInBackground(Void... voids) {
MagiskManager magiskManager = getMagiskManager();
if (magiskManager == null) return -1;
try {
copyToCache();
if (!unzipAndCheck()) return cleanup(0);
publishProgress(magiskManager.getString(R.string.zip_install_progress_msg, mFilename));
ret = Shell.su(
"BOOTMODE=true sh " + mScriptFile + " dummy 1 " + mCachedFile,
"if [ $? -eq 0 ]; then echo true; else echo false; fi"
);
if (!Utils.isValidShellResponse(ret)) return -1;
Logger.dev("FlashZip: Console log:");
for (String line : ret) {
Logger.dev(line);
}
if (Boolean.parseBoolean(ret.get(ret.size() - 1)))
return cleanup(1);
mList.add(magiskManager.getString(R.string.copying_msg));
} catch (Throwable e) {
mCachedFile.delete();
try (
InputStream in = magiskManager.getContentResolver().openInputStream(mUri);
OutputStream outputStream = new FileOutputStream(mCachedFile)
) {
if (in == null) throw new FileNotFoundException();
byte buffer[] = new byte[1024];
int length;
while ((length = in.read(buffer)) > 0)
outputStream.write(buffer, 0, length);
} catch (FileNotFoundException e) {
mList.add("! Invalid Uri");
throw e;
} catch (IOException e) {
mList.add("! Cannot copy to cache");
throw e;
}
if (!unzipAndCheck()) return 0;
mList.add(magiskManager.getString(R.string.zip_install_progress_msg, mFilename));
magiskManager.shell.su(mList,
"BOOTMODE=true sh " + mScriptFile + " dummy 1 " + mCachedFile +
" && echo 'Success!' || echo 'Failed!'"
);
if (TextUtils.equals(mList.get(mList.size() - 1), "Success!"))
return 1;
} catch (Exception e) {
e.printStackTrace();
}
return cleanup(-1);
return -1;
}
// -1 = error, manual install; 0 = invalid zip; 1 = success
@Override
protected void onPostExecute(Integer result) {
progress.dismiss();
MagiskManager magiskManager = getMagiskManager();
if (magiskManager == null) return;
magiskManager.shell.su_raw(
"rm -rf " + mCachedFile.getParent() + "/*",
"rm -rf " + MagiskManager.TMP_FOLDER_PATH
);
switch (result) {
case -1:
Toast.makeText(magiskManager, magiskManager.getString(R.string.install_error), Toast.LENGTH_LONG).show();
Utils.showUriSnack(activity, mUri);
break;
mList.add(magiskManager.getString(R.string.install_error));
Utils.showUriSnack(getActivity(), mUri);
return;
case 0:
Toast.makeText(magiskManager, magiskManager.getString(R.string.invalid_zip), Toast.LENGTH_LONG).show();
break;
mList.add(magiskManager.getString(R.string.invalid_zip));
return;
case 1:
onSuccess();
// Success
break;
}
new LoadModules(magiskManager).exec();
super.onPostExecute(result);
}
protected void onSuccess() {
magiskManager.updateCheckDone.trigger();
new LoadModules(activity).exec();
new AlertDialogBuilder(activity)
.setTitle(R.string.reboot_title)
.setMessage(R.string.reboot_msg)
.setPositiveButton(R.string.reboot, (dialogInterface, i) -> Shell.su(true, "reboot"))
.setNegativeButton(R.string.no_thanks, null)
.show();
}
}

View File

@@ -1,30 +0,0 @@
package com.topjohnwu.magisk.asyncs;
import android.app.Activity;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Utils;
public class GetBootBlocks extends RootTask<Void, Void, Void> {
public GetBootBlocks(Activity context) {
super(context);
}
@Override
protected Void doInRoot(Void... params) {
magiskManager.blockList = Shell.su(
"find /dev/block -type b -maxdepth 1 | grep -v -E \"loop|ram|dm-0\""
);
if (magiskManager.bootBlock == null) {
magiskManager.bootBlock = Utils.detectBootImage();
}
return null;
}
@Override
protected void onPostExecute(Void v) {
magiskManager.blockDetectionDone.trigger();
super.onPostExecute(v);
}
}

View File

@@ -1,9 +1,10 @@
package com.topjohnwu.magisk.asyncs;
import android.app.Activity;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.adapters.ApplicationAdapter;
import java.util.Collections;
@@ -12,12 +13,14 @@ import java.util.List;
public class LoadApps extends ParallelTask<Void, Void, Void> {
public LoadApps(Activity context) {
public LoadApps(Context context) {
super(context);
}
@Override
protected Void doInBackground(Void... voids) {
MagiskManager magiskManager = getMagiskManager();
if (magiskManager == null) return null;
PackageManager pm = magiskManager.getPackageManager();
List<ApplicationInfo> list = pm.getInstalledApplications(0);
for (Iterator<ApplicationInfo> i = list.iterator(); i.hasNext(); ) {
@@ -34,6 +37,8 @@ public class LoadApps extends ParallelTask<Void, Void, Void> {
@Override
protected void onPostExecute(Void v) {
new MagiskHide(activity).list();
MagiskManager magiskManager = getMagiskManager();
if (magiskManager == null) return;
new MagiskHide(magiskManager).list();
}
}

View File

@@ -1,6 +1,6 @@
package com.topjohnwu.magisk.asyncs;
import android.app.Activity;
import android.content.Context;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.module.BaseModule;
@@ -9,23 +9,24 @@ import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.magisk.utils.ValueSortedMap;
public class LoadModules extends RootTask<Void, Void, Void> {
public class LoadModules extends ParallelTask<Void, Void, Void> {
public LoadModules(Activity context) {
public LoadModules(Context context) {
super(context);
}
@Override
protected Void doInRoot(Void... voids) {
protected Void doInBackground(Void... voids) {
MagiskManager magiskManager = getMagiskManager();
if (magiskManager == null) return null;
Logger.dev("LoadModules: Loading modules");
magiskManager.moduleMap = new ValueSortedMap<>();
for (String path : Utils.getModList(MagiskManager.MAGISK_PATH)) {
for (String path : Utils.getModList(magiskManager.shell, MagiskManager.MAGISK_PATH)) {
Logger.dev("LoadModules: Adding modules from " + path);
Module module;
try {
module = new Module(path);
Module module = new Module(magiskManager.shell, path);
magiskManager.moduleMap.put(module.getId(), module);
} catch (BaseModule.CacheModException ignored) {}
}
@@ -36,6 +37,8 @@ public class LoadModules extends RootTask<Void, Void, Void> {
@Override
protected void onPostExecute(Void v) {
MagiskManager magiskManager = getMagiskManager();
if (magiskManager == null) return;
magiskManager.moduleLoadDone.trigger();
super.onPostExecute(v);
}

View File

@@ -1,9 +1,10 @@
package com.topjohnwu.magisk.asyncs;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.text.TextUtils;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
import com.topjohnwu.magisk.module.BaseModule;
import com.topjohnwu.magisk.module.Repo;
@@ -41,11 +42,11 @@ public class LoadRepos extends ParallelTask<Void, Void, Void> {
private RepoDatabaseHelper repoDB;
private SharedPreferences prefs;
public LoadRepos(Activity context) {
public LoadRepos(Context context) {
super(context);
prefs = magiskManager.prefs;
prefs = getMagiskManager().prefs;
String prefsPath = context.getApplicationInfo().dataDir + "/shared_prefs";
repoDB = new RepoDatabaseHelper(magiskManager);
repoDB = new RepoDatabaseHelper(context);
// Legacy data cleanup
File old = new File(prefsPath, "RepoMap.xml");
if (old.exists() || !prefs.getString("repomap", "empty").equals("empty")) {
@@ -54,7 +55,7 @@ public class LoadRepos extends ParallelTask<Void, Void, Void> {
repoDB.clearRepo();
}
etags = new ArrayList<>(
Arrays.asList(magiskManager.prefs.getString(ETAG_KEY, "").split(",")));
Arrays.asList(prefs.getString(ETAG_KEY, "").split(",")));
}
private void loadJSON(String jsonString) throws Exception {
@@ -154,6 +155,8 @@ public class LoadRepos extends ParallelTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... voids) {
MagiskManager magiskManager = getMagiskManager();
if (magiskManager == null) return null;
Logger.dev("LoadRepos: Loading repos");
cached = repoDB.getRepoMap(false);
@@ -183,6 +186,8 @@ public class LoadRepos extends ParallelTask<Void, Void, Void> {
@Override
protected void onPostExecute(Void v) {
MagiskManager magiskManager = getMagiskManager();
if (magiskManager == null) return;
magiskManager.repoLoadDone.trigger();
super.onPostExecute(v);
}

View File

@@ -1,25 +1,25 @@
package com.topjohnwu.magisk.asyncs;
import android.app.Activity;
import android.content.Context;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.MagiskManager;
import java.util.List;
public class MagiskHide extends RootTask<Object, Void, Void> {
public class MagiskHide extends ParallelTask<Object, Void, Void> {
private boolean isList = false;
public MagiskHide() {}
public MagiskHide(Activity context) {
public MagiskHide(Context context) {
super(context);
}
@Override
protected Void doInRoot(Object... params) {
protected Void doInBackground(Object... params) {
MagiskManager magiskManager = getMagiskManager();
if (magiskManager == null) return null;
String command = (String) params[0];
List<String> ret = Shell.su("magiskhide --" + command);
List<String> ret = magiskManager.shell.su("magiskhide --" + command);
if (isList) {
magiskManager.magiskHideList = ret;
}
@@ -28,6 +28,8 @@ public class MagiskHide extends RootTask<Object, Void, Void> {
@Override
protected void onPostExecute(Void v) {
MagiskManager magiskManager = getMagiskManager();
if (magiskManager == null) return;
if (isList) {
magiskManager.magiskHideDone.trigger();
}
@@ -52,7 +54,7 @@ public class MagiskHide extends RootTask<Object, Void, Void> {
public void list() {
isList = true;
if (magiskManager == null) return;
if (getMagiskManager() == null) return;
exec("ls");
}

View File

@@ -1,23 +1,44 @@
package com.topjohnwu.magisk.asyncs;
import android.app.Activity;
import android.content.Context;
import android.os.AsyncTask;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Utils;
import java.lang.ref.WeakReference;
public abstract class ParallelTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
protected Activity activity;
protected MagiskManager magiskManager;
private WeakReference<Activity> weakActivity;
private WeakReference<MagiskManager> weakMagiskManager;
private Runnable callback = null;
public ParallelTask() {}
public ParallelTask(Context context) {
weakMagiskManager = new WeakReference<>(Utils.getMagiskManager(context));
}
public ParallelTask(Activity context) {
activity = context;
magiskManager = Utils.getMagiskManager(context);
this((Context) context);
weakActivity = new WeakReference<>(context);
}
protected Activity getActivity() {
return weakActivity.get();
}
protected MagiskManager getMagiskManager() {
return weakMagiskManager.get();
}
protected Shell getShell() {
MagiskManager magiskManager = getMagiskManager();
return magiskManager == null ? null : getMagiskManager().shell;
}
@SuppressWarnings("unchecked")

View File

@@ -1,58 +0,0 @@
package com.topjohnwu.magisk.asyncs;
import android.app.Activity;
import android.app.ProgressDialog;
import android.net.Uri;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Utils;
public class ProcessMagiskZip extends ParallelTask<Void, Void, Boolean> {
private Uri mUri;
private ProgressDialog progressDialog;
private String mBoot;
private boolean mEnc, mVerity;
public ProcessMagiskZip(Activity context, Uri uri, String boot, boolean enc, boolean verity) {
super(context);
mUri = uri;
mBoot = boot;
mEnc = enc;
mVerity = verity;
}
@Override
protected void onPreExecute() {
progressDialog = ProgressDialog.show(activity,
activity.getString(R.string.zip_process_title),
activity.getString(R.string.zip_unzip_msg));
}
@Override
protected Boolean doInBackground(Void... params) {
if (Shell.rootAccess()) {
synchronized (Shell.lock) {
Shell.su("rm -f /dev/.magisk",
(mBoot != null) ? "echo \"BOOTIMAGE=" + mBoot + "\" >> /dev/.magisk" : "",
"echo \"KEEPFORCEENCRYPT=" + String.valueOf(mEnc) + "\" >> /dev/.magisk",
"echo \"KEEPVERITY=" + String.valueOf(mVerity) + "\" >> /dev/.magisk"
);
}
return true;
}
return false;
}
@Override
protected void onPostExecute(Boolean result) {
progressDialog.dismiss();
if (result) {
new FlashZip(activity, mUri).exec();
} else {
Utils.showUriSnack(activity, mUri);
}
super.onPostExecute(result);
}
}

View File

@@ -2,9 +2,11 @@ package com.topjohnwu.magisk.asyncs;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.net.Uri;
import android.widget.Toast;
import com.topjohnwu.magisk.FlashActivity;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.utils.Shell;
@@ -30,6 +32,8 @@ public class ProcessRepoZip extends ParallelTask<Void, Void, Boolean> {
@Override
protected void onPreExecute() {
Activity activity = getActivity();
if (activity == null) return;
progressDialog = ProgressDialog.show(activity,
activity.getString(R.string.zip_process_title),
activity.getString(R.string.zip_process_msg));
@@ -37,12 +41,14 @@ public class ProcessRepoZip extends ParallelTask<Void, Void, Boolean> {
@Override
protected Boolean doInBackground(Void... params) {
Activity activity = getActivity();
if (activity == null) return null;
try {
// Create temp file
File temp1 = new File(magiskManager.getCacheDir(), "1.zip");
File temp2 = new File(magiskManager.getCacheDir(), "2.zip");
magiskManager.getCacheDir().mkdirs();
File temp1 = new File(activity.getCacheDir(), "1.zip");
File temp2 = new File(activity.getCacheDir(), "2.zip");
activity.getCacheDir().mkdirs();
temp1.createNewFile();
temp2.createNewFile();
@@ -82,16 +88,17 @@ public class ProcessRepoZip extends ParallelTask<Void, Void, Boolean> {
@Override
protected void onPostExecute(Boolean result) {
Activity activity = getActivity();
if (activity == null) return;
progressDialog.dismiss();
if (result) {
if (Shell.rootAccess() && mInstall) {
new FlashZip(activity, mUri).exec();
activity.startActivity(new Intent(activity, FlashActivity.class).setData(mUri));
} else {
Utils.showUriSnack(activity, mUri);
}
} else {
Toast.makeText(activity, R.string.process_error, Toast.LENGTH_LONG).show();
Utils.getMagiskManager(activity).toast(R.string.process_error, Toast.LENGTH_LONG);
}
}
}

View File

@@ -1,33 +0,0 @@
package com.topjohnwu.magisk.asyncs;
import android.app.Activity;
import com.topjohnwu.magisk.utils.Shell;
public abstract class RootTask <Params, Progress, Result> extends ParallelTask<Params, Progress, Result> {
public RootTask() {}
public RootTask(Activity context) {
super(context);
}
@SafeVarargs
@Override
final protected Result doInBackground(Params... params) {
synchronized (Shell.lock) {
return doInRoot(params);
}
}
@SuppressWarnings("unchecked")
abstract protected Result doInRoot(Params... params);
@SuppressWarnings("unchecked")
@Override
public void exec(Params... params) {
if (Shell.rootAccess()) {
super.exec(params);
}
}
}

View File

@@ -1,6 +1,7 @@
package com.topjohnwu.magisk.module;
import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Utils;
public class Module extends BaseModule {
@@ -8,9 +9,9 @@ public class Module extends BaseModule {
private String mRemoveFile, mDisableFile, mUpdateFile;
private boolean mEnable, mRemove, mUpdated;
public Module(String path) throws CacheModException {
public Module(Shell shell, String path) throws CacheModException {
parseProps(Utils.readFile(path + "/module.prop"));
parseProps(Utils.readFile(shell, path + "/module.prop"));
mRemoveFile = path + "/remove";
mDisableFile = path + "/disable";
@@ -27,33 +28,33 @@ public class Module extends BaseModule {
Logger.dev("Creating Module, id: " + getId());
mEnable = !Utils.itemExist(mDisableFile);
mRemove = Utils.itemExist(mRemoveFile);
mUpdated = Utils.itemExist(mUpdateFile);
mEnable = !Utils.itemExist(shell, mDisableFile);
mRemove = Utils.itemExist(shell, mRemoveFile);
mUpdated = Utils.itemExist(shell, mUpdateFile);
}
public void createDisableFile() {
public void createDisableFile(Shell shell) {
mEnable = false;
Utils.createFile(mDisableFile);
Utils.createFile(shell, mDisableFile);
}
public void removeDisableFile() {
public void removeDisableFile(Shell shell) {
mEnable = true;
Utils.removeItem(mDisableFile);
Utils.removeItem(shell, mDisableFile);
}
public boolean isEnabled() {
return mEnable;
}
public void createRemoveFile() {
public void createRemoveFile(Shell shell) {
mRemove = true;
Utils.createFile(mRemoveFile);
Utils.createFile(shell, mRemoveFile);
}
public void deleteRemoveFile() {
public void deleteRemoveFile(Shell shell) {
mRemove = false;
Utils.removeItem(mRemoveFile);
Utils.removeItem(shell, mRemoveFile);
}
public boolean willBeRemoved() {

View File

@@ -12,7 +12,6 @@ import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.utils.Utils;
public abstract class DownloadReceiver extends BroadcastReceiver {
public Context mContext;
public String mFilename;
long downloadID;
@@ -20,7 +19,6 @@ public abstract class DownloadReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
mContext = context;
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
String action = intent.getAction();
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {

View File

@@ -7,6 +7,7 @@ import android.net.Uri;
import android.os.Build;
import android.support.v4.content.FileProvider;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.utils.Utils;
import java.io.File;
@@ -22,20 +23,20 @@ public class ManagerUpdate extends BroadcastReceiver {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Intent install = new Intent(Intent.ACTION_INSTALL_PACKAGE);
install.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Uri content = FileProvider.getUriForFile(mContext,
Uri content = FileProvider.getUriForFile(context,
"com.topjohnwu.magisk.provider", new File(uri.getPath()));
install.setData(content);
mContext.startActivity(install);
context.startActivity(install);
} else {
Intent install = new Intent(Intent.ACTION_VIEW);
install.setDataAndType(uri, "application/vnd.android.package-archive");
install.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(install);
context.startActivity(install);
}
}
},
intent.getStringExtra("link"),
intent.getStringExtra(MagiskManager.INTENT_LINK),
Utils.getLegalFilename("MagiskManager-v" +
intent.getStringExtra("version") + ".apk"));
intent.getStringExtra(MagiskManager.INTENT_VERSION) + ".apk"));
}
}

View File

@@ -4,24 +4,15 @@ 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) {
new CheckUpdates(this, true){
@Override
protected Void doInBackground(Void... voids) {
magiskManager.updateMagiskInfo();
magiskManager.updateNotification = magiskManager.prefs.getBoolean("notification", true);
return super.doInBackground(voids);
}
@Override
protected void onPostExecute(Void v) {
jobFinished(params, false);
super.onPostExecute(v);
}
}.exec();
Utils.getMagiskManager(this).updateMagiskInfo();
new CheckUpdates(this, true)
.setCallBack(() -> jobFinished(params, false)).exec();
return true;
}

View File

@@ -0,0 +1,36 @@
package com.topjohnwu.magisk.utils;
import android.support.v7.widget.RecyclerView;
import java.util.ArrayList;
public class AdaptiveList<E> extends ArrayList<E> {
private Runnable callback;
private RecyclerView mView;
public AdaptiveList(RecyclerView v) {
mView = v;
}
public void updateView() {
mView.getAdapter().notifyDataSetChanged();
mView.scrollToPosition(mView.getAdapter().getItemCount() - 1);
}
public void setCallback(Runnable cb) {
callback = cb;
}
public boolean add(E e) {
boolean ret = super.add(e);
if (ret) {
if (callback == null) {
updateView();
} else {
callback.run();
}
}
return ret;
}
}

View File

@@ -11,23 +11,39 @@ public class Logger {
public static final String MAIN_TAG = "Magisk";
public static final String DEBUG_TAG = "MagiskManager";
public static void debug(String line) {
Log.d(DEBUG_TAG, "DEBUG: " + line);
}
public static void debug(String fmt, Object... args) {
Log.d(DEBUG_TAG, "DEBUG: " + String.format(Locale.US, fmt, args));
debug(String.format(Locale.US, fmt, args));
}
public static void error(String line) {
Log.e(MAIN_TAG, "MANAGERERROR: " + line);
}
public static void error(String fmt, Object... args) {
Log.e(MAIN_TAG, "MANAGERERROR: " + String.format(Locale.US, fmt, args));
error(String.format(Locale.US, fmt, args));
}
public static void dev(String line) {
if (MagiskManager.devLogging) {
Log.d(DEBUG_TAG, line);
}
}
public static void dev(String fmt, Object... args) {
if (MagiskManager.devLogging) {
Log.d(DEBUG_TAG, String.format(Locale.US, fmt, args));
dev(String.format(Locale.US, fmt, args));
}
public static void shell(String line) {
if (MagiskManager.shellLogging) {
Log.d(DEBUG_TAG, "SHELL: " + line);
}
}
public static void shell(boolean root, String fmt, Object... args) {
if (MagiskManager.shellLogging) {
Log.d(DEBUG_TAG, (root ? "MANAGERSU: " : "MANAGERSH: ") + String.format(Locale.US, fmt, args));
}
public static void shell(String fmt, Object... args) {
shell(String.format(Locale.US, fmt, args));
}
}

View File

@@ -1,11 +1,14 @@
package com.topjohnwu.magisk.utils;
import com.topjohnwu.magisk.asyncs.RootTask;
import android.content.Context;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Collection;
import java.util.List;
/**
@@ -16,214 +19,150 @@ public class Shell {
// -1 = problematic/unknown issue; 0 = not rooted; 1 = properly rooted
public static int rootStatus;
public static final Object lock = new Object();
private static boolean isInit = false;
private static Process rootShell;
private static DataOutputStream rootSTDIN;
private static StreamGobbler rootSTDOUT;
private static List<String> rootOutList = Collections.synchronizedList(new ArrayList<String>());
private final Process shellProcess;
private final DataOutputStream STDIN;
private final DataInputStream STDOUT;
public static void init() {
private boolean isValid;
isInit = true;
private Shell() {
rootStatus = 1;
Process process = null;
DataOutputStream in = null;
DataInputStream out = null;
try {
rootShell = Runtime.getRuntime().exec("su");
rootStatus = 1;
} catch (IOException err) {
// No root
process = Runtime.getRuntime().exec("su");
in = new DataOutputStream(process.getOutputStream());
out = new DataInputStream(process.getInputStream());
} catch (IOException e) {
rootStatus = 0;
return;
}
rootSTDIN = new DataOutputStream(rootShell.getOutputStream());
rootSTDOUT = new StreamGobbler(rootShell.getInputStream(), rootOutList, true);
rootSTDOUT.start();
// Setup umask and PATH
su("umask 022");
List<String> ret = su("echo -BOC-", "id");
if (ret == null) {
// Something wrong with root, not allowed?
rootStatus = -1;
return;
}
for (String line : ret) {
if (line.contains("uid=")) {
// id command is working, let's see if we are actually root
rootStatus = line.contains("uid=0") ? rootStatus : -1;
return;
} else if (!line.contains("-BOC-")) {
rootStatus = -1;
return;
while (true) {
if (rootAccess()) {
try {
in.write(("id\n").getBytes("UTF-8"));
in.flush();
String s = new BufferedReader(new InputStreamReader(out)).readLine();
if (s.isEmpty() || !s.contains("uid=0")) {
in.close();
out.close();
process.destroy();
throw new IOException();
}
} catch (IOException e) {
rootStatus = -1;
continue;
}
break;
} else {
// Try to gain non-root sh
try {
process = Runtime.getRuntime().exec("sh");
in = new DataOutputStream(process.getOutputStream());
out = new DataInputStream(process.getInputStream());
} catch (IOException e) {
// Nothing works....
shellProcess = null;
STDIN = null;
STDOUT = null;
isValid = false;
return;
}
break;
}
}
isValid = true;
shellProcess = process;
STDIN = in;
STDOUT = out;
sh_raw("umask 022");
}
public static Shell getShell() {
return new Shell();
}
public static Shell getShell(Context context) {
return Utils.getMagiskManager(context).shell;
}
public static boolean rootAccess() {
return isInit && rootStatus > 0;
return rootStatus > 0;
}
public static List<String> sh(String... commands) {
List<String> res = Collections.synchronizedList(new ArrayList<String>());
try {
Process process = Runtime.getRuntime().exec("sh");
DataOutputStream STDIN = new DataOutputStream(process.getOutputStream());
StreamGobbler STDOUT = new StreamGobbler(process.getInputStream(), res);
STDOUT.start();
try {
for (String write : commands) {
STDIN.write((write + "\n").getBytes("UTF-8"));
STDIN.flush();
Logger.shell(false, write);
}
STDIN.write("exit\n".getBytes("UTF-8"));
STDIN.flush();
} catch (IOException e) {
if (!e.getMessage().contains("EPIPE")) {
throw e;
}
}
process.waitFor();
try {
STDIN.close();
} catch (IOException e) {
// might be closed already
}
STDOUT.join();
process.destroy();
} catch (IOException | InterruptedException e) {
// shell probably not found
res = null;
}
public List<String> sh(String... commands) {
List<String> res = new ArrayList<>();
if (!isValid) return res;
sh(res, commands);
return res;
}
// Run with the same shell by default
public static List<String> su(String... commands) {
return su(false, commands);
}
public static List<String> su(boolean newShell, String... commands) {
List<String> res;
Process process;
DataOutputStream STDIN;
StreamGobbler STDOUT;
// Create the default shell if not init
if (!newShell && !isInit) {
init();
}
if (!newShell && !rootAccess()) {
return null;
}
if (newShell) {
res = Collections.synchronizedList(new ArrayList<String>());
public void sh_raw(String... commands) {
if (!isValid) return;
synchronized (shellProcess) {
try {
process = Runtime.getRuntime().exec("su");
STDIN = new DataOutputStream(process.getOutputStream());
STDOUT = new StreamGobbler(process.getInputStream(), res);
// Run the new shell with busybox and proper umask
STDIN.write(("umask 022\n").getBytes("UTF-8"));
STDIN.flush();
} catch (IOException err) {
return null;
for (String command : commands) {
STDIN.write((command + "\n").getBytes("UTF-8"));
STDIN.flush();
}
} catch (IOException e) {
e.printStackTrace();
shellProcess.destroy();
isValid = false;
}
STDOUT.start();
} else {
process = rootShell;
STDIN = rootSTDIN;
STDOUT = rootSTDOUT;
res = rootOutList;
res.clear();
}
}
public void sh(Collection<String> output, String... commands) {
if (!isValid) return;
try {
for (String write : commands) {
STDIN.write((write + "\n").getBytes("UTF-8"));
STDIN.flush();
Logger.shell(true, write);
}
if (newShell) {
STDIN.write("exit\n".getBytes("UTF-8"));
STDIN.flush();
process.waitFor();
shellProcess.exitValue();
isValid = false;
return; // The process is dead, return
} catch (IllegalThreadStateException ignored) {
// This should be the expected result
}
synchronized (shellProcess) {
StreamGobbler out = new StreamGobbler(this.STDOUT, output);
out.start();
sh_raw(commands);
sh_raw("echo \'-shell-done-\'");
try { out.join(); } catch (InterruptedException ignored) {}
}
}
try {
STDIN.close();
} catch (IOException ignore) {
// might be closed already
}
public List<String> su(String... commands) {
if (!rootAccess()) return sh();
return sh(commands);
}
STDOUT.join();
process.destroy();
} else {
STDIN.write(("echo\n").getBytes("UTF-8"));
STDIN.flush();
STDIN.write(("echo \'-root-done-\'\n").getBytes("UTF-8"));
STDIN.flush();
while (true) {
try {
// Process terminated, it means the interactive shell has some issues
process.exitValue();
rootStatus = -1;
return null;
} catch (IllegalThreadStateException e) {
// Process still running, gobble output until done
int end = res.size() - 1;
if (end > 0) {
if (res.get(end).equals("-root-done-")) {
res.remove(end);
if (res.get(end -1).isEmpty()) {
res.remove(end -1);
}
break;
}
}
try { STDOUT.join(100); } catch (InterruptedException err) {
rootStatus = -1;
return null;
}
}
}
}
} catch (IOException e) {
if (!e.getMessage().contains("EPIPE")) {
Logger.dev("Shell: Root shell error...");
rootStatus = -1;
return null;
}
} catch(InterruptedException e) {
Logger.dev("Shell: Root shell error...");
rootStatus = -1;
public void su_raw(String... commands) {
if (!rootAccess()) return;
sh_raw(commands);
}
public void su(Collection<String> output, String... commands) {
if (!rootAccess()) return;
sh(output, commands);
}
public static abstract class AbstractList<E> extends java.util.AbstractList<E> {
@Override
public abstract boolean add(E e);
@Override
public E get(int i) {
return null;
}
return new ArrayList<>(res);
}
public static void su_async(List<String> result, String... commands) {
new RootTask<Void, Void, Void>() {
@Override
protected Void doInRoot(Void... params) {
List<String> ret = Shell.su(commands);
if (result != null) result.addAll(ret);
return null;
}
}.exec();
@Override
public int size() {
return 0;
}
}
}

View File

@@ -1,10 +1,12 @@
package com.topjohnwu.magisk.utils;
import android.text.TextUtils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;
import java.util.Collection;
/**
* Modified by topjohnwu, based on Chainfire's libsuperuser
@@ -13,40 +15,38 @@ import java.util.List;
public class StreamGobbler extends Thread {
private BufferedReader reader = null;
private List<String> writer = null;
private boolean isRoot = false;
private Collection<String> writer = null;
/**
* <p>StreamGobbler constructor</p>
*
* <p>We use this class because shell STDOUT and STDERR should be read as quickly as
* <p>We use this class because sh STDOUT and STDERR should be read as quickly as
* possible to prevent a deadlock from occurring, or Process.waitFor() never
* returning (as the buffer is full, pausing the native process)</p>
*
* @param inputStream InputStream to read from
* @param outputList {@literal List<String>} to write to, or null
*/
public StreamGobbler(InputStream inputStream, List<String> outputList) {
public StreamGobbler(InputStream inputStream, Collection<String> outputList) {
try {
while (inputStream.available() != 0) {
inputStream.skip(inputStream.available());
}
} catch (IOException ignored) {}
reader = new BufferedReader(new InputStreamReader(inputStream));
writer = outputList;
}
public StreamGobbler(InputStream inputStream, List<String> outputList, boolean root) {
reader = new BufferedReader(new InputStreamReader(inputStream));
writer = outputList;
isRoot = root;
}
@Override
public void run() {
// keep reading the InputStream until it ends (or an error occurs)
try {
String line;
while ((line = reader.readLine()) != null) {
if (TextUtils.equals(line, "-shell-done-"))
return;
writer.add(line);
if (!line.equals("-root-done-") && !line.isEmpty()) {
Logger.shell(isRoot, "OUT: " + line);
}
Logger.shell(line);
}
} catch (IOException e) {
// reader probably closed, expected exit condition

View File

@@ -43,37 +43,31 @@ public class Utils {
private static final int MAGISK_UPDATE_NOTIFICATION_ID = 1;
private static final int APK_UPDATE_NOTIFICATION_ID = 2;
public static boolean itemExist(String path) {
String command = "if [ -e " + path + " ]; then echo true; else echo false; fi";
List<String> ret = Shell.su(command);
public static boolean itemExist(Shell shell, String path) {
String command = "[ -e " + path + " ] && echo true || echo false";
List<String> ret = shell.su(command);
return isValidShellResponse(ret) && Boolean.parseBoolean(ret.get(0));
}
public static void createFile(String path) {
public static void createFile(Shell shell, String path) {
String folder = path.substring(0, path.lastIndexOf('/'));
String command = "mkdir -p " + folder + " 2>/dev/null; touch " + path + " 2>/dev/null; if [ -f \"" + path + "\" ]; then echo true; else echo false; fi";
Shell.su_async(null, command);
String command = "mkdir -p " + folder + " 2>/dev/null; touch " + path + " 2>/dev/null;";
shell.su_raw(command);
}
public static void removeItem(String path) {
String command = "rm -rf " + path + " 2>/dev/null; if [ -e " + path + " ]; then echo false; else echo true; fi";
Shell.su_async(null, command);
public static void removeItem(Shell shell, String path) {
String command = "rm -rf " + path + " 2>/dev/null";
shell.su_raw(command);
}
public static List<String> getModList(String path) {
String command = "find " + path + " -type d -maxdepth 1 ! -name \"*.core\" ! -name \"*lost+found\" ! -name \"*magisk\"";
return Shell.su(command);
public static List<String> getModList(Shell shell, String path) {
String command = "ls -d " + path + "/* | grep -v lost+found";
return shell.su(command);
}
public static List<String> readFile(String path) {
List<String> ret;
String command = "cat " + path;
if (Shell.rootAccess()) {
ret = Shell.su(command);
} else {
ret = Shell.sh(command);
}
return ret;
public static List<String> readFile(Shell shell, String path) {
String command = "cat " + path + " | sed '$a\\ ' | sed '$d'";
return shell.su(command);
}
public static void dlAndReceive(Context context, DownloadReceiver receiver, String link, String filename) {
@@ -114,23 +108,6 @@ public class Utils {
.replace("#", "").replace("@", "").replace("*", "");
}
public static String detectBootImage() {
String[] commands = {
"for PARTITION in kern-a KERN-A android_boot ANDROID_BOOT kernel KERNEL boot BOOT lnx LNX; do",
"BOOTIMAGE=`readlink /dev/block/by-name/$PARTITION || " +
"readlink /dev/block/platform/*/by-name/$PARTITION || " +
"readlink /dev/block/platform/*/*/by-name/$PARTITION`",
"if [ ! -z \"$BOOTIMAGE\" ]; then break; fi",
"done",
"echo \"$BOOTIMAGE\""
};
List<String> ret = Shell.su(commands);
if (isValidShellResponse(ret)) {
return ret.get(0);
}
return null;
}
public static boolean lowercaseContains(CharSequence string, CharSequence nonNullLowercaseSearch) {
return !TextUtils.isEmpty(string) && string.toString().toLowerCase().contains(nonNullLowercaseSearch);
}
@@ -219,6 +196,8 @@ public class Utils {
.setAutoCancel(true);
Intent intent = new Intent(magiskManager, SplashActivity.class);
intent.putExtra(MagiskManager.INTENT_SECTION, "install");
intent.putExtra(MagiskManager.INTENT_VERSION, magiskManager.remoteMagiskVersionString);
intent.putExtra(MagiskManager.INTENT_LINK, magiskManager.magiskLink);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(magiskManager);
stackBuilder.addParentStack(SplashActivity.class);
stackBuilder.addNextIntent(intent);
@@ -239,8 +218,8 @@ public class Utils {
.setVibrate(new long[]{0, 100, 100, 100})
.setAutoCancel(true);
Intent intent = new Intent(magiskManager, ManagerUpdate.class);
intent.putExtra("link", magiskManager.managerLink);
intent.putExtra("version", magiskManager.remoteManagerVersionString);
intent.putExtra(MagiskManager.INTENT_LINK, magiskManager.managerLink);
intent.putExtra(MagiskManager.INTENT_VERSION, magiskManager.remoteManagerVersionString);
PendingIntent pendingIntent = PendingIntent.getBroadcast(magiskManager,
APK_UPDATE_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(pendingIntent);

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:background="@android:color/black"
tools:context="com.topjohnwu.magisk.FlashActivity">
<include layout="@layout/toolbar"/>
<HorizontalScrollView
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="0dp">
<android.support.v7.widget.RecyclerView
android:id="@+id/flash_logs"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:layoutManager="android.support.v7.widget.LinearLayoutManager">
</android.support.v7.widget.RecyclerView>
</HorizontalScrollView>
<LinearLayout
android:id="@+id/button_panel"
style="?android:buttonStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:background="@android:color/darker_gray"
android:orientation="horizontal"
android:visibility="gone">
<Button
android:id="@+id/no_thanks"
style="?android:borderlessButtonStyle"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"
android:text="@string/close" />
<Button
android:id="@+id/reboot"
style="?android:borderlessButtonStyle"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"
android:text="@string/reboot" />
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="monospace"
android:textColor="@android:color/white"
android:textSize="10sp" />

View File

@@ -22,9 +22,9 @@
<string name="root_error">Υπάρχει root αλλά όχι άδεια για root, δεν επιτρέπεται;</string>
<string name="not_rooted">Δεν υπάρχει root</string>
<string name="proper_root">Rooted κανονικά</string>
<string name="safetyNet_check_text">Πατήστε για αρχή ελέγχου SafetyNet</string>
<string name="safetyNet_check_text">Πατήστε για έλεγχο του SafetyNet</string>
<string name="checking_safetyNet_status">Έλεγχος κατάστασης SafetyNet…</string>
<string name="safetyNet_check_success">Επιτυχής έλεγχος SafetyNet</string>
<string name="safetyNet_check_success">Επιτυχημένος έλεγχος SafetyNet</string>
<string name="safetyNet_connection_failed">Αδυναμία σύνδεσης στο Google API</string>
<string name="safetyNet_connection_suspended">Η σύνδεση στο Google API διακόπηκε</string>
<string name="safetyNet_no_response">Αδυναμία ελέγχου SafetyNet, δεν έχει Internet;</string>
@@ -41,7 +41,7 @@
<string name="boot_image_title">Τοποθεσία Εικόνας Boot</string>
<string name="detect_button">Εύρεση</string>
<string name="advanced_settings_title">Προηγμένες ρυθμίσεις</string>
<string name="keep_force_encryption">Διατήρηση force encryption</string>
<string name="keep_force_encryption">Διατήρηση επιβεβλημένης κρυπτογράφησης</string>
<string name="keep_dm_verity">Διατήρηση dm-verity</string>
<string name="current_magisk_title">Εγκατεστημένη έκδοση Magisk: %1$s</string>
<string name="install_magisk_title">Τελευταία έκδοση Magisk: %1$s</string>
@@ -60,7 +60,7 @@
<string name="disable_file_created">Η ενότητα θα απενεργοποιηθεί στην επόμενη επανεκκίνηση</string>
<string name="disable_file_removed">Η ενότητα θα ενεργοποιηθεί στην επόμενη επανεκκίνηση</string>
<string name="author">Δημιουργήθηκε από τον/την %1$s</string>
<string name="fab_flash_zip">Flash Module Zip</string>
<string name="fab_flash_zip">Flash Zip Ενότητας</string>
<!--Repo Fragment-->
<string name="update_available">Διαθέσιμη Ενημέρωση</string>
@@ -118,7 +118,7 @@
<string name="repo_cache_cleared">Η Repo cache καθαρίστηκε</string>
<string name="safetyNet_hide_notice">Αυτή η εφαρμογή χρησιμοποιεί SafetyNet\nΉδη διαχειρίζεται από το MagiskHide από προεπιλογή</string>
<string name="start_magiskhide">Εκκίνηση MagiskHide …</string>
<string name="no_magisksu_title">Δεν χρισημοποιείται το MagiskSU!</string>
<string name="no_magisksu_title">Δεν χρησιμοποιείται το MagiskSU!</string>
<string name="no_magisksu_msg">Δεν είστε rooted με το MagiskSU, η χρήση του MagiskHide μόνο μπορεί να μην είναι αρκετή!\nΔεν υποτηρίζεται επισήμως, και θα χρειαζόσασταν πρόσθετα εργαλεία (π.χ. suhide) για να περάσετε το Safety Net.</string>
<string name="understand">Καταλαβαίνω</string>
<string name="process_error">Σφάλμα διαδικασίας</string>
@@ -134,14 +134,14 @@
<string name="settings_dark_theme_summary">Ενεργοποίηση σκούρου θέματος</string>
<string name="settings_notification_title">Ειδοποίηση Ενημέρωσης</string>
<string name="settings_notification_summary">Εμφάνιση ειδοποιήσεων ενημέρωσης όταν είναι διαθέσιμη νέα έκδοση</string>
<string name="settings_clear_cache_title">Εκκαθάριση Repo Cache</string>
<string name="settings_clear_cache_summary">Καθαρίζει τις κρυφές πληροφορίες για online repos, αναγκάζει την εφαρμογή να ανανεώσει online</string>
<string name="settings_clear_cache_title">Εκκαθάριση προσωρινής μνήμης αποθετηρίων</string>
<string name="settings_clear_cache_summary">Καθαρίζει τις κρυφές πληροφορίες για απευθείας συνδεδεμένα αποθετήρια, αναγκάζει την εφαρμογή να κάνει ανανέωση σε απευθείας σύνδεση</string>
<string name="settings_core_only_title">Magisk Λειτουργία Πυρήνα Μόνο</string>
<string name="settings_core_only_summary">Ενεργοποίηση μόνο των λειτουργιών πυρήνα, όλες οι ενότητες δεν θα ενεργοποιηθούν. Τα MagiskSU, MagiskHide, και systemless hosts θα παραμείνουν ενεργά</string>
<string name="settings_core_only_summary">Ενεργοποίηση μόνο των λειτουργιών πυρήνα, καμία από τις ενότητες δεν θα ενεργοποιηθεί. Τα MagiskSU, MagiskHide, και systemless hosts θα παραμείνουν ενεργά</string>
<string name="settings_magiskhide_summary">Κρύβει το Magisk από διάφορες ανιχνεύσεις</string>
<string name="settings_busybox_title">Ενεργοποίηση BusyBox</string>
<string name="settings_busybox_summary">Bind mount Magisk\'s built-in busybox to xbin</string>
<string name="settings_busybox_summary">Υποχρεωτική προσάρτηση του ενσωματωμένου busybox του Magisk στο xbin</string>
<string name="settings_hosts_title">Systemless hosts</string>
<string name="settings_hosts_summary">Υποστήριξη Systemless hosts για εφαρμογές Adblock</string>
@@ -155,7 +155,7 @@
<string name="settings_su_request_60">60 δευτερόλεπτα</string>
<string name="superuser_access">Πρόσβαση Υπερχρήστη</string>
<string name="auto_response">Αυτόματη Απόκριση</string>
<string name="request_timeout">Request Timeout</string>
<string name="request_timeout">Χρονικό όριο Αιτήματος</string>
<string name="superuser_notification">Ειδοποίηση Υπερχρήστη</string>
<string name="request_timeout_summary">%1$s δευτερόλεπτα</string>
<string name="settings_su_reauth_title">Επανα-πιστοποίηση μετά από αναβάθμιση</string>
@@ -172,7 +172,7 @@
<string name="mount_namespace_mode">Λειτουργία προσάρτησης χώρου ονομάτων</string>
<string name="settings_ns_global">Καθολικός Χώρος Ονομάτων</string>
<string name="settings_ns_requester">Κληρονομησε Χώρο Ονομάτων</string>
<string name="settings_ns_requester">Κληρονόμησε Χώρο Ονομάτων</string>
<string name="settings_ns_isolate">Απομονωμένος Χώρος Ονομάτων</string>
<string name="global_summary">Όλες οι συνεδρίες root χρησιμοποιούν τον καθολικό χώρο oνομάτων προσάρτησης</string>
<string name="requester_summary">Οι συνεδρίες root θα κληρονομούν το χώρο ονομάτων του αιτούντα τους</string>
@@ -200,14 +200,14 @@
<string name="su_allow_toast">Στο %1$s παραχορούνται δικαιώματα υπερχρήστη</string>
<string name="su_deny_toast">Στο %1$s απορρίπτονται τα δικαιώματα υπερχρήστη</string>
<string name="no_apps_found">Δεν βρέθηκαν εφαρμογές</string>
<string name="su_snack_grant">Τα δικαιώματα υπερχρήστη του %1$s παραχορούνται</string>
<string name="su_snack_deny">Τα δικαιώματα υπερχρήστη του %1$s απορρίπτονται</string>
<string name="su_snack_grant">Παραχορούνται δικαιώματα υπερχρήστη στο %1$s</string>
<string name="su_snack_deny">Δεν παραχορούνται δικαιώματα υπερχρήστη στο %1$s</string>
<string name="su_snack_notif_on">Οι ειδοποιήσεις του %1$s είναι ενεργοποιημένες</string>
<string name="su_snack_notif_off">Οι ειδοποιήσεις του %1$s είναι απενεργοποιημένες</string>
<string name="su_snack_log_on">Η καταγραφή του %1$s είναι ενεργοποιημένη</string>
<string name="su_snack_log_off">Η καταγραφή του %1$s είναι απενεργοποιημένη</string>
<string name="su_snack_revoke">%1$s δικαιώματα ανακαλούνται</string>
<string name="su_revoke_title">Ανάκληση?</string>
<string name="su_snack_revoke">Τα δικαιώματα του %1$s ανακαλούνται</string>
<string name="su_revoke_title">Ανάκληση;</string>
<string name="su_revoke_msg">Επιβεβαίωση για ανάκληση δικαιωμάτων %1$s;</string>
<string name="toast">Toast</string>
<string name="none">Κανένα</string>

View File

@@ -73,7 +73,7 @@
<string name="app_developers">Desarroladores principales</string>
<string name="app_developers_"><![CDATA[Aplicación creada por <a href="https://github.com/topjohnwu">topjohnwu</a> en colaboración con <a href="https://github.com/d8ahazard">Digitalhigh</a> y <a href="https://github.com/dvdandroid">Dvdandroid</a>.]]></string>
<string name="app_changelog">Registro de cambios de la aplicación</string>
<string name="translators">Gawenda, netizen, Deiki, Nosi></string>
<string name="translators">Gawenda, netizen, Deiki, Nosi>, dark-basic</string>
<string name="app_version">Versión de la aplicación</string>
<string name="app_source_code">Código fuente</string>
<string name="donation">Donar</string>
@@ -117,17 +117,19 @@
<string name="process_error">Error de proceso</string>
<string name="internal_storage">El zip es almacenado en:\n[Internal Storage]%1$s</string>
<string name="zip_process_title">Procesando</string>
<string name="flashing">Flasheando</string>
<!--Settings Activity -->
<string name="settings_general_category">General</string>
<string name="settings_dark_theme_title">Tema oscuro</string>
<string name="settings_dark_theme_summary">Habilita el tema oscuro</string>
<string name="settings_notification_title">Notificar Actualización</string>
<string name="settings_notification_summary">Notificar cuando una nueva versión esté disponible</string>
<string name="settings_clear_cache_title">Limpiar caché del repositorio</string>
<string name="settings_clear_cache_summary">Limpiar la información en caché para los repositorios en línea, fuerza a la aplicación a actualizar en línea</string>
<string name="settings_core_only_summary">Habilitar sólo funciones principales, no se cargarán todos los módulos. MagiskSU, MagiskHide, y archivo hosts fuera de la partición de sistema seguirán habilitados</string>
<string name="settings_magiskhide_summary">Ocultar Magisk de varias detecciones</string>
<string name="settings_busybox_title">Habilitar BusyBox</string>
<string name="settings_busybox_summary">Montar el busybox interno de Magisk en xbin</string>
<string name="settings_hosts_title">Habilitar archivo hosts fuera de la partición de sistema</string>
<string name="settings_hosts_summary">Soporte para aplicaciones de bloqueo de publicidad fuera de la partición de sistema</string>
@@ -144,7 +146,26 @@
<string name="request_timeout">Tiempo de petición</string>
<string name="superuser_notification">Notificación de superusuario</string>
<string name="request_timeout_summary">%1$s segundos</string>
<string name="settings_su_reauth_title">Re-autenticación</string>
<string name="settings_su_reauth_summary">Pedir permisos de superusuario nuevamente si una aplicación es actualizada o reinstalada</string>
<string name="multiuser_mode">Modo MultiUsuario</string>
<string name="settings_owner_only">Sólo Administrador del Dispositivo</string>
<string name="settings_owner_manage">Administrador del Dispositivo</string>
<string name="settings_user_independent">Usuario Independiente</string>
<string name="owner_only_summary">Sólo el administrador tiene acceso root</string>
<string name="owner_manage_summary">Sólo el administrador puede supervisar el acceso root y recibir solicitudes de otros usuarios</string>
<string name="user_indepenent_summary">Cada usuario tiene separadas sus propias reglas de root </string>
<string name="multiuser_hint_owner_request">Se ha enviado una solicitud al administrador del dispositivo. Por favor, cambie a la cuenta del administrador y conceda el permiso</string>
<string name="mount_namespace_mode">Montar Namespace </string>
<string name="settings_ns_global">Global Namespace</string>
<string name="settings_ns_requester">Heredar Namespace</string>
<string name="settings_ns_isolate">Aislar Namespace</string>
<string name="global_summary">Todas las sesiones de root utilizan el soporte Global Namespace</string>
<string name="requester_summary">Las sesiones de root heredarán las peticiones Namespace</string>
<string name="isolate_summary">Cada sesión root tendrá su propia Namespace</string>
<string name="settings_development_category">Desarrollo de la aplicación</string>
<string name="settings_developer_logging_title">Habilitar información avanzada de depuración en el registro</string>
<string name="settings_developer_logging_summary">Activar esto para grabar más información en el registro</string>

View File

@@ -28,12 +28,16 @@
<string name="not_rooted">Sem root</string>
<string name="proper_root">Rooteado</string>
<string name="safetyNet_check_text">Pressione para checar o SafetyNet</string>
<string name="checking_safetyNet_status">Checando status do SafetyNet…</string>
<string name="checking_safetyNet_status">Verificando status do SafetyNet…</string>
<string name="safetyNet_check_success">SafetyNet verificado</string>
<string name="safetyNet_connection_failed">Não é possível conectar-se à API do Google</string>
<string name="safetyNet_connection_suspended">A conexão com API do Google foi suspensa</string>
<string name="safetyNet_no_response">Não é possível verificar o SafetyNet, sem Internet?</string>
<string name="safetyNet_fail">SafetyNet Falhou: CTS profile mismatch</string>
<string name="safetyNet_pass">SafetyNet Passado</string>
<string name="safetyNet_network_loss">Conexão com a rede perdida</string>
<string name="safetyNet_service_disconnected">O serviço foi morto</string>
<string name="safetyNet_res_invalid">A resposta é inválida</string>
<string name="root_info_warning">Funcionalidade muito limitada</string>
<!--Install Fragment-->
@@ -126,6 +130,9 @@
<string name="internal_storage">O zip foi salvo em:\n[Armazenamento interno]%1$s</string>
<string name="zip_process_title">Processando</string>
<string name="manual_boot_image">Por Favor, selecione manualmente a Boot Image</string>
<string name="manager_update_title">Nova atualização do Magisk Manager disponível!</string>
<string name="manager_download_install">Pressione para baixar e instalar</string>
<string name="magisk_updates">Atualizações do Magisk</string>
<!--Settings Fragment -->
<string name="settings_general_category">Geral</string>
@@ -157,7 +164,26 @@
<string name="request_timeout">Tempo limite de solicitação</string>
<string name="superuser_notification">Notificação do superusuário</string>
<string name="request_timeout_summary">%1$s segundos</string>
<string name="settings_su_reauth_title">Re-autenticar após a atualização</string>
<string name="settings_su_reauth_summary">Re-autenticar permissões de superusuário após as atualizações de um aplicativo</string>
<string name="multiuser_mode">Modo Multiusuário</string>
<string name="settings_owner_only">Apenas Proprietário do Dispositivo</string>
<string name="settings_owner_manage">Proprietário do dispositivo gerenciado</string>
<string name="settings_user_independent">Usuário Independente</string>
<string name="owner_only_summary">Somente o proprietário possui acesso de root</string>
<string name="owner_manage_summary">Somente o proprietário pode gerenciar o acesso root e receber paineis de solicitações</string>
<string name="user_indepenent_summary">Cada usuário tem suas próprias regras raiz separadas</string>
<string name="multiuser_hint_owner_request">Um pedido foi enviado ao proprietário do dispositivo. Mude para o proprietário e conceda a permissão</string>
<string name="mount_namespace_mode">Modo namespace de montagem</string>
<string name="settings_ns_global">Global namespace</string>
<string name="settings_ns_requester">Herdar namespace</string>
<string name="settings_ns_isolate">Isolar namespace</string>
<string name="global_summary">Todas as sessões raiz usam o namespace de montagem global</string>
<string name="requester_summary">As sessões de raiz herdarão o namespace do seu solicitante</string>
<string name="isolate_summary">Cada sessão raiz terá seu próprio namespace isolado</string>
<string name="settings_development_category">Desenvolvimento</string>
<string name="settings_developer_logging_title">Ativar registro mais detalhado</string>
<string name="settings_developer_logging_summary">Marque essa opção para habilitar um registro mais detalhado</string>

View File

@@ -24,7 +24,7 @@
<string name="root_error">Root присутствует, но отсутствует разрешение</string>
<string name="not_rooted">Root отсутствует</string>
<string name="proper_root">Корректный root-доступ</string>
<string name="safetyNet_check_text">Нажмите, чтобы начать проверку SafetyNet</string>
<string name="safetyNet_check_text">Проверить статус SafetyNet</string>
<string name="checking_safetyNet_status">Проверка статуса SafetyNet…</string>
<string name="safetyNet_check_success">Проверка SafetyNet пройдена</string>
<string name="safetyNet_connection_failed">Невозможно подключиться к Google API</string>

View File

@@ -216,5 +216,8 @@
<string name="settings_ns_global">全域 Namespace</string>
<string name="settings_ns_isolate">獨立 Namespace</string>
<string name="settings_ns_requester">繼承 Namespace</string>
<string name="reinstall">重新安裝</string>
<string name="update">更新</string>
<string name="magisk_updates">Magisk 更新</string>
</resources>

View File

@@ -55,6 +55,8 @@
<string name="uninstall_magisk_title">Uninstall Magisk</string>
<string name="uninstall_magisk_msg">This will remove all modules, MagiskSU, and potentially encrypt your data if not encrypted\nAre you sure to continue?</string>
<string name="version_none">(None)</string>
<string name="reinstall">Re-Install</string>
<string name="update">Update</string>
<!--Module Fragment-->
<string name="no_info_provided">(No info provided)</string>
@@ -133,6 +135,7 @@
<string name="manager_update_title">New Magisk Manager Update Available!</string>
<string name="manager_download_install">Press to download and install</string>
<string name="magisk_updates">Magisk Updates</string>
<string name="flashing">Flashing</string>
<!--Settings Activity -->
<string name="settings_general_category">General</string>

View File

@@ -7,7 +7,7 @@ buildscript {
maven { url "https://maven.google.com" }
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.0-alpha6'
classpath 'com.android.tools.build:gradle:3.0.0-alpha7'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files