diff --git a/.idea/artifacts/XiaoMiTool_jar2.xml b/.idea/artifacts/XiaoMiTool_jar2.xml
new file mode 100644
index 0000000..1f9d68c
--- /dev/null
+++ b/.idea/artifacts/XiaoMiTool_jar2.xml
@@ -0,0 +1,22 @@
+
+
+ $PROJECT_DIR$/out/artifacts/XiaoMiTool_jar2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/com_mortennobel_java_image_scaling_0_8_6.xml b/.idea/libraries/com_mortennobel_java_image_scaling_0_8_6.xml
new file mode 100644
index 0000000..3c6e4db
--- /dev/null
+++ b/.idea/libraries/com_mortennobel_java_image_scaling_0_8_6.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/jMTPe.xml b/.idea/libraries/jMTPe.xml
deleted file mode 100644
index 67bb056..0000000
--- a/.idea/libraries/jMTPe.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/libraries/lib.xml b/.idea/libraries/lib.xml
new file mode 100644
index 0000000..526231b
--- /dev/null
+++ b/.idea/libraries/lib.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 0548357..35bdbab 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 099edcf..9788782 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -3,10 +3,71 @@
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -34,41 +95,45 @@
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
+
+
-
-
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -76,68 +141,66 @@
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
+
+
+
+
@@ -155,23 +218,6 @@
- com.xiaomitool.v2.test.Rtest.
- adn
- progre
- listener
- fetching
- RUNNING RU
- FLLLL
- getDownloadFile
- selecte
- PROGRESS_TEXT
- PARTITION_FORMATTING
- REBOOTING_TO_MODE
- SEARCHING_LATEST_ROM
- Format speci
- PROCEDURE_EXC_DETAILS
- Missin
- installable
rebooting_
abort
LATEST_ROM
@@ -185,6 +231,23 @@
Download
Extract
download
+ getrom
+ Overla
+ give
+ Your device is of
+ suffix
+ TODO
+ Magisk
+ .run
+ running now
+ WAITIN
+ WAITING_DEVICE_ACTIVE
+ 163846982
+ PROCEDURE EXC
+ setCo
+ content
+ imageWr
+ ...
protected
@@ -203,60 +266,63 @@
checkAccessible
+
+
+
@@ -276,16 +342,20 @@
-
-
-
-
+
+
+
+
+
+
+
+
@@ -341,6 +411,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -452,6 +553,14 @@
+
+
+
+
+
+
+
+
@@ -459,6 +568,14 @@
+
+
+
+
+
+
+
+
@@ -479,27 +596,28 @@
+
+
+
+
-
-
-
-
+
-
+
-
+
-
+
@@ -529,7 +647,7 @@
-
+
@@ -640,11 +758,11 @@
-
-
+
+
@@ -772,47 +890,58 @@
-
+
+
+
-
+
+
+
+
+
+
+
+
+
+
-
+
-
-
+
-
-
+
+
-
-
+
+
-
+
-
+
+
@@ -829,7 +958,7 @@
file://$PROJECT_DIR$/src/com/xiaomitool/v2/xiaomi/miuithings/DefaultRequestParams.java
- 67
+ 69
@@ -842,504 +971,436 @@
-
+
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
+
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1350,11 +1411,11 @@
- XiaoMiTool:jar
+ XiaoMiTool:jar2
-
+
@@ -1402,7 +1463,8 @@
-
+
+
@@ -1422,11 +1484,11 @@
- org.apache.commons:commons-compress:1.17
+ com.mortennobel:java-image-scaling:0.8.6
-
+
diff --git a/XiaoMiTool.iml b/XiaoMiTool.iml
index 41332df..b75e6f7 100644
--- a/XiaoMiTool.iml
+++ b/XiaoMiTool.iml
@@ -1,11 +1,11 @@
-
+
-
+
@@ -23,5 +23,7 @@
+
+
\ No newline at end of file
diff --git a/proguard.conf b/proguard.conf
index 97dc552..5db2e00 100644
--- a/proguard.conf
+++ b/proguard.conf
@@ -1,8 +1,8 @@
--injars out\artifacts\XiaoMiTool_jar\XiaoMiTool.jar
--outjars out\artifacts\XiaoMiTool.jar
+-injars out\artifacts\XiaoMiTool_jar2\XiaoMiTool.jar
+-outjars out\artifacts\XiaoMiTool_jar2\XiaoMiTool_guarded.jar
--libraryjars 'C:\Program Files\Java\jdk1.8.0_172\jre\lib\rt.jar'
--libraryjars 'C:\Program Files\Java\jdk1.8.0_172\jre\lib\ext\jfxrt.jar'
+-libraryjars 'C:\Program Files\Java\jdk-11\jmods'
+-libraryjars out\artifacts\XiaoMiTool_jar2\openjfx-jmods
-dontshrink
-dontoptimize
@@ -30,8 +30,6 @@
;
}
-
-
# Keep - Applications. Keep all application classes, along with their 'main' methods.
-keepclasseswithmembers public class * {
public static void main(java.lang.String[]);
@@ -58,6 +56,20 @@
native ;
}
+# Remove - System method calls. Remove all invocations of System
+# methods without side effects whose return values are not used.
+-assumenosideeffects public class java.lang.System {
+ public static long currentTimeMillis();
+ static java.lang.Class getCallerClass();
+ public static int identityHashCode(java.lang.Object);
+ public static java.lang.SecurityManager getSecurityManager();
+ public static java.util.Properties getProperties();
+ public static java.lang.String getProperty(java.lang.String);
+ public static java.lang.String getenv(java.lang.String);
+ public static java.lang.String mapLibraryName(java.lang.String);
+ public static java.lang.String getProperty(java.lang.String,java.lang.String);
+}
+
# Remove - Math method calls. Remove all invocations of Math
# methods without side effects whose return values are not used.
-assumenosideeffects public class java.lang.Math {
@@ -306,21 +318,6 @@
public java.lang.String substring(int,int);
}
-# Remove - System method calls. Remove all invocations of System
-# methods without side effects whose return values are not used.
--assumenosideeffects public class java.lang.System {
- public static long currentTimeMillis();
- static java.lang.Class getCallerClass();
- public static int identityHashCode(java.lang.Object);
- public static java.lang.SecurityManager getSecurityManager();
- public static java.util.Properties getProperties();
- public static java.lang.String getProperty(java.lang.String);
- public static java.lang.String getenv(java.lang.String);
- public static java.lang.String mapLibraryName(java.lang.String);
- public static java.lang.String getProperty(java.lang.String,java.lang.String);
-
-}
-
-assumenosideeffects public class com.xiaomitool.v2.logging.Log {
- public static *** debug(...);
+ public static *** debug(...);
}
diff --git a/src/com/xiaomitool/v2/adb/AdbCommons.java b/src/com/xiaomitool/v2/adb/AdbCommons.java
index 4818a37..8df4ab2 100644
--- a/src/com/xiaomitool/v2/adb/AdbCommons.java
+++ b/src/com/xiaomitool/v2/adb/AdbCommons.java
@@ -98,7 +98,7 @@ public class AdbCommons {
return runner.getOutputString();
}
- private static String adb_shellWithOr(String cmd, String device, int timeout){
+ public static String adb_shellWithOr(String cmd, String device, int timeout){
cmd += " || echo "+CHECK_RETURN_CODE;
String output = adb_shell(cmd, device, timeout);
return (output == null || output.contains(CHECK_RETURN_CODE)) ? null : output;
diff --git a/src/com/xiaomitool/v2/adb/AdbCommunication.java b/src/com/xiaomitool/v2/adb/AdbCommunication.java
index 9b458ab..3728dbb 100644
--- a/src/com/xiaomitool/v2/adb/AdbCommunication.java
+++ b/src/com/xiaomitool/v2/adb/AdbCommunication.java
@@ -12,18 +12,23 @@ import java.util.Map;
public class AdbCommunication {
public static Thread trackDevicesThread, refreshDevicesThread;
- private static boolean canInterrupt = true, isTrackDeviceActive=false;
+ private static int cannotInterruptCount = 0; private static boolean isTrackDeviceActive=false;
private static final int REFRESH_TIME_MS = 2500;
private static AdbRunner trackDeviceProcess;
+ private static final Object sync = new Object();
+ public static boolean canInterrupt(){
+ return cannotInterruptCount == 0;
+ }
+
public static void startServer(){
- if (!canInterrupt){
+ if (!canInterrupt()){
Log.debug("Cannot interrupt adb connection");
}
AdbCommons.start_server();
}
public static void killServer(){
- if (!canInterrupt){
+ if (!canInterrupt()){
Log.debug("Cannot interrupt adb connection");
return;
}
@@ -34,14 +39,14 @@ public class AdbCommunication {
startServer();
}
- public static boolean canInterrupt() {
- return canInterrupt;
- }
+
public static void registerAutoScanDevices(){
- if (refreshDevicesThread != null){
- return;
+ synchronized (sync) {
+ if (refreshDevicesThread != null) {
+ return;
+ }
}
Runnable trackDevices = new Runnable() {
@Override
@@ -109,15 +114,19 @@ public class AdbCommunication {
refreshFastbootDevices();
}
- public static void closeAccess() {
- canInterrupt = false;
+ public static void getAllAccess() {
+ cannotInterruptCount++;
unregisterAutoScanDevices();
}
- public static void openAccess(){
- canInterrupt = true;
- registerAutoScanDevices();
+ public static void giveAllAccess(){
+ if (cannotInterruptCount > 0){
+ cannotInterruptCount--;
+ }
+ if (canInterrupt()) {
+ registerAutoScanDevices();
+ }
}
- private static void unregisterAutoScanDevices(){
+ public static void unregisterAutoScanDevices(){
if (trackDeviceProcess != null){
trackDeviceProcess.kill();
}
diff --git a/src/com/xiaomitool/v2/adb/FastbootCommons.java b/src/com/xiaomitool/v2/adb/FastbootCommons.java
index 6f382f5..1b115c3 100644
--- a/src/com/xiaomitool/v2/adb/FastbootCommons.java
+++ b/src/com/xiaomitool/v2/adb/FastbootCommons.java
@@ -183,4 +183,15 @@ public class FastbootCommons {
}
return flash(serial,dummyPath.toFile(), "antirbpass");
}
+ public static boolean oemRebootRecovery(String serial){
+ FastbootRunner runner = FastbootCommons.command_fast(serial,5,"oem","reboot-recovery");
+ if (runner == null || runner.getExitValue() != 0){
+ return false;
+ }
+ String out = runner.getOutputString();
+ if (out == null || out.toLowerCase().contains("fail")){
+ return false;
+ }
+ return true;
+ }
}
diff --git a/src/com/xiaomitool/v2/adb/device/Device.java b/src/com/xiaomitool/v2/adb/device/Device.java
index 22d4020..f9a926b 100644
--- a/src/com/xiaomitool/v2/adb/device/Device.java
+++ b/src/com/xiaomitool/v2/adb/device/Device.java
@@ -5,8 +5,8 @@ import com.xiaomitool.v2.adb.AdbCommons;
import com.xiaomitool.v2.adb.AdbException;
import com.xiaomitool.v2.adb.FastbootCommons;
import com.xiaomitool.v2.logging.Log;
+import com.xiaomitool.v2.utility.NotNull;
import com.xiaomitool.v2.utility.WaitSemaphore;
-import org.jetbrains.annotations.NotNull;
public class Device {
@@ -104,6 +104,8 @@ public class Device {
isConnected = connected;
if (connected){
deviceActiveSem.setPermits(1);
+ } else {
+ deviceActiveSem.setPermits(0);
}
}
public boolean isConnected() {
@@ -135,6 +137,7 @@ public class Device {
return rebootNoWait(toStatus, false);
}
private boolean rebootInternal(Status toStatus, boolean wait) throws InterruptedException, AdbException {
+ deviceActiveSem.setPermits(0);
if (Status.FASTBOOT.equals(this.status)){
return fastbootReboot(toStatus, wait);
} else {
@@ -144,26 +147,30 @@ public class Device {
private boolean fastbootReboot(Status toStatus, boolean wait) throws AdbException, InterruptedException {
if (Status.FASTBOOT.equals(toStatus)){
FastbootCommons.rebootBootloader(serial);
- return rebootWait(toStatus);
+ return wait ? rebootWait(toStatus) : true;
} else if (Status.EDL.equals(toStatus)){
FastbootCommons.oemEdl(serial);
- return rebootWait(toStatus);
- } else {
- if (!Status.DEVICE.equals(toStatus) && !wait){
- throw new AdbException("Cannot reboot from fastboot to "+toStatus.toString()+" without waiting");
+ return wait ? rebootWait(toStatus) : true;
+ } else if (Status.SIDELOAD.equals(toStatus) || Status.RECOVERY.equals(toStatus)){
+ if (FastbootCommons.oemRebootRecovery(serial)){
+ return wait ? rebootNoWait(toStatus) : true;
}
- FastbootCommons.reboot(serial);
- if (!wait){
+ }
+ if (!Status.DEVICE.equals(toStatus) && !wait){
+ throw new AdbException("Cannot reboot from fastboot to "+toStatus.toString()+" without waiting");
+ }
+ FastbootCommons.reboot(serial);
+ if (!wait){
+ return true;
+ }
+ if (!rebootWait(Status.DEVICE)){
+ if (isConnected && toStatus.equals(this.status)){
return true;
}
- if (!rebootWait(Status.DEVICE)){
- if (isConnected && toStatus.equals(this.status)){
- return true;
- }
- return false;
- }
- return reboot(toStatus,false);
+ return false;
}
+ return reboot(toStatus,false);
+
}
@@ -183,6 +190,8 @@ public class Device {
String status = toStatus.toString();
if (Status.FASTBOOT.equals(toStatus)){
status = "bootloader";
+ } else if (Status.SIDELOAD.equals(toStatus)){
+ status = Status.RECOVERY.toString();
}
AdbCommons.reboot(serial,status);
return !wait || rebootWait(toStatus);
diff --git a/src/com/xiaomitool/v2/adb/device/DeviceAnswers.java b/src/com/xiaomitool/v2/adb/device/DeviceAnswers.java
index 2139d40..4c6b4fd 100644
--- a/src/com/xiaomitool/v2/adb/device/DeviceAnswers.java
+++ b/src/com/xiaomitool/v2/adb/device/DeviceAnswers.java
@@ -4,8 +4,11 @@ import com.xiaomitool.v2.adb.AdbCommons;
import com.xiaomitool.v2.crypto.Hash;
import com.xiaomitool.v2.logging.Log;
import com.xiaomitool.v2.process.AdbRunner;
+import com.xiaomitool.v2.rom.MiuiRom;
import com.xiaomitool.v2.utility.YesNoMaybe;
import com.xiaomitool.v2.utility.utils.StrUtils;
+import com.xiaomitool.v2.xiaomi.miuithings.Branch;
+import com.xiaomitool.v2.xiaomi.miuithings.MiuiVersion;
import com.xiaomitool.v2.xiaomi.miuithings.UnlockStatus;
import java.util.HashMap;
@@ -108,4 +111,11 @@ public class DeviceAnswers {
setAnswer(NEED_DEVICE_DEBUG, answer);
}
+ public MiuiRom.Specie getCurrentSpecie() {
+ String product = (String) device.getDeviceProperties().get(DeviceProperties.CODENAME);
+ MiuiVersion version = MiuiVersion.fromObject(device.getDeviceProperties().get(DeviceProperties.FULL_VERSION));
+ Branch branch = version == null ? Branch.DEVELOPER : version.getBranch();
+ branch = branch == null ? Branch.DEVELOPER : branch;
+ return MiuiRom.Specie.fromStringBranch(product,branch);
+ }
}
diff --git a/src/com/xiaomitool/v2/adb/device/DeviceGroups.java b/src/com/xiaomitool/v2/adb/device/DeviceGroups.java
index 4188ec0..1af4f42 100644
--- a/src/com/xiaomitool/v2/adb/device/DeviceGroups.java
+++ b/src/com/xiaomitool/v2/adb/device/DeviceGroups.java
@@ -6,9 +6,134 @@ public class DeviceGroups {
private static final HashSet ALWAYS_UNLOCKED_DEVICES = getAlwaysUnlockedSet();
private static final HashSet EEA_REGION_DEVICES = getEeaRegionDevices();
private static final HashSet ANDROID_ONE_DEVICES = getAndroidOneDevices();
+ private static final HashSet RECOVERY_SAFE = removeUnsafeDevices(getCurrentDevices());
+
+ public static boolean isSafeToChangeRecoveryLocked(String codename){
+ codename = stripCodename(codename);
+ for (String c : RECOVERY_SAFE){
+ if (codename.startsWith(c)){
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static final HashSet removeUnsafeDevices(HashSet toRemove){
+ toRemove.remove("ugg");
+ toRemove.remove("ugglite");
+ toRemove.remove("rosy");
+ toRemove.remove("riva");
+ toRemove.remove("vince");
+ toRemove.remove("whyred");
+ toRemove.remove("versace");
+ toRemove.remove("wayne");
+ toRemove.remove("ysl");
+ toRemove.remove("sirius");
+ toRemove.remove("polaris");
+ toRemove.remove("sakura");
+ toRemove.remove("sakura_india");
+ toRemove.remove("ursa");
+ toRemove.remove("beryllium");
+ toRemove.remove("comet");
+ toRemove.remove("clover");
+ toRemove.remove("cactus");
+ toRemove.remove("cereus");
+ toRemove.remove("nitrogen");
+ toRemove.remove("dipper");
+ toRemove.remove("tulip");
+ toRemove.remove("platina");
+ toRemove.remove("lilium");
+ toRemove.remove("equuleus");
+ toRemove.remove("perseus");
+ toRemove.remove("cepheus");
+ toRemove.remove("onc");
+ toRemove.remove("onclite");
+ toRemove.remove("lavender");
+ toRemove.remove("grus");
+ toRemove.remove("violet");
+ toRemove.remove("davinci");
+ toRemove.remove("raphael");
+ return toRemove;
+ }
+
+ private static final HashSet getCurrentDevices(){
+ HashSet set = new HashSet<>();
+ set.add("mione");
+ set.add("aries");
+ set.add("taurus");
+ set.add("pisces");
+ set.add("cancro");
+ set.add("armani");
+ set.add("mocha");
+ set.add("hammerhead");
+ set.add("dior");
+ set.add("virgo");
+ set.add("gucci");
+ set.add("ferrari");
+ set.add("leo");
+ set.add("hermes");
+ set.add("libra");
+ set.add("latte");
+ set.add("ido");
+ set.add("hennessy");
+ set.add("aqua");
+ set.add("gemini");
+ set.add("kenzo");
+ set.add("capricorn");
+ set.add("scorpio");
+ set.add("hydrogen");
+ set.add("land");
+ set.add("omega");
+ set.add("markw");
+ set.add("nikel");
+ set.add("natrium");
+ set.add("lithium");
+ set.add("helium");
+ set.add("prada");
+ set.add("mido");
+ set.add("rolex");
+ set.add("meri");
+ set.add("sagit");
+ set.add("santoni");
+ set.add("cappu");
+ set.add("oxygen");
+ set.add("tiffany");
+ set.add("jason");
+ set.add("ugglite");
+ set.add("ugg");
+ set.add("tissot");
+ set.add("chiron");
+ set.add("riva");
+ set.add("vince");
+ set.add("rosy");
+ set.add("dipper");
+ set.add("whyred");
+ set.add("polaris");
+ set.add("wayne");
+ set.add("nitrogen");
+ set.add("ysl");
+ set.add("sirius");
+ set.add("sakura");
+ set.add("cactus");
+ set.add("cereus");
+ set.add("beryllium");
+ set.add("clover");
+ set.add("ursa");
+ set.add("platina");
+ set.add("perseus");
+ set.add("equuleus");
+ set.add("cepheus");
+ set.add("lotus");
+ set.add("lavender");
+ set.add("grus");
+ return set;
+ }
+
private static final HashSet getAndroidOneDevices(){
HashSet set = new HashSet<>();
set.add("tiare");
+ set.add("tissot_sprout");
+ set.add("jasmine_sprout");
return set;
}
private static HashSet getEeaRegionDevices(){
@@ -46,7 +171,7 @@ public class DeviceGroups {
return set;
}
- private static String stripCode(String codename){
+ public static String stripCodename(String codename){
if (codename == null){
return "";
}
@@ -57,21 +182,21 @@ public class DeviceGroups {
if (codename == null){
return false;
}
- return ALWAYS_UNLOCKED_DEVICES.contains(stripCode(codename));
+ return ALWAYS_UNLOCKED_DEVICES.contains(stripCodename(codename));
}
public static boolean hasEEARegion(String codename){
if (codename == null){
return false;
}
- return EEA_REGION_DEVICES.contains(stripCode(codename));
+ return EEA_REGION_DEVICES.contains(stripCodename(codename));
}
public static boolean isAndroidOneDevice(String codename){
if (codename == null){
return false;
}
- codename = stripCode(codename);
+ codename = stripCodename(codename);
return codename.contains("_sprout") || ANDROID_ONE_DEVICES.contains(codename);
}
}
diff --git a/src/com/xiaomitool/v2/adb/device/DeviceManager.java b/src/com/xiaomitool/v2/adb/device/DeviceManager.java
index 4046c36..86086a4 100644
--- a/src/com/xiaomitool/v2/adb/device/DeviceManager.java
+++ b/src/com/xiaomitool/v2/adb/device/DeviceManager.java
@@ -21,7 +21,12 @@ public class DeviceManager {
public static void initScanThreads(){
AdbCommunication.registerAutoScanDevices();
}
- public void addDevice(Device device){
+
+ public static void stopScanThreads() {
+ AdbCommunication.unregisterAutoScanDevices();
+ }
+
+ public static void addDevice(Device device){
deviceMap.put(device.getSerial(), device);
}
public static void setSelectedDevice(Device device){
diff --git a/src/com/xiaomitool/v2/engine/ToolManager.java b/src/com/xiaomitool/v2/engine/ToolManager.java
index a5173f7..e6c5e29 100644
--- a/src/com/xiaomitool/v2/engine/ToolManager.java
+++ b/src/com/xiaomitool/v2/engine/ToolManager.java
@@ -17,9 +17,9 @@ import java.util.ArrayList;
import java.util.List;
public class ToolManager {
- public static String TOOL_VERSION = "8.9.24";
+ public static String TOOL_VERSION = "9.3.14";
public static String URL_DONATION = "https://xiaomitool.page.link/donate";
- public static String TOOL_VERSION_EX = "unbrick only";
+ public static String TOOL_VERSION_EX = "alpha";
public static String XMT_HOST = "https://www.xiaomitool.com/V2/";
public static String URL_UPDATE = XMT_HOST+"update.php";
public static String URL_LATEST = XMT_HOST+"latest";
diff --git a/src/com/xiaomitool/v2/engine/actions/ActionsDynamic.java b/src/com/xiaomitool/v2/engine/actions/ActionsDynamic.java
index d9e4ec2..b9d9ed8 100644
--- a/src/com/xiaomitool/v2/engine/actions/ActionsDynamic.java
+++ b/src/com/xiaomitool/v2/engine/actions/ActionsDynamic.java
@@ -5,8 +5,12 @@ import com.xiaomitool.v2.adb.device.Device;
import com.xiaomitool.v2.adb.device.DeviceManager;
import com.xiaomitool.v2.adb.device.DeviceProperties;
import com.xiaomitool.v2.engine.CommonsMessages;
+import com.xiaomitool.v2.engine.ToolManager;
import com.xiaomitool.v2.gui.GuiUtils;
+import com.xiaomitool.v2.gui.PopupWindow;
import com.xiaomitool.v2.gui.WindowManager;
+import com.xiaomitool.v2.gui.deviceView.Animatable;
+import com.xiaomitool.v2.gui.deviceView.DeviceRecoveryView;
import com.xiaomitool.v2.gui.deviceView.DeviceView;
import com.xiaomitool.v2.gui.drawable.DrawableManager;
import com.xiaomitool.v2.gui.other.DeviceTableEntry;
@@ -17,8 +21,13 @@ import com.xiaomitool.v2.procedure.GuiListener;
import com.xiaomitool.v2.procedure.ProcedureRunner;
import com.xiaomitool.v2.procedure.RInstall;
import com.xiaomitool.v2.procedure.RMessage;
+import com.xiaomitool.v2.procedure.device.ManageDevice;
+import com.xiaomitool.v2.procedure.device.RebootDevice;
import com.xiaomitool.v2.procedure.install.GenericInstall;
+import com.xiaomitool.v2.procedure.install.InstallException;
import com.xiaomitool.v2.procedure.uistuff.ChooseProcedure;
+import com.xiaomitool.v2.procedure.uistuff.ConfirmationProcedure;
+import com.xiaomitool.v2.utility.CommandClass;
import com.xiaomitool.v2.utility.Nullable;
import com.xiaomitool.v2.utility.RunnableMessage;
@@ -38,9 +47,13 @@ import javafx.scene.layout.*;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
+import javafx.stage.Stage;
+
+import java.util.HashMap;
import static com.xiaomitool.v2.engine.CommonsMessages.NOOP;
+
public class ActionsDynamic {
public static RunnableMessage MAIN_SCREEN_LOADING(LRes message){
return MAIN_SCREEN_LOADING(message.toString());
@@ -81,7 +94,7 @@ public class ActionsDynamic {
case RECOVERY:
msg = LRes.NO_DEVICE_CONNECTED_RECOVERY;
button = LRes.HT_GO_RECOVERY;
- howto = ActionsStatic.HOWTO_GO_RECOVERY();
+ howto = HOWTO_GO_RECOVERY(null);
break;
case FASTBOOT:
msg = LRes.NO_DEVICE_CONNECTED_FASTBOOT;
@@ -90,6 +103,7 @@ public class ActionsDynamic {
default:
msg = LRes.NO_DEVICE_CONNECTED;
button = LRes.HT_ENABLE_USB_DEBUG;
+ howto = ActionsDynamic.HOW_TO_ENABLE_USB_DEBUGGING(null, DeviceManager.getDevices().isEmpty());
}
ButtonPane pane = new ButtonPane(LRes.SEARCH_AGAIN, button);
@@ -103,7 +117,7 @@ public class ActionsDynamic {
vBox.setAlignment(Pos.CENTER);
vBox.setSpacing(20);
pane.setContent(vBox);
- Log.debug("SDSDSASEW");
+
WindowManager.setMainContent(pane);
DeviceManager.addMessageReceiver(pane.getIdClickReceiver());
int message = CommonsMessages.NOOP;
@@ -111,7 +125,7 @@ public class ActionsDynamic {
message=pane.waitClick();
if (message > 0 && howto != null){
message = howto.run();
- if (message >= 0){
+ if (message == 0){
message = NOOP;
}
}
@@ -240,7 +254,7 @@ public class ActionsDynamic {
vBox.setAlignment(Pos.CENTER);
vBox.setSpacing(20);
pane.setContent(vBox);
- WindowManager.setMainContent(pane);
+ WindowManager.setMainContent(pane, false);
IDClickReceiver receiver = pane.getIdClickReceiver();
DeviceManager.addMessageReceiver(receiver);
int message = NOOP;
@@ -254,14 +268,16 @@ public class ActionsDynamic {
if (message == 0){
MAIN_SCREEN_LOADING(LRes.SEARCHING_CONNECTED_DEVICES).run();
ActionsStatic.RESTART_ADB_SERVER().run();
+ WindowManager.removeTopContent();
if (device.isConnected()){
break;
}
- WindowManager.removeTopContent();
+
}
message = NOOP;
}
DeviceManager.removeMessageReceiver(receiver);
+ WindowManager.removeTopContent();
return 1;
};
}
@@ -272,6 +288,7 @@ public class ActionsDynamic {
public static RunnableMessage WAIT_USB_DEBUG_ENABLE(Device device, String text){
return () -> {
ButtonPane pane = new ButtonPane(LRes.HT_ENABLE_USB_DEBUG, LRes.SEARCH_AGAIN);
+ pane.setContentText(LRes.WAITING_USB_ENABLE.toString(LRes.SEARCH_AGAIN));
WindowManager.setMainContent(pane);
IDClickReceiver receiver = pane.getIdClickReceiver();
DeviceManager.addMessageReceiver(receiver);
@@ -285,11 +302,18 @@ public class ActionsDynamic {
}
if (message == 1){
ActionsStatic.RESTART_ADB_SERVER().run();
- if (device.isTurnedOn()){
+ WindowManager.removeTopContent();
+ if (device.isTurnedOn() || Device.Status.UNAUTHORIZED.equals(device.getStatus())){
break;
}
- WindowManager.removeTopContent();
+
+ } else if (message == 0){
+ if (HOW_TO_ENABLE_USB_DEBUGGING(device, true).run() > 0){
+ if (device.isTurnedOn() || Device.Status.UNAUTHORIZED.equals(device.getStatus())){
+ break;
+ }
+ }
}
message = NOOP;
@@ -299,6 +323,52 @@ public class ActionsDynamic {
return 0;};
}
+ public static RunnableMessage REBOOT_STOCK_RECOVERY(Device device, boolean force){
+ return new RunnableMessage() {
+ @Override
+ public int run() throws InterruptedException {
+
+ if (device == null){
+ return 0;
+ }
+ if (!force){
+ if (Device.Status.SIDELOAD.equals(device.getStatus())){
+ return 1;
+ }
+ }
+ REQUIRE_DEVICE_CONNECTED(device).run();
+ REQUIRE_DEVICE_AUTH(device).run();
+ if (Device.Status.FASTBOOT.equals(device.getStatus())){
+ try {
+ if (!device.reboot(Device.Status.DEVICE)){
+ throw new AdbException("Failed to reboot device to device mode");
+ }
+ } catch (AdbException e) {
+ REQUIRE_DEVICE_CONNECTED(device).run();
+ REQUIRE_DEVICE_AUTH(device).run();
+ if (!device.isTurnedOn()){
+ return 0;
+ }
+
+ }
+ }
+ Thread.sleep(1000);
+ try {
+ if (!device.rebootNoWait(Device.Status.SIDELOAD,force)){
+ throw new AdbException("Failed to reboot device to recovery mode");
+ }
+ HOWTO_GO_RECOVERY(device).run();
+ if(!device.waitStatus(Device.Status.SIDELOAD)){
+ throw new AdbException("Failed to reboot device to recovery mode");
+ }
+ } catch (AdbException e) {
+ return 0;
+ }
+ return 1;
+ }
+ };
+ }
+
public static RunnableMessage FIND_DEVICE_INFO(Device device){
return () -> {
Text texts[] = new Text[10];
@@ -354,7 +424,7 @@ public class ActionsDynamic {
ActionsUtil.setDevicePropertiesText(device, texts);
}
- if (!device.getDeviceProperties().getSideloadProperties().isParsed()) {
+ if (!device.getDeviceProperties().getSideloadProperties().isParsed() && !device.getDeviceProperties().getRecoveryProperties().isParsed()) {
try {
if (UnlockStatus.UNLOCKED.equals(device.getAnswers().getUnlockStatus())) {
Platform.runLater(new Runnable() {
@@ -365,11 +435,13 @@ public class ActionsDynamic {
});
} else {
- if (!device.reboot(Device.Status.RECOVERY)) {
- throw new Exception("Failed to reboot to recovery");
- } else {
- ActionsUtil.setDevicePropertiesText(device, texts);
+
+ boolean result = REBOOT_STOCK_RECOVERY(device, true).run() != 0;
+ if (!result){
+ throw new Exception("Failed to reboot to stock recovery");
}
+ ActionsUtil.setDevicePropertiesText(device, texts);
+
}
} catch (Exception e) {
Platform.runLater(new Runnable() {
@@ -411,32 +483,31 @@ public class ActionsDynamic {
IDClickReceiver receiver = pane.getIdClickReceiver();
DeviceManager.addMessageReceiver(receiver);
- DeviceView deviceView = new DeviceView(DeviceView.DEVICE_18_9, 900);
+ DeviceView deviceView = new DeviceView(DeviceView.DEVICE_18_9, 880);
ImageView image = new ImageView(DrawableManager.getPng(DrawableManager.DEVICE_AUTH).toString());
- image.setViewport(new Rectangle2D(0,image.getImage().getHeight()-2160,1080,2160));
+ //image.setViewport(new Rectangle2D(0,image.getImage().getHeight()-2160,1080,2160));
deviceView.setContent(image);
deviceView.buildCircleTransition(800,2060,Animation.INDEFINITE);
- Pane cropped = GuiUtils.crop(deviceView,0,550,900/deviceView.getOuterAspectRatio(),350);
- cropped.setPrefSize(900/deviceView.getOuterAspectRatio(),350);
- GuiUtils.debug(cropped);
+
+ //GuiUtils.debug(cropped);
Text text = new Text(LRes.AUTH_DEVICE_TEXT.toString());
text.setTextAlignment(TextAlignment.CENTER);
text.setFont(Font.font(16));
text.setWrappingWidth(600);
- VBox vBox = new VBox(GuiUtils.center(cropped),text);
+ VBox vBox = new VBox(GuiUtils.center(DeviceView.crop(deviceView,310,570)),text);
vBox.setAlignment(Pos.CENTER);
vBox.setSpacing(20);
pane.setContent(vBox);
- WindowManager.setMainContent(pane);
+ WindowManager.setMainContent(pane, false);
int messsage = NOOP;
while (messsage == NOOP){
messsage = receiver.waitClick();
if(messsage == CommonsMessages.DEVICE_UPDATE_FINISH){
- if (!device.needAuthorization()){
+ if (!device.needAuthorization() && device.isConnected()){
break;
}
}
@@ -447,16 +518,20 @@ public class ActionsDynamic {
DeviceManager.refresh();
if (device.needAuthorization()) {
ActionsStatic.RESTART_ADB_SERVER().run();
- if (!device.needAuthorization()){
+ if (!device.needAuthorization() && device.isConnected()){
break;
} else {
- WindowManager.setMainContent(pane);
+ WindowManager.setMainContent(pane, false);
}
+ } else {
+ break;
}
messsage = NOOP;
}
}
+
DeviceManager.removeMessageReceiver(receiver);
+ WindowManager.removeTopContent();
return 1;
};
}
@@ -489,27 +564,159 @@ public class ActionsDynamic {
//runner.setContext("prop_"+DeviceProperties.CODENAME,"whyred");
//FastbootFetch.findAllLatestFastboot().run(runner);
+ RInstall main = GenericInstall.main();
try {
+ try {
+ /* RebootDevice.rebootNoWaitIfConnected().run(runner);
Log.debug("PRO0 CHOOSE CAT");
ChooseProcedure.chooseRomCategory().run(runner);
Log.debug("PRO0 CHOOSE ROM");
ChooseProcedure.chooseRom().run(runner);
Log.debug("PRO0 FETCH RESOURCE");
+ ConfirmationProcedure.confirmInstallableProcedure().run(runner);
+ ConfirmationProcedure.confirmInstallationStart().run(runner);
+ runner.text(LRes.WAITING_DEVICE_ACTIVE);
+ ManageDevice.waitDevice(60);
+ GenericInstall.satisfyAllRequirements().run(runner);
GenericInstall.resourceFetchWait().run(runner);
- Log.debug("PRO0 INSTALL ROM");
+ DeviceManager.refresh();
GenericInstall.runInstallProcedure().run(runner);
- Log.debug("PRO0 FINISHED");
- GenericInstall.installationSuccess().run(runner);
- } catch (Exception e){
- e.printStackTrace();
- } catch (RMessage rMessage) {
+ GenericInstall.installationSuccess().run(runner);*/
+ main.run(runner);
+
+ } catch (Exception e) {
+ try {
+ runner.handleException(new InstallException(e.getMessage(), InstallException.Code.INTERNAL_ERROR, false), main);
+ } catch (InstallException e1) {
+ throw new RMessage(e1);
+ }
+ }
+ }catch (RMessage rMessage) {
+ Log.debug("RMSG: "+rMessage.getCmd());
rMessage.printStackTrace();
+ if (CommandClass.Command.ABORT.equals(rMessage.getCmd())){
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ ActionsStatic.MOD_CHOOSE_SCREEN().run();
+ } catch (InterruptedException e) {
+ Log.debug(e.getMessage());
+ }
+ }
+ }).start();
+ return 0;
+ }
+ Stage st = WindowManager.launchPopup(new PopupWindow.ImageTextPopup("A fatal error has occured: type: "+rMessage.getCmd()+"\nThe tool will now exit",PopupWindow.Icon.ERROR));
+ if (st != null){
+ while (st.isShowing()){
+ Thread.sleep(100);
+ }
+
+ }
+ ToolManager.exit(1);
+ System.exit(1);
}
-
-
return 0;
}
};
}
+ public static RunnableMessage HOWTO_GO_RECOVERY(Device device){
+ return HOWTO_GO_RECOVERY(true,device);
+ }
+
+ public static RunnableMessage HOWTO_GO_RECOVERY(boolean rebootingText, Device device){
+ return () -> {
+ ButtonPane buttonPane = new ButtonPane(LRes.OK_FINISHED);
+ SidePane sidePane = new SidePane();
+ DeviceRecoveryView deviceRecoveryView = new DeviceRecoveryView(DeviceView.DEVICE_18_9,640);
+ deviceRecoveryView.animateSelectThird(3000);
+ String nText;
+ if (!rebootingText){
+ nText = "1) "+LRes.BTN_VOLUP_POW.toString()+"\n2) "+LRes.HT_RECOVERY_TEXT_1.toString();
+ } else {
+ nText = LRes.ENTER_STOCK_RECOVERY_MODE.toString();
+ }
+ Text t = new Text(nText);
+
+ t.setWrappingWidth(400);
+ t.setFont(Font.font(15));
+ sidePane.setLeft(t);
+ Pane p = DeviceView.crop(deviceRecoveryView,410);
+
+ //GuiUtils.debug(p);
+ sidePane.setRight(GuiUtils.center(p));
+ buttonPane.setContent(sidePane);
+ WindowManager.setMainContent(buttonPane,false);
+ DeviceManager.addMessageReceiver(buttonPane.getIdClickReceiver());
+ int msg = NOOP;
+ int exitcode = 0;
+ while (msg < 0){
+ exitcode = 0;
+
+ if (device != null && device.isConnected() && Device.Status.SIDELOAD.equals(device.getStatus())){
+ exitcode = 1;
+ break;
+ }
+ //Log.debug(msg+" - "+DeviceManager.count(Device.Status.SIDELOAD));
+ msg = buttonPane.waitClick();
+ }
+ DeviceManager.removeMessageReceiver(buttonPane.getIdClickReceiver());
+ WindowManager.removeTopContent();
+ return exitcode;
+ };
+ }
+
+ public static RunnableMessage HOW_TO_ENABLE_USB_DEBUGGING(Device device, boolean search){
+ return new RunnableMessage() {
+ @Override
+ public int run() throws InterruptedException {
+ String[] text = new String[]{LRes.HT_ENABLE_USB_1.toString(),LRes.HT_ENABLE_USB_2.toString(),LRes.HT_ENABLE_USB_3.toString(),LRes.HT_ENABLE_USB_4.toString(),LRes.HT_ENABLE_USB_5.toString(),LRes.HT_ENABLE_USB_6.toString(),LRes.HT_ENABLE_USB_7.toString(),};
+ Image[] images = new Image[7];
+ for (int i = 0; i<7; i++){
+ images[i] = new Image(DrawableManager.getPng("usbdbg"+(i+1)).toString(),false);
+ }
+ HashMap animations = new HashMap<>();
+ animations.put(0,new Animatable.AnimationPayload(664,1323,Animation.INDEFINITE, true));
+ animations.put(1,new Animatable.AnimationPayload(400,435,Animation.INDEFINITE, true));
+ animations.put(2,new Animatable.AnimationPayload(400,1050,Animation.INDEFINITE, true));
+ animations.put(3,new Animatable.AnimationPayload(400,1954,Animation.INDEFINITE, true));
+ animations.put(4,new Animatable.AnimationPayload(400,1746,Animation.INDEFINITE, true));
+ animations.put(5,new Animatable.AnimationPayload(950,1082,Animation.INDEFINITE, true));
+ animations.put(6,new Animatable.AnimationPayload(800,2060,Animation.INDEFINITE, true));
+ DeviceImgInstructionPane instructionPane = new DeviceImgInstructionPane(WindowManager.getContentHeight() + 30, WindowManager.getContentHeight() - 10, text, images, animations);
+ //instructionPane.setOverlayLen(WindowManager.requireOverlayPane(), 0.6);
+
+ IDClickReceiver receiver = instructionPane.getIdClickReceiver();
+ WindowManager.setMainContent(instructionPane, false);
+ if (search) {
+ DeviceManager.addMessageReceiver(receiver);
+ }
+ int message = CommonsMessages.NOOP;
+ int exitcode = 0;
+ while (message < 0){
+ exitcode = 0;
+ if (device != null) {
+ if (Device.Status.DEVICE.equals(device.getStatus()) || Device.Status.UNAUTHORIZED.equals(device.getStatus())) {
+ exitcode = 1;
+ break;
+ }
+ } else {
+ if (message == CommonsMessages.NEW_DEVICE && !DeviceManager.getDevices().isEmpty()){
+ Device d = DeviceManager.getFirstDevice();
+ if (d != null && (Device.Status.DEVICE.equals(d.getStatus()) || Device.Status.UNAUTHORIZED.equals(d.getStatus()))) {
+ exitcode = 1;
+ break;
+ }
+ }
+ }
+ message = instructionPane.waitClick();
+ }
+ DeviceManager.removeMessageReceiver(receiver);
+ WindowManager.removeTopContent();
+ return exitcode;
+ }
+ };
+ }
}
diff --git a/src/com/xiaomitool/v2/engine/actions/ActionsStatic.java b/src/com/xiaomitool/v2/engine/actions/ActionsStatic.java
index b4a81ca..47b32c5 100644
--- a/src/com/xiaomitool/v2/engine/actions/ActionsStatic.java
+++ b/src/com/xiaomitool/v2/engine/actions/ActionsStatic.java
@@ -182,34 +182,7 @@ public class ActionsStatic {
};
}
- public static RunnableMessage HOWTO_GO_RECOVERY(){
- return () -> {
- ButtonPane buttonPane = new ButtonPane(LRes.OK_UNDERSTAND);
- SidePane sidePane = new SidePane();
- DeviceRecoveryView deviceRecoveryView = new DeviceRecoveryView(DeviceView.DEVICE_18_9,640);
- deviceRecoveryView.animateSelectThird(2500);
- Text t = new Text("1) "+LRes.BTN_VOLUP_POW.toString()+"\n2) "+LRes.HT_RECOVERY_TEXT_1.toString());
- t.setWrappingWidth(400);
- t.setFont(Font.font(15));
- sidePane.setLeft(t);
- Pane p = DeviceView.crop(deviceRecoveryView,410);
-
- GuiUtils.debug(p);
- sidePane.setRight(GuiUtils.center(p));
- buttonPane.setContent(sidePane);
- WindowManager.setMainContent(buttonPane,false);
- DeviceManager.addMessageReceiver(buttonPane.getIdClickReceiver());
- int msg = NOOP;
- while (msg < 0 && (msg != CommonsMessages.NEW_DEVICE || DeviceManager.count(Device.Status.SIDELOAD) < 1)){
- //Log.debug(msg+" - "+DeviceManager.count(Device.Status.SIDELOAD));
- msg = buttonPane.waitClick();
- }
- DeviceManager.removeMessageReceiver(buttonPane.getIdClickReceiver());
- WindowManager.removeTopContent();
- return msg;
- };
- }
public static RunnableMessage REQUIRE_INTERNET_CONNECTION(){
return () -> {
@@ -260,6 +233,8 @@ public class ActionsStatic {
+
+
public static RunnableMessage FIRST_DISCLAIMER() { return () -> {
ButtonPane buttonPane = new ButtonPane(LRes.DONT_AGREE.toString(),LRes.AGREE.toString());
Text t2 = new Text(RawManager.getDisclaimer());
@@ -280,7 +255,7 @@ public class ActionsStatic {
DeviceView running = new DeviceView(DeviceView.DEVICE_18_9,height, Color.WHITE,null), recover = new DeviceView(DeviceView.DEVICE_18_9,height, Color.BLACK, null);
running.getImagePane().setCursor(Cursor.HAND);
recover.getImagePane().setCursor(Cursor.HAND);
- recover.setContent(DrawableManager.getPng(DrawableManager.FASTBOOT_LOGO));
+ recover.setContent(DrawableManager.getPng(DrawableManager.FASTBOOT_LOGO),true);
running.setContent(DrawableManager.getPng(DrawableManager.MIUI10));
Button b1 = new CustomButton(LRes.CHOOSE);
Button b2 = new CustomButton(LRes.CHOOSE);
@@ -310,4 +285,6 @@ public class ActionsStatic {
});
return idClickReceiver.waitClick()%2;
};}
+
+
}
diff --git a/src/com/xiaomitool/v2/gui/WindowManager.java b/src/com/xiaomitool/v2/gui/WindowManager.java
index 5a6e7ec..3eb6633 100644
--- a/src/com/xiaomitool/v2/gui/WindowManager.java
+++ b/src/com/xiaomitool/v2/gui/WindowManager.java
@@ -9,6 +9,7 @@ import com.xiaomitool.v2.gui.visual.ToastPane;
import com.xiaomitool.v2.gui.visual.VisiblePane;
import com.xiaomitool.v2.logging.Log;
import com.xiaomitool.v2.utility.Pointer;
+import com.xiaomitool.v2.utility.SilentCompleteFuture;
import javafx.application.Platform;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader;
@@ -149,17 +150,30 @@ public class WindowManager {
toastPane.toast(message);
}
public static Stage launchPopup(PopupWindow popupWindow){
- Pointer pointer = new Pointer();
- PopupController controller = new PopupController(popupWindow);
- Stage stage = launchWindow(FRAME_POPUP,controller,null,pointer);
- Parent p = stage.getScene().getRoot();
- StackPane mainPane = (StackPane) p;
- ((Pane) pointer.pointed).setMinSize(popupWindow.getWidth(),popupWindow.getHeight());
+ SilentCompleteFuture future = new SilentCompleteFuture<>();
- stage.setTitle(DEFAULT_TITLE);
- stage.sizeToScene();
- centerStage(stage);
- return stage;
+ Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ Pointer pointer = new Pointer();
+ PopupController controller = new PopupController(popupWindow);
+ Stage stage = launchWindow(FRAME_POPUP,controller,null,pointer);
+ Parent p = stage.getScene().getRoot();
+ StackPane mainPane = (StackPane) p;
+ ((Pane) pointer.pointed).setMinSize(popupWindow.getWidth(),popupWindow.getHeight());
+
+ stage.setTitle(DEFAULT_TITLE);
+ stage.sizeToScene();
+ centerStage(stage);
+ future.complete(stage);
+ }
+ };
+ if (Platform.isFxApplicationThread()){
+ runnable.run();
+ } else {
+ Platform.runLater(runnable);
+ }
+ return future.getSilently();
}
private static Stage launchWindow(String fxml, DefaultController controller, Stage primaryStage){
@@ -268,4 +282,8 @@ public class WindowManager {
});
}
+
+ public static OverlayPane requireOverlayPane() {
+ return mainOverlay;
+ }
}
diff --git a/src/com/xiaomitool/v2/gui/deviceView/Animatable.java b/src/com/xiaomitool/v2/gui/deviceView/Animatable.java
index 8ce0a56..593c326 100644
--- a/src/com/xiaomitool/v2/gui/deviceView/Animatable.java
+++ b/src/com/xiaomitool/v2/gui/deviceView/Animatable.java
@@ -3,4 +3,32 @@ package com.xiaomitool.v2.gui.deviceView;
public interface Animatable {
public void animate(int times, long duration);
+
+
+ public static class AnimationPayload {
+ private double x,y;
+ private int times;
+ private boolean unique;
+ public AnimationPayload(double x, double y, int times, boolean unique){
+ this.x = x;
+ this.y = y;
+ this.times = times;
+ this.unique = unique;
+ }
+ public double getX(){
+ return x;
+ }
+
+ public double getY() {
+ return y;
+ }
+
+ public int getTimes() {
+ return times;
+ }
+
+ public boolean isUnique() {
+ return unique;
+ }
+ }
}
diff --git a/src/com/xiaomitool/v2/gui/deviceView/AnimatableDeviceView.java b/src/com/xiaomitool/v2/gui/deviceView/AnimatableDeviceView.java
new file mode 100644
index 0000000..cbf4c29
--- /dev/null
+++ b/src/com/xiaomitool/v2/gui/deviceView/AnimatableDeviceView.java
@@ -0,0 +1,42 @@
+package com.xiaomitool.v2.gui.deviceView;
+
+import com.xiaomitool.v2.utility.RunnableWithArg;
+
+public abstract class AnimatableDeviceView extends DeviceView implements Animatable {
+
+ public AnimatableDeviceView(DeviceImage deviceImage, double wantedHeight) {
+ super(deviceImage, wantedHeight);
+ }
+
+ private RunnableWithArg animationCallback;
+ public void setAnimationCallback(RunnableWithArg animationCallback){
+ this.animationCallback = animationCallback;
+ }
+
+ @Override
+ public void animate(int times, long duration) {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ for (int i = 0; i {
selectOption(1);
diff --git a/src/com/xiaomitool/v2/gui/deviceView/DeviceView.java b/src/com/xiaomitool/v2/gui/deviceView/DeviceView.java
index 96fd82a..32eb7dc 100644
--- a/src/com/xiaomitool/v2/gui/deviceView/DeviceView.java
+++ b/src/com/xiaomitool/v2/gui/deviceView/DeviceView.java
@@ -1,5 +1,9 @@
package com.xiaomitool.v2.gui.deviceView;
+import com.mortennobel.imagescaling.AdvancedResizeOp;
+import com.mortennobel.imagescaling.MultiStepRescaleOp;
+import com.mortennobel.imagescaling.ResampleFilters;
+import com.mortennobel.imagescaling.ResampleOp;
import com.xiaomitool.v2.gui.GuiUtils;
import com.xiaomitool.v2.gui.drawable.DrawableManager;
import com.xiaomitool.v2.gui.visual.OverlayPane;
@@ -7,9 +11,12 @@ import com.xiaomitool.v2.logging.Log;
import com.xiaomitool.v2.utility.Nullable;
import com.xiaomitool.v2.utility.Pointer;
+import com.xiaomitool.v2.utility.utils.NumberUtils;
import javafx.animation.*;
+import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
+import javafx.embed.swing.SwingFXUtils;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Rectangle2D;
@@ -24,10 +31,16 @@ import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;
+import java.awt.*;
+import java.awt.image.BufferedImage;
import java.net.URL;
+import java.util.HashSet;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ExecutionException;
public class DeviceView extends StackPane {
- private DeviceImage deviceImage;
+ protected DeviceImage deviceImage;
private ImageView deviceBorders, displayedImageView;
protected double wantedHeight, scaleRatio;
private Color background = Color.rgb(49, 53, 57), innerShadow = Color.BLACK;
@@ -93,6 +106,7 @@ public class DeviceView extends StackPane {
super.getChildren().add(containerPane);
}
+ protected double contentScaleRatio;
public void setContent(ImageView image){
setContent(image,false);
}
@@ -107,7 +121,7 @@ public class DeviceView extends StackPane {
setContent(new Image(url.toString()));
}
public void setContent(URL url, boolean keepratio){
- setContent(new Image(url.toString(), keepratio));
+ setContent(new Image(url.toString(), false),keepratio);
}
public double getOuterAspectRatio(){
return deviceImage.getOuterHeight()/deviceImage.getOuterWidth();
@@ -115,49 +129,147 @@ public class DeviceView extends StackPane {
public double getInnerAspectRatio(){
return deviceImage.getInnerHeight()/deviceImage.getInnerWidth();
}
-
- public void setContent(ImageView image, boolean keepRatio) {
-
-
- image.setPreserveRatio(true);
- displayedImage = image.getImage();
- if (keepRatio){
- image.setViewport(new Rectangle2D(0,-1*(deviceImage.getInnerHeight()-displayedImage.getHeight())/2,displayedImage.getWidth(), deviceImage.getInnerHeight()));
+ private double getInnerHeight(){
+ return this.deviceImage.getInnerHeight()*this.scaleRatio;
}
- //image.setSmooth(true);
- double offsetX = 0, offsetY =0 , height, width;
- if (image.getViewport() != null){
- height = image.getViewport().getHeight();
- width = image.getViewport().getWidth();
- offsetX = image.getViewport().getMinX();
- offsetY = image.getViewport().getMinY();
+ private double getInnerWidth(){
+ return this.deviceImage.getInnerWidth()*this.scaleRatio;
+ }
+
+ protected double imageOffsetX, imageOffsetY;
+ protected boolean keepRatio;
+ public void setContent(ImageView image, boolean keepRatio) {
+ this.keepRatio = keepRatio;
+ displayedImage = image.getImage();
+ Log.debug("Original image size: "+displayedImage.getHeight()+"x"+displayedImage.getWidth());
+ double origImgRatio = displayedImage.getHeight()/displayedImage.getWidth();
+ int resizeWidth, resizeHeight;
+ Rectangle2D viewport;
+ if (origImgRatio < this.getInnerAspectRatio()){
+ resizeWidth = NumberUtils.double2int(this.getInnerWidth());
+ resizeHeight = NumberUtils.double2int(keepRatio ? resizeWidth*origImgRatio : this.getInnerHeight());
+ imageOffsetY = keepRatio ? (this.getInnerHeight()-resizeHeight)/2 : 0;
+ imageOffsetX = 0;
+ viewport = keepRatio ? new Rectangle2D(0,-1*imageOffsetY,this.getInnerWidth(), this.getInnerHeight()) : null;
+
} else {
+ resizeHeight = NumberUtils.double2int(this.getInnerHeight());
+ resizeWidth = NumberUtils.double2int(keepRatio ? resizeHeight/origImgRatio : this.getInnerWidth());
+ imageOffsetX = keepRatio ? (this.getInnerWidth()-resizeWidth)/2 : 0;
+ imageOffsetY = 0;
+ viewport = keepRatio ? new Rectangle2D(-1*imageOffsetX,0,this.getInnerWidth(), this.getInnerHeight()) : null;
+ }
+ Log.debug("Viewport: "+viewport);
+ Log.debug("iOX = "+imageOffsetX+", iOY = "+imageOffsetY);
+ contentScaleRatio = ((double) resizeHeight)/displayedImage.getHeight();
+ Log.debug("RSW: "+resizeWidth+", RSH: "+resizeHeight+", CSR: "+contentScaleRatio+", DIH:"+displayedImage.getHeight());
+ BufferedImage srcImg = SwingFXUtils.fromFXImage(image.getImage(), null);
+ BufferedImage img = new BufferedImage(resizeWidth, resizeHeight, srcImg.getType());
+ if (true){
+ MultiStepRescaleOp resampleOp = new MultiStepRescaleOp(resizeWidth, resizeHeight, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+ resampleOp.doFilter(srcImg, img, resizeWidth, resizeHeight);
+ } else {
+ ResampleOp resampleOp = new ResampleOp(resizeWidth, resizeHeight);
+ resampleOp.setFilter(ResampleFilters.getBiCubicFilter());
+ resampleOp.doFilter(srcImg, img, resizeWidth, resizeHeight);
+ }
+ Log.debug("Raw resized img: "+img.getHeight()+"x"+img.getWidth());
+
+ //
+
+ Image resizedImage = SwingFXUtils.toFXImage(img, null);
+ image.setImage(resizedImage);
+ if (viewport != null){
+ image.setViewport(viewport);
+ }
+ image.setFitWidth(NumberUtils.double2int(this.getInnerWidth()));
+ image.setFitHeight(NumberUtils.double2int(this.getInnerHeight()));
+ Log.debug("Result: "+image.getFitHeight()+"x"+image.getFitWidth()+" -img-> "+image.getImage().getHeight()+"x"+image.getImage().getWidth());
+
+ contentPane.getChildren().clear();
+
+ displayedImageView = image;
+
+ imageWrapPane = new Pane(image);
+
+
+ contentPane.getChildren().clear();
+ contentPane.getChildren().add(GuiUtils.center(imageWrapPane));
+
+
+ /* //image.setSmooth(true);
+ double offsetX = 0, offsetY =0 , height, width;
+ image.setPreserveRatio(true);
+ if (keepRatio){
+
+ Log.debug("KEEEPRATIIOOO");
+ height = deviceImage.getInnerHeight();
+ width = displayedImage.getWidth();
+
+ offsetX = 0;
+ offsetY = -1*(deviceImage.getInnerHeight()-displayedImage.getHeight())/2;
+ image.setViewport(new Rectangle2D(offsetX,offsetY,width,height));
+ } else {
+ Log.debug("DONTT KEEEPRATIIOOO");
height = displayedImage.getHeight();
+ Log.debug("Height: "+displayedImage.getHeight());
width = displayedImage.getWidth();
}
+ Log.debug("Width: "+width+", height: "+height);
double setHeight = height-offsetY;
double setWidth = width-offsetX;
double innerRatio = deviceImage.getInnerHeight()/deviceImage.getInnerWidth();
double imgRatio = setHeight/setWidth;
+ double wantedH = -1, wantedW = -1;
if (innerRatio < imgRatio){
Log.debug("Image is taller than frame: height -> "+setHeight);
- double h = Double.min(setHeight, deviceImage.getInnerHeight()*scaleRatio);
+ wantedH = Double.min(setHeight, deviceImage.getInnerHeight()*scaleRatio);
//h = keepRatio ? Double.min(h, displayedImage.getHeight()) : h;
- image.setFitHeight(h);
+ Log.debug("Wanted height: "+wantedH);
+
} else {
- double w = Double.min(setWidth, deviceImage.getInnerWidth()*scaleRatio);
+ wantedW = Double.min(setWidth, deviceImage.getInnerWidth()*scaleRatio);
//w = keepRatio ? Double.min(w, displayedImage.getWidth()) : w;
Log.debug("Image is wider than frame: width -> "+(deviceImage.getInnerWidth()*scaleRatio));
- image.setFitWidth(w);
+ Log.debug("Wanted width: "+wantedW);
}
+ int dstH = new Double(wantedH).intValue(), dstW = new Double(wantedW).intValue();
+ if (dstH < 0){
+ Log.debug(keepRatio);
+ Log.debug("DstH: "+dstH+", DstW: "+dstW);
+ Log.debug(displayedImage.impl_getUrl());
+ Log.debug("Dst ration: "+(displayedImage.getHeight()/displayedImage.getWidth()));
+ dstH = new Double(keepRatio ? (dstW*displayedImage.getHeight()/displayedImage.getWidth()) : dstW*getInnerAspectRatio()).intValue();
+ } else if (dstW <0){
+ Log.debug("DstH: "+dstH+", DstW: "+dstW);
+ Log.debug(displayedImage.impl_getUrl());
+ Log.debug("Dst ration: "+(displayedImage.getWidth()/displayedImage.getHeight()));
+ dstW = new Double(keepRatio ? (dstH*displayedImage.getWidth()/displayedImage.getHeight()) : dstH/getInnerAspectRatio()).intValue();
+ }
+ Log.debug("DstH: "+dstH+", DstW: "+dstW);
+
+ BufferedImage srcImg = SwingFXUtils.fromFXImage(image.getImage(), null);
+ BufferedImage img = new BufferedImage(dstW, dstH, srcImg.getType());
+ new ResampleOp(dstW, dstH).doFilter(srcImg, img, dstW, dstH);
+ Image dstImg = SwingFXUtils.toFXImage(img, null);
+ image.setSmooth(false);
+ image.setImage(dstImg);
+
+
+ if (wantedW >= 0){
+ image.setFitWidth(wantedW);
+ }
+ if (wantedH >= 0){
+ image.setFitHeight(wantedH);
+ }
//image.setFitHeight(wantedHeight * deviceImage.getInnerHeight() / deviceImage.getOuterHeight());
contentPane.getChildren().clear();
displayedImageView = image;
imageWrapPane = new Pane(image);
- contentPane.getChildren().add(GuiUtils.center(imageWrapPane));
+ contentPane.getChildren().add(GuiUtils.center(imageWrapPane));*/
}
@@ -255,7 +367,7 @@ public class DeviceView extends StackPane {
double centerY = (pos.top + pos.height / 2) ;
rectangle.setFill(Color.RED);
containerPane.getChildren().add(rectangle);
- Transition transition = buildCircleTransition(centerX,centerY,times,false);
+ Transition transition = buildCircleTransition(centerX,centerY,times,false,false);
transition.statusProperty().addListener((observable, oldValue, newValue) -> {
Log.debug(oldValue.toString()+" -> "+newValue.toString());
if (Animation.Status.STOPPED.equals(newValue)){
@@ -272,15 +384,52 @@ public class DeviceView extends StackPane {
}
public Transition buildCircleTransition(double x, double y, int times){
- return buildCircleTransition(x,y,times,true);
+ return buildCircleTransition(x,y,times,true, true);
}
- public Transition buildCircleTransition(double x, double y, int times, boolean addBorder){
- Circle circle = new Circle(x*scaleRatio+(addBorder ? deviceImage.getLeftOffset()*scaleRatio : 0), y*scaleRatio+(addBorder ? deviceImage.getTopOffset() * scaleRatio : 0), wantedHeight / 12);
+ private final ConcurrentLinkedQueue circlesAnimation = new ConcurrentLinkedQueue<>();
+ public void removeCircleAnimation(){
+ Log.debug("Clearing anims");
+ synchronized (circlesAnimation) {
+ for (Transition transition : circlesAnimation) {
+ Log.debug("Clearing trans: "+transition.toString());
+ transition.stop();
+ }
+ circlesAnimation.clear();
+ }
+ }
+
+ public Transition buildCircleTransition(double x, double y, int times, boolean addBorder, boolean unique){
+ if (!Platform.isFxApplicationThread()){
+ CompletableFuture future = new CompletableFuture<>();
+ Platform.runLater(new Runnable() {
+ @Override
+ public void run() {
+
+
+ future.complete(buildCircleTransition(x,y,times,addBorder, unique));
+ }
+ });
+ try {
+ return future.get();
+ } catch (InterruptedException e) {
+ removeCircleAnimation();
+ return null;
+ } catch (ExecutionException e) {
+ removeCircleAnimation();
+ return null;
+ }
+ }
+ if(unique){
+ removeCircleAnimation();
+ }
+ Circle circle = new Circle(x*scaleRatio+(addBorder ? deviceImage.getLeftOffset()*scaleRatio+imageOffsetX : 0), y*scaleRatio+(addBorder ? imageOffsetY+deviceImage.getTopOffset() * scaleRatio : 0), wantedHeight / 12);
circle.setStroke(Color.RED);
+ circle.setStrokeWidth(3);
circle.setFill(Color.TRANSPARENT);
circle.setVisible(false);
circle.setOpacity(0);
+ circle.setMouseTransparent(true);
Transition transition = getCircleTransition(circle, times);
containerPane.getChildren().add(circle);
@@ -290,13 +439,16 @@ public class DeviceView extends StackPane {
Log.debug(oldValue.toString()+" -> "+newValue.toString());
if (Animation.Status.STOPPED.equals(newValue)){
circle.setVisible(false);
- containerPane.getChildren().removeAll(circle);
+ containerPane.getChildren().remove(circle);
} else if (Animation.Status.RUNNING.equals(newValue)){
circle.setVisible(true);
}
}
});
transition.play();
+ synchronized (circlesAnimation){
+ this.circlesAnimation.add(transition);
+ }
return transition;
}
diff --git a/src/com/xiaomitool/v2/gui/drawable/device_auth.png b/src/com/xiaomitool/v2/gui/drawable/device_auth.png
index 9a4f607..e6375c2 100644
Binary files a/src/com/xiaomitool/v2/gui/drawable/device_auth.png and b/src/com/xiaomitool/v2/gui/drawable/device_auth.png differ
diff --git a/src/com/xiaomitool/v2/gui/drawable/milogo.png b/src/com/xiaomitool/v2/gui/drawable/milogo.png
new file mode 100644
index 0000000..23aabfb
Binary files /dev/null and b/src/com/xiaomitool/v2/gui/drawable/milogo.png differ
diff --git a/src/com/xiaomitool/v2/gui/drawable/pcsuiteicon.png b/src/com/xiaomitool/v2/gui/drawable/pcsuiteicon.png
index 0bd52be..f543c46 100644
Binary files a/src/com/xiaomitool/v2/gui/drawable/pcsuiteicon.png and b/src/com/xiaomitool/v2/gui/drawable/pcsuiteicon.png differ
diff --git a/src/com/xiaomitool/v2/gui/drawable/usbdbg1.png b/src/com/xiaomitool/v2/gui/drawable/usbdbg1.png
new file mode 100644
index 0000000..25ad0df
Binary files /dev/null and b/src/com/xiaomitool/v2/gui/drawable/usbdbg1.png differ
diff --git a/src/com/xiaomitool/v2/gui/drawable/usbdbg2.png b/src/com/xiaomitool/v2/gui/drawable/usbdbg2.png
new file mode 100644
index 0000000..9db067d
Binary files /dev/null and b/src/com/xiaomitool/v2/gui/drawable/usbdbg2.png differ
diff --git a/src/com/xiaomitool/v2/gui/drawable/usbdbg3.png b/src/com/xiaomitool/v2/gui/drawable/usbdbg3.png
new file mode 100644
index 0000000..0e8c59e
Binary files /dev/null and b/src/com/xiaomitool/v2/gui/drawable/usbdbg3.png differ
diff --git a/src/com/xiaomitool/v2/gui/drawable/usbdbg4.png b/src/com/xiaomitool/v2/gui/drawable/usbdbg4.png
new file mode 100644
index 0000000..2b9d4c5
Binary files /dev/null and b/src/com/xiaomitool/v2/gui/drawable/usbdbg4.png differ
diff --git a/src/com/xiaomitool/v2/gui/drawable/usbdbg5.png b/src/com/xiaomitool/v2/gui/drawable/usbdbg5.png
new file mode 100644
index 0000000..b863597
Binary files /dev/null and b/src/com/xiaomitool/v2/gui/drawable/usbdbg5.png differ
diff --git a/src/com/xiaomitool/v2/gui/drawable/usbdbg6.png b/src/com/xiaomitool/v2/gui/drawable/usbdbg6.png
new file mode 100644
index 0000000..56c968d
Binary files /dev/null and b/src/com/xiaomitool/v2/gui/drawable/usbdbg6.png differ
diff --git a/src/com/xiaomitool/v2/gui/drawable/usbdbg7.png b/src/com/xiaomitool/v2/gui/drawable/usbdbg7.png
new file mode 100644
index 0000000..c7aaf65
Binary files /dev/null and b/src/com/xiaomitool/v2/gui/drawable/usbdbg7.png differ
diff --git a/src/com/xiaomitool/v2/gui/visual/DeviceImgInstructionPane.java b/src/com/xiaomitool/v2/gui/visual/DeviceImgInstructionPane.java
new file mode 100644
index 0000000..69dd23c
--- /dev/null
+++ b/src/com/xiaomitool/v2/gui/visual/DeviceImgInstructionPane.java
@@ -0,0 +1,149 @@
+package com.xiaomitool.v2.gui.visual;
+
+
+
+import com.xiaomitool.v2.gui.GuiUtils;
+import com.xiaomitool.v2.gui.deviceView.Animatable;
+import com.xiaomitool.v2.gui.deviceView.AnimatableDeviceView;
+import com.xiaomitool.v2.gui.deviceView.DeviceView;
+import com.xiaomitool.v2.language.LRes;
+import com.xiaomitool.v2.logging.Log;
+import com.xiaomitool.v2.utility.RunnableWithArg;
+import javafx.animation.Transition;
+import javafx.application.Platform;
+import javafx.geometry.Insets;
+import javafx.scene.control.Button;
+import javafx.scene.image.Image;
+import javafx.scene.layout.StackPane;
+import javafx.scene.text.Font;
+import javafx.scene.text.Text;
+import javafx.scene.text.TextAlignment;
+
+import java.util.HashMap;
+import java.util.List;
+
+public class DeviceImgInstructionPane extends SidePane {
+
+
+ private String[] texts;
+ private Image[] images;
+ private HashMap animations;
+
+ public DeviceImgInstructionPane(double wantedHeight, double maxHeight, String[] texts, Image[] images, HashMap animations){
+ this.texts = texts;
+ this.images = images;
+ this.animations = animations;
+ build(wantedHeight,maxHeight);
+ }
+
+
+ private int index = 0;
+ private Button prevButton, nextButton;
+ private Text instructionText;
+ private AnimatableDeviceView deviceView;
+ private ButtonPane closeButtonPane;
+ private Transition currentAnimation = null;
+ private void build(double wantedHeight, double maxHeight){
+ if (texts == null || images == null || texts.length != images.length || texts.length == 0){
+ throw new IllegalArgumentException("Please, pass not null, not empty arrays with same num of elements");
+ }
+ ButtonPane buttonPane = new ButtonPane(LRes.PREV_STEP, LRes.NEXT_STEP);
+ closeButtonPane = new ButtonPane(LRes.OK_FINISHED);
+ List btns = buttonPane.getButtons();
+ prevButton = btns.get(0);
+ nextButton = btns.get(1);
+ prevButton.setDisable(true);
+ instructionText = new Text();
+ instructionText.setWrappingWidth(400);
+ instructionText.setFont(Font.font(15));
+ instructionText.setTextAlignment(TextAlignment.CENTER);
+ buttonPane.setContent(instructionText);
+ deviceView = new AnimatableDeviceView(DeviceView.DEVICE_18_9, wantedHeight) {
+ @Override
+ public boolean animate(int step) throws InterruptedException {
+ if (animations != null) {
+ Animatable.AnimationPayload payload = animations.get(step);
+ if (payload != null) {
+ currentAnimation = this.buildCircleTransition(payload.getX(), payload.getY(), payload.getTimes(), true, payload.isUnique());
+ } else {
+ deviceView.removeCircleAnimation();
+ }
+ }
+ return index < texts.length -1;
+ }
+ };
+ deviceView.setContent(images[index]);
+ buttonPane.getIdClickReceiver().addListener(new RunnableWithArg() {
+ @Override
+ public void run(Object arg) {
+ int click = (int) arg;
+ if (click == 0){
+ index--;
+ } else {
+ index++;
+ }
+ loadStep();
+ }
+ });
+ closeButtonPane.setContent(buttonPane);
+ StackPane stackPane = new StackPane(closeButtonPane);
+ stackPane.setPadding(new Insets(20,0,30,20));
+ loadStep();
+ this.setLeft(stackPane);
+ this.setRight(GuiUtils.center(DeviceView.crop(deviceView, wantedHeight, wantedHeight > maxHeight ? (wantedHeight - maxHeight)/2 : 0)));
+ }
+
+ public void animate() throws InterruptedException {
+ deviceView.animate(index);
+ }
+
+ public void setOverlayLen(OverlayPane pane, double zoomRatio){
+ Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ deviceView.setLenOverlay(pane,300, zoomRatio);
+ }
+ };
+ if (!Platform.isFxApplicationThread()){
+ Platform.runLater(runnable);
+ } else {
+ runnable.run();
+ }
+
+ }
+
+ private void loadStep(){
+ Image toLoadImg = images[index];
+ String toLoadText = LRes.STEP.toString()+" ["+(index+1)+"/"+(texts.length)+"]:\n"+texts[index];
+
+ if (index == 0){
+ prevButton.setDisable(true);
+ } else {
+ prevButton.setDisable(false);
+ }
+ if (index == texts.length -1){
+ nextButton.setDisable(true);
+ } else{
+ nextButton.setDisable(false);
+ }
+ instructionText.setText(toLoadText);
+ deviceView.setContent(toLoadImg);
+ try {
+ animate();
+ } catch (InterruptedException e) {
+ Log.warn("Animation error: "+e.getMessage());
+ }
+ }
+
+ public int waitClick() throws InterruptedException {
+ return this.closeButtonPane.waitClick();
+ }
+
+ public IDClickReceiver getIdClickReceiver(){
+ return this.closeButtonPane.getIdClickReceiver();
+ }
+
+
+
+
+}
diff --git a/src/com/xiaomitool/v2/gui/visual/IDClickReceiver.java b/src/com/xiaomitool/v2/gui/visual/IDClickReceiver.java
index db60d57..73b908d 100644
--- a/src/com/xiaomitool/v2/gui/visual/IDClickReceiver.java
+++ b/src/com/xiaomitool/v2/gui/visual/IDClickReceiver.java
@@ -2,13 +2,23 @@ package com.xiaomitool.v2.gui.visual;
import com.xiaomitool.v2.logging.Log;
import com.xiaomitool.v2.utility.MessageReceiver;
+import com.xiaomitool.v2.utility.RunnableWithArg;
import com.xiaomitool.v2.utility.WaitSemaphore;
import javafx.scene.Node;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
public class IDClickReceiver implements MessageReceiver {
+ private final List listeners = Collections.synchronizedList(new ArrayList<>());
+ public void addListener(RunnableWithArg args){
+ synchronized (listeners) {
+ this.listeners.add(args);
+ }
+ }
+
private static class IDNode {
public int id;
public Node node;
@@ -49,9 +59,20 @@ public class IDClickReceiver implements MessageReceiver {
return lastClickID;
}
public void message(int message){
+ synchronized (listeners){
+ for (RunnableWithArg runnable : listeners){
+ runnable.run(message);
+ }
+ }
lastClickID =message;
semaphore.increase();
}
+ public boolean removeListener(RunnableWithArg arg){
+ return this.listeners.remove(arg);
+ }
+
+
+
}
diff --git a/src/com/xiaomitool/v2/gui/visual/InstallPane.java b/src/com/xiaomitool/v2/gui/visual/InstallPane.java
index 2e70d3d..76dc48a 100644
--- a/src/com/xiaomitool/v2/gui/visual/InstallPane.java
+++ b/src/com/xiaomitool/v2/gui/visual/InstallPane.java
@@ -1,6 +1,6 @@
package com.xiaomitool.v2.gui.visual;
-import com.sun.glass.ui.Window;
+
import com.xiaomitool.v2.engine.actions.ActionsStatic;
import com.xiaomitool.v2.gui.WindowManager;
import com.xiaomitool.v2.language.LRes;
@@ -67,7 +67,7 @@ public class InstallPane extends StackPane {
stackTrace = StrUtils.firstNLines(stackTrace,5);
ErrorPane errorPane = new ErrorPane(LRes.CANCEL, LRes.STEP_BACK, LRes.TRY_AGAIN);
errorPane.setTitle(LRes.PROCEDURE_EXC_TITLE.toString(), Color.rgb(128,0,0));
- errorPane.setText(LRes.PROCEDURE_EXC_TEXT.toString()+"\n\n"+LRes.PROCEDURE_EXC_DETAILS.toString(exception.getCode().toString()));
+ errorPane.setText(LRes.PROCEDURE_EXC_TEXT.toString(LRes.PROCEDURE_EXC_DETAILS.toString(exception.getCode().toString(), exception.getMessage())+"\n",LRes.TRY_AGAIN, LRes.STEP_BACK, LRes.CANCEL));
Text t2 = new Text(LRes.PROCEDURE_EXC_ADV_DETAILS.toString()+": "+exception.getMessage()+"\n"+stackTrace+(stackTrace.length() != len ? "\n..." : ""));
t2.setTextAlignment(TextAlignment.CENTER);
t2.setWrappingWidth(WindowManager.getContentWidth()-100);
@@ -83,16 +83,6 @@ public class InstallPane extends StackPane {
}
WindowManager.removeTopContent();
if (msg == 0){
- new Thread(new Runnable() {
- @Override
- public void run() {
- try {
- ActionsStatic.MOD_CHOOSE_SCREEN().run();
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
- }).start();
this.sendCommand(Command.ABORT);
} else if (msg == 1){
this.sendCommand(Command.UPLEVEL);
diff --git a/src/com/xiaomitool/v2/language/LRes.java b/src/com/xiaomitool/v2/language/LRes.java
index 45ab3f4..cc81480 100644
--- a/src/com/xiaomitool/v2/language/LRes.java
+++ b/src/com/xiaomitool/v2/language/LRes.java
@@ -38,7 +38,7 @@ public enum LRes {
SELECTED_DEVICE("Selected device: %s"),
TRY_AGAIN("Try again"),
DEVICE_NOT_CONNECTED("It seems your device is not connected. Please make sure your device is on, connected to your pc and with usb debugging enabled"),
- AUTH_DEVICE_TEXT("Your device is offline or not authorized. Please authorize your device by clicking on the popup window that is displayed on its screen."),
+ AUTH_DEVICE_TEXT("Your device is offline or not authorized. Please authorize your device by clicking on the popup window that is displayed on its screen.\nIf the device is in recovery mode or doesn't have the popup, try unplugging and pluggin back in the cable"),
YES("Yes"),
NO("No"),
IRRELEVANT("Irrilevant"),
@@ -78,7 +78,7 @@ public enum LRes {
PARTITION_FORMATTED("Partition formatted"),
FETCHING_RECOVERY_INFO("Retrieving recovery information"),
DONATE("Make a donation"),
- DONATE_DONT("Don't give a ****"),
+ DONATE_DONT("Make the dev bankrupt"),
DONATE_TEXT("This tool has been developed thanks to the users donations.\nThe developer of this tool isn't paid for his work and he is paying for services with his pocket money, leaving this tool free.\nIf you want to show your gratitude to him or support this project you can still offer him a beer by making a donation.\n\nWould you like to make a donation?"),
INSTALL_SUCCESS("Installation completed succesfully!"),
DOWNLOADING_ROM_FILE("Downloading rom file..."),
@@ -102,8 +102,8 @@ public enum LRes {
LOGIN_CANCELED("Login canceled or failed"),
LOGIN_SUCCESS("Logged in succesfully"),
PROCEDURE_EXC_TITLE("Procedure error occurred"),
- PROCEDURE_EXC_TEXT("There was an error during the installation producedure.\nYou can choose between trying again starting from the failed step, or step back to a previous step and trying again the procedure, or cancel the operation and abort the installation process."),
- PROCEDURE_EXC_DETAILS("Error details:\nError code: %s"),
+ PROCEDURE_EXC_TEXT("There was an error during the installation producedure.\n%s\nPress \"%s\" to try again the failed procedure.\nPress \"%s\" to go a step back and try again the entire latest step\nPress \"%s\" to cancel the procedure and abort the installation process"),
+ PROCEDURE_EXC_DETAILS("Error type: %s\nError description: %s"),
PROCEDURE_EXC_ADV_DETAILS("Advanced error details"),
CANCEL("Cancel"),
STEP_BACK("Step back"),
@@ -161,17 +161,18 @@ public enum LRes {
REBOOT_RECOVERY_FAILED("Your device should enter recovery mode, but it seems it failed to do that or is not recognized or not connected.\nPlease hold the volume+ and power button to reboot it to recovery mode.\n\nFail cause: %s"),
REBOOT_BOOTLOADER_FAILED("Your device should enter fastboot mode, but it seems it failed to do that or is not recognized or not connected.\nPlease hold the volume- and power button to reboot it to fastboot mode.\n\nFail cause: %s"),
REBOOT_STOCKRECOVERY_FAILED("Your device should enter stock recovery mode, but it seems it failed to do that or is not recognized or not connected.\nPlease hold the volume+ and power button to reboot it to stock recovery mode.\n\nFail cause: %s"),
- FLASHING_STUFF("Flashing %s..."),
- BOOTING_STUFF("Booting %s..."),
+ FLASHING_STUFF("Flashing %s"),
+ BOOTING_STUFF("Booting %s"),
CREATING_DEST_DIR("Creating destination directory"),
- WAITING_DEVICE_ACTIVE("Waiting device active status"),
+ WAITING_DEVICE_ACTIVE("Waiting for device %s connection"),
SEARCHING_LATEST_FASTBOOT("Searching latest fastboot rom: %s"),
CALCULATING_MD5("Calculating md5 hash of file"),
SEARCHING_XIAOMIEU_ROM("Searching latest Xiaomi.eu rom: %s"),
SEARCHING_LATEST_MAGISK("Searching latest magisk zip file"),
VALIDATING_PKG_ROM("Trying to validate rom package with miui server"),
- OTA_REQUEST_RUN("Running ota request to miui server"),
SEARCHING_LATEST_RECOVERY_ROM("Searching latest recovery rom: %s"),
+ SEARCHING_LATEST_OTA_ROM("Searching latest rom available through ota: %s"),
+ REQUEST_OTA_INSTALLATION_TOKEN("Requesting ota installation token for rom: %s"),
BACK_TO_CATEGORIES("Back to categories"),
BACK_TO_CATEGORIES_TEXT("Go back and choose another category of installables"),
STARTING_MIUI_SIDELOAD("Starting MIUI sideload procedure"),
@@ -185,8 +186,47 @@ public enum LRes {
ENABLE_USB_DEBUG_IF_NOT("Enable usb debugging (if not enabled)"),
EXTRACTING_ROM_FILE("Extracting rom file..."),
CHOOSE_PROCEDURE_CATEGORY("Please choose procedure category"),
- CHOOSE_PROCEDURE("Please choose which rom/procedure you want")
+ CHOOSE_PROCEDURE("Please choose which rom/procedure you want"),
+ ENTER_STOCK_RECOVERY_MODE("Your device is rebooting to stock recovery mode.\nYou should see this menu in a few seconds.\nPlease use the volume down button to move to the third option and then the power button to select it.\nThis will enable the required adb connection between the device and this pc."),
+ SKIP("Skip"),
+ NO_INTERNET_BEFORE_FETCH("No internet connection found!\n\nThis tool uses Internet connection to find automaticcaly the latest roms and mods that you can install on your device.\nWithout internet connection this feature will not work.\nYou should enable your internet connection to get the best experience out of this tool.\nChoose if you want to skip this feature or enable internet connection and press try again"),
+ CONFIRM_INSTALLATION_START("You are about to start the installation procedure!\nThis is the last confirmation request before XiaoMiTool will do all of its things and manage your device with advanced procedures\n\nYou are going to lose all (or some part) of the personal data (photos, files, applications) on your device, so DO A BACKUP if you want to save them.\nBy pressing \"%s\" you are responsible of lost data, bricked devices and all the possible outcomes of the procedure\n"),
+ NEXT_STEP("Next step"),
+ PREV_STEP("Previous step"),
+ HT_ENABLE_USB_1("Go the device setting by pressing the settings icon in the launcher"),
+ HT_ENABLE_USB_2("Tap the \"About phone\" option and enter \"All specs\" menu"),
+ HT_ENABLE_USB_3("Tap several (about ten) times the \"MIUI version\" entry in the menu until you get the message \"You are now a developer\""),
+ HT_ENABLE_USB_4("Go back to the main settings menu and tap the \"Additional settings\" voice"),
+ HT_ENABLE_USB_5("Scroll down and tap the \"Developer options\" menu option"),
+ HT_ENABLE_USB_6("Scroll down and find the \"USB Debugging\" option"),
+ HT_ENABLE_USB_7("Tap the toggle to enable the USB debugging and press \"OK\" to confirm"),
+ OK_FINISHED("Ok, finished all"),
+ WAITING_USB_ENABLE("Waiting for USB Debugging connection\nPlease enable the USB Debugging on your device and then press \"%s\"!"),
+ STEP("Step"),
+ CAN_TAKE_COUPLE_MIN("Please wait, it can take a couple of minutes"),
+ UNL_UNKNOWN_ERROR("Unknown error: %d"),
+ UNL_ERR_10000("10000:Request parameter error"),
+ UNL_ERR_10001("10001:Signature verification failed"),
+ UNL_ERR_10002("10002:The same IP request too often"),
+ UNL_ERR_10003("10003:Internal server error"),
+ UNL_ERR_10004("10004:Request has expired"),
+ UNL_ERR_10005("10005:Invalid Nonce request"),
+ UNL_ERR_10006("10006:Client version is too low"),
+ UNL_ERR_20030("You have already unlocked a device recently.\nYou should wait at least 30 days from the first unlock to unlock another device"),
+ UNL_ERR_20031("This device is not binded to your account.\nTurn on your device and bind your account to the device by going in MIUI's Settings > Developer options > Mi Unlock status."),
+ UNL_ERR_20032("Failed to generate signature value required to unlock"),
+ UNL_ERR_20033("User portrait scores too low or black"),
+ UNL_ERR_20034("Current account cannot unlock this device"),
+ UNL_ERR_20035("This tool is outdated, if you want to unlock your device then go to unlock.update.miui.com to download the latest version of MiUnlock"),
+ UNL_ERR_20036("Your account is binded to this device for not enough time.\nYou have to wait %d days and %d hours before you can unlock this device"),
+ UNL_ERR_20036_NOHOURS("You have to wait %d days and %d hours before you can unlock your device"),
+ UNL_ERR_20037("Unlock number has reached the upper limit"),
+ UNL_ERR_20041("Your account isn't associated with a phone number\nGo to account.xiaomi.com and associate it with your phone number"),
+ UNLOCK_ERROR_TEXT("Failed to unlock your device, Xiaomi server returned error %d:\nError description: %s"),
+ UNLOCK_CHECKING_ACCOUNT("Checking account unlock availability"),
+ UNLOCK_CHECKING_DEVICE("Checking device unlock capability")
;
+
private String text;
LRes(String defaultText){
diff --git a/src/com/xiaomitool/v2/logging/Log.java b/src/com/xiaomitool/v2/logging/Log.java
index 72359b4..659029b 100644
--- a/src/com/xiaomitool/v2/logging/Log.java
+++ b/src/com/xiaomitool/v2/logging/Log.java
@@ -36,7 +36,7 @@ public class Log {
public static void debug(Object arg){
- log(PREFIX_DEBUG,arg);
+ //log(PREFIX_DEBUG,arg);
}
public static void debugLine(){
Log.debug("-----------------------------");
diff --git a/src/com/xiaomitool/v2/procedure/ProcedureRunner.java b/src/com/xiaomitool/v2/procedure/ProcedureRunner.java
index 178f31a..e5d1484 100644
--- a/src/com/xiaomitool/v2/procedure/ProcedureRunner.java
+++ b/src/com/xiaomitool/v2/procedure/ProcedureRunner.java
@@ -4,8 +4,10 @@ import com.xiaomitool.v2.adb.device.Device;
import com.xiaomitool.v2.adb.device.DeviceManager;
import com.xiaomitool.v2.adb.device.DeviceProperties;
import com.xiaomitool.v2.gui.visual.InstallPane;
+import com.xiaomitool.v2.logging.Log;
import com.xiaomitool.v2.procedure.install.InstallException;
import com.xiaomitool.v2.rom.Installable;
+import com.xiaomitool.v2.utility.utils.StrUtils;
import javafx.scene.layout.Pane;
@@ -137,10 +139,14 @@ public class ProcedureRunner extends GuiListener {
private HashMap context = new HashMap<>();
public void setContext(String key, Object value){
context.put(key,value);
+ Log.debug("Context var set: "+key+" -> "+StrUtils.str(value));
}
public Object getContext(String key){
- return context.get(key);
+
+ Object res = context.get(key);
+ Log.debug("Getting context var: "+key+" -> "+StrUtils.str(res));
+ return res;
}
public Object requireContext(String key) throws InstallException {
Object res = getContext(key);
diff --git a/src/com/xiaomitool/v2/procedure/RInstall.java b/src/com/xiaomitool/v2/procedure/RInstall.java
index b39cb95..545da93 100644
--- a/src/com/xiaomitool/v2/procedure/RInstall.java
+++ b/src/com/xiaomitool/v2/procedure/RInstall.java
@@ -5,9 +5,17 @@ import com.xiaomitool.v2.procedure.ProcedureRunner;
import com.xiaomitool.v2.procedure.install.InstallException;
public abstract class RInstall {
-
+ private StackTraceElement[] creationStack;
+ public RInstall(){
+ creationStack = Thread.currentThread().getStackTrace();
+ }
public abstract void run(ProcedureRunner runner) throws InstallException, RMessage, InterruptedException;
+
+ void runInternal(ProcedureRunner runner) throws InstallException, RMessage, InterruptedException {
+ Log.debug("Running now procedure: "+this.toString(2));
+ run(runner);
+ }
protected int flags;
public boolean hasFlag(int flag){
@@ -26,4 +34,15 @@ public abstract class RInstall {
return RNode.sequence(this, Procedures.runStackedProcedures());
}
+ public String toString(int stackElements){
+ stackElements+=3;
+ stackElements = Integer.min(stackElements, creationStack.length);
+ StringBuilder builder = new StringBuilder(creationStack[3].toString());
+ for (int i = 4; i< stackElements; ++i){
+ builder.append(" -> ");
+ builder.append(creationStack[i].toString());
+ }
+ return builder.toString();
+ }
+
}
diff --git a/src/com/xiaomitool/v2/procedure/RNode.java b/src/com/xiaomitool/v2/procedure/RNode.java
index 9db0005..26e166d 100644
--- a/src/com/xiaomitool/v2/procedure/RNode.java
+++ b/src/com/xiaomitool/v2/procedure/RNode.java
@@ -39,6 +39,10 @@ public abstract class RNode extends RInstall {
this.type = type;
this.chidren = chidren;
}
+ @Override
+ void runInternal(ProcedureRunner runner) throws InstallException, RMessage, InterruptedException {
+ run(runner);
+ }
public static RNode sequence(RInstall... chidren){
return new RNodeSequence(chidren);
@@ -72,7 +76,7 @@ public abstract class RNode extends RInstall {
for (RInstall install : chidren) {
do {
try {
- install.run(runner);
+ install.runInternal(runner);
break;
} catch (InstallException e) {
Log.debug("PROCEDURE EXC: "+e.getMessage());
@@ -90,10 +94,19 @@ public abstract class RNode extends RInstall {
}
throw new RMessage(cmd);
}
+ } catch (RMessage rMessage){
+ if (rMessage.getCmd().equals(CommandClass.Command.RETRY)){
+ continue;
+ } else if (rMessage.getCmd().equals(CommandClass.Command.UPLEVEL)){
+ cmd = CommandClass.Command.UPLEVEL;
+ break;
+ } else {
+ throw rMessage;
+ }
}
} while (true);
if (CommandClass.Command.UPLEVEL.equals(cmd)){
- this.run(runner);
+ this.runInternal(runner);
return;
}
}
@@ -118,7 +131,7 @@ public abstract class RNode extends RInstall {
}
try {
cause = install;
- install.run(runner);
+ install.runInternal(runner);
allRight = true;
} catch (InstallException e){
exception = e;
@@ -137,6 +150,24 @@ public abstract class RNode extends RInstall {
}
}
+ public static RInstall conditional(String keyBoolean, RInstall ifTrue, RInstall ifFalse){
+ return new RInstall() {
+ @Override
+ public void run(ProcedureRunner runner) throws InstallException, RMessage, InterruptedException {
+ Boolean ifBody = (Boolean) runner.requireContext(keyBoolean);
+ if (ifBody){
+ if (ifTrue != null) {
+ ifTrue.run(runner);
+ }
+ } else {
+ if(ifFalse != null) {
+ ifFalse.run(runner);
+ }
+ }
+ }
+ };
+ }
+
diff --git a/src/com/xiaomitool/v2/procedure/device/ManageDevice.java b/src/com/xiaomitool/v2/procedure/device/ManageDevice.java
index 19094cc..4a25715 100644
--- a/src/com/xiaomitool/v2/procedure/device/ManageDevice.java
+++ b/src/com/xiaomitool/v2/procedure/device/ManageDevice.java
@@ -6,7 +6,6 @@ import com.xiaomitool.v2.adb.device.DeviceAnswers;
import com.xiaomitool.v2.adb.device.DeviceManager;
import com.xiaomitool.v2.engine.actions.ActionsDynamic;
import com.xiaomitool.v2.gui.WindowManager;
-import com.xiaomitool.v2.gui.visual.ButtonPane;
import com.xiaomitool.v2.language.LRes;
import com.xiaomitool.v2.procedure.*;
import com.xiaomitool.v2.procedure.install.InstallException;
@@ -26,18 +25,8 @@ public class ManageDevice {
}
public static RInstall checkIfTwrpInstalled(){
- return RNode.sequence(new RInstall() {
- @Override
- public void run(ProcedureRunner runner) throws InstallException, RMessage, InterruptedException {
- Device device = Procedures.requireDevice(runner);
- try {
- device.requireAccessibile();
- } catch (AdbException e) {
- throw new InstallException(e);
- }
- }
- },
- RNode.fallback(RNode.sequence(RebootDevice.rebootRecovery(true, false), TwrpInstall.checkIfIsInTwrp(), new RInstall() {
+ return RNode.sequence(requireAccessible(),
+ RNode.fallback(RNode.sequence(RebootDevice.rebootRecovery(true, false, 15), TwrpInstall.checkIfIsInTwrp(), new RInstall() {
@Override
public void run(ProcedureRunner runner) throws InstallException, RMessage, InterruptedException {
Device device = Procedures.requireDevice(runner);
@@ -46,8 +35,10 @@ public class ManageDevice {
}), new RInstall() {
@Override
public void run(ProcedureRunner runner) throws InstallException, RMessage, InterruptedException {
+
Device device = Procedures.requireDevice(runner);
device.getAnswers().setAnswer(DeviceAnswers.HAS_TWRP, YesNoMaybe.NO);
+ ActionsDynamic.HOWTO_GO_RECOVERY(device).run();
}
}));
@@ -62,10 +53,10 @@ public class ManageDevice {
}
});
}
- public static RInstall checkAccessible(){
- return checkAccessible(true);
+ public static RInstall requireAccessible(){
+ return requireAccessible(true);
}
- public static RInstall checkNoUnauthOffline(boolean refresh){
+ public static RInstall requireNoUnauthOffline(boolean refresh){
return new RInstall() {
@Override
public void run(ProcedureRunner runner) throws InstallException, RMessage, InterruptedException {
@@ -83,7 +74,7 @@ public class ManageDevice {
};
}
- public static RInstall checkConnected(boolean refresh){
+ public static RInstall requireConnected(boolean refresh){
return new RInstall() {
@Override
public void run(ProcedureRunner runner) throws InstallException, RMessage, InterruptedException {
@@ -95,23 +86,39 @@ public class ManageDevice {
return;
}
ActionsDynamic.REQUIRE_DEVICE_CONNECTED(device).run();
+ //ActionsDynamic.REQUIRE_DEVICE_AUTH(device).run();
}
};
}
- public static RInstall checkAccessible(boolean refresh){
- return RNode.sequence(checkConnected(refresh), checkNoUnauthOffline(refresh));
+ public static RInstall waitRequireAccessible(int timeout, Device.Status showTextExpectedDeviceStatus){
+ return RNode.sequence(RNode.setSkipOnException(waitDevice(timeout, showTextExpectedDeviceStatus)), requireAccessible(true));
+ }
+
+ public static RInstall requireAccessible(boolean refresh){
+ return RNode.sequence(requireConnected(refresh), requireNoUnauthOffline(refresh));
}
- public static RInstall waitDevice(int timeout){
+ public static RInstall waitDevice(int timeout, Device.Status showTextExpectedDeviceStatus){
return new RInstall() {
@Override
public void run(ProcedureRunner runner) throws InstallException, RMessage, InterruptedException {
Device device = Procedures.requireDevice(runner);
-
- if (device.waitActive(timeout) == null){
+ if (showTextExpectedDeviceStatus != null){
+ String connectionType = Device.Status.DEVICE.equals(showTextExpectedDeviceStatus) ? "ADB": showTextExpectedDeviceStatus.toString();
+ runner.text(LRes.WAITING_DEVICE_ACTIVE.toString(connectionType));
+ }
+ int t = timeout;
+ if (t < 2){
+ t = 2;
+ }
+ t-=2;
+ Thread.sleep(1500);
+ device.setConnected(false);
+ DeviceManager.refresh();
+ if (device.waitActive(t) == null){
throw new InstallException("Waited device for "+timeout+" seconds but it wasn't active", InstallException.Code.WAIT_DEVICE_TIMEOUT, true);
}
diff --git a/src/com/xiaomitool/v2/procedure/device/RebootDevice.java b/src/com/xiaomitool/v2/procedure/device/RebootDevice.java
index df19f59..84ac685 100644
--- a/src/com/xiaomitool/v2/procedure/device/RebootDevice.java
+++ b/src/com/xiaomitool/v2/procedure/device/RebootDevice.java
@@ -4,7 +4,7 @@ import com.xiaomitool.v2.adb.AdbException;
import com.xiaomitool.v2.adb.device.Device;
import com.xiaomitool.v2.adb.device.DeviceManager;
import com.xiaomitool.v2.engine.CommonsMessages;
-import com.xiaomitool.v2.engine.actions.ActionsStatic;
+import com.xiaomitool.v2.engine.actions.ActionsDynamic;
import com.xiaomitool.v2.gui.WindowManager;
import com.xiaomitool.v2.gui.visual.ButtonPane;
import com.xiaomitool.v2.language.LRes;
@@ -15,7 +15,7 @@ import com.xiaomitool.v2.procedure.install.InstallException;
public class RebootDevice {
public static RInstall rebootDevice(boolean wait, boolean force){
- return new RInstall() {
+ return RNode.sequence(ManageDevice.requireAccessible(),new RInstall() {
@Override
public void run(ProcedureRunner runner) throws InstallException, RMessage, InterruptedException {
Device device = Procedures.requireDevice(runner);
@@ -23,6 +23,7 @@ public class RebootDevice {
runner.text(LRes.REBOOTING_TO_MODE.toString(Device.Status.DEVICE.toString()));
try {
if (wait) {
+ runner.text(LRes.WAITING_DEVICE_ACTIVE.toString(Device.Status.DEVICE.toString()));
result = device.reboot(Device.Status.DEVICE, force);
} else {
result = device.rebootNoWait(Device.Status.DEVICE, force);
@@ -34,12 +35,14 @@ public class RebootDevice {
throw new InstallException("Failed to reboot device to recovery mode", InstallException.Code.REBOOT_FAILED, false);
}
}
- };
+ });
}
-
public static RInstall rebootRecovery(boolean wait, boolean force){
- return RNode.sequence(rebootDeviceIfNoAdbAccessible(),new RInstall() {
+ return rebootRecovery(wait,force,-1);
+ }
+ public static RInstall rebootRecovery(boolean wait, boolean force, int timeout){
+ return RNode.sequence(ManageDevice.requireAccessible(),rebootDeviceIfNoAdbAccessible(),new RInstall() {
@Override
public void run(ProcedureRunner runner) throws InstallException, RMessage, InterruptedException {
Device device = Procedures.requireDevice(runner);
@@ -47,7 +50,15 @@ public class RebootDevice {
runner.text(LRes.REBOOTING_TO_MODE.toString(Device.Status.RECOVERY.toString()));
try {
if (wait) {
- result = device.reboot(Device.Status.RECOVERY, force);
+ runner.text(LRes.WAITING_DEVICE_ACTIVE.toString(Device.Status.RECOVERY.toString()));
+ if (timeout <= 0) {
+ result = device.reboot(Device.Status.RECOVERY, force);
+ } else {
+ result = device.rebootNoWait(Device.Status.RECOVERY, force);
+ Thread.sleep(2000);
+ device.setConnected(false);
+ result = result && device.waitStatus(Device.Status.RECOVERY, timeout);
+ }
} else {
result = device.rebootNoWait(Device.Status.RECOVERY, force);
}
@@ -62,11 +73,21 @@ public class RebootDevice {
}
public static RInstall rebootNoWaitIfConnected(){
- return RNode.fallback(rebootDevice(false,false), Procedures.doNothing());
+ return new RInstall() {
+ @Override
+ public void run(ProcedureRunner runner) throws InstallException, RMessage, InterruptedException {
+ Device device = Procedures.requireDevice(runner);
+ try {
+ device.rebootNoWait(Device.Status.DEVICE,false);
+ } catch (Throwable e) {
+ Log.debug("Device not rebooted, but it doesn't matter");
+ }
+ }
+ };
}
public static RInstall requireRecovery(){
- return RNode.sequence(ManageDevice.checkAccessible(),new RInstall() {
+ return RNode.sequence(new RInstall() {
@Override
public void run(ProcedureRunner runner) throws InstallException, RMessage, InterruptedException {
Device device = Procedures.requireDevice(runner);
@@ -125,7 +146,7 @@ public class RebootDevice {
}
public static RInstall rebootBootloader(boolean wait, boolean force){
- return new RInstall() {
+ return RNode.sequence(ManageDevice.requireAccessible(),new RInstall() {
@Override
public void run(ProcedureRunner runner) throws InstallException, RMessage, InterruptedException {
Device device = Procedures.requireDevice(runner);
@@ -133,7 +154,7 @@ public class RebootDevice {
runner.text(LRes.REBOOTING_TO_MODE.toString(Device.Status.FASTBOOT.toString()));
try {
if (wait) {
- runner.text(LRes.WAITING_DEVICE_ACTIVE);
+ runner.text(LRes.WAITING_DEVICE_ACTIVE.toString(Device.Status.FASTBOOT.toString()));
result = device.reboot(Device.Status.FASTBOOT, force);
} else {
result = device.rebootNoWait(Device.Status.FASTBOOT, force);
@@ -145,7 +166,7 @@ public class RebootDevice {
throw new InstallException("Failed to reboot device to fastboot mode", InstallException.Code.REBOOT_FAILED, false);
}
}
- };
+ });
}
public static RInstall requireFastboot(){
@@ -201,25 +222,14 @@ public class RebootDevice {
});
}
- public static RInstall rebootStockRecovery(boolean wait, boolean force){
- return RNode.sequence(rebootDeviceIfNoAdbAccessible(),new RInstall() {
+ public static RInstall rebootStockRecovery(boolean force){
+ return RNode.sequence(ManageDevice.requireAccessible(),rebootDeviceIfNoAdbAccessible(),new RInstall() {
@Override
public void run(ProcedureRunner runner) throws InstallException, RMessage, InterruptedException {
Device device = Procedures.requireDevice(runner);
- boolean result = false;
- runner.text(LRes.REBOOTING_TO_MODE.toString(Device.Status.FASTBOOT.toString()));
- try {
- if (wait) {
- runner.text(LRes.WAITING_DEVICE_ACTIVE);
- result = device.reboot(Device.Status.FASTBOOT, force);
- } else {
- result = device.rebootNoWait(Device.Status.FASTBOOT, force);
- }
- } catch (AdbException e){
- throw new InstallException(e);
- }
+ boolean result = ActionsDynamic.REBOOT_STOCK_RECOVERY(device,force).run() != 0;
if (!result){
- throw new InstallException("Failed to reboot device to fastboot mode", InstallException.Code.REBOOT_FAILED, false);
+ throw new InstallException("Failed to reboot device to stock recovery mode", InstallException.Code.REBOOT_FAILED, false);
}
}
});
@@ -240,7 +250,7 @@ public class RebootDevice {
}
public static RInstall requireStockRecovery(){
- return RNode.sequence(ManageDevice.checkAccessible(),new RInstall() {
+ return RNode.sequence(new RInstall() {
@Override
public void run(ProcedureRunner runner) throws InstallException, RMessage, InterruptedException {
Device device = Procedures.requireDevice(runner);
@@ -252,13 +262,7 @@ public class RebootDevice {
while (true) {
Log.debug(device.getStatus());
try {
- rebootStockRecovery(false, false).setFlag(RNode.FLAG_THROWRAWEXCEPTION, true).run(runner);
- ActionsStatic.HOWTO_GO_RECOVERY().run();
- try {
- device.waitStatus(Device.Status.SIDELOAD, 120);
- } catch (AdbException e) {
- throw new InstallException(e);
- }
+ rebootStockRecovery(false).setFlag(RNode.FLAG_THROWRAWEXCEPTION, true).run(runner);
return;
} catch (Exception e) {
ButtonPane buttonPane = new ButtonPane(LRes.TRY_AGAIN, LRes.ABORT);
diff --git a/src/com/xiaomitool/v2/procedure/fetch/StockRecoveryFetch.java b/src/com/xiaomitool/v2/procedure/fetch/StockRecoveryFetch.java
index f3b8872..2945105 100644
--- a/src/com/xiaomitool/v2/procedure/fetch/StockRecoveryFetch.java
+++ b/src/com/xiaomitool/v2/procedure/fetch/StockRecoveryFetch.java
@@ -87,12 +87,12 @@ public class StockRecoveryFetch {
}
public static RInstall allLatestOta(){
- return RNode.setSkipOnException(RNode.sequence(
+ return RNode.skipOnException(
fetchOnlyIfDeviceLockedOrNoFastboot(MiuiRom.Specie.CHINA_STABLE, findInstallWay(MiuiRom.Specie.CHINA_STABLE)),
fetchOnlyIfDeviceLockedOrNoFastboot(MiuiRom.Specie.CHINA_DEVELOPER,findInstallWay(MiuiRom.Specie.CHINA_DEVELOPER)),
fetchOnlyIfDeviceLockedOrNoFastboot(MiuiRom.Specie.GLOBAL_STABLE,findInstallWay(MiuiRom.Specie.GLOBAL_STABLE)),
fetchOnlyIfDeviceLockedOrNoFastboot(MiuiRom.Specie.GLOBAL_DEVELOPER,findInstallWay(MiuiRom.Specie.GLOBAL_DEVELOPER))
- ));
+ );
}
public static RInstall validatePkgRom(){
@@ -120,6 +120,7 @@ public class StockRecoveryFetch {
}
params.setPkg(md5);
HashMap rom ;
+ runner.text(LRes.REQUEST_OTA_INSTALLATION_TOKEN.toString(specie.toString()));
try {
rom = MiuiRomOta.otaV3_request(params);
} catch (XiaomiProcedureException e) {
@@ -225,7 +226,7 @@ public class StockRecoveryFetch {
params.setPkg(md5);
}
HashMap rom ;
- runner.text(LRes.SEARCHING_LATEST_RECOVERY_ROM.toString(params.getSpecie().toString()));
+ runner.text(LRes.SEARCHING_LATEST_OTA_ROM.toString(params.getSpecie().toString()));
try {
rom = MiuiRomOta.otaV3_request(params);
} catch (XiaomiProcedureException e) {
@@ -246,14 +247,14 @@ public class StockRecoveryFetch {
if (installable == null){
throw new InstallException("Ota response doesn't contain an installable rom data", InstallException.Code.MISSING_PROPERTY, true);
}
- if (!installable.hasInstallToken() && !UnlockStatus.UNLOCKED.equals(Procedures.requireDevice(runner).getAnswers().getUnlockStatus()) && installable.getMd5() != null && !installable.getMd5().isEmpty()){
+ /*if (!installable.hasInstallToken() && !UnlockStatus.UNLOCKED.equals(Procedures.requireDevice(runner).getAnswers().getUnlockStatus()) && installable.getMd5() != null && !installable.getMd5().isEmpty()){
runner.setContext(SRF_MD5, installable.getMd5());
this.run(runner);
return;
- }
+ }*/
- chooser.add(id, installable);
+ //chooser.add(id, installable);
Procedures.setInstallable(runner,installable);
diff --git a/src/com/xiaomitool/v2/procedure/install/AdbInstall.java b/src/com/xiaomitool/v2/procedure/install/AdbInstall.java
index cead1f5..2e28b39 100644
--- a/src/com/xiaomitool/v2/procedure/install/AdbInstall.java
+++ b/src/com/xiaomitool/v2/procedure/install/AdbInstall.java
@@ -46,6 +46,7 @@ public class AdbInstall {
if (destinationpath == null){
destinationpath = "/sdcard/";
}
+ String outputPath = destinationpath.endsWith("/") ? (destinationpath+FilenameUtils.getName(fileToPush.toString())) : destinationpath;
Device device = Procedures.requireDevice(runner);
ProgressPane.DefProgressPane defProgressPane = new ProgressPane.DefProgressPane();
UpdateListener listener = defProgressPane.getUpdateListener(200);
@@ -56,7 +57,7 @@ public class AdbInstall {
defProgressPane.setText(LRes.STARTING_TASK);
}
});
- AdbPushTask pushTask = new AdbPushTask(listener,fileToPush, destinationpath, device.getSerial());
+ AdbPushTask pushTask = new AdbPushTask(listener,fileToPush, outputPath, device.getSerial());
WindowManager.setMainContent(defProgressPane, false);
TaskManager.getInstance().startSameThread(pushTask);
pushTask.waitFinished();
@@ -64,7 +65,7 @@ public class AdbInstall {
if (pushTask.getError() != null){
throw new InstallException(new AdbException("Failed to push file on device: "+pushTask.getError().getMessage()));
}
- runner.setContext(OUTPUT_DST_PATH, destinationpath.endsWith("/") ? (destinationpath+FilenameUtils.getName(fileToPush.toString())) : destinationpath);
+ runner.setContext(OUTPUT_DST_PATH, outputPath);
}
};
diff --git a/src/com/xiaomitool/v2/procedure/install/FastbootInstall.java b/src/com/xiaomitool/v2/procedure/install/FastbootInstall.java
index 07e86c6..decff4f 100644
--- a/src/com/xiaomitool/v2/procedure/install/FastbootInstall.java
+++ b/src/com/xiaomitool/v2/procedure/install/FastbootInstall.java
@@ -1,5 +1,6 @@
package com.xiaomitool.v2.procedure.install;
+import com.xiaomitool.v2.adb.AdbException;
import com.xiaomitool.v2.adb.AdbUtils;
import com.xiaomitool.v2.adb.FastbootCommons;
import com.xiaomitool.v2.adb.device.Device;
@@ -34,6 +35,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Comparator;
+import java.util.HashMap;
import java.util.List;
import java.util.function.BiPredicate;
import java.util.regex.Matcher;
@@ -165,7 +167,12 @@ public class FastbootInstall {
String line = (String) arg;
Matcher m = p.matcher(line);
if (m.find()){
- procedureRunner.text(m.group(1));
+ String text = m.group(1);
+ procedureRunner.text(text);
+ if (text.toLowerCase().endsWith("system")){
+ procedureRunner.text(LRes.CAN_TAKE_COUPLE_MIN);
+ }
+
}
lastLine.pointed = line;
}
@@ -187,6 +194,8 @@ public class FastbootInstall {
}
+ private static final HashMap UNLOCK_TOKEN_CACHE = new HashMap<>();
+
@ExportFunction("unlock_bootlaoder")
public static RInstall unlockBootloader(){
return RNode.sequence(RebootDevice.requireFastboot(), new RInstall() {
@@ -205,11 +214,13 @@ public class FastbootInstall {
throw new InstallException("Failed to get the device unlock token", InstallException.Code.INFO_RETRIVE_FAILED, true);
}
try {
+ runner.text(LRes.UNLOCK_CHECKING_ACCOUNT);
String info = UnlockCommonRequests.userInfo();
if (info != null){
//TODO
Log.debug(info);
}
+ runner.text(LRes.UNLOCK_CHECKING_DEVICE);
String alert = UnlockCommonRequests.deviceClear((String) device.getDeviceProperties().get(DeviceProperties.CODENAME));
if (alert != null){
//TODO
@@ -228,53 +239,69 @@ public class FastbootInstall {
if (click != 0){
throw new InstallException("Unlock procedure aborted, cannot continue", InstallException.Code.ABORTED, true);
}
- token = AdbUtils.parseFastbootVar("token",FastbootCommons.getvar("token", device.getSerial()));
- if (token == null){
- throw new InstallException("Failed to get the device unlock token", InstallException.Code.INFO_RETRIVE_FAILED, true);
- }
- try {
- String unlockData = null;
- if (false) { //TODO REMOVE FAKE UNLOCK
+ while (true) {
+ token = AdbUtils.parseFastbootVar("token", FastbootCommons.getvar("token", device.getSerial()));
+ if (token == null) {
+ throw new InstallException("Failed to get the device unlock token", InstallException.Code.INFO_RETRIVE_FAILED, true);
+ }
+ try {
+ String unlockData = null;
unlockData = UnlockCommonRequests.ahaUnlock(token, (String) device.getDeviceProperties().get(DeviceProperties.CODENAME), (String) device.getDeviceProperties().getFastbootProperties().get("", ""), (String) device.getDeviceProperties().getFastbootProperties().get("", ""), "");
- } else {
- unlockData = "{\"code\":0,\"encryptData\":\"86B700178A7F1BF7886D7A3FA05EBDEECA3095AB4D091C2750A313F1310EF2E898DB92ED3CA7E1FD09E17A92D93BEF63C15653ECB848E672B9C892C4A172F117D671F64A49846A768E95FEAD6643F09D723AC90847D738B8AC12BDB29C8DE2B3AB9A855729A655BDF8A5A0E69B8AC102E78C673D836A808D400157F7FF305BE567AAB5D640FCFBA3229B63F77D81DDABCF2CC01B57E7400D0081547B99F92E2F470CB5B7DF2F43318F0F9205A315FFD19981FC9662371BA4C157A528B448B1BAF5D1D950E349504240DB4BA4AE57C57B85B24834F6130651D555437C24BE0EFFEB7E1277925AFB1D631B66012BBA78CE6161F7C6AF50021BF0849BF15AFD6F42\",\"description\":\"私钥签名成功\",\"uid\":\"1606054557\"}";
- }
- if (unlockData == null){
- throw new InstallException("Failed to get the unlock data required", InstallException.Code.INFO_RETRIVE_FAILED, true);
- }
- Log.debug(unlockData);
- JSONObject json = new JSONObject(unlockData);
- int code = json.optInt("code",-100);
- String description = json.optString("description", "null");
- String encryptData = json.optString("encryptData",null);
+ if (unlockData == null) {
+ throw new InstallException("Failed to get the unlock data required", InstallException.Code.INFO_RETRIVE_FAILED, true);
+ }
+ Log.debug(unlockData);
+ JSONObject json = new JSONObject(unlockData);
+ int code = json.optInt("code", -100);
+ String description = json.optString("description", "null");
+ String encryptData = json.optString("encryptData", null);
+ Log.debug(description);
+ if (code != 0 || encryptData == null) {
+ // throw new InstallException("The server responded, but the unlock is not permitted, code: " + code + ", description: " + description, InstallException.Code.XIAOMI_EXCEPTION, true);
+ ButtonPane unlockButtonPane = new ButtonPane(LRes.TRY_AGAIN, LRes.ABORT);
+ unlockButtonPane.setContentText(LRes.UNLOCK_ERROR_TEXT.toString(code, UnlockCommonRequests.getUnlockCodeMeaning(code,json)));
+ WindowManager.setMainContent(unlockButtonPane,false);
+ int choice = unlockButtonPane.waitClick();
+ WindowManager.removeTopContent();
+ if(choice == 0){
+ continue;
+ } else {
+ throw InstallException.ABORT_EXCEPTION;
+ }
+ } else {
+ UNLOCK_TOKEN_CACHE.put(token, encryptData);
+ }
+ YesNoMaybe unlocked = FastbootCommons.oemUnlock(device.getSerial(), encryptData);
+ if (YesNoMaybe.NO.equals(unlocked)) {
+ throw new InstallException("Failed to unlock the device, fastboot exit with status non zero or internal error", InstallException.Code.UNLOCK_ERROR, true);
+ }
+ device.getDeviceProperties().getFastbootProperties().put(DeviceProperties.X_LOCKSTATUS, UnlockStatus.UNKNOWN);
+ Thread.sleep(1000);
+ DeviceManager.refresh();
+ try {
+ device.waitStatus(Device.Status.FASTBOOT, 4);
+ Device.Status status = device.getStatus();
+ if (Device.Status.FASTBOOT.equals(status)) {
+ device.getDeviceProperties().getFastbootProperties().parse(true);
+ }
+ if (UnlockStatus.LOCKED.equals(device.getAnswers().getUnlockStatus())) {
+ throw new InstallException("Failed to unlock the device, the procedure failed during the unlock command, the device doens't seem to be unlocked", UNLOCK_ERROR, true);
+ }
+ } catch (AdbException e){
+ //Device not in fastboot after 4 seconds = unlock success;
+ Log.debug("Successful unlock!");
+ }
+ break;
- if (code != 0 || encryptData == null){
- throw new InstallException("The server responded, but the unlock is not permitted, code: "+code+", description: "+description, InstallException.Code.XIAOMI_EXCEPTION, true);
+ } catch (XiaomiProcedureException e) {
+ throw new InstallException(e);
+ } catch (CustomHttpException e) {
+ throw new InstallException(e);
+ } catch (InstallException e) {
+ throw e;
+ } catch (Exception e){
+ throw new InstallException("Internal error while parsing unlock data: "+e.getMessage(), InstallException.Code.INTERNAL_ERROR, true);
}
- YesNoMaybe unlocked = FastbootCommons.oemUnlock(device.getSerial(), encryptData);
- if (YesNoMaybe.NO.equals(unlocked)){
- throw new InstallException("Failed to unlock the device, fastboot exit with status non zero or internal error", InstallException.Code.UNLOCK_ERROR, true);
- }
- device.getDeviceProperties().getFastbootProperties().put(DeviceProperties.X_LOCKSTATUS, UnlockStatus.UNKNOWN);
- Thread.sleep(1000);
- DeviceManager.refresh();
- device.waitStatus(Device.Status.FASTBOOT, 4);
- Device.Status status = device.getStatus();
- if (Device.Status.FASTBOOT.equals(status)){
- device.getDeviceProperties().getFastbootProperties().parse(true);
- }
- if (UnlockStatus.LOCKED.equals(device.getAnswers().getUnlockStatus())){
- throw new InstallException("Failed to unlock the device, the procedure failed during the unlock command, the device doens't seem to be unlocked", UNLOCK_ERROR, true);
- }
-
- } catch (XiaomiProcedureException e) {
- throw new InstallException(e);
- } catch (CustomHttpException e) {
- throw new InstallException(e);
- } catch (InstallException e){
- throw e;
- } catch (Exception e){
- throw new InstallException("Internal error while parsing unlock data: "+e.getMessage(), InstallException.Code.INTERNAL_ERROR, true);
}
}
diff --git a/src/com/xiaomitool/v2/procedure/install/GenericInstall.java b/src/com/xiaomitool/v2/procedure/install/GenericInstall.java
index 14af243..727c208 100644
--- a/src/com/xiaomitool/v2/procedure/install/GenericInstall.java
+++ b/src/com/xiaomitool/v2/procedure/install/GenericInstall.java
@@ -1,7 +1,9 @@
package com.xiaomitool.v2.procedure.install;
+import com.xiaomitool.v2.adb.AdbCommunication;
import com.xiaomitool.v2.adb.device.Device;
import com.xiaomitool.v2.adb.device.DeviceAnswers;
+import com.xiaomitool.v2.adb.device.DeviceManager;
import com.xiaomitool.v2.adb.device.DeviceProperties;
import com.xiaomitool.v2.engine.ToolManager;
import com.xiaomitool.v2.engine.actions.ActionsDynamic;
@@ -16,6 +18,7 @@ import com.xiaomitool.v2.procedure.*;
import com.xiaomitool.v2.procedure.device.ManageDevice;
import com.xiaomitool.v2.procedure.device.RebootDevice;
import com.xiaomitool.v2.procedure.uistuff.ChooseProcedure;
+import com.xiaomitool.v2.procedure.uistuff.ConfirmationProcedure;
import com.xiaomitool.v2.rom.Installable;
import com.xiaomitool.v2.rom.chooser.InstallationRequirement;
import com.xiaomitool.v2.tasks.UpdateListener;
@@ -32,6 +35,7 @@ import javafx.scene.text.TextAlignment;
import org.apache.commons.io.FilenameUtils;
import java.util.Objects;
+import java.util.concurrent.ConcurrentLinkedQueue;
import static com.xiaomitool.v2.engine.CommonsMessages.NOOP;
@@ -117,6 +121,7 @@ public class GenericInstall {
return RNode.sequence(RebootDevice.rebootNoWaitIfConnected(), new RInstall() {
@Override
public void run(ProcedureRunner runner) throws InstallException, InterruptedException {
+ DeviceManager.stopScanThreads();
DonationPane donationPane = new DonationPane();
WindowManager.setMainContent(donationPane);
int msg = NOOP;
@@ -173,7 +178,7 @@ public class GenericInstall {
ActionsDynamic.WAIT_USB_DEBUG_ENABLE(device).run();
}
try {
- ManageDevice.waitDevice(30).setFlag(RNode.FLAG_THROWRAWEXCEPTION, true).run(runner);
+ ManageDevice.waitDevice(30, Device.Status.DEVICE).setFlag(RNode.FLAG_THROWRAWEXCEPTION, true).run(runner);
} catch (InstallException e){
Log.warn("Starting next requirement satisfaction without device active");
}
@@ -206,4 +211,32 @@ public class GenericInstall {
};
}
+ public static RInstall main(){
+ return RNode.sequence(RebootDevice.rebootNoWaitIfConnected(),
+ ChooseProcedure.chooseRomCategory(),
+ ChooseProcedure.chooseRom(),
+ RNode.sequence(ConfirmationProcedure.confirmInstallableProcedure(),
+ ConfirmationProcedure.confirmInstallationStart(),
+ RNode.sequence(ManageDevice.waitRequireAccessible(30, Device.Status.DEVICE),
+ GenericInstall.satisfyAllRequirements(),
+ GenericInstall.resourceFetchWait(),
+ GenericInstall.runInstallProcedure(),
+ GenericInstall.installationSuccess())));
+ /*RebootDevice.rebootNoWaitIfConnected().run(runner);
+ Log.debug("PRO0 CHOOSE CAT");
+ ChooseProcedure.chooseRomCategory().run(runner);
+ Log.debug("PRO0 CHOOSE ROM");
+ ChooseProcedure.chooseRom().run(runner);
+ Log.debug("PRO0 FETCH RESOURCE");
+ ConfirmationProcedure.confirmInstallableProcedure().run(runner);
+ ConfirmationProcedure.confirmInstallationStart().run(runner);
+ runner.text(LRes.WAITING_DEVICE_ACTIVE);
+ ManageDevice.waitDevice(60);
+ GenericInstall.satisfyAllRequirements().run(runner);
+ GenericInstall.resourceFetchWait().run(runner);
+ DeviceManager.refresh();
+ GenericInstall.runInstallProcedure().run(runner);
+ GenericInstall.installationSuccess().run(runner);*/
+ }
+
}
diff --git a/src/com/xiaomitool/v2/procedure/install/StockRecoveryInstall.java b/src/com/xiaomitool/v2/procedure/install/StockRecoveryInstall.java
index 7b03cb7..222d2b3 100644
--- a/src/com/xiaomitool/v2/procedure/install/StockRecoveryInstall.java
+++ b/src/com/xiaomitool/v2/procedure/install/StockRecoveryInstall.java
@@ -169,7 +169,7 @@ public class StockRecoveryInstall {
if (StrUtils.isNullOrEmpty(token)){
throw new InstallException("Empty install token", InstallException.Code.SIDELOAD_INSTALL_FAILED, false);
}
- AdbSideloadTask sideloadTask = new AdbSideloadTask(installable.getFinalFile(), token);
+ AdbSideloadTask sideloadTask = new AdbSideloadTask(installable.getFinalFile(), token, device.getSerial());
ProgressPane.DefProgressPane defProgressPane = new ProgressPane.DefProgressPane();
UpdateListener listener = defProgressPane.getUpdateListener(300);
final Pointer textInstalling = new Pointer();
@@ -221,13 +221,13 @@ public class StockRecoveryInstall {
progressPane.setContentText(LRes.MTP_INSTALLING_FILE.toString() + "\n" + LRes.DONT_REBOOT_DEVICE.toString());
WindowManager.setMainContent(progressPane, false);
LocalDateTime startTime = LocalDateTime.now();
- AdbCommunication.closeAccess();
+ AdbCommunication.getAllAccess();
try {
adbRunner.runWait(3600);
} catch (IOException e) {
throw new InstallException("Failed to start mtpinstall process: " + e.getMessage(), InstallException.Code.INTERNAL_ERROR, true);
}
- AdbCommunication.openAccess();
+ AdbCommunication.giveAllAccess();
WindowManager.removeTopContent();
Duration timeElapsed = Duration.between(startTime, LocalDateTime.now());
if (adbRunner.getExitValue() != 0) {
diff --git a/src/com/xiaomitool/v2/procedure/install/TwrpInstall.java b/src/com/xiaomitool/v2/procedure/install/TwrpInstall.java
index e2a9889..ff0064d 100644
--- a/src/com/xiaomitool/v2/procedure/install/TwrpInstall.java
+++ b/src/com/xiaomitool/v2/procedure/install/TwrpInstall.java
@@ -20,6 +20,8 @@ import com.xiaomitool.v2.utility.YesNoMaybe;
import com.xiaomitool.v2.utility.utils.StrUtils;
import javafx.application.Platform;
+import java.io.File;
+
public class TwrpInstall {
public static RInstall flashTwrp(){
//Log.printStackTrace(new Exception());
@@ -39,6 +41,8 @@ public class TwrpInstall {
throw new InstallException(new AdbException("Fastboot flash recovery failed, output:" + StrUtils.str(flashOutput)));
}
FastbootCommons.boot(device.getSerial(),installable.getFinalFile());
+ Thread.sleep(1500);
+ device.setConnected(false);
}
}, GenericInstall.updateDeviceStatus(null,true,null));
}
@@ -54,6 +58,7 @@ public class TwrpInstall {
}
});
}
+private static final String ERASE_DATA_KEY = "erase_the_data";
@ExportFunction("install_zip_viatwrp")
public static RInstall installZip(){
@@ -61,10 +66,12 @@ public class TwrpInstall {
@Override
public void run(ProcedureRunner runner) throws InstallException, RMessage, InterruptedException {
Device device = Procedures.requireDevice(runner);
- String path = "/sdcard/xmt_push";
+ String path = "/sdcard/xmt_push/";
runner.text(LRes.CREATING_DEST_DIR);
if (!AdbCommons.fileExists(path, device.getSerial())){
- AdbCommons.command("mkdir "+path, device.getSerial());
+ if (AdbCommons.adb_shellWithOr("mkdir "+path, device.getSerial(), 4) == null){
+ path = "/sdcard/";
+ }
}
runner.setContext(AdbInstall.DESTINATION_PATH, path);
}
@@ -72,6 +79,13 @@ public class TwrpInstall {
@Override
public void run(ProcedureRunner runner) throws InstallException, RMessage, InterruptedException {
Device device = Procedures.requireDevice(runner);
+ Installable installable = Procedures.requireInstallable(runner);
+ File file = installable.getFinalFile();
+ boolean eraseData = true;
+ if (file != null && file.exists()){
+ eraseData = file.length() > 300000000;
+ }
+ runner.setContext(ERASE_DATA_KEY, Boolean.valueOf(eraseData));
String outputPath = (String) runner.requireContext(AdbInstall.OUTPUT_DST_PATH);
//ProgressPane.DefProgressPane progressPane = new ProgressPane.DefProgressPane();
UpdateListener listener = new UpdateListener();
@@ -96,7 +110,22 @@ public class TwrpInstall {
}
- }, wipeDataCacheOnTwrp());
+ }, RNode.conditional(ERASE_DATA_KEY, wipeDataCacheOnTwrp(), wipeCacheOnTwrp()));
+ }
+
+ public static RInstall wipeCacheOnTwrp(){
+ return RNode.sequence(RebootDevice.requireRecovery(), new RInstall() {
+ @Override
+ public void run(ProcedureRunner runner) throws InstallException, RMessage, InterruptedException {
+ Device device = Procedures.requireDevice(runner);
+ boolean result = false;
+ result |= (AdbCommons.adb_shell("twrp wipe cache",device.getSerial(), 8) != null);
+ // result |= (AdbCommons.adb_shell("twrp wipe data", device.getSerial(), 10) != null);
+ if (!result){
+ throw new InstallException("Failed to wipe cache: twrp command failed", InstallException.Code.WIPE_FAILED, true ) ;
+ }
+ }
+ });
}
public static RInstall wipeDataCacheOnTwrp(){
diff --git a/src/com/xiaomitool/v2/procedure/uistuff/ChooseProcedure.java b/src/com/xiaomitool/v2/procedure/uistuff/ChooseProcedure.java
index 22e6b74..f83b8e3 100644
--- a/src/com/xiaomitool/v2/procedure/uistuff/ChooseProcedure.java
+++ b/src/com/xiaomitool/v2/procedure/uistuff/ChooseProcedure.java
@@ -24,7 +24,7 @@ import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
-import sun.net.ResourceManager;
+
import java.util.HashMap;
import java.util.LinkedList;
@@ -95,6 +95,7 @@ public class ChooseProcedure {
runner.setContext(IS_CHOOSEN_PROCEDURE, Boolean.TRUE);
} else {
Installable choosenIntallable = optionsInstallable.get(i);
+ Log.debug("Choosen installable: "+choosenIntallable.toLogString());
Procedures.setInstallable(runner, choosenIntallable);
Log.debug("CHOOSEN INSTALLABLE: "+choosenIntallable);
runner.setContext(IS_CHOOSEN_PROCEDURE, Boolean.FALSE);
@@ -111,7 +112,7 @@ public class ChooseProcedure {
@Override
public void run(ProcedureRunner runner) throws InstallException, RMessage, InterruptedException {
ChooserPane.Choice[] choices = new ChooserPane.Choice[]{
- new ChooserPane.Choice(LRes.CHOOSE_XIAOMI_TITLE.toString(), LRes.CHOOSE_XIAOMI_SUB.toString(), ResourcesManager.b64toImage(ResourceImages.IMG_MILOGO)),
+ new ChooserPane.Choice(LRes.CHOOSE_XIAOMI_TITLE.toString(), LRes.CHOOSE_XIAOMI_SUB.toString(), new Image(DrawableManager.getPng("milogo.png").toString())),
new ChooserPane.Choice(LRes.CHOOSE_CUSTOM_TITLE.toString(), LRes.CHOOSE_CUSTOM_SUB.toString(), new Image(DrawableManager.getPng("lineage.png").toString())),
new ChooserPane.Choice(LRes.CHOOSE_MOD_TITLE.toString(), LRes.CHOOSE_MOD_SUB.toString(), new Image(DrawableManager.getPng("magisk.png").toString())),
new ChooserPane.Choice(LRes.CHOOSE_UNLOCK_TITLE.toString(), LRes.CHOOSE_UNLOCK_SUB.toString(), new Image(DrawableManager.getPng("locker.png").toString()))
@@ -129,17 +130,19 @@ public class ChooseProcedure {
int i = chooserPane.getIdClickReceiver().waitClick();
WindowManager.removeTopContent();
RInstall toDoNext = null;
+ String noInternetMsg = LRes.NO_INTERNET_BEFORE_FETCH.toString();
+ String keySkip = "key_skip_no_int";
switch (i){
case 0:
- toDoNext = GenericFetch.fetchAllOfficial().next();
+ toDoNext = RNode.sequence(ConfirmationProcedure.suggestInternetIfMissing(noInternetMsg, keySkip), RNode.conditional(keySkip, null, GenericFetch.fetchAllOfficial().next()));
idGroup = InstallableChooser.IdGroup.officialRom;
break;
case 1:
- toDoNext = GenericFetch.fetchAllUnofficial();
+ toDoNext = RNode.sequence(ConfirmationProcedure.suggestInternetIfMissing(noInternetMsg, keySkip),RNode.conditional(keySkip, null,GenericFetch.fetchAllUnofficial()));
idGroup = InstallableChooser.IdGroup.unofficialRoms;
break;
case 2:
- toDoNext = GenericFetch.fetchAllMods();
+ toDoNext = RNode.sequence(ConfirmationProcedure.suggestInternetIfMissing(noInternetMsg, keySkip),RNode.conditional(keySkip, null,GenericFetch.fetchAllMods()));
idGroup = InstallableChooser.IdGroup.modsAndStuff;
break;
case 3:
diff --git a/src/com/xiaomitool/v2/procedure/uistuff/ConfirmationProcedure.java b/src/com/xiaomitool/v2/procedure/uistuff/ConfirmationProcedure.java
index 690ea84..8e1b721 100644
--- a/src/com/xiaomitool/v2/procedure/uistuff/ConfirmationProcedure.java
+++ b/src/com/xiaomitool/v2/procedure/uistuff/ConfirmationProcedure.java
@@ -4,24 +4,31 @@ import com.xiaomitool.v2.adb.device.Device;
import com.xiaomitool.v2.gui.WindowManager;
import com.xiaomitool.v2.gui.visual.ButtonPane;
import com.xiaomitool.v2.language.LRes;
+import com.xiaomitool.v2.logging.Log;
import com.xiaomitool.v2.procedure.*;
import com.xiaomitool.v2.procedure.install.InstallException;
import com.xiaomitool.v2.rom.Installable;
import com.xiaomitool.v2.rom.chooser.InstallationRequirement;
+import com.xiaomitool.v2.utility.utils.InetUtils;
import java.util.List;
public class ConfirmationProcedure {
+ public static final String KEY_BOOL_CONFIRM_STEPS = "bool_confirm_steps";
+
+
public static RInstall confirmInstallableProcedure(){
- return RNode.fallback(new RInstall() {
+ return RNode.sequence(new RInstall() {
@Override
public void run(ProcedureRunner runner) throws InstallException, RMessage, InterruptedException {
- Procedures.saveProcedure("confirmInstallableProcedure",this).run(runner);
+ Procedures.saveProcedure("confirmInstallableProcedure",confirmInstallableProcedure()).run(runner);
Installable installable = Procedures.requireInstallable(runner);
Device device = Procedures.requireDevice(runner);
List requirements = InstallationRequirement.getAllInstallableRequirements(installable,device);
+ runner.setContext(KEY_BOOL_CONFIRM_STEPS, Boolean.TRUE);
if (requirements.isEmpty()){
+ Log.debug("No requirements for this installation");
return;
}
StringBuilder text = new StringBuilder(LRes.CONFIRM_REQUIREMENTS_TEXT.toString(LRes.CONTINUE.toString(), LRes.CANCEL.toString()));
@@ -34,10 +41,51 @@ public class ConfirmationProcedure {
int click = buttonPane.waitClick();
WindowManager.removeTopContent();
if (click != 0){
- throw new InstallException("You should not see this text", InstallException.Code.INTERNAL_ERROR, false);
+ runner.setContext(KEY_BOOL_CONFIRM_STEPS, Boolean.FALSE);
}
}
- },RNode.sequence(ChooseProcedure.chooseRom(), Procedures.runSavedProcedure("confirmInstallableProcedure")));
+ },RNode.conditional(KEY_BOOL_CONFIRM_STEPS, null, RNode.sequence(ChooseProcedure.chooseRom(), Procedures.runSavedProcedure("confirmInstallableProcedure"))));
+ }
+
+ public static RInstall suggestInternetIfMissing(String message, String keyWasSkipped){
+ return new RInstall() {
+ @Override
+ public void run(ProcedureRunner runner) throws InstallException, RMessage, InterruptedException {
+ if(InetUtils.isInternetAvailable()){
+ runner.setContext(keyWasSkipped, Boolean.FALSE);
+ return;
+ }
+ ButtonPane buttonPane = new ButtonPane(LRes.SKIP, LRes.TRY_AGAIN);
+ buttonPane.setContentText(message);
+ WindowManager.setMainContent(buttonPane,false);
+ int click = buttonPane.waitClick();
+ WindowManager.removeTopContent();
+ if (click == 0){
+ runner.setContext(keyWasSkipped, Boolean.TRUE);
+ return;
+ }
+ this.run(runner);
+ }
+ };
+ }
+
+ private static final String KEY_BOOL_CONFIRM_INSTALL = "bool_confirm_install";
+ public static RInstall confirmInstallationStart(){
+ return RNode.sequence(new RInstall() {
+ @Override
+ public void run(ProcedureRunner runner) throws InstallException, RMessage, InterruptedException {
+ Procedures.saveProcedure("confirmInstallationStart", confirmInstallationStart()).run(runner);
+ runner.setContext(KEY_BOOL_CONFIRM_INSTALL, Boolean.TRUE);
+ ButtonPane buttonPane = new ButtonPane(LRes.CONTINUE, LRes.CANCEL);
+ buttonPane.setContentText(LRes.CONFIRM_INSTALLATION_START.toString(LRes.CONTINUE));
+ WindowManager.setMainContent(buttonPane,false);
+ int click = buttonPane.waitClick();
+ WindowManager.removeTopContent();
+ if (click != 0){
+ runner.setContext(KEY_BOOL_CONFIRM_INSTALL, Boolean.FALSE);
+ }
+ }
+ }, RNode.conditional(KEY_BOOL_CONFIRM_INSTALL, null, RNode.sequence(ChooseProcedure.chooseRom(), confirmInstallableProcedure(), Procedures.runSavedProcedure("confirmInstallationStart"))));
}
}
diff --git a/src/com/xiaomitool/v2/resources/ResourceImages.java b/src/com/xiaomitool/v2/resources/ResourceImages.java
index fb6fa43..7db6f79 100644
--- a/src/com/xiaomitool/v2/resources/ResourceImages.java
+++ b/src/com/xiaomitool/v2/resources/ResourceImages.java
@@ -2,8 +2,7 @@ package com.xiaomitool.v2.resources;
public class ResourceImages {
- public static final String IMG_MILOGO = "iVBORw0KGgoAAAANSUhEUgAAAJcAAACXCAMAAAAvQTlLAAAAe1BMVEX/ZwD/////XQD/aw7/aQf/YAD/u6H/pYv/r5T/oYT/qo7/4tj/tJb/bSX/ZAD/7OX/5t7/sp3/cjL/bx//9/T/8ez/vaj/fT//cTn/xLD/VwD/cz7/g0v/pYb/hFf/rpf/lnf/kWP/lnH/iVX/0MH/TwD/gFD/imD/18hqqZsfAAACDklEQVR4nO3c21LCMBSF4exioJxSrARoQUXFw/s/oXLTJoXqrFHMZmb9l20x38BMi02oEfGrtdHUeuVFjCw2tUtNiXL1phDjN7pUx+zGm1WdWnGmujJrfW/X10e5Sy3oaZAa0BNdWHRh0YVFFxZdWHRh0YVFFxZdWHRh0YVFFxZdWHRh0YVFFxZdWHRh0YV1va4MzNq+CZTosF+6XDkbQs3u9w+DcxN0rowOK7+f/vnJZUcCt1ysbk5l2SQ6aGz/3XWs2nY/qSzX4JLivTN/qMQlMoxhalySRzA9LpmFMEUueQsG1+SaznW6ZN+OrspVvOp0ybw571/Udbjrbf6ST5cnrrw57V/UNapdbzarHz66rkNzqrioa/L99xObDTuuj2b4lC7jss47Nr1T4fr6ltVxNWewtC5jK6WuZ50uV3qVLmOLtK46/O+mDq6DaV3usAxrL9BpXa5cRDtu2+sNXXTRRRdddNFFF1100UUXXXTRRRdddF2Paxz9tftm+Hnsemzvy8X3V9v793+6HsCVk1lQOw3lnsLtefvjYfsc7nhqX1CG2ye/XD9xnFs5v0ok2h6uMYl2hKNHL/hh1Otdn5MmurDowqILiy4surDowqILiy4surDowqILiy4surDowqILiy4surDowqILS69L6/NEtT5/VeXzarfeSKHv+b5bL0bEV7vBjZ4Gu8qLfAI+Zj0OJQ+UpAAAAABJRU5ErkJggg==";
- public static final String IMG_ANDROIDICON = "PD94bWwgdmVyc2lvbj0iMS4wIiA/PjwhRE9DVFlQRSBzdmcgIFBVQkxJQyAnLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4nICAnaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkJz48c3ZnIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDUwIDUwIiBpZD0iTGF5ZXJfMSIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIDAgNTAgNTAiIHhtbDpzcGFjZT0icHJlc2VydmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxnIGlkPSJEcm9pZCI+PHBhdGggZD0iTTE3LDE5LjV2MTNjMCwwLDAuMiwxLDEsMWgybDAsNC4xYzAsMS45LDIsMS45LDIsMS45czIsMCwyLTJ2LTRoMWgxdjRjMCwyLDIsMiwyLDIgICBzMiwwLDItMS45bDAtNC4xaDJjMC44LDAsMS0xLDEtMXYtMTNoLThIMTd6IiBmaWxsPSIjQjNDODMzIiBpZD0iYm90dG9tX2FuZF9mb290Ii8+PHBhdGggZD0iTTM2LDE5LjVjLTEuMSwwLTIsMC45LTIsMnY3YzAsMS4xLDAuOSwyLDIsMnMyLTAuOSwyLTJ2LTdDMzgsMjAuNCwzNy4xLDE5LjUsMzYsMTkuNXoiIGZpbGw9IiNCM0M4MzMiIGlkPSJSaWdodF9oYW5kIi8+PHBhdGggZD0iTTE0LDE5LjVjLTEuMSwwLTIsMC45LTIsMnY3YzAsMS4xLDAuOSwyLDIsMnMyLTAuOSwyLTJ2LTdDMTYsMjAuNCwxNS4xLDE5LjUsMTQsMTkuNXoiIGZpbGw9IiNCM0M4MzMiIGlkPSJMZWZ0X2hhbmQiLz48cGF0aCBkPSJNMzMsMTFjMC0wLjMtMC4yLTAuNS0wLjUtMC41Yy0wLjIsMC0wLjMsMC4xLTAuNCwwLjJMMzAsMTIuOGMtMS4zLTAuOC0zLTEuMy01LTEuMyAgIGMtMiwwLTMuNywwLjUtNSwxLjNsLTIuMS0yLjFjLTAuMS0wLjEtMC4yLTAuMi0wLjQtMC4yYy0wLjMsMC0wLjUsMC4yLTAuNSwwLjVjMCwwLjEsMC4xLDAuMywwLjIsMC40bDIsMi4xICAgYy0xLjQsMS4zLTIuMiwzLTIuMiw1LjFoMTZjMC0yLTAuOC0zLjgtMi4yLTUuMWwyLTIuMUMzMi45LDExLjMsMzMsMTEuMiwzMywxMXogTTIxLDE2LjVjLTAuNiwwLTEtMC40LTEtMXMwLjQtMSwxLTFzMSwwLjQsMSwxICAgUzIxLjYsMTYuNSwyMSwxNi41eiBNMjksMTYuNWMtMC42LDAtMS0wLjQtMS0xczAuNC0xLDEtMXMxLDAuNCwxLDFTMjkuNiwxNi41LDI5LDE2LjV6IiBmaWxsPSIjQjNDODMzIiBpZD0iSGVhZCIvPjwvZz48cGF0aCBkPSJNMjUsMUMxMS43LDEsMSwxMS43LDEsMjVzMTAuNywyNCwyNCwyNHMyNC0xMC43LDI0LTI0UzM4LjMsMSwyNSwxeiBNMjUsNDRDMTQuNSw0NCw2LDM1LjUsNiwyNVMxNC41LDYsMjUsNiAgczE5LDguNSwxOSwxOVMzNS41LDQ0LDI1LDQ0eiIgZmlsbD0iI0IzQzgzMyIvPjwvc3ZnPg==";
+
public static final String IMG_CHINAICON = "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";
public static final String IMG_GLOBALICON = "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";
diff --git a/src/com/xiaomitool/v2/rom/Installable.java b/src/com/xiaomitool/v2/rom/Installable.java
index 659a7ea..40cbccc 100644
--- a/src/com/xiaomitool/v2/rom/Installable.java
+++ b/src/com/xiaomitool/v2/rom/Installable.java
@@ -8,6 +8,7 @@ import com.xiaomitool.v2.utility.Choiceable;
import com.xiaomitool.v2.utility.CommandClass;
import com.xiaomitool.v2.utility.WaitSemaphore;
import com.xiaomitool.v2.utility.utils.SettingsUtils;
+import com.xiaomitool.v2.utility.utils.StrUtils;
import com.xiaomitool.v2.xiaomi.miuithings.Codebase;
import com.xiaomitool.v2.xiaomi.miuithings.MiuiVersion;
@@ -162,6 +163,11 @@ public abstract class Installable extends CommandClass implements Choiceable {
return extractInternal(listener);
}
+
+ public String toLogString() {
+ return this.getClass().getSimpleName()+" - "+StrUtils.str(this.getType())+"[url:"+StrUtils.str(this.downloadUrl)+",dl:"+StrUtils.str(downloadedFile)+",final:"+StrUtils.str(finalFile)+"]";
+ }
+
public void fetchResources(UpdateListener downloadListener, UpdateListener extractListener) {
if (fetchResourceThread != null && fetchResourceThread.isAlive()){
return;
diff --git a/src/com/xiaomitool/v2/rom/MiuiRom.java b/src/com/xiaomitool/v2/rom/MiuiRom.java
index 3a41d83..ab1febb 100644
--- a/src/com/xiaomitool/v2/rom/MiuiRom.java
+++ b/src/com/xiaomitool/v2/rom/MiuiRom.java
@@ -154,6 +154,10 @@ public abstract class MiuiRom extends Installable {
protected Specie specie;
+ public Specie getSpecie() {
+ return specie;
+ }
+
@Override
public String getDownloadUrl() {
if (downloadUrl != null && !downloadUrl.isEmpty()){
diff --git a/src/com/xiaomitool/v2/rom/MiuiTgzRom.java b/src/com/xiaomitool/v2/rom/MiuiTgzRom.java
index c5e1740..699d8df 100644
--- a/src/com/xiaomitool/v2/rom/MiuiTgzRom.java
+++ b/src/com/xiaomitool/v2/rom/MiuiTgzRom.java
@@ -5,6 +5,7 @@ import com.xiaomitool.v2.logging.Log;
import com.xiaomitool.v2.procedure.RInstall;
import com.xiaomitool.v2.procedure.install.FastbootInstall;
import com.xiaomitool.v2.tasks.*;
+import com.xiaomitool.v2.utility.utils.StrUtils;
import com.xiaomitool.v2.xiaomi.miuithings.Codebase;
import com.xiaomitool.v2.xiaomi.miuithings.MiuiVersion;
@@ -64,6 +65,8 @@ public class MiuiTgzRom extends MiuiRom {
}
+
+
@Override
protected Object extractInternal(UpdateListener listener) throws Exception {
Log.debug("Extracting tgz rom: "+filename);
diff --git a/src/com/xiaomitool/v2/rom/ZipRom.java b/src/com/xiaomitool/v2/rom/ZipRom.java
index 8b041b9..476f546 100644
--- a/src/com/xiaomitool/v2/rom/ZipRom.java
+++ b/src/com/xiaomitool/v2/rom/ZipRom.java
@@ -34,8 +34,5 @@ public abstract class ZipRom extends Installable {
return TwrpInstall.installZip();
}
- /*@Override
- public ChooserPane.Choice getChoice() {
- return null;//TODO
- }*/
+
}
diff --git a/src/com/xiaomitool/v2/rom/chooser/InstallationRequirement.java b/src/com/xiaomitool/v2/rom/chooser/InstallationRequirement.java
index c6fdd99..98e213d 100644
--- a/src/com/xiaomitool/v2/rom/chooser/InstallationRequirement.java
+++ b/src/com/xiaomitool/v2/rom/chooser/InstallationRequirement.java
@@ -13,6 +13,8 @@ import com.xiaomitool.v2.procedure.install.GenericInstall;
import com.xiaomitool.v2.procedure.install.StockRecoveryInstall;
import com.xiaomitool.v2.procedure.install.TwrpInstall;
import com.xiaomitool.v2.rom.Installable;
+import com.xiaomitool.v2.rom.MiuiRom;
+import com.xiaomitool.v2.rom.MiuiZipRom;
import com.xiaomitool.v2.utility.YesNoMaybe;
import com.xiaomitool.v2.xiaomi.miuithings.UnlockStatus;
@@ -46,7 +48,7 @@ public abstract class InstallationRequirement {
public static final InstallationRequirement STOCKRECOVERY_REACHABLE = new InstallationRequirement("stock recovery reachable", USB_DEBUG_ENABLED) {
@Override
public boolean isSatisfied(Device device) {
- return !device.getDeviceProperties().getSideloadProperties().isFailed();
+ return !YesNoMaybe.YES.equals(device.getAnswers().hasTwrpRecovery()) && device.getDeviceProperties().getSideloadProperties().isParsed();
}
@Override
@@ -182,13 +184,36 @@ public abstract class InstallationRequirement {
}
public static InstallationRequirement[] getInstallableRequirements(Installable installable, Device device){
- List requirementList = new ArrayList<>();
+ List requirementList = new LinkedList<>();
switch (installable.getType()){
case RECOVERY:
- if (installable.hasInstallToken() || installable.isOfficial() && UnlockStatus.UNLOCKED.equals(device.getAnswers().getUnlockStatus())){
- requirementList.add(STOCKRECOVERY_REACHABLE);
- } else {
+ Log.debug("Trying get install recovery requirements...");
+ Log.debug("Has iToken? "+installable.hasInstallToken());
+ Log.debug("Is official? "+installable.isOfficial());
+ Log.debug("Bootloader status: "+device.getAnswers().getUnlockStatus());
+ Log.debug("Stock recovery reachable? "+STOCKRECOVERY_REACHABLE.isSatisfied(device));
+ boolean hasStockRecovery = (!UnlockStatus.UNLOCKED.equals(device.getAnswers().getUnlockStatus()) || STOCKRECOVERY_REACHABLE.isSatisfied(device));
+ boolean isStockRecoveryInstallable = (installable.hasInstallToken() || installable.isOfficial()) && (installable instanceof MiuiZipRom);
+ boolean isUnsafeCrossRegionInstallation = false;
+ if (isStockRecoveryInstallable){
+ MiuiZipRom zipRom = (MiuiZipRom) installable;
+ MiuiRom.Specie wantToInstallSpecie = zipRom.getSpecie();
+ MiuiRom.Specie currentSpecie = device.getAnswers().getCurrentSpecie();
+ if (currentSpecie.getZone() != wantToInstallSpecie.getZone()){
+ Log.debug("Different rom zone installation, this might be an unsafe cross region installation");
+ String product = (String) device.getDeviceProperties().get(DeviceProperties.CODENAME);
+ isUnsafeCrossRegionInstallation = !DeviceGroups.isSafeToChangeRecoveryLocked(product);
+ }
+ }
+ if (!isStockRecoveryInstallable){
requirementList.add(TWRP_INSTALLED);
+ } else {
+ if (isUnsafeCrossRegionInstallation) {
+ requirementList.add(UNLOCKED_BOOTLOADER);
+ }
+ if (!hasStockRecovery) {
+ requirementList.add(STOCKRECOVERY_REACHABLE);
+ }
}
break;
case IMAGE:
diff --git a/src/com/xiaomitool/v2/tasks/AdbSideloadTask.java b/src/com/xiaomitool/v2/tasks/AdbSideloadTask.java
index 345f2fe..66269d3 100644
--- a/src/com/xiaomitool/v2/tasks/AdbSideloadTask.java
+++ b/src/com/xiaomitool/v2/tasks/AdbSideloadTask.java
@@ -1,5 +1,6 @@
package com.xiaomitool.v2.tasks;
+import com.xiaomitool.v2.adb.AdbCommunication;
import com.xiaomitool.v2.adb.AdbException;
import com.xiaomitool.v2.procedure.install.InstallException;
import com.xiaomitool.v2.process.AdbRunner;
@@ -15,15 +16,17 @@ import java.util.regex.Pattern;
public class AdbSideloadTask extends Task {
private static final Pattern PROGRESS_MATCH = Pattern.compile("\\[\\s*(\\d+)/\\s*(\\d+)\\]");
private File fileToSideload;
- private String token;
- public AdbSideloadTask(File fileToSideload, String token){
+ private String token, serial;
+ public AdbSideloadTask(File fileToSideload, String token, String serial){
this.fileToSideload = fileToSideload;
this.token = token;
+ this.serial = serial;
}
@Override
protected void startInternal() {
AdbRunner runner = new AdbRunner();
+ runner.setDeviceSerial(serial);
runner.addArgument("sideload");
if (fileToSideload == null){
this.error(new InstallException("null file to sideload", InstallException.Code.INTERNAL_ERROR, false));
@@ -55,12 +58,16 @@ public class AdbSideloadTask extends Task {
}
});
+ AdbCommunication.getAllAccess();
try {
runner.runWait();
} catch (IOException e) {
this.error(e);
return;
+ } finally {
+ AdbCommunication.giveAllAccess();
}
+
if (runner.getExitValue() != 0){
error(new InstallException(new AdbException("adb sideload exited with status: "+runner.getExitValue())));
return;
diff --git a/src/com/xiaomitool/v2/test/GuiTest.java b/src/com/xiaomitool/v2/test/GuiTest.java
index 89c8596..1fbf2f7 100644
--- a/src/com/xiaomitool/v2/test/GuiTest.java
+++ b/src/com/xiaomitool/v2/test/GuiTest.java
@@ -1,29 +1,141 @@
package com.xiaomitool.v2.test;
+import com.xiaomitool.v2.adb.device.Device;
+import com.xiaomitool.v2.engine.ToolManager;
+import com.xiaomitool.v2.engine.actions.ActionsDynamic;
import com.xiaomitool.v2.gui.GuiUtils;
import com.xiaomitool.v2.gui.WindowManager;
-import com.xiaomitool.v2.gui.visual.ChooserPane;
-import com.xiaomitool.v2.gui.visual.DragAndDropPane;
-import com.xiaomitool.v2.gui.visual.TextStackPane;
+import com.xiaomitool.v2.gui.deviceView.Animatable;
+import com.xiaomitool.v2.gui.deviceView.AnimatableDeviceView;
+import com.xiaomitool.v2.gui.deviceView.DeviceRecoveryView;
+import com.xiaomitool.v2.gui.deviceView.DeviceView;
+import com.xiaomitool.v2.gui.drawable.DrawableManager;
+import com.xiaomitool.v2.gui.visual.*;
+import com.xiaomitool.v2.logging.Log;
import com.xiaomitool.v2.resources.ResourcesConst;
import com.xiaomitool.v2.resources.ResourcesManager;
import javafx.animation.*;
import javafx.application.Application;
+import javafx.application.Platform;
+import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
+import javafx.scene.image.Image;
import javafx.scene.layout.*;
import javafx.scene.paint.*;
import javafx.scene.shape.Circle;
+import javafx.scene.text.Font;
import javafx.scene.text.Text;
+import javafx.scene.text.TextAlignment;
import javafx.stage.Stage;
import javafx.util.Duration;
+import java.net.URL;
+
public class GuiTest extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
- Pane pane = new StackPane();
+ ToolManager.init(primaryStage);
+ String[] texts = new String[]{"Prova1", "Prova2", "Prova3", "Prova4"};
+ URL[] imgs = new URL[]{DrawableManager.getPng("usbdbg1"),DrawableManager.getPng("usbdbg2"),DrawableManager.getPng("usbdbg3"),DrawableManager.getPng("usbdbg4")};
+ Image[] imgsfx = new Image[imgs.length];
+ for (int i = 0; i extends CompletableFuture {
+ public T getSilently(){
+ try {
+ return super.get();
+ } catch (Throwable e) {
+ return null;
+ }
+ }
+}
diff --git a/src/com/xiaomitool/v2/utility/utils/NumberUtils.java b/src/com/xiaomitool/v2/utility/utils/NumberUtils.java
index 9e0a986..2bc29e4 100644
--- a/src/com/xiaomitool/v2/utility/utils/NumberUtils.java
+++ b/src/com/xiaomitool/v2/utility/utils/NumberUtils.java
@@ -21,4 +21,8 @@ public class NumberUtils {
return null;
}
}
+
+ public static int double2int(double number) {
+ return Double.valueOf(number).intValue();
+ }
}
diff --git a/src/com/xiaomitool/v2/xiaomi/XiaomiUtilities.java b/src/com/xiaomitool/v2/xiaomi/XiaomiUtilities.java
index 8071d0f..cbfb1bd 100644
--- a/src/com/xiaomitool/v2/xiaomi/XiaomiUtilities.java
+++ b/src/com/xiaomitool/v2/xiaomi/XiaomiUtilities.java
@@ -1,6 +1,7 @@
package com.xiaomitool.v2.xiaomi;
+import com.xiaomitool.v2.adb.device.DeviceGroups;
import com.xiaomitool.v2.utility.NotNull;
public class XiaomiUtilities {
@@ -29,6 +30,7 @@ public class XiaomiUtilities {
}
public static String deviceToXiaomiEuName(@NotNull String codename) {
+ codename = DeviceGroups.stripCodename(codename);
if (codename.equalsIgnoreCase("aries")) {
codename = "MI2";
} else if (codename.equalsIgnoreCase("aqua")) {
@@ -145,6 +147,30 @@ public class XiaomiUtilities {
codename = "HM6";
} else if (codename.equalsIgnoreCase("sakura")) {
codename = "HM6Pro";
+ } else if (codename.equalsIgnoreCase("ursa")) {
+ codename = "MI8Explorer";
+ } else if (codename.equalsIgnoreCase("beryllium")) {
+ codename = "POCOF1";
+ } else if (codename.equalsIgnoreCase("clover")) {
+ codename = "MIPAD4";
+ } else if (codename.equalsIgnoreCase("perseus")) {
+ codename = "MIMix3";
+ } else if (codename.equalsIgnoreCase("platina")) {
+ codename = "MI8LITE";
+ } else if (codename.equalsIgnoreCase("tulip")) {
+ codename = "HMNote6Pro";
+ } else if (codename.equalsIgnoreCase("equuleus")) {
+ codename = "MI8Pro";
+ } else if (codename.equalsIgnoreCase("lavender")) {
+ codename = "HMNote7";
+ } else if (codename.equalsIgnoreCase("cepheus")) {
+ codename = "MI9";
+ } else if (codename.equalsIgnoreCase("grus")) {
+ codename = "MI9SE";
+ } else if (codename.equalsIgnoreCase("onclite")) {
+ codename = "HM7";
+ } else if (codename.equalsIgnoreCase("violet")) {
+ codename = "HMNote7Pro";
}
return codename;
}
diff --git a/src/com/xiaomitool/v2/xiaomi/miuithings/Branch.java b/src/com/xiaomitool/v2/xiaomi/miuithings/Branch.java
index 5f14505..ed169e7 100644
--- a/src/com/xiaomitool/v2/xiaomi/miuithings/Branch.java
+++ b/src/com/xiaomitool/v2/xiaomi/miuithings/Branch.java
@@ -14,6 +14,10 @@ public enum Branch {
this.code = code;
}
+ public static boolean isDev(Branch branch) {
+ return !Branch.STABLE.equals(branch);
+ }
+
public void setCode(String code) {
this.code = code;
}
diff --git a/src/com/xiaomitool/v2/xiaomi/miuithings/DefaultRequestParams.java b/src/com/xiaomitool/v2/xiaomi/miuithings/DefaultRequestParams.java
index ac25588..963e7ad 100644
--- a/src/com/xiaomitool/v2/xiaomi/miuithings/DefaultRequestParams.java
+++ b/src/com/xiaomitool/v2/xiaomi/miuithings/DefaultRequestParams.java
@@ -1,5 +1,7 @@
package com.xiaomitool.v2.xiaomi.miuithings;
+import com.xiaomitool.v2.adb.device.Device;
+import com.xiaomitool.v2.adb.device.DeviceGroups;
import com.xiaomitool.v2.logging.Log;
import com.xiaomitool.v2.rom.MiuiRom;
import com.xiaomitool.v2.utility.Nullable;
@@ -51,7 +53,7 @@ public class DefaultRequestParams extends RequestParams implements Cloneable {
map.put("v","MIUI-"+version.toString());
map.put("bv",version.getBigVersion());
map.put("c",codebase.toString());
- map.put("d",device+specie.getSuffix());
+ map.put("d",device+((DeviceGroups.hasEEARegion(device) && isInternational() && !Branch.isDev(branch)) ? "_eea" : "")+specie.getSuffix()); //TODO fix this mess with specie
map.put("g",androidHash);
if (!isInternational()){
map.put("i",imeiHash);
diff --git a/src/com/xiaomitool/v2/xiaomi/miuithings/MiuiVersion.java b/src/com/xiaomitool/v2/xiaomi/miuithings/MiuiVersion.java
index f401c56..8a7a764 100644
--- a/src/com/xiaomitool/v2/xiaomi/miuithings/MiuiVersion.java
+++ b/src/com/xiaomitool/v2/xiaomi/miuithings/MiuiVersion.java
@@ -8,6 +8,17 @@ import java.util.List;
import static com.xiaomitool.v2.xiaomi.miuithings.MiuiVersion.CompareStatus.NEWER;
public class MiuiVersion extends KeepOriginClass {
+
+ public static MiuiVersion fromObject(Object miuiVersion){
+ if (miuiVersion instanceof String){
+ return new MiuiVersion((String) miuiVersion);
+ } else if (miuiVersion instanceof MiuiVersion){
+ return (MiuiVersion) miuiVersion;
+ } else {
+ return null;
+ }
+ }
+
private Branch branch;
private List numbers;
private int bigversion;
diff --git a/src/com/xiaomitool/v2/xiaomi/romota/MiuiRomOta.java b/src/com/xiaomitool/v2/xiaomi/romota/MiuiRomOta.java
index c476c3d..35eb509 100644
--- a/src/com/xiaomitool/v2/xiaomi/romota/MiuiRomOta.java
+++ b/src/com/xiaomitool/v2/xiaomi/romota/MiuiRomOta.java
@@ -1,5 +1,6 @@
package com.xiaomitool.v2.xiaomi.romota;
+import com.xiaomitool.v2.adb.device.DeviceGroups;
import com.xiaomitool.v2.crypto.Hash;
import com.xiaomitool.v2.inet.CustomHttpException;
import com.xiaomitool.v2.inet.EasyHttp;
@@ -239,6 +240,10 @@ public class MiuiRomOta {
String region = params.isInternational() ? "global" : "cn";
String n = params.getCarrier();
String lang = params.getLanguage();
+ if (DeviceGroups.hasEEARegion(device) && params.isInternational() && !Branch.isDev(branch)){
+ region = "eea"; //TODO should be in specie
+ device = device.replace("_global","_eea_global");
+ }
String url = String.format("http://update.miui.com/updates/miota-fullrom.php?d=%s&b=%s&r=%s&n=%s&l=%s", device, branch.getCode(), region, n, lang);
String result;
result = EasyHttp.get(url).getBody();
@@ -275,7 +280,9 @@ public class MiuiRomOta {
Branch b = params.getBranch();
b = b == null ? Branch.STABLE : b.getDual();
String branch = Branch.STABLE.equals(b) ? "1" : "0";
-
+ if (DeviceGroups.hasEEARegion(device) && params.isInternational() && !Branch.isDev(b)){
+ device = device.replace("_global","_eea_global"); //TODO should be in specie
+ }
String url = "https://update.miui.com/updates/v1/latestverinfo.php";
EasyResponse response;
@@ -306,7 +313,12 @@ public class MiuiRomOta {
public static MiuiTgzRom latestFastboot2_request(RequestParams params) throws XiaomiProcedureException, CustomHttpException {
String device = params.getModDevice();
Branch branch = params.getBranch();
+
String region = params.isInternational() ? "global" : "cn";
+ if (DeviceGroups.hasEEARegion(device) && params.isInternational() && !Branch.isDev(branch)){
+ region = "eea"; //TODO should be in specie
+ device = device.replace("_global","_eea_global");
+ }
String n = params.getCarrier();
String url = String.format("https://update.miui.com/updates/v1/fullromdownload.php?d=%s&b=%s&r=%s&n=%s", device, branch.getCode(), region, n), dl;
diff --git a/src/com/xiaomitool/v2/xiaomi/unlock/UnlockCommonRequests.java b/src/com/xiaomitool/v2/xiaomi/unlock/UnlockCommonRequests.java
index 4906fa8..6306c6b 100644
--- a/src/com/xiaomitool/v2/xiaomi/unlock/UnlockCommonRequests.java
+++ b/src/com/xiaomitool/v2/xiaomi/unlock/UnlockCommonRequests.java
@@ -1,6 +1,8 @@
package com.xiaomitool.v2.xiaomi.unlock;
import com.xiaomitool.v2.inet.CustomHttpException;
+import com.xiaomitool.v2.language.LRes;
+import com.xiaomitool.v2.logging.Log;
import com.xiaomitool.v2.utility.utils.StrUtils;
import com.xiaomitool.v2.xiaomi.XiaomiKeystore;
import com.xiaomitool.v2.xiaomi.XiaomiProcedureException;
@@ -10,8 +12,58 @@ import java.util.Base64;
import java.util.HashMap;
public class UnlockCommonRequests {
+ private static final HashMap UNLOCK_CODE_MEANING = buildUnlockCodeMeaning();
+ public static String getUnlockCodeMeaning(int code, JSONObject object){
+ LRes languageResource = UNLOCK_CODE_MEANING.get(code);
+ if (languageResource == null){
+ return LRes.UNL_UNKNOWN_ERROR.toString(code);
+ }
+ String toReturn;
+ if (code == 20036){
+ int hours = -1;{
+ try {
+ hours = object.getJSONObject("data").getInt("waitHour");
+ } catch (Throwable t){
+ Log.error("Failed to get waitHour for unlock");
+ }
+ if (hours >= 0){
+ int days = hours/24;
+ int leftHours = hours%24;
+ toReturn = languageResource.toString(days, leftHours);
+ } else {
+ toReturn = LRes.UNL_ERR_20036_NOHOURS.toString();
+ }
+ }
+ } else {
+ toReturn = languageResource.toString();
+ }
+ return toReturn;
+ }
+
+ private static HashMap buildUnlockCodeMeaning(){
+ HashMap map = new HashMap<>();
+ map.put(10000, LRes.UNL_ERR_10000);
+ map.put(10001, LRes.UNL_ERR_10001);
+ map.put(10002, LRes.UNL_ERR_10002);
+ map.put(10003, LRes.UNL_ERR_10003);
+ map.put(10004, LRes.UNL_ERR_10004);
+ map.put(10005, LRes.UNL_ERR_10005);
+ map.put(10006, LRes.UNL_ERR_10006);
+ map.put(20030, LRes.UNL_ERR_20030);
+ map.put(20031, LRes.UNL_ERR_20031);
+ map.put(20032, LRes.UNL_ERR_20032);
+ map.put(20033, LRes.UNL_ERR_20033);
+ map.put(20034, LRes.UNL_ERR_20034);
+ map.put(20035, LRes.UNL_ERR_20035);
+ map.put(20036, LRes.UNL_ERR_20036);
+ map.put(20037, LRes.UNL_ERR_20037);
+ map.put(20041, LRes.UNL_ERR_20037);
+ return map;
+ }
+
+
private static final String SID = "miui_unlocktool_client";
- private static final String CLIENT_VERSION = "3.3.525.23";
+ private static final String CLIENT_VERSION = "3.3.827.31";
private static final String NONCEV2 = "/api/v2/nonce";
private static final String USERINFOV3 = "/api/v3/unlock/userinfo";
private static final String DEVICECLEARV3 = "/api/v2/unlock/device/clear";
diff --git a/src_rel2/META-INF/MANIFEST.MF b/src_rel2/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..fc81cfc
--- /dev/null
+++ b/src_rel2/META-INF/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Main-Class: com.xiaomitool.v2.gui.MainWindow
+