diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3dd7c0da..1b60136d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -12,9 +12,10 @@ diff --git a/app/src/main/java/df/root/D2Status.java b/app/src/main/java/df/root/D2Status.java index fd14ebe7..6a16deb3 100644 --- a/app/src/main/java/df/root/D2Status.java +++ b/app/src/main/java/df/root/D2Status.java @@ -67,19 +67,6 @@ static final class Result { this.maintenance = maintenance; this.detail = detail; } - - /** The three submenu rows, worded the way the pill means them. */ - String lockscreenLabel() { - return lockscreen ? "Yes" : "No"; - } - - String maintenanceLabel() { - return maintenance ? "On" : "Off"; - } - - String deviceLabel() { - return supported ? "Supported" : "Unsupported"; - } } /** diff --git a/app/src/main/java/df/root/MainActivity.java b/app/src/main/java/df/root/MainActivity.java index 0ddd6775..45e761bc 100644 --- a/app/src/main/java/df/root/MainActivity.java +++ b/app/src/main/java/df/root/MainActivity.java @@ -30,6 +30,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.concurrent.Executor; import java.util.concurrent.Executors; @@ -227,28 +228,28 @@ private void driveProgress(String t) { // v4.0 renamed every marker and dropped the "env adopted" / "partitions set // ro" confirmations, so these steps are re-keyed to the new table. if (t.startsWith("kernel module: launching bootstrap")) { - setSeg2(0.14f, "Verification", 0xFFFFFFFF); + setSeg2(0.14f, R.string.progress_verification, 0xFFFFFFFF); } if (t.startsWith("bootstrap: loading app preferences file")) { - setSeg2(0.28f, "Verification", 0xFFFFFFFF); + setSeg2(0.28f, R.string.progress_verification, 0xFFFFFFFF); } if (t.startsWith("bootstrap: cloning zygote env") || t.startsWith("bootstrap: WARNING - clone zygote env failed")) { - setSeg2(0.42f, "Verification", 0xFFFFFFFF); + setSeg2(0.42f, R.string.progress_verification, 0xFFFFFFFF); } if (t.startsWith("bootstrap: setting partitions ro") || t.startsWith("bootstrap: WARNING - set partitions ro failed")) { - setSeg2(0.57f, "Verification", 0xFFFFFFFF); + setSeg2(0.57f, R.string.progress_verification, 0xFFFFFFFF); } if (t.startsWith("bootstrap: disabling ksu modules")) { - setSeg2(0.71f, "Verification", 0xFFFFFFFF); + setSeg2(0.71f, R.string.progress_verification, 0xFFFFFFFF); } if (t.startsWith("bootstrap: starting SU daemon")) { - setSeg2(0.85f, "Verification", 0xFFFFFFFF); + setSeg2(0.85f, R.string.progress_verification, 0xFFFFFFFF); } if (t.equals("ksud start: SUCCESS")) { setSeg1(1f); - setSeg2(1f, "Verified", 0xFFFFFFFF); + setSeg2(1f, R.string.progress_verified, 0xFFFFFFFF); } if (exploitPhase.equals("cleanup") && t.startsWith("restoring")) { cleanupSteps++; @@ -257,11 +258,11 @@ private void driveProgress(String t) { private void setSeg1(float p) { seg1 = p; - binding.twoStep.setSeg1(p, Math.round(p * 100) + "%"); + binding.twoStep.setSeg1(p, getString(R.string.progress_percent, Math.round(p * 100))); } - private void setSeg2(float p, String label, int color) { - binding.twoStep.setSeg2(p, label, color); + private void setSeg2(float p, int labelRes, int color) { + binding.twoStep.setSeg2(p, labelRes, color); } /** VIS4: the room the stack keeps under the advanced-log card, which is also @@ -371,7 +372,7 @@ private static String stripHeader(String t) { java.util.regex.Matcher m = java.util.regex.Pattern .compile("^===\\s*(.*?)\\s*===$").matcher(t); if (!m.matches()) return t; - String h = m.group(1).toUpperCase(); + String h = m.group(1).toUpperCase(Locale.ROOT); // The native binary still prints "=== exploit (patching files) ==="; // the UI shows the short form. if (h.equals("EXPLOIT (PATCHING FILES)")) return "PATCHING"; @@ -395,17 +396,72 @@ private boolean isHeader(String line) { /** Header lines render big, white and bold; the rest is dimmed. */ private CharSequence styleLogLine(String line) { - SpannableString ss = new SpannableString(line); + SpannableString ss = new SpannableString(localizeLogLine(line)); if (isHeader(line)) { - ss.setSpan(new StyleSpan(Typeface.BOLD), 0, line.length(), 0); - ss.setSpan(new RelativeSizeSpan(1.3f), 0, line.length(), 0); - ss.setSpan(new ForegroundColorSpan(0xFFFFFFFF), 0, line.length(), 0); + ss.setSpan(new StyleSpan(Typeface.BOLD), 0, ss.length(), 0); + ss.setSpan(new RelativeSizeSpan(1.3f), 0, ss.length(), 0); + ss.setSpan(new ForegroundColorSpan(0xFFFFFFFF), 0, ss.length(), 0); } else { - ss.setSpan(new ForegroundColorSpan(0xFFB3B3B3), 0, line.length(), 0); + ss.setSpan(new ForegroundColorSpan(0xFFB3B3B3), 0, ss.length(), 0); } return ss; } + /** Localize display text after parsing so stored and exported logs keep their original format. */ + private String localizeLogLine(String line) { + switch (line) { + case LOG_HEADER: return getString(R.string.log_system); + case "SETUP": return getString(R.string.log_setup); + case "PATCHING": return getString(R.string.log_patching); + case "INIT": return getString(R.string.log_init); + case "CLEANUP": return getString(R.string.log_cleanup); + case "EXPLOIT SUCCESS": return getString(R.string.log_success); + case "already hooked, skipping": return getString(R.string.log_already_hooked); + case "ERROR: Developer Options → 'Log buffer size' is set to Off.": + return getString(R.string.log_buffer_off); + case "Change it to any other value and retry.": return getString(R.string.log_buffer_retry); + case "no SU manager selected": return getString(R.string.log_no_manager); + case "patch: DEVICE NOT VULNERABLE": return getString(R.string.log_not_vulnerable); + default: break; + } + + String removed = " is no longer installed"; + if (line.endsWith(removed)) { + return getString(R.string.log_manager_removed, + line.substring(0, line.length() - removed.length())); + } + String missingKsud = "libksud.so not found in "; + if (line.startsWith(missingKsud)) { + return getString(R.string.log_ksud_not_found, line.substring(missingKsud.length())); + } + + int separator = line.indexOf(": "); + if (separator < 0) return line; + String value = line.substring(separator + 2); + switch (line.substring(0, separator)) { + case "app version": return getString(R.string.log_app_version, value); + case "manufacturer": return getString(R.string.log_manufacturer, value); + case "model": return getString(R.string.log_model, value); + case "android version": return getString(R.string.log_android_version, value); + case "security patch": return getString(R.string.log_security_patch, value); + case "kernel version": return getString(R.string.log_kernel_version, value); + case "exception": return getString(R.string.log_exception, value); + case "EXPLOIT FAILED": return getString(R.string.log_failure, localizeFailureReason(value)); + default: return line; + } + } + + private String localizeFailureReason(String reason) { + switch (reason) { + case "READING PREFS FAILED": return getString(R.string.log_reason_prefs); + case "DISABLE MODULES FAILED": return getString(R.string.log_reason_modules); + case "KSUD NONZERO EXIT": return getString(R.string.log_reason_ksud); + case "CHECK LOGCAT & DMESG": return getString(R.string.log_reason_check_logs); + case "FAILED TO PATCH FILES": return getString(R.string.log_reason_patch); + default: return reason; + } + } + private void saveLog() { try (FileOutputStream out = new FileOutputStream(lastLogFile)) { out.write(logBuffer.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8)); @@ -445,7 +501,7 @@ private String readLastLog() { // carried both overloads since the first API level. StringBuffer sb = new StringBuffer(); while (m.find()) m.appendReplacement(sb, java.util.regex.Matcher - .quoteReplacement(m.group().toLowerCase())); + .quoteReplacement(m.group().toLowerCase(Locale.ROOT))); m.appendTail(sb); return sb.toString(); } catch (IOException e) { @@ -533,8 +589,8 @@ protected void onCreate(Bundle savedInstanceState) { .getBoolean("disable_modules", false)); moduleRefresh = false; binding.modulesSubtitle.setText(binding.switchModules.isChecked() - ? "Enabled - Active at next reroot" - : "Disabled - No Modules on reroot"); + ? R.string.modules_enabled + : R.string.modules_disabled); binding.switchModules.setOnCheckedChangeListener((btn, on) -> { if (moduleRefresh) return; btn.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP); @@ -566,7 +622,7 @@ protected void onCreate(Bundle savedInstanceState) { // been set, because that is the combination the headline renders // in - and the update pill has to be exactly this wide to cover it. headlinePx = Math.round( - titleView.getPaint().measureText("DirtyFrag")); + titleView.getPaint().measureText(getString(R.string.app_name))); } } }); @@ -630,18 +686,18 @@ && createDeviceProtectedStorageContext() .getSharedPreferences("dfroot", MODE_PRIVATE) .getBoolean("last_run_success", false); boolean lastFailed = hasLastLog - && last.toLowerCase().contains("=== exploit failed"); + && last.toLowerCase(Locale.ROOT).contains("=== exploit failed"); if (hookedNoRoot) { // Nothing at all used to be drawn on this path: a boot that was hooked without root // showed an empty bar, which reads as "nothing happened" when the truth is that the // patch landed and the root did not. The two segments may disagree, so they do. This // also keeps the bar from contradicting the pill: the setFailedState() branch below // relabels the pill "Run exploit", while the boot block above settled it on "Reboot". - binding.twoStep.setSeg1(1f, "100%"); - binding.twoStep.setSeg2(1f, "No su", 0xFFE57373); + binding.twoStep.setSeg1(1f, getString(R.string.progress_percent, 100)); + binding.twoStep.setSeg2(1f, R.string.progress_no_su, 0xFFE57373); } else if (rootedNow || lastSuccess) { - binding.twoStep.setSeg1(1f, "100%"); - binding.twoStep.setSeg2(1f, "Verified", 0xFFFFFFFF); + binding.twoStep.setSeg1(1f, getString(R.string.progress_percent, 100)); + binding.twoStep.setSeg2(1f, R.string.progress_verified, 0xFFFFFFFF); } else if (lastFailed) { setFailedState(); } @@ -652,14 +708,14 @@ && createDeviceProtectedStorageContext() if (!runArmed) { runArmed = true; v.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP); - binding.btnRun.setText("Are you sure"); + binding.btnRun.setText(R.string.run_confirm); return; } runArmed = false; v.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP); running = true; binding.btnRun.setEnabled(false); - binding.btnRun.setText("Running"); + binding.btnRun.setText(R.string.running); // Same dark greyed-out styling as the Rooted state. binding.btnRun.setTextColor(0xFF6E6E6E); binding.btnRun.setBackgroundTintList(ColorStateList.valueOf(0xFF1F1F1F)); @@ -716,7 +772,7 @@ && createDeviceProtectedStorageContext() // -- Open Github Page row -- TextView ghRow = new TextView(this); ghRow.setBackgroundResource(R.drawable.menu_row_highlight); - ghRow.setText("Open Github Page"); + ghRow.setText(R.string.open_github); ghRow.setGravity(android.view.Gravity.CENTER_VERTICAL | android.view.Gravity.START); ghRow.setPadding((int) (20 * md), 0, 0, 0); ghRow.setTextColor(0xFFE8E8E8); @@ -739,7 +795,7 @@ && createDeviceProtectedStorageContext() // word in it. -- TextView rlRow = new TextView(this); rlRow.setBackgroundResource(R.drawable.menu_row_highlight); - rlRow.setText("Reset UI Layout"); + rlRow.setText(R.string.reset_ui_layout); rlRow.setGravity(android.view.Gravity.CENTER_VERTICAL | android.view.Gravity.START); rlRow.setPadding((int) (20 * md), 0, 0, 0); rlRow.setTextColor(0xFFE8E8E8); @@ -769,7 +825,7 @@ && createDeviceProtectedStorageContext() exRow.setPadding((int) (20 * md), 0, (int) (20 * md), 0); exRow.setBackgroundResource(R.drawable.menu_row_highlight); TextView exText = new TextView(this); - exText.setText("Expert Mode"); + exText.setText(R.string.expert_mode); exText.setTextColor(0xFFE8E8E8); exText.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 15); exRow.addView(exText, new android.widget.LinearLayout.LayoutParams( @@ -858,8 +914,8 @@ && createDeviceProtectedStorageContext() * disabled toggles, titles suffixed with (Expert). */ private void applyExpertMode() { boolean ok = expertMode; - binding.tvAutorunTitle.setText(ok ? "Autorun" : "Autorun (Expert)"); - binding.tvRebootTitle.setText(ok ? "Auto reboot" : "Auto reboot (Expert)"); + binding.tvAutorunTitle.setText(ok ? R.string.autorun : R.string.autorun_expert); + binding.tvRebootTitle.setText(ok ? R.string.auto_reboot : R.string.auto_reboot_expert); binding.tvAutorunTitle.setTextColor(ok ? 0xFFFFFFFF : 0xFF6E6E6E); binding.tvAutorunDesc.setTextColor(ok ? 0xFF9E9E9E : 0xFF5A5A5A); binding.tvRebootTitle.setTextColor(ok ? 0xFFFFFFFF : 0xFF6E6E6E); @@ -965,7 +1021,7 @@ public void onCardSelectionCleared() { b.setImageTintList(ColorStateList.valueOf(0xFF3A3A3C)); b.setScaleType(android.widget.ImageView.ScaleType.CENTER); b.setPadding(0, 0, 0, 0); - b.setContentDescription("Hide card"); + b.setContentDescription(getString(R.string.hide_card)); // Above the lifted card (18dp), below the menu dim layer (30dp). b.setElevation(26 * md); b.setVisibility(View.GONE); @@ -1239,8 +1295,8 @@ private void applyModuleState(boolean on) { // The subtitle is the whole message. Nothing is announced over the bottom // of the screen for a switch the user can already see move. binding.modulesSubtitle.setText(on - ? "Enabled - Active at next reroot" - : "Disabled - No Modules on reroot"); + ? R.string.modules_enabled + : R.string.modules_disabled); } // ---- SU manager picker -------------------------------------------------- @@ -1290,8 +1346,8 @@ private void loadSuManagerPref() { if (suManagerPkg == null) sp.edit().remove("su_manager").apply(); } binding.suManagerSubtitle.setText(suManagerPkg == null - ? "Not selected - required" - : suManagerLabel + " (" + suManagerPkg + ")"); + ? getString(R.string.su_manager_required) + : getString(R.string.su_manager_selected, suManagerLabel, suManagerPkg)); binding.suManagerSubtitle.setTextColor(suManagerPkg == null ? 0xFFE57373 : 0xFF9E9E9E); } @@ -1310,7 +1366,7 @@ private void showSuManagerPopup() { if (apps.isEmpty()) { TextView none = new TextView(this); - none.setText("No app with libksud.so found"); + none.setText(R.string.su_manager_not_found); none.setGravity(android.view.Gravity.CENTER_VERTICAL | android.view.Gravity.START); none.setPadding((int) (20 * md), (int) (12 * md), (int) (20 * md), (int) (12 * md)); none.setTextColor(0xFF6E6E6E); @@ -1414,7 +1470,7 @@ private void selectSuManager(SuManagerEntry e) { .edit().putString("su_manager", e.packageName).apply(); suManagerPkg = e.packageName; suManagerLabel = e.label; - binding.suManagerSubtitle.setText(e.label + " (" + e.packageName + ")"); + binding.suManagerSubtitle.setText(getString(R.string.su_manager_selected, e.label, e.packageName)); binding.suManagerSubtitle.setTextColor(0xFF9E9E9E); updateRunButton(); } @@ -1452,7 +1508,7 @@ private void updateRunButton() { } boolean ok = suManagerPkg != null; binding.btnRun.setEnabled(ok); - binding.btnRun.setText("Run exploit"); + binding.btnRun.setText(R.string.run_exploit); binding.btnRun.setTextColor(ok ? 0xFFE0E0E0 : 0xFF6E6E6E); binding.btnRun.setBackgroundTintList(ColorStateList.valueOf(ok ? 0xFF7A7A7A : 0xFF1F1F1F)); binding.btnRun.setStrokeColor( @@ -1482,7 +1538,7 @@ private void showUpdatePill() { if (word <= 0) { // The font failed to load, so no title paint measured the headline. A // pill that just fits its own word is honest; a guessed width is not. - word = Math.round(binding.updateLabel.getPaint().measureText("Update")); + word = Math.round(binding.updateLabel.getPaint().measureText(getString(R.string.update))); } // Wider than the word it replaces. The headline's advance is exactly as wide // as a word, and a capsule cut to that reads as a badge clipped to its own @@ -1896,13 +1952,18 @@ private void showRecoveryPopup() { box.setBackgroundResource(R.drawable.popup_bg); box.setPadding(0, (int) (6 * md), 0, (int) (6 * md)); - box.addView(odinRow("DL/Recovery", dlRecoveryLabel(shown, vault), verdictValue), odinRowLp(md)); + box.addView(odinRow(R.string.recovery_download, dlRecoveryLabel(shown, vault), verdictValue), + odinRowLp(md)); box.addView(odinSep(md), odinSepLp(md)); - box.addView(odinRow("Lockscreen", shown.lockscreenLabel()), odinRowLp(md)); + box.addView(odinRow(R.string.recovery_lockscreen, + getString(shown.lockscreen ? R.string.recovery_yes : R.string.recovery_no)), odinRowLp(md)); box.addView(odinSep(md), odinSepLp(md)); - box.addView(odinRow("Maintenance Mode", shown.maintenanceLabel()), odinRowLp(md)); + box.addView(odinRow(R.string.recovery_maintenance, + getString(shown.maintenance ? R.string.recovery_on : R.string.recovery_off)), odinRowLp(md)); box.addView(odinSep(md), odinSepLp(md)); - box.addView(odinRow("Device", shown.deviceLabel()), odinRowLp(md)); + box.addView(odinRow(R.string.recovery_device, + getString(shown.supported ? R.string.recovery_supported : R.string.recovery_unsupported)), + odinRowLp(md)); // Whether either switch below can be moved at all. Expert Mode arms them; the firmware has // to have something for them to do. A device whose firmware never gates download mode has @@ -1920,7 +1981,7 @@ private void showRecoveryPopup() { box.addView(odinSep(md), odinSepLp(md)); final OneUiSwitch[] writeSw = new OneUiSwitch[1]; final TextView[] writeName = new TextView[1]; - box.addView(odinToggleRow(switchName("Recovery Writer", writerLive, shown.supported), + box.addView(odinToggleRow(switchName(R.string.recovery_writer, writerLive, shown.supported), vault != null && vault.ok && vault.atCommand == 1, writerLive, writeSw, writeName), odinRowLp(md)); if (writerLive) { @@ -1929,10 +1990,10 @@ private void showRecoveryPopup() { // a root manager that is not answering must not be flippable into a loop of su // calls. Its answer is the only thing allowed to move the thumb again. writeSw[0].setEnabled(false); - writeName[0].setText(want ? "Writing flag ..." : "Clearing flag ..."); + writeName[0].setText(want ? R.string.recovery_writing : R.string.recovery_clearing); writeDmcFlag(want, answered -> { verdictValue[0].setText(dlRecoveryLabel(shown, answered)); - writeName[0].setText(switchName("Recovery Writer", writerLive, shown.supported)); + writeName[0].setText(switchName(R.string.recovery_writer, writerLive, shown.supported)); // What the vault answered, not what was asked for. The firmware-that-does-not // -gate path hands back the vault as it was, which before a read is no vault // at all - in which case the honest position is off. @@ -1967,8 +2028,10 @@ private void showRecoveryPopup() { // owner has costs one suffix on a row they are already reading, and saves them finding out // by rebooting. final boolean wantAuto = sp.getBoolean(PREF_D2_AUTO, false); - String autoName = switchName("Autorun Writer", writerLive, shown.supported); - if (wantAuto && writerLive && !bootPathEnabled()) autoName += " (at reroot)"; + String autoName = switchName(R.string.recovery_autorun_writer, writerLive, shown.supported); + if (wantAuto && writerLive && !bootPathEnabled()) { + autoName = getString(R.string.recovery_at_reroot, autoName); + } box.addView(odinToggleRow(autoName, wantAuto, writerLive, autoSw, null), odinRowLp(md)); autoSw[0].setOnCheckedChangeListener((view, checked) -> @@ -2086,8 +2149,8 @@ public void getOutline(View view, android.graphics.Outline outline) { * its near-white, so the half of the pair that can only be read is the dimmer * one and the answer is what the eye lands on. */ - private android.widget.LinearLayout odinRow(String label, String value) { - return odinRow(label, value, null); + private android.widget.LinearLayout odinRow(int labelRes, String value) { + return odinRow(labelRes, value, null); } /** @@ -2095,7 +2158,7 @@ private android.widget.LinearLayout odinRow(String label, String value) { * can change need that: the answer has to be painted into the menu that is already open, * not left describing the vault as it was before the press. */ - private android.widget.LinearLayout odinRow(String label, String value, + private android.widget.LinearLayout odinRow(int labelRes, String value, android.widget.TextView[] slot) { float md = getResources().getDisplayMetrics().density; android.widget.LinearLayout row = new android.widget.LinearLayout(this); @@ -2104,7 +2167,7 @@ private android.widget.LinearLayout odinRow(String label, String value, row.setPadding((int) (20 * md), 0, (int) (20 * md), 0); TextView name = new TextView(this); - name.setText(label); + name.setText(labelRes); name.setTextColor(0xFF9E9E9E); name.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 15); row.addView(name, new android.widget.LinearLayout.LayoutParams( @@ -2202,10 +2265,12 @@ private static android.widget.LinearLayout.LayoutParams odinSepLp(float md) { * writer switch controls is not given a row of its own any more; its thumb is that byte, on the * same card. */ - private static String dlRecoveryLabel(D2Status.Result est, D2Writer.Vault v) { - if (!est.supported) return "Not gated here"; - if (v != null && v.ok) return v.gated() ? "D2 Blocked" : "Allowed"; - return "Unknown"; + private String dlRecoveryLabel(D2Status.Result est, D2Writer.Vault v) { + if (!est.supported) return getString(R.string.recovery_not_gated); + if (v != null && v.ok) { + return getString(v.gated() ? R.string.recovery_blocked : R.string.recovery_allowed); + } + return getString(R.string.recovery_unknown); } /** @@ -2215,9 +2280,10 @@ private static String dlRecoveryLabel(D2Status.Result est, D2Writer.Vault v) { * armed yet" and "will never be live here" is the difference between coming back later and * coming back never. */ - private static String switchName(String base, boolean live, boolean supported) { - if (!supported) return base + " (no gate)"; - return live ? base : base + " (Expert)"; + private String switchName(int nameRes, boolean live, boolean supported) { + String base = getString(nameRes); + if (!supported) return getString(R.string.recovery_no_gate, base); + return live ? base : getString(R.string.recovery_expert, base); } @@ -2228,10 +2294,10 @@ private static String switchName(String base, boolean live, boolean supported) { private void setFailedState() { failedRun = true; binding.twoStep.setFailed(true); - binding.twoStep.setSeg1(seg1, "Failure"); - binding.twoStep.setSeg2(1f, "Reboot", 0xFFE57373); + binding.twoStep.setSeg1(seg1, getString(R.string.progress_failure)); + binding.twoStep.setSeg2(1f, R.string.progress_reboot, 0xFFE57373); binding.btnRun.setEnabled(false); - binding.btnRun.setText("Run exploit"); + binding.btnRun.setText(R.string.run_exploit); binding.btnRun.setTextColor(0xFF6E6E6E); binding.btnRun.setBackgroundTintList(ColorStateList.valueOf(0xFF1F1F1F)); binding.btnRun.setStrokeColor(ColorStateList.valueOf(0xFF1F1F1F)); @@ -2252,10 +2318,10 @@ private void setFailedState() { private void setHookedNoRootState() { failedRun = true; binding.twoStep.setFailed(false); - binding.twoStep.setSeg1(1f, "100%"); - binding.twoStep.setSeg2(1f, "No su", 0xFFE57373); + binding.twoStep.setSeg1(1f, getString(R.string.progress_percent, 100)); + binding.twoStep.setSeg2(1f, R.string.progress_no_su, 0xFFE57373); binding.btnRun.setEnabled(false); - binding.btnRun.setText("Reboot"); + binding.btnRun.setText(R.string.progress_reboot); binding.btnRun.setTextColor(0xFF6E6E6E); binding.btnRun.setBackgroundTintList(ColorStateList.valueOf(0xFF1F1F1F)); binding.btnRun.setStrokeColor(ColorStateList.valueOf(0xFF1F1F1F)); @@ -2271,7 +2337,7 @@ private void setRootedState(boolean rooted) { int bg = (rooted || !ok) ? 0xFF1F1F1F : 0xFF7A7A7A; int stroke = (rooted || !ok) ? 0xFF1F1F1F : 0xFFA6A6A6; binding.btnRun.setEnabled(ok); - binding.btnRun.setText(rooted ? "Rooted" : "Run exploit"); + binding.btnRun.setText(rooted ? R.string.rooted : R.string.run_exploit); binding.btnRun.setTextColor(fg); binding.btnRun.setBackgroundTintList(ColorStateList.valueOf(bg)); binding.btnRun.setStrokeColor(ColorStateList.valueOf(stroke)); @@ -2534,7 +2600,7 @@ private void runExploit() { boolean rooted = runRc == 0 || RootState.state() == RootState.State.ROOTED; if (rooted) { setRootedState(true); - binding.twoStep.setSeg2(1f, "Verified", 0xFFFFFFFF); + binding.twoStep.setSeg2(1f, R.string.progress_verified, 0xFFFFFFFF); } else if (RootState.hooked()) { // The hook is in and the payload ran; whatever stopped the chain sits downstream // of the mutex - which is also why the pill asks for a reboot instead of offering diff --git a/app/src/main/java/df/root/TwoStepProgressView.java b/app/src/main/java/df/root/TwoStepProgressView.java index 8538cf09..905a852d 100644 --- a/app/src/main/java/df/root/TwoStepProgressView.java +++ b/app/src/main/java/df/root/TwoStepProgressView.java @@ -30,8 +30,8 @@ public class TwoStepProgressView extends View { private float seg1 = 0f; private float seg2 = 0f; - private String leftLabel = "Waiting"; - private String rightLabel = "Verification"; + private String leftLabel; + private String rightLabel; private int rightColor = 0xFF8E8E8E; private boolean failed; private float greyMix = 0f; @@ -41,6 +41,8 @@ public class TwoStepProgressView extends View { public TwoStepProgressView(Context context, AttributeSet attrs) { super(context, attrs); + leftLabel = context.getString(R.string.progress_waiting); + rightLabel = context.getString(R.string.progress_verification); textPaint.setTextAlign(Paint.Align.CENTER); } @@ -51,11 +53,11 @@ public void setSeg1(float p, String label) { invalidate(); } - public void setSeg2(float p, String label, int labelColor) { + public void setSeg2(float p, int labelRes, int labelColor) { seg2 = clamp(p); - rightLabel = label; + rightLabel = getContext().getString(labelRes); rightColor = labelColor; - if ("Verified".equals(label) && seg1 >= 1f) { + if (labelRes == R.string.progress_verified && seg1 >= 1f) { scheduleGreyOut(); } else { cancelGreyOut(); @@ -73,8 +75,8 @@ public void setFailed(boolean f) { public void reset() { cancelGreyOut(); failed = false; - setSeg1(0f, "Waiting"); - setSeg2(0f, "Verification", 0xFFFFFFFF); + setSeg1(0f, getContext().getString(R.string.progress_waiting)); + setSeg2(0f, R.string.progress_verification, 0xFFFFFFFF); } /** When the bar reaches 100% + Verified, ease the fill from blue to grey diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index c545c95c..12d42924 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -25,7 +25,7 @@ android:layout_height="56dp" android:contentInsetStart="0dp" android:contentInsetStartWithNavigation="0dp" - android:title="DirtyFrag" + android:title="@string/app_name" android:titleMarginStart="32dp" android:titleTextAppearance="@style/ToolbarTitle"> @@ -38,7 +38,7 @@ android:layout_gravity="end|center_vertical" android:layout_marginEnd="16dp" android:background="@drawable/bg_circle" - android:contentDescription="Menu" + android:contentDescription="@string/menu" android:scaleType="center" android:src="@drawable/ic_more" /> @@ -67,7 +67,7 @@ android:layout_marginStart="32dp" android:background="@drawable/bg_update_pill" android:clickable="true" - android:contentDescription="Update available, open the release page" + android:contentDescription="@string/update_available_description" android:focusable="true" android:visibility="gone"> @@ -80,7 +80,7 @@ android:layout_height="match_parent" android:layout_gravity="start|center_vertical" android:gravity="center" - android:text="Update" + android:text="@string/update" android:textColor="#FF101418" android:textSize="16sp" android:textStyle="bold" /> @@ -117,7 +117,7 @@ android:layout_marginEnd="6dp" android:background="@drawable/bg_odin_pill" android:clickable="true" - android:contentDescription="Recovery mode: toggle download-mode authorisation" + android:contentDescription="@string/recovery_description" android:focusable="true" android:gravity="center_vertical" android:minWidth="44dp" @@ -137,7 +137,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="5dp" - android:text="Recovery" + android:text="@string/recovery" android:textColor="#FFD9D9D9" android:textSize="12sp" android:textStyle="bold" /> @@ -223,7 +223,7 @@ @@ -231,7 +231,7 @@ android:id="@+id/suManagerSubtitle" android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="Not selected - required" + android:text="@string/su_manager_required" android:textColor="#FF9E9E9E" android:textSize="12sp" /> @@ -242,7 +242,7 @@ android:layout_width="20dp" android:layout_height="20dp" android:layout_gravity="center_vertical" - android:contentDescription="Select" + android:contentDescription="@string/select" android:src="@drawable/ic_chevron" /> @@ -285,7 +285,7 @@ android:id="@+id/tvAutorunTitle" android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="Autorun" + android:text="@string/autorun" android:textColor="#FFFFFFFF" android:textSize="16sp" /> @@ -293,7 +293,7 @@ android:id="@+id/tvAutorunDesc" android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="Runs the exploit after boot" + android:text="@string/autorun_description" android:textColor="#FF9E9E9E" android:textSize="12sp" /> @@ -325,7 +325,7 @@ android:id="@+id/tvRebootTitle" android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="Auto reboot" + android:text="@string/auto_reboot" android:textColor="#FFFFFFFF" android:textSize="16sp" /> @@ -333,7 +333,7 @@ android:id="@+id/tvRebootDesc" android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="Soft reboots after root" + android:text="@string/auto_reboot_description" android:textColor="#FF9E9E9E" android:textSize="12sp" /> @@ -381,7 +381,7 @@ @@ -389,7 +389,7 @@ android:id="@+id/modulesSubtitle" android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="Enabled - active at next reboot" + android:text="@string/modules_enabled" android:textColor="#FF9E9E9E" android:textSize="12sp" /> @@ -436,14 +436,14 @@ @@ -545,7 +545,7 @@ android:paddingEnd="28dp" android:elevation="12dp" android:stateListAnimator="@null" - android:text="Run exploit" + android:text="@string/run_exploit" android:textSize="14sp" android:backgroundTint="#FF7A7A7A" android:textColor="#FFE0E0E0" @@ -572,7 +572,7 @@ android:background="@drawable/bg_pill" android:backgroundTint="#FF2E2E30" android:clickable="true" - android:contentDescription="Open SU manager" + android:contentDescription="@string/open_su_manager" android:elevation="12dp" android:enabled="false" android:focusable="true" @@ -590,7 +590,7 @@ android:layout_gravity="start|center_vertical" android:layout_marginStart="22dp" android:alpha="0" - android:text="Manager" + android:text="@string/manager" android:textColor="#FF1F1F1F" android:textSize="14sp" /> @@ -600,7 +600,7 @@ android:layout_height="20dp" android:layout_gravity="end|center_vertical" android:layout_marginEnd="17dp" - android:contentDescription="Open" + android:contentDescription="@string/open" android:src="@drawable/ic_chevron" /> @@ -630,7 +630,7 @@ android:layout_alignBottom="@id/btnRun" android:layout_marginStart="33dp" android:background="@drawable/bg_circle_grey_ripple" - android:contentDescription="Save log to Downloads" + android:contentDescription="@string/save_log" android:elevation="12dp" android:padding="0dp" android:scaleType="center" diff --git a/app/src/main/res/values-b+zh+Hans/strings.xml b/app/src/main/res/values-b+zh+Hans/strings.xml new file mode 100644 index 00000000..027ef557 --- /dev/null +++ b/app/src/main/res/values-b+zh+Hans/strings.xml @@ -0,0 +1,99 @@ + + + + 菜单 + 更新 + 有可用更新,打开发布页面 + 打开 GitHub 项目 + 重置界面布局 + 专家模式 + 隐藏卡片 + + + Root 管理器 + 请选择管理器 + %1$s(%2$s) + 未找到包含 libksud.so 的应用 + 选择 + 开机自动提权 + 开机自动提权(需专家模式) + 设备开机后自动尝试获取 Root + 自动软重启 + 自动软重启(需专家模式) + 提权成功后执行软重启 + KernelSU 模块 + 已启用,下次提权时加载模块 + 已禁用,下次提权时跳过模块 + 显示高级日志 + 查看可滚动的完整日志 + + + 开始提权 + 确认提权 + 提权中 + 已提权 + 管理器 + 打开 Root 管理器 + 打开 + 将日志保存到下载目录 + 等待开始 + 验证 + 已验证 + 未获取 su + 提权失败 + 请重启 + %1$d%% + + + 下载模式 + 打开下载模式授权菜单 + 下载/恢复模式 + 锁屏凭据 + 维护模式 + 设备支持 + 写入下载授权 + 自动写入授权 + %1$s(再次提权时) + %1$s(此固件不适用) + %1$s(需专家模式) + 正在写入授权… + 正在清除授权… + 此固件无此限制 + D2 已限制 + 允许进入 + 状态未知 + 支持 + 不支持 + 已设置 + 未设置 + 已开启 + 已关闭 + + + 系统信息 + 准备 + 修补文件 + 初始化 + 清理 + 提权成功 + 提权失败:%1$s + 应用版本:%1$s + 制造商:%1$s + 设备型号:%1$s + Android 版本:%1$s + 安全补丁:%1$s + 内核版本:%1$s + 已检测到提权挂钩,跳过本次运行 + 开发者选项中的「日志缓冲区大小」已关闭。 + 请选择一个日志缓冲区大小,再重试。 + 尚未选择 Root 管理器 + %1$s 已卸载 + 在 %1$s 中找不到 libksud.so + 修补检查显示设备不受此漏洞影响 + 异常:%1$s + 读取应用设置失败 + 停用模块失败 + ksud 退出并返回错误 + 请检查 logcat 和 dmesg 日志 + 修补文件失败 + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..f8e82b93 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,101 @@ + + + DirtyFrag + + + Menu + Update + Update available, open the release page + Open GitHub Page + Reset UI Layout + Expert Mode + Hide card + + + SU Manager + Not selected - required + "%1$s (%2$s)" + No app with libksud.so found + Select + Autorun + Autorun (Expert) + Runs the exploit after boot + Auto reboot + Auto reboot (Expert) + Soft reboots after root + KernelSU Modules + Enabled - Active at next reroot + Disabled - No Modules on reroot + Show advanced log + Shows full scrollable log + + + Run exploit + Are you sure + Running + Rooted + Manager + Open SU manager + Open + Save log to Downloads + Waiting + Verification + Verified + No su + Failure + Reboot + %1$d%% + + + Recovery + Recovery mode: toggle download-mode authorisation + DL/Recovery + Lockscreen + Maintenance Mode + Device + Recovery Writer + Autorun Writer + %1$s (at reroot) + %1$s (no gate) + %1$s (Expert) + Writing flag ... + Clearing flag ... + Not gated here + D2 Blocked + Allowed + Unknown + Supported + Unsupported + Yes + No + On + Off + + + SYSTEM + SETUP + PATCHING + INIT + CLEANUP + EXPLOIT SUCCESS + EXPLOIT FAILED: %1$s + app version: %1$s + manufacturer: %1$s + model: %1$s + android version: %1$s + security patch: %1$s + kernel version: %1$s + already hooked, skipping + ERROR: Developer Options → \'Log buffer size\' is set to Off. + Change it to any other value and retry. + no SU manager selected + %1$s is no longer installed + libksud.so not found in %1$s + patch: DEVICE NOT VULNERABLE + exception: %1$s + READING PREFS FAILED + DISABLE MODULES FAILED + KSUD NONZERO EXIT + CHECK LOGCAT & DMESG + FAILED TO PATCH FILES + diff --git a/app/src/main/res/xml/locales_config.xml b/app/src/main/res/xml/locales_config.xml new file mode 100644 index 00000000..768c5699 --- /dev/null +++ b/app/src/main/res/xml/locales_config.xml @@ -0,0 +1,5 @@ + + + + +