Merge pull request #592 from AnBui2004/master

3.9.2
This commit is contained in:
An Bui 2026-03-29 20:48:14 +07:00 committed by GitHub
commit 81995d45df
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 642 additions and 69 deletions

View file

@ -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

Binary file not shown.

View file

@ -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<ResponseBody> getRawJson(@Url String url);
@Streaming
@GET
Call<ResponseBody> downloadFile(@Url String url);
@POST
Call<ResponseBody> post(@Url String url, @Body RequestBody body);
}

View file

@ -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<ResponseBody> call, @NonNull Response<ResponseBody> 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<ResponseBody> call, Throwable t) {
callback.onResult(false, null, t);
}
});
}
}

View file

@ -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";
}

View file

@ -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);

View file

@ -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() {

View file

@ -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");
}
}
}
}

View file

@ -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)) {

View file

@ -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")) {

View file

@ -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 = "";

View file

@ -50,4 +50,6 @@ public class DataMainRoms {
public int bootFrom = 0;
public boolean isShowBootMenu = false;
public boolean isUseUefi = false;
}

View file

@ -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);
}

View file

@ -81,8 +81,11 @@ public class VmsHomeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
Config.QMPPort = current.qmpPort;
}
Config.vmID = current.vmID;
String env = StartVM.env(activity, current);
MainStartVM.startNow(activity, current.itemName, env, current.vmID, current.itemIcon);
new Thread(() -> {
String env = StartVM.env(activity, current);
activity.runOnUiThread(() -> MainStartVM.startNow(activity, current.itemName, env, current.vmID, current.itemIcon));
}).start();
});
myHolder.cdRoms.setOnLongClickListener(v -> {

View file

@ -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();
}
}

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -266,56 +266,24 @@
android:paddingBottom="8dp"
android:text="@string/boot" />
<LinearLayout
android:id="@+id/ln_bootfrom"
<com.vectras.vm.view.SelectBoxView
android:id="@+id/sbv_bootfrom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingVertical="8dp"
android:gravity="center_vertical"
android:background="?attr/selectableItemBackground">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/boot_from"
android:textSize="18sp" />
<TextView
android:id="@+id/tv_bootfrom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/defaulttext" />
</LinearLayout>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/keyboard_arrow_right_24px" />
</LinearLayout>
app:title="@string/boot_from"
app:subtitle="@string/defaulttext" />
<LinearLayout
android:id="@+id/ln_showbootmenu"
<com.vectras.vm.view.CheckBoxView
android:id="@+id/cbv_showbootmenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="8dp"
android:paddingStart="16dp"
android:paddingEnd="8dp"
android:gravity="center_vertical"
android:background="?attr/selectableItemBackground">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/show_boot_menu" />
<CheckBox
android:id="@+id/cb_showbootmenu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"/>
</LinearLayout>
app:setText="@string/show_boot_menu"/>
<com.vectras.vm.view.CheckBoxView
android:id="@+id/cbv_useuefi"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:setText="@string/use_uefi_for_x64_only"/>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="8dp"
android:paddingStart="16dp"
android:paddingEnd="8dp"
android:gravity="center_vertical"
android:background="?attr/selectableItemBackground">
<TextView
android:id="@+id/tv_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Text" />
<CheckBox
android:id="@+id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"/>
</LinearLayout>

View file

@ -195,6 +195,78 @@
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/ln_tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingVertical="8dp"
android:paddingHorizontal="16dp"
android:text="@string/tools"
android:textColor="?attr/colorControlNormal"
android:textSize="22sp"/>
<LinearLayout
android:id="@+id/ln_virtio"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:paddingVertical="8dp"
android:paddingHorizontal="16dp"
android:background="?attr/selectableItemBackground">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/deployed_code_24px"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:padding="8dp"
android:layout_weight="1"
android:textColor="?attr/colorControlNormal"
android:text="@string/virtio_tools_for_windows"/>
<ImageView
android:id="@+id/iv_ejectvirtio"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/eject_24px"
android:background="?attr/selectableItemBackgroundBorderless"/>
</LinearLayout>
<LinearLayout
android:id="@+id/ln_3dfx"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:paddingVertical="8dp"
android:paddingHorizontal="16dp"
android:background="?attr/selectableItemBackground">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/deployed_code_24px"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:padding="8dp"
android:layout_weight="1"
android:textColor="?attr/colorControlNormal"
android:text="@string/threedfx_wrappers"/>
<ImageView
android:id="@+id/iv_eject3dfx"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/eject_24px"
android:background="?attr/selectableItemBackgroundBorderless"/>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/ln_user_interface"
android:layout_width="match_parent"

View file

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingVertical="8dp"
android:gravity="center_vertical"
android:background="?attr/selectableItemBackground">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Title"
android:textSize="18sp" />
<TextView
android:id="@+id/tv_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Subtitle" />
</LinearLayout>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/keyboard_arrow_right_24px" />
</LinearLayout>

View file

@ -524,6 +524,14 @@
<string name="tap_to_view">Chạm để xem.</string>
<string name="view_in_an_bui_app">Xem trong ứng dụng An Bùi</string>
<string name="view_in_an_bui_app_note">Bạn có thể xem và lấy nó ở đây.</string>
<string name="tools">Công cụ</string>
<string name="threedfx_wrappers">Trình bao bọc 3dfx</string>
<string name="virtio_tools_for_windows">Công cụ VirtIO cho Windows</string>
<string name="download_required">Càn tải xuống</string>
<string name="this_tool_needs_to_be_downloaded_before_use">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.</string>
<string name="download_failed_note">Tải xuống thất bại. Hãy thử lại sau.</string>
<string name="virtio_tools_for_windows_is_now_ready_to_use">Công cụ VirtIO cho Windows đã sẵn sàng để sử dụng</string>
<string name="do_you_want_to_insert_it_into_the_optical_drive_right_now">Bạn có muốn gắn nó vào ổ đĩa quang ngay không?</string>
<!--======================TERMUX STRINGS====================-->

View file

@ -37,4 +37,14 @@
</declare-styleable>
<declare-styleable name="SelectBoxView">
<attr name="title" format="string"/>
<attr name="subtitle" format="string"/>
</declare-styleable>
<declare-styleable name="CheckBoxView">
<attr name="setText" format="string"/>
<attr name="setChecked" format="boolean"/>
</declare-styleable>
</resources>

View file

@ -533,6 +533,14 @@
<string name="tap_to_view">Tap to view.</string>
<string name="view_in_an_bui_app">View in the An Bui app</string>
<string name="view_in_an_bui_app_note">You can view and get it here.</string>
<string name="tools">Tools</string>
<string name="threedfx_wrappers">3dfx wrappers</string>
<string name="virtio_tools_for_windows">VirtIO tools for Windows</string>
<string name="download_required">Download required</string>
<string name="this_tool_needs_to_be_downloaded_before_use">This tool needs to be downloaded before use. You will be notified when the download is complete.</string>
<string name="download_failed_note">Download failed. Please try again later.</string>
<string name="virtio_tools_for_windows_is_now_ready_to_use">VirtIO tools for Windows is now ready to use</string>
<string name="do_you_want_to_insert_it_into_the_optical_drive_right_now">Do you want to insert it into the optical drive right now?</string>
<!--======================TERMUX STRINGS====================-->

View file

@ -5,11 +5,11 @@
"url": "https://github.com/xoureldeen/Vectras-VM-Android/releases",
"Message": "<h2>3.9.0</h2>\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": "<h2>3.9.1</h2>Bugs fixed.",
"MessageBeta": "<h2>3.9.2</h2>Bugs fixed.",
"cancellableBeta": true
}