diff --git a/app/build.gradle b/app/build.gradle index 1c16820..a75dc13 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,8 +12,8 @@ android { applicationId "com.vectras.vm" minSdk minApi targetSdk targetApi - versionCode 95 - versionName "3.9.1" + versionCode 96 + versionName "3.9.2" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" multiDexEnabled true diff --git a/app/src/main/assets/roms/3dfx-wrappers.iso b/app/src/main/assets/roms/3dfx-wrappers.iso new file mode 100644 index 0000000..7595a48 Binary files /dev/null and b/app/src/main/assets/roms/3dfx-wrappers.iso differ diff --git a/app/src/main/java/com/anbui/elephant/retrofit2utils/ApiService.java b/app/src/main/java/com/anbui/elephant/retrofit2utils/ApiService.java index b0d8f21..efe1611 100644 --- a/app/src/main/java/com/anbui/elephant/retrofit2utils/ApiService.java +++ b/app/src/main/java/com/anbui/elephant/retrofit2utils/ApiService.java @@ -6,12 +6,17 @@ import retrofit2.Call; import retrofit2.http.Body; import retrofit2.http.GET; import retrofit2.http.POST; +import retrofit2.http.Streaming; import retrofit2.http.Url; public interface ApiService { @GET Call getRawJson(@Url String url); + @Streaming + @GET + Call downloadFile(@Url String url); + @POST Call post(@Url String url, @Body RequestBody body); } diff --git a/app/src/main/java/com/anbui/elephant/retrofit2utils/Retrofit2Utils.java b/app/src/main/java/com/anbui/elephant/retrofit2utils/Retrofit2Utils.java index d5ee1ab..41cc431 100644 --- a/app/src/main/java/com/anbui/elephant/retrofit2utils/Retrofit2Utils.java +++ b/app/src/main/java/com/anbui/elephant/retrofit2utils/Retrofit2Utils.java @@ -2,6 +2,9 @@ package com.anbui.elephant.retrofit2utils; import androidx.annotation.NonNull; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; import java.net.SocketTimeoutException; import java.util.concurrent.TimeUnit; @@ -85,4 +88,58 @@ public class Retrofit2Utils { } }); } + + public interface DownloadCallback { + void onProgress(int percent); + void onResult(boolean success, String path, Throwable error); + } + + public static void download(String url, String outputPath, DownloadCallback callback) { + api.downloadFile(url).enqueue(new Callback<>() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if (!response.isSuccessful() || response.body() == null) { + callback.onResult(false, null, new Exception("Response error")); + return; + } + + new Thread(() -> { + try (InputStream in = response.body().byteStream(); + OutputStream out = new FileOutputStream(outputPath)) { + + byte[] buffer = new byte[8192]; + long total = response.body().contentLength(); + long downloaded = 0; + + int read; + int lastPercent = 0; + + while ((read = in.read(buffer)) != -1) { + out.write(buffer, 0, read); + downloaded += read; + + if (total > 0) { + int percent = (int) (downloaded * 100 / total); + if (percent != lastPercent) { + lastPercent = percent; + callback.onProgress(percent); + } + } + } + + out.flush(); + callback.onResult(true, outputPath, null); + + } catch (Exception e) { + callback.onResult(false, null, e); + } + }).start(); + } + + @Override + public void onFailure(Call call, Throwable t) { + callback.onResult(false, null, t); + } + }); + } } diff --git a/app/src/main/java/com/vectras/vm/AppConfig.java b/app/src/main/java/com/vectras/vm/AppConfig.java index 3f5e250..4d089bc 100644 --- a/app/src/main/java/com/vectras/vm/AppConfig.java +++ b/app/src/main/java/com/vectras/vm/AppConfig.java @@ -117,4 +117,7 @@ public class AppConfig { public static String patreonLink = "https://www.patreon.com/VectrasTeam"; + public static String virtIOWinUrl = "https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.285-1/virtio-win.iso"; + public static String virtIOWinUrlMd5 = "9e650d0e7c6e017a91ca299c8f7ed766"; + } diff --git a/app/src/main/java/com/vectras/vm/ExportRomActivity.java b/app/src/main/java/com/vectras/vm/ExportRomActivity.java index 8ff74e8..0bde3c3 100644 --- a/app/src/main/java/com/vectras/vm/ExportRomActivity.java +++ b/app/src/main/java/com/vectras/vm/ExportRomActivity.java @@ -141,6 +141,7 @@ public class ExportRomActivity extends AppCompatActivity { vmConfigMap.put("bootFrom", current.bootFrom); vmConfigMap.put("isShowBootMenu", current.isShowBootMenu); + vmConfigMap.put("isUseUefi", current.isUseUefi); vmConfigMap.put("qemu", current.itemExtra.replace(getRomPath, "OhnoIjustrealizeditsmidnightandIstillhavetodothis")); vmConfigMap.put("arch", current.itemArch); diff --git a/app/src/main/java/com/vectras/vm/SplashActivity.java b/app/src/main/java/com/vectras/vm/SplashActivity.java index 8996df4..d035761 100644 --- a/app/src/main/java/com/vectras/vm/SplashActivity.java +++ b/app/src/main/java/com/vectras/vm/SplashActivity.java @@ -102,7 +102,7 @@ public class SplashActivity extends AppCompatActivity { Log.e(TAG, "Create roms-data.json file failed: ", e); } - FileInstaller.installFiles(this, true); + //FileInstaller.installFiles(this, true); } private void setupFolders() { diff --git a/app/src/main/java/com/vectras/vm/StartVM.java b/app/src/main/java/com/vectras/vm/StartVM.java index ce112a7..0499bb0 100644 --- a/app/src/main/java/com/vectras/vm/StartVM.java +++ b/app/src/main/java/com/vectras/vm/StartVM.java @@ -9,6 +9,7 @@ import com.vectras.qemu.MainSettingsManager; import com.vectras.qemu.utils.RamInfo; import com.vectras.vm.creator.VMCreatorSelector; import com.vectras.vm.main.vms.DataMainRoms; +import com.vectras.vm.setupwizard.SetupFeatureCore; import com.vectras.vm.utils.FileUtils; import com.vectras.vm.utils.TextUtils; @@ -21,6 +22,7 @@ public class StartVM { public static String cache; public static String cdrompath; + private static boolean isUseUefi; public static String env(Activity activity, DataMainRoms vmData) { if (VMManager.isNeedLoadMigrate() && FileUtils.isFileExists(AppConfig.vmFolder + Config.vmID + "/snapshot.sh")) { @@ -49,6 +51,7 @@ public class StartVM { extraParams = bootParams + extraParams; cdrompath = vmData.imgCdrom; + isUseUefi = vmData.isUseUefi; return env(activity, extraParams, vmData.itemPath, false); } @@ -212,7 +215,7 @@ public class StartVM { bios += "file=" + AppConfig.basefiledir + "QEMU_EFI.img,format=raw,readonly=on,if=pflash"; bios += " -drive "; bios += "file=" + AppConfig.basefiledir + "QEMU_VARS.img,format=raw,if=pflash"; - } else if (MainSettingsManager.getArch(activity).equals("X86_64") && MainSettingsManager.getuseUEFI(activity)) { + } else if (MainSettingsManager.getArch(activity).equals("X86_64") && (MainSettingsManager.getuseUEFI(activity) || isUseUefi)) { bios = "-drive "; bios += "file=" + AppConfig.basefiledir + "RELEASEX64_OVMF.fd,format=raw,readonly=on,if=pflash"; bios += " -drive "; @@ -221,6 +224,8 @@ public class StartVM { bios = "-bios "; bios += AppConfig.basefiledir + "bios-vectras.bin"; } + + extractFirmware(activity); } String machine = "-M "; @@ -279,6 +284,8 @@ public class StartVM { params.add("defer"); } + isUseUefi = false; + return String.join(" ", params); } @@ -355,4 +362,28 @@ public class StartVM { .trim(); } + public static void extractFirmware(Context context) { + if (MainSettingsManager.useDefaultBios(context)) { + String arch = MainSettingsManager.getArch(context); + + FileUtils.createDirectory(AppConfig.basefiledir); + + if (arch.equals("ARM64")) { + if (!FileUtils.isFileExists(AppConfig.basefiledir + "QEMU_EFI.img")) + SetupFeatureCore.copyAssetToFile(context, "roms/QEMU_EFI.img", AppConfig.basefiledir + "QEMU_EFI.img"); + + if (!FileUtils.isFileExists(AppConfig.basefiledir + "QEMU_VARS.img")) + SetupFeatureCore.copyAssetToFile(context, "roms/QEMU_VARS.img", AppConfig.basefiledir + "QEMU_VARS.img"); + } else if (arch.equals("X86_64") && (MainSettingsManager.getuseUEFI(context) || isUseUefi)) { + if (!FileUtils.isFileExists(AppConfig.basefiledir + "RELEASEX64_OVMF.fd")) + SetupFeatureCore.copyAssetToFile(context, "roms/RELEASEX64_OVMF.fd", AppConfig.basefiledir + "RELEASEX64_OVMF.fd"); + + if (!FileUtils.isFileExists(AppConfig.basefiledir + "RELEASEX64_OVMF_VARS.fd")) + SetupFeatureCore.copyAssetToFile(context, "roms/RELEASEX64_OVMF_VARS.fd", AppConfig.basefiledir + "RELEASEX64_OVMF_VARS.fd"); + } else { + if (!FileUtils.isFileExists(AppConfig.basefiledir + "bios-vectras.bin")) + SetupFeatureCore.copyAssetToFile(context, "roms/bios-vectras.bin", AppConfig.basefiledir + "bios-vectras.bin"); + } + } + } } diff --git a/app/src/main/java/com/vectras/vm/VMManager.java b/app/src/main/java/com/vectras/vm/VMManager.java index caca7db..22893de 100644 --- a/app/src/main/java/com/vectras/vm/VMManager.java +++ b/app/src/main/java/com/vectras/vm/VMManager.java @@ -8,6 +8,7 @@ import static java.lang.Thread.sleep; import android.androidVNC.ConnectionBean; import android.androidVNC.VncCanvasActivity; import android.app.Activity; +import android.app.NotificationManager; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -25,7 +26,10 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; +import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationManagerCompat; +import com.anbui.elephant.retrofit2utils.Retrofit2Utils; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.textfield.TextInputLayout; import com.google.gson.Gson; @@ -45,9 +49,11 @@ import com.vectras.vm.main.core.MainStartVM; import com.vectras.vm.main.vms.DataMainRoms; import com.vectras.vm.settings.VNCSettingsActivity; import com.vectras.vm.settings.X11DisplaySettingsActivity; +import com.vectras.vm.setupwizard.SetupFeatureCore; import com.vectras.vm.utils.DialogUtils; import com.vectras.vm.utils.FileUtils; import com.vectras.vm.utils.JSONUtils; +import com.vectras.vm.utils.NotificationUtils; import com.vectras.vm.utils.ProgressDialog; import com.vectras.vm.utils.TextUtils; import com.vectras.vterm.Terminal; @@ -227,6 +233,7 @@ public class VMManager { public static boolean hideVM(String vmId) { return FileUtils.rename(AppConfig.vmFolder + vmId, "_" + vmId); } + public static boolean unHideVM(String vmPath) { return FileUtils.rename(vmPath, new File(vmPath).getName().replace("_", "")); } @@ -305,7 +312,8 @@ public class VMManager { _activity.runOnUiThread(() -> new Handler(Looper.getMainLooper()).postDelayed(() -> { progressDialog.reset(); MainActivity.refeshVMListNow(); - if (!result) DialogUtils.oopsDialog(_activity, _activity.getString(R.string.an_error_occurred_while_deleting_the_vm)); + if (!result) + DialogUtils.oopsDialog(_activity, _activity.getString(R.string.an_error_occurred_while_deleting_the_vm)); }, 500)); }).start(); }, @@ -336,7 +344,8 @@ public class VMManager { if (isVmFilesInUse(vmId, vmList)) { isKeptSomeFiles = true; isCompleted = hideVM(vmId); - if (isCompleted) vmList = vmList.replace(AppConfig.vmFolder + vmId, AppConfig.vmFolder + "_" + vmId); + if (isCompleted) + vmList = vmList.replace(AppConfig.vmFolder + vmId, AppConfig.vmFolder + "_" + vmId); } else { isCompleted = FileUtils.delete(new File(AppConfig.vmFolder + vmId)); } @@ -893,6 +902,11 @@ public class VMManager { public static void showChangeRemovableDevicesDialog(Activity _activity, VncCanvasActivity vncCanvasActivity) { new Thread(() -> { + if (FileUtils.isFileExists(AppConfig.vmFolder + Config.vmID + "/snapshot.sh")) { + String snapshotParams = FileUtils.readAFile(AppConfig.vmFolder + Config.vmID + "/snapshot.sh"); + if (!snapshotParams.isEmpty()) lastQemuCommand = snapshotParams; + } + String allDevice = getAllDevicesInQemu(); _activity.runOnUiThread(() -> { @@ -901,7 +915,7 @@ public class VMManager { .setView(_view) .create(); - _view.findViewById(R.id.ln_pause).setOnClickListener( v -> { + _view.findViewById(R.id.ln_pause).setOnClickListener(v -> { DialogUtils.twoDialog( _activity, _activity.getString(R.string.pause), @@ -978,8 +992,31 @@ public class VMManager { ejectCDROM(_activity); _dialog.dismiss(); }); + + if (!allDevice.contains(AppConfig.basefiledir + "3dfx-wrappers.iso")) _view.findViewById(R.id.iv_eject3dfx).setVisibility(View.GONE); + + _view.findViewById(R.id.ln_3dfx).setOnClickListener(v -> { + if (allDevice.contains(AppConfig.basefiledir + "3dfx-wrappers.iso")) { + ejectCDROM(_activity); + } else { + mount3dfxWrappersTool(_activity); + } + _dialog.dismiss(); + }); + + if (!allDevice.contains(AppConfig.basefiledir + "virtio-win.iso")) _view.findViewById(R.id.iv_ejectvirtio).setVisibility(View.GONE); + + _view.findViewById(R.id.ln_virtio).setOnClickListener(v -> { + if (allDevice.contains(AppConfig.basefiledir + "virtio-win.iso")) { + ejectCDROM(_activity); + } else { + mountVirtIOWinTool(_activity); + } + _dialog.dismiss(); + }); } else { _view.findViewById(R.id.ln_cdrom).setVisibility(View.GONE); + _view.findViewById(R.id.ln_tools).setVisibility(View.GONE); } if (allDevice.contains("floppy0")) { @@ -1163,6 +1200,116 @@ public class VMManager { }, 200); } + public static void mount3dfxWrappersTool(Activity activity) { + new Thread(() -> { + if (!FileUtils.isFileExists(AppConfig.basefiledir + "3dfx-wrappers.iso")) + SetupFeatureCore.copyAssetToFile(activity, "roms/3dfx-wrappers.iso", AppConfig.basefiledir + "3dfx-wrappers.iso"); + + activity.runOnUiThread(() -> changeCDROM(AppConfig.basefiledir + "3dfx-wrappers.iso", activity)); + }).start(); + } + + public static void mountVirtIOWinTool(Activity activity) { + new Thread(() -> { + if (!FileUtils.isFileExists(AppConfig.basefiledir + "virtio-win.iso")) { + FileUtils.delete(new File(AppConfig.basefiledir + "virtio-win.bin")); + activity.runOnUiThread(() -> DialogUtils.twoDialog( + activity, + activity.getString(R.string.download_required), + activity.getString(R.string.this_tool_needs_to_be_downloaded_before_use), + activity.getString(R.string.ok), + activity.getString(R.string.cancel), + true, + R.drawable.arrow_downward_24px, + true, + () -> new Thread(() -> { + + int notificationId = 30; + + if (!NotificationUtils.isChannelExist(NotificationUtils.downloadChannelId, activity)) { + NotificationUtils.createChannel("Download", "View the file download process.", + NotificationUtils.downloadChannelId, NotificationManager.IMPORTANCE_DEFAULT, activity); + } + + NotificationManagerCompat manager = NotificationManagerCompat.from(VectrasApp.getContext()); + NotificationCompat.Builder builder = new NotificationCompat.Builder(VectrasApp.getContext(), NotificationUtils.downloadChannelId) + .setSmallIcon(R.drawable.arrow_cool_down_24px) + .setContentTitle(activity.getString(R.string.virtio_tools_for_windows)) + .setContentText("0%") + .setPriority(NotificationCompat.PRIORITY_LOW) + .setOngoing(true) + .setOnlyAlertOnce(true); + + builder.setProgress(100, 0, false); + manager.notify(notificationId, builder.build()); + + Retrofit2Utils.download(AppConfig.virtIOWinUrl, AppConfig.basefiledir + "virtio-win.bin", new Retrofit2Utils.DownloadCallback() { + @Override + public void onProgress(int percent) { + builder.setProgress(100, percent, false) + .setContentText(percent + "%"); + manager.notify(notificationId, builder.build()); + Log.d("DL", percent + "%"); + } + + @Override + public void onResult(boolean success, String path, Throwable error) { + if (success) { + FileUtils.move(AppConfig.basefiledir + "virtio-win.bin", AppConfig.basefiledir + "virtio-win.iso"); + + if (DialogUtils.isAllowShow(activity)) { + NotificationUtils.recall(activity, notificationId); + + activity.runOnUiThread(() -> DialogUtils.twoDialog( + activity, + activity.getString(R.string.virtio_tools_for_windows_is_now_ready_to_use), + activity.getString(R.string.do_you_want_to_insert_it_into_the_optical_drive_right_now), + activity.getString(R.string.ok), + activity.getString(R.string.cancel), + true, + R.drawable.check_24px, + true, + () -> changeCDROM(AppConfig.basefiledir + "virtio-win.iso", activity), + null, + null) + ); + } else { + Context context = VectrasApp.getContext(); + + builder.setProgress(0, 0, false) + .setSmallIcon(R.drawable.check_24px) + .setContentText(context.getString(R.string.virtio_tools_for_windows_is_now_ready_to_use)) + .setOngoing(false); + + manager.notify(notificationId, builder.build()); + } + } else { + FileUtils.delete(new File(AppConfig.basefiledir + "virtio-win.bin")); + + if (DialogUtils.isAllowShow(activity)) { + activity.runOnUiThread(() -> DialogUtils.oopsDialog(activity, activity.getString(R.string.download_failed_note))); + } else { + Context context = VectrasApp.getContext(); + + builder.setProgress(0, 0, false) + .setSmallIcon(R.drawable.error_96px) + .setContentText(context.getString(R.string.download_failed_note)) + .setOngoing(false); + + manager.notify(notificationId, builder.build()); + } + } + } + }); + }).start(), + null, + null)); + } else { + changeCDROM(AppConfig.basefiledir + "virtio-win.iso", activity); + } + }).start(); + } + public static void changeCDROM(String _path, Activity _activity) { new Thread(() -> { if (isUsingQ35(lastQemuCommand)) { diff --git a/app/src/main/java/com/vectras/vm/creator/VMCreatorActivity.java b/app/src/main/java/com/vectras/vm/creator/VMCreatorActivity.java index 4299bcb..b92efba 100644 --- a/app/src/main/java/com/vectras/vm/creator/VMCreatorActivity.java +++ b/app/src/main/java/com/vectras/vm/creator/VMCreatorActivity.java @@ -23,6 +23,7 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.textfield.TextInputLayout; import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; import com.vectras.qemu.MainSettingsManager; import com.vectras.vm.AppConfig; import com.vectras.vm.Fragment.CreateImageDialogFragment; @@ -73,6 +74,7 @@ public class VMCreatorActivity extends AppCompatActivity { private String thumbnailPath = ""; private String vmID = VMManager.idGenerator(); private boolean isShowBootMenu = false; + private boolean isUseUefi = false; private int bootFrom = 0; @Override @@ -238,12 +240,14 @@ public class VMCreatorActivity extends AppCompatActivity { binding.lineardisclaimer.setOnClickListener(v -> DialogUtils.oneDialog(this, getResources().getString(R.string.dont_miss_out), getResources().getString(R.string.disclaimer_when_using_rom), getResources().getString(R.string.i_agree), true, R.drawable.verified_user_24px, true, null, null)); - binding.lnShowbootmenu.setOnClickListener(v -> binding.cbShowbootmenu.toggle()); - binding.cbShowbootmenu.setOnCheckedChangeListener((button, isChecked) -> isShowBootMenu = isChecked); + binding.cbvShowbootmenu.setOnCheckedChangeListener((v, isChecked) -> isShowBootMenu = isChecked); - binding.lnBootfrom.setOnClickListener(v -> VMCreatorSelector.bootFrom(this, bootFrom, ((position, name, value) -> { + binding.cbvUseuefi.setOnCheckedChangeListener((v, isChecked) -> isUseUefi = isChecked); + if (!MainSettingsManager.getArch(this).equals("X86_64")) binding.cbvUseuefi.setVisibility(View.GONE); + + binding.sbvBootfrom.setOnClickListener(v -> VMCreatorSelector.bootFrom(this, bootFrom, ((position, name, value) -> { bootFrom = position; - binding.tvBootfrom.setText(name); + binding.sbvBootfrom.setSubtitle(name); }))); modify = getIntent().getBooleanExtra("MODIFY", false); @@ -564,9 +568,16 @@ public class VMCreatorActivity extends AppCompatActivity { } bootFrom = current.bootFrom; - binding.tvBootfrom.setText(Objects.requireNonNull(VMCreatorSelector.getBootFrom(this, current.bootFrom).get("name")).toString()); + binding.sbvBootfrom.setSubtitle(Objects.requireNonNull(VMCreatorSelector.getBootFrom(this, current.bootFrom).get("name")).toString()); isShowBootMenu = current.isShowBootMenu; - binding.cbShowbootmenu.setChecked(isShowBootMenu); + binding.cbvShowbootmenu.setChecked(isShowBootMenu); + + if (MainSettingsManager.getArch(this).equals("X86_64")) { + isUseUefi = current.isUseUefi; + binding.cbvUseuefi.setChecked(isUseUefi); + } else { + binding.cbvUseuefi.setVisibility(View.GONE); + } } } @@ -698,6 +709,7 @@ public class VMCreatorActivity extends AppCompatActivity { vmConfigMap.put("imgArch", MainSettingsManager.getArch(this)); vmConfigMap.put("bootFrom", bootFrom); vmConfigMap.put("isShowBootMenu", isShowBootMenu); + vmConfigMap.put("isUseUefi", isUseUefi); vmConfigMap.put("vmID", vmID); vmConfigMap.put("qmpPort", 8080); return vmConfigMap; @@ -995,7 +1007,13 @@ public class VMCreatorActivity extends AppCompatActivity { return; } - loadConfig(new Gson().fromJson(FileUtils.readFromFile(this, new File(AppConfig.vmFolder + vmID + "/rom-data.json")), DataMainRoms.class)); + try { + loadConfig(new Gson().fromJson(FileUtils.readFromFile(this, new File(AppConfig.vmFolder + vmID + "/rom-data.json")), DataMainRoms.class)); + } catch (JsonSyntaxException e) { + DialogUtils.oneDialog(this, getResources().getString(R.string.oops), getResources().getString(R.string.error_CR_CVBI4), getResources().getString(R.string.ok), true, R.drawable.warning_48px, true, null, null); + return; + } + JSONObject jObj = new JSONObject(FileUtils.readFromFile(this, new File(AppConfig.vmFolder + vmID + "/rom-data.json"))); if (jObj.has("vmID")) { diff --git a/app/src/main/java/com/vectras/vm/main/core/PendingCommand.java b/app/src/main/java/com/vectras/vm/main/core/PendingCommand.java index e3c5c03..2833128 100644 --- a/app/src/main/java/com/vectras/vm/main/core/PendingCommand.java +++ b/app/src/main/java/com/vectras/vm/main/core/PendingCommand.java @@ -48,9 +48,13 @@ public class PendingCommand { } else { com.vectras.vm.StartVM.cdrompath = ""; Config.vmID = VMManager.idGenerator(); - String env = StartVM.env(activity, AppConfig.pendingCommand, "", true); - MainStartVM.startNow(activity, "Quick run", env, Config.vmID, null); - VMManager.lastQemuCommand = AppConfig.pendingCommand; + new Thread(() -> { + String env = StartVM.env(activity, AppConfig.pendingCommand, "", true); + activity.runOnUiThread(() -> { + MainStartVM.startNow(activity, "Quick run", env, Config.vmID, null); + VMManager.lastQemuCommand = AppConfig.pendingCommand; + }); + }).start(); } } AppConfig.pendingCommand = ""; diff --git a/app/src/main/java/com/vectras/vm/main/vms/DataMainRoms.java b/app/src/main/java/com/vectras/vm/main/vms/DataMainRoms.java index 4a67fa4..d328c31 100644 --- a/app/src/main/java/com/vectras/vm/main/vms/DataMainRoms.java +++ b/app/src/main/java/com/vectras/vm/main/vms/DataMainRoms.java @@ -50,4 +50,6 @@ public class DataMainRoms { public int bootFrom = 0; public boolean isShowBootMenu = false; + + public boolean isUseUefi = false; } diff --git a/app/src/main/java/com/vectras/vm/main/vms/VmsFragment.java b/app/src/main/java/com/vectras/vm/main/vms/VmsFragment.java index 3555eed..482d45f 100644 --- a/app/src/main/java/com/vectras/vm/main/vms/VmsFragment.java +++ b/app/src/main/java/com/vectras/vm/main/vms/VmsFragment.java @@ -156,6 +156,12 @@ public class VmsFragment extends Fragment implements CallbackInterface.HomeCallT } catch (JSONException ignored) { romsMainData.isShowBootMenu = false; } + try { + romsMainData.isUseUefi = json_data.getBoolean("isUseUefi"); + } catch (JSONException ignored) { + romsMainData.isUseUefi = false; + } + romsMainData.itemExtra = json_data.getString("imgExtra"); tempdata.add(romsMainData); } diff --git a/app/src/main/java/com/vectras/vm/main/vms/VmsHomeAdapter.java b/app/src/main/java/com/vectras/vm/main/vms/VmsHomeAdapter.java index 6323739..64c3a28 100644 --- a/app/src/main/java/com/vectras/vm/main/vms/VmsHomeAdapter.java +++ b/app/src/main/java/com/vectras/vm/main/vms/VmsHomeAdapter.java @@ -81,8 +81,11 @@ public class VmsHomeAdapter extends RecyclerView.Adapter { + String env = StartVM.env(activity, current); + activity.runOnUiThread(() -> MainStartVM.startNow(activity, current.itemName, env, current.vmID, current.itemIcon)); + }).start(); }); myHolder.cdRoms.setOnLongClickListener(v -> { diff --git a/app/src/main/java/com/vectras/vm/utils/FileUtils.java b/app/src/main/java/com/vectras/vm/utils/FileUtils.java index 55f3970..81000e2 100644 --- a/app/src/main/java/com/vectras/vm/utils/FileUtils.java +++ b/app/src/main/java/com/vectras/vm/utils/FileUtils.java @@ -21,6 +21,7 @@ import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; +import java.security.MessageDigest; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; @@ -1047,4 +1048,37 @@ public class FileUtils { Log.e(TAG, "openFolder: " + e.getMessage()); } } + + public static String getMd5(String filePath) { + InputStream inputStream = null; + try { + inputStream = new FileInputStream(filePath); + byte[] buffer = new byte[1024]; + MessageDigest digest = MessageDigest.getInstance("MD5"); + int numRead = 0; + while (numRead != -1) { + numRead = inputStream.read(buffer); + if (numRead > 0) + digest.update(buffer, 0, numRead); + } + byte [] md5Bytes = digest.digest(); + return convertHashToString(md5Bytes); + } catch (Exception e) { + return null; + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (Exception e) { } + } + } + } + + private static String convertHashToString(byte[] md5Bytes) { + String returnVal = ""; + for (byte md5Byte : md5Bytes) { + returnVal += Integer.toString((md5Byte & 0xff) + 0x100, 16).substring(1); + } + return returnVal.toLowerCase(); + } } diff --git a/app/src/main/java/com/vectras/vm/utils/NotificationUtils.java b/app/src/main/java/com/vectras/vm/utils/NotificationUtils.java index c19f772..b80d507 100644 --- a/app/src/main/java/com/vectras/vm/utils/NotificationUtils.java +++ b/app/src/main/java/com/vectras/vm/utils/NotificationUtils.java @@ -37,10 +37,14 @@ public class NotificationUtils { public static final int NO_ICON = -1; public static String generalChannelId = "general"; + public static String downloadChannelId = "download"; public static void createAllChannel(Context context) { createChannel("General", "Receive new notifications.", - "general", NotificationManager.IMPORTANCE_DEFAULT, context); + generalChannelId, NotificationManager.IMPORTANCE_DEFAULT, context); + + createChannel("Download", "View the file download process.", + downloadChannelId, NotificationManager.IMPORTANCE_DEFAULT, context); } @SuppressLint("MissingPermission") @@ -157,6 +161,12 @@ public class NotificationUtils { } } + public static void recall(Context context, int id) { + if (context == null) return; + NotificationManager notificationManager = context.getSystemService(NotificationManager.class); + notificationManager.cancel(id); + } + public static boolean isPermissionGranted(Context context) { if (Build.VERSION.SDK_INT >= 33) { return ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED; diff --git a/app/src/main/java/com/vectras/vm/view/CheckBoxView.java b/app/src/main/java/com/vectras/vm/view/CheckBoxView.java new file mode 100644 index 0000000..728656b --- /dev/null +++ b/app/src/main/java/com/vectras/vm/view/CheckBoxView.java @@ -0,0 +1,76 @@ +package com.vectras.vm.view; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.widget.CheckBox; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.vectras.vm.R; + +public class CheckBoxView extends LinearLayout { + private TextView tvTitle; + private CheckBox checkBox; + + public interface OnCheckedChangeListener { + void onCheckedChanged(CheckBoxView view, boolean isChecked); + } + + private OnCheckedChangeListener listener; + + public void setOnCheckedChangeListener(OnCheckedChangeListener listener) { + this.listener = listener; + } + + public CheckBoxView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs); + } + + private void init(Context context, AttributeSet attrs) { + LayoutInflater.from(context).inflate(R.layout.checkbox_view, this, true); + + tvTitle = findViewById(R.id.tv_title); + checkBox = findViewById(R.id.checkbox); + + if (attrs != null) { + TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CheckBoxView); + + String title = typedArray.getString(R.styleable.CheckBoxView_setText); + boolean isChecked = typedArray.getBoolean(R.styleable.CheckBoxView_setChecked, false); + + if (title != null) tvTitle.setText(title); + checkBox.setChecked(isChecked); + + typedArray.recycle(); + } + + findViewById(R.id.root).setOnClickListener(v -> checkBox.setChecked(!checkBox.isChecked())); + + checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> { + if (listener != null) { + listener.onCheckedChanged(this, isChecked); + } + }); + } + + public void setText(String title) { + tvTitle.setText(title); + } + + public void setChecked(boolean isCheck) { + checkBox.setChecked(isCheck); + } + + public boolean isChecked() { + return checkBox.isChecked(); + } + + public void setEnabled(boolean isEnabled) { + findViewById(R.id.root).setEnabled(isEnabled); + findViewById(R.id.root).setAlpha(0.5f); + checkBox.setEnabled(isEnabled); + } +} diff --git a/app/src/main/java/com/vectras/vm/view/SelectBoxView.java b/app/src/main/java/com/vectras/vm/view/SelectBoxView.java new file mode 100644 index 0000000..6a0527b --- /dev/null +++ b/app/src/main/java/com/vectras/vm/view/SelectBoxView.java @@ -0,0 +1,52 @@ +package com.vectras.vm.view; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.vectras.vm.R; + +public class SelectBoxView extends LinearLayout { + + private TextView tvTitle, tvSubtitle; + + public SelectBoxView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs); + } + + private void init(Context context, AttributeSet attrs) { + LayoutInflater.from(context).inflate(R.layout.select_box_view, this, true); + + tvTitle = findViewById(R.id.tv_title); + tvSubtitle = findViewById(R.id.tv_subtitle); + + if (attrs != null) { + TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SelectBoxView); + + String title = typedArray.getString(R.styleable.SelectBoxView_title); + String subtitle = typedArray.getString(R.styleable.SelectBoxView_subtitle); + + if (title != null) tvTitle.setText(title); + if (subtitle != null) tvSubtitle.setText(subtitle); + + typedArray.recycle(); + } + } + + public void setTitle(String title) { + tvTitle.setText(title); + } + + public void setSubtitle(String subtitle) { + tvSubtitle.setText(subtitle); + } + + public void setEnabled(boolean isEnabled) { + findViewById(R.id.root).setEnabled(isEnabled); + findViewById(R.id.root).setAlpha(0.5f); + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_vm_creator.xml b/app/src/main/res/layout/activity_vm_creator.xml index fce093f..68efc9a 100644 --- a/app/src/main/res/layout/activity_vm_creator.xml +++ b/app/src/main/res/layout/activity_vm_creator.xml @@ -266,56 +266,24 @@ android:paddingBottom="8dp" android:text="@string/boot" /> - - - - - - - + app:title="@string/boot_from" + app:subtitle="@string/defaulttext" /> - - - - + app:setText="@string/show_boot_menu"/> + + diff --git a/app/src/main/res/layout/checkbox_view.xml b/app/src/main/res/layout/checkbox_view.xml new file mode 100644 index 0000000..83e7b2c --- /dev/null +++ b/app/src/main/res/layout/checkbox_view.xml @@ -0,0 +1,22 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_change_removable_devices.xml b/app/src/main/res/layout/dialog_change_removable_devices.xml index e475f42..e4f16b8 100644 --- a/app/src/main/res/layout/dialog_change_removable_devices.xml +++ b/app/src/main/res/layout/dialog_change_removable_devices.xml @@ -195,6 +195,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index f99f1ca..fcd38c4 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -524,6 +524,14 @@ Chạm để xem. Xem trong ứng dụng An Bùi Bạn có thể xem và lấy nó ở đây. + Công cụ + Trình bao bọc 3dfx + Công cụ VirtIO cho Windows + Càn tải xuống + Công cụ này cần được tải xuống để sử dụng. Bạn sẽ được thông báo khi quá trình tải xuống hoàn thành. + Tải xuống thất bại. Hãy thử lại sau. + Công cụ VirtIO cho Windows đã sẵn sàng để sử dụng + Bạn có muốn gắn nó vào ổ đĩa quang ngay không? diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index eb902ce..a734760 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -37,4 +37,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8f8902a..d1a5590 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -533,6 +533,14 @@ Tap to view. View in the An Bui app You can view and get it here. + Tools + 3dfx wrappers + VirtIO tools for Windows + Download required + This tool needs to be downloaded before use. You will be notified when the download is complete. + Download failed. Please try again later. + VirtIO tools for Windows is now ready to use + Do you want to insert it into the optical drive right now? diff --git a/web/data/UpdateConfig.json b/web/data/UpdateConfig.json index 65c61b4..209662e 100644 --- a/web/data/UpdateConfig.json +++ b/web/data/UpdateConfig.json @@ -5,11 +5,11 @@ "url": "https://github.com/xoureldeen/Vectras-VM-Android/releases", "Message": "

3.9.0

\nBugs fixed.", "cancellable": true, - "versionCodeBeta":"95", - "versionNameBeta":"3.9.1", - "versionNameBetas":"3.0.0,3.1.0,3.2.1,3.2.2,3.2.3,3.2.4,3.2.5,3.2.6,3.2.7,3.2.8,3.2.9,3.2.10,3.3.1,3.3.2,3.3.3,3.3.4,3.3.5,3.3.6,3.3.7,3.3.8,3.3.9,3.4.1,3.4.2,3.4.3,3.4.4,3.4.5,3.4.6,3.4.7,3.4.8,3.4.9,3.5.1,3.5.2,3.5.3,3.5.4,3.5.5,3.5.6,3.5.7,3.5.8,3.5.9,3.6.1,3.6.2,3.6.3,3.6.4,3.6.5,3.6.6,3.6.7,3.6.8,3.6.9,3.7.1,3.7.2,3.7.3,3.7.4,3.7.5,3.7.6,3.7.7,3.7.8,3.7.9,3.8.0,3.8.1,3.8.2,3.8.3,3.8.4,3.8.5,3.8.6,3.8.7,3.8.8,3.8.9,3.9.0,3.9.1", + "versionCodeBeta":"96", + "versionNameBeta":"3.9.2", + "versionNameBetas":"3.0.0,3.1.0,3.2.1,3.2.2,3.2.3,3.2.4,3.2.5,3.2.6,3.2.7,3.2.8,3.2.9,3.2.10,3.3.1,3.3.2,3.3.3,3.3.4,3.3.5,3.3.6,3.3.7,3.3.8,3.3.9,3.4.1,3.4.2,3.4.3,3.4.4,3.4.5,3.4.6,3.4.7,3.4.8,3.4.9,3.5.1,3.5.2,3.5.3,3.5.4,3.5.5,3.5.6,3.5.7,3.5.8,3.5.9,3.6.1,3.6.2,3.6.3,3.6.4,3.6.5,3.6.6,3.6.7,3.6.8,3.6.9,3.7.1,3.7.2,3.7.3,3.7.4,3.7.5,3.7.6,3.7.7,3.7.8,3.7.9,3.8.0,3.8.1,3.8.2,3.8.3,3.8.4,3.8.5,3.8.6,3.8.7,3.8.8,3.8.9,3.9.0,3.9.1,3.9.2", "sizeBeta": "45 MB", "urlBeta": "https://github.com/AnBui2004/Vectras-VM-Emu-Android/releases", - "MessageBeta": "

3.9.1

Bugs fixed.", + "MessageBeta": "

3.9.2

Bugs fixed.", "cancellableBeta": true }