diff --git a/app/build.gradle b/app/build.gradle index c30de84..d49107d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,8 +12,8 @@ android { applicationId "com.vectras.vm" minSdk minApi targetSdk targetApi - versionCode 105 - versionName "4.0.1" + versionCode 106 + versionName "4.0.2" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" multiDexEnabled true diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e8f787e..787ced8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -156,7 +156,7 @@ android:name="com.vectras.qemu.MainVNCActivity" android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize" android:launchMode="singleTask" - android:screenOrientation="landscape" + android:screenOrientation="sensorLandscape" android:theme="@style/Theme.FullScreen" android:windowSoftInputMode="adjustPan" /> + + diff --git a/app/src/main/java/android/androidVNC/VncCanvas.java b/app/src/main/java/android/androidVNC/VncCanvas.java index ef60e97..b3dc0eb 100644 --- a/app/src/main/java/android/androidVNC/VncCanvas.java +++ b/app/src/main/java/android/androidVNC/VncCanvas.java @@ -303,22 +303,8 @@ public class VncCanvas extends AppCompatImageView { Log.i(TAG, "Desktop name is " + rfb.desktopName); Log.i(TAG, "Desktop size is " + rfb.framebufferWidth + " x " + rfb.framebufferHeight); - boolean useFull = false; - int capacity = BCFactory.getInstance().getBCActivityManager() - .getMemoryClass(Utils.getActivityManager(getContext())); - if (connection.getForceFull() == BitmapImplHint.AUTO) { - if (rfb.framebufferWidth * rfb.framebufferHeight * FullBufferBitmapData.CAPACITY_MULTIPLIER <= capacity - * 1024 * 1024) { - useFull = true; - } - } else { - useFull = (connection.getForceFull() == BitmapImplHint.FULL); - } - if (!useFull) { - bitmapData = new LargeBitmapData(rfb, this, dx, dy, capacity); - } else { - bitmapData = new FullBufferBitmapData(rfb, this, capacity); - } + initBitmapData(dx, dy); + mouseX = rfb.framebufferWidth / 2; mouseY = rfb.framebufferHeight / 2; @@ -333,6 +319,29 @@ public class VncCanvas extends AppCompatImageView { pendingColorModel = null; } + void initBitmapData(int dx, int dy) { + int capacity = BCFactory.getInstance().getBCActivityManager() + .getMemoryClass(Utils.getActivityManager(getContext())); + + boolean useFull = false; + if (connection.getForceFull() == BitmapImplHint.AUTO) { + if (rfb.framebufferWidth * rfb.framebufferHeight * FullBufferBitmapData.CAPACITY_MULTIPLIER + <= capacity * 1024 * 1024) { + useFull = true; + } + } else { + useFull = (connection.getForceFull() == BitmapImplHint.FULL); + } + + if (bitmapData != null) bitmapData.dispose(); + + if (!useFull) { + bitmapData = new LargeBitmapData(rfb, this, dx, dy, capacity); + } else { + bitmapData = new FullBufferBitmapData(rfb, this, capacity); + } + } + public void setColorModel(COLORMODEL cm) { // Only update if color model changes if (colorModel == null || !colorModel.equals(cm)) { @@ -395,6 +404,11 @@ public class VncCanvas extends AppCompatImageView { rfb.setFramebufferSize(rw, rh); // - updateFramebufferSize(); Log.v(TAG, "rfb.EncodingNewFBSize"); + + Point size = new Point(); + VncCanvasActivity.display.getSize(size); + initBitmapData(size.x, size.y); + reload(); break; } diff --git a/app/src/main/java/android/androidVNC/VncCanvasActivity.java b/app/src/main/java/android/androidVNC/VncCanvasActivity.java index b7fd01a..a8f9c41 100644 --- a/app/src/main/java/android/androidVNC/VncCanvasActivity.java +++ b/app/src/main/java/android/androidVNC/VncCanvasActivity.java @@ -1821,6 +1821,12 @@ public abstract class VncCanvasActivity extends AppCompatActivity { // Remove old canvas ViewGroup parent = findViewById(R.id.vnc_canvas_layout); + + if (vncCanvas != null) { + vncCanvas.closeConnection(); + vncCanvas.onDestroy(); + } + parent.removeView(vncCanvas); // Create new canvas diff --git a/app/src/main/java/com/vectras/qemu/MainSettingsManager.java b/app/src/main/java/com/vectras/qemu/MainSettingsManager.java index 9888beb..cf2cd89 100644 --- a/app/src/main/java/com/vectras/qemu/MainSettingsManager.java +++ b/app/src/main/java/com/vectras/qemu/MainSettingsManager.java @@ -1046,7 +1046,7 @@ public class MainSettingsManager extends AppCompatActivity public static Boolean getForceRefeshVNCDisplay(Context context) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - return prefs.getBoolean("forceRefeshVNCDisplay", true); + return prefs.getBoolean("forceRefeshVNCDisplay", false); } public static void setQuickStart(Context context, Boolean _boolean) { diff --git a/app/src/main/java/com/vectras/qemu/MainVNCActivity.java b/app/src/main/java/com/vectras/qemu/MainVNCActivity.java index 393af80..3c477bf 100644 --- a/app/src/main/java/com/vectras/qemu/MainVNCActivity.java +++ b/app/src/main/java/com/vectras/qemu/MainVNCActivity.java @@ -114,6 +114,8 @@ public class MainVNCActivity extends VncCanvasActivity { super.onCreate(b); getContext = this; + UIUtils.edgeToEdge(this); + UIUtils.setOnApplyWindowInsetsListener(binding.vncMainLayout); initializeControlFragment(); initializeDesktopControl(); initializeGameControl(); @@ -175,6 +177,8 @@ public class MainVNCActivity extends VncCanvasActivity { streamAudio = new StreamAudio(this); streamAudio.setFile(VmFileManager.findAudioRaw(this, Config.vmID)); + + if (!isConnected) tryReconnect(); } private void setDefaulViewMode() { @@ -187,7 +191,7 @@ public class MainVNCActivity extends VncCanvasActivity { // screenMode = VNCScreenMode.FitToScreen; setLayout(getResources().getConfiguration()); - UIUtils.setOrientation(this); + //UIUtils.setOrientation(this); } @Override @@ -766,12 +770,14 @@ public class MainVNCActivity extends VncCanvasActivity { setUIModeMobile(screenMode == VNCScreenMode.FitToScreen); binding.lnNosignal.setVisibility(View.GONE); + binding.lnConnecting.setVisibility(View.GONE); this.vncCanvas.setFocusableInTouchMode(true); // syncCursorViewWithBitmap(); if (VmAudioManager.currentVmId.equals(Config.vmID) && VmAudioManager.streamAudio.isPlaying()) streamAudio.setCross(VmAudioManager.streamAudio); if (!streamAudio.isPlaying()) streamAudio.play(); + }); } @@ -847,14 +853,16 @@ public class MainVNCActivity extends VncCanvasActivity { binding.lnNosignal.setVisibility(View.VISIBLE); binding.lnConnecting.setVisibility(View.GONE); } else { - isTrying = false; - if (Config.forceRefeshVNCDisplay) { runOnUiThread(() -> { startActivity(new Intent(MainVNCActivity.this, MainVNCActivity.class)); overridePendingTransition(0, 0); finish(); }); + } else { + isTrying = false; + binding.lnNosignal.setVisibility(View.GONE); + binding.lnConnecting.setVisibility(View.GONE); } } } diff --git a/app/src/main/java/com/vectras/vm/RomInfo.java b/app/src/main/java/com/vectras/vm/RomInfo.java index 700f22e..59f8934 100644 --- a/app/src/main/java/com/vectras/vm/RomInfo.java +++ b/app/src/main/java/com/vectras/vm/RomInfo.java @@ -25,6 +25,7 @@ import com.vectras.vm.databinding.ActivityRomInfoBinding; import com.vectras.vm.utils.DialogUtils; import com.vectras.vm.utils.FileUtils; import com.vectras.vm.utils.ImageUtils; +import com.vectras.vm.utils.IntentUtils; import com.vectras.vm.utils.PackageUtils; import java.io.File; @@ -165,7 +166,13 @@ public class RomInfo extends AppCompatActivity { }); if (getIntent().hasExtra("isRomInfo") && getIntent().getBooleanExtra("isRomInfo", false)) { - binding.btnPick.setOnClickListener(v -> romPicker.launch("*/*")); + binding.btnPick.setOnClickListener(v -> { + try { + romPicker.launch("*/*"); + } catch (Exception e) { + IntentUtils.showErrorDialog(this); + } + }); } else { binding.btnPick.setVisibility(View.GONE); } @@ -326,7 +333,8 @@ public class RomInfo extends AppCompatActivity { String likeContent = getString(R.string.like); boolean isLiked = interaction.isLiked(); likeContent = (likes == 0) ? getString(R.string.like) : interaction.getFormatedLikeCount(); - if (isLiked) binding.btnLike.setIcon(ContextCompat.getDrawable(RomInfo.this, R.drawable.thumb_up_filled_24px)); + if (isLiked) + binding.btnLike.setIcon(ContextCompat.getDrawable(RomInfo.this, R.drawable.thumb_up_filled_24px)); binding.btnLike.setText(likeContent); binding.lnAllViews.setVisibility(View.VISIBLE); 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 482a106..0f88376 100644 --- a/app/src/main/java/com/vectras/vm/creator/VMCreatorActivity.java +++ b/app/src/main/java/com/vectras/vm/creator/VMCreatorActivity.java @@ -44,6 +44,7 @@ import com.vectras.vm.utils.ImageUtils; import com.vectras.vm.utils.IntentUtils; import com.vectras.vm.utils.JSONUtils; import com.vectras.vm.utils.PackageUtils; +import com.vectras.vm.utils.ProgressDialog; import com.vectras.vm.utils.UIUtils; import org.json.JSONException; @@ -87,7 +88,11 @@ public class VMCreatorActivity extends AppCompatActivity { finish(); return true; } else if (id == R.id.add_file) { + try { filePicker.launch("*/*"); + } catch (Exception e) { + IntentUtils.showErrorDialog(this); + } return true; } else if (id == R.id.show_in_folder) { FileUtils.openFolder(this, VmFileManager.getPath(vmID)); @@ -126,8 +131,20 @@ public class VMCreatorActivity extends AppCompatActivity { binding.btnCreate.setOnClickListener(v -> startCreateVM()); - binding.drive.setOnClickListener(v -> diskPicker.launch("*/*")); - binding.driveField.setOnClickListener(v -> diskPicker.launch("*/*")); + binding.drive.setOnClickListener(v -> { + try { + diskPicker.launch("*/*"); + } catch (Exception e) { + IntentUtils.showErrorDialog(this); + } + }); + binding.driveField.setOnClickListener(v -> { + try { + diskPicker.launch("*/*"); + } catch (Exception e) { + IntentUtils.showErrorDialog(this); + } + }); binding.driveField.setEndIconOnClickListener(v -> { if (Objects.requireNonNull(binding.drive.getText()).toString().isEmpty()) { @@ -147,10 +164,21 @@ public class VMCreatorActivity extends AppCompatActivity { true, R.drawable.hard_drive_24px, true, - () -> diskPicker.launch("*/*"), + () -> { + try { + diskPicker.launch("*/*"); + } catch (Exception e) { + IntentUtils.showErrorDialog(this); + } + }, () -> { if (binding.drive.getText().toString().contains(VmFileManager.quickGetPath(vmID))) { - FileUtils.delete(new File(Objects.requireNonNull(binding.drive.getText()).toString())); + ProgressDialog progressDialog1 = new ProgressDialog(this); + progressDialog1.show(); + new Thread(() -> { + FileUtils.delete(new File(Objects.requireNonNull(binding.drive.getText()).toString())); + runOnUiThread(progressDialog1::reset); + }).start(); } binding.drive.setText(""); binding.driveField.setEndIconDrawable(R.drawable.add_24px); @@ -168,7 +196,13 @@ public class VMCreatorActivity extends AppCompatActivity { } }); - View.OnClickListener cdromClickListener = v -> isoPicker.launch("*/*"); + View.OnClickListener cdromClickListener = v -> { + try { + isoPicker.launch("*/*"); + } catch (Exception e) { + IntentUtils.showErrorDialog(this); + } + }; binding.cdrom.setOnClickListener(cdromClickListener); binding.cdromField.setOnClickListener(cdromClickListener); @@ -310,7 +344,11 @@ public class VMCreatorActivity extends AppCompatActivity { } else if (getIntent().hasExtra("importcvbinow")) { setDefault(); - cvbiPicker.launch("*/*"); + try { + cvbiPicker.launch("*/*"); + } catch (Exception e) { + IntentUtils.showErrorDialog(this); + } } else { setDefault(); if (MainSettingsManager.autoCreateDisk(this)) { @@ -777,10 +815,14 @@ public class VMCreatorActivity extends AppCompatActivity { } private void selectedDiskFile(Uri _content_describer, boolean _addtodrive) { + ProgressDialog progressDialog1 = new ProgressDialog(this); + progressDialog1.show(); new Thread(() -> { if (FileUtils.isValidFilePath(this, FileUtils.getPath(this, _content_describer), false)) { File selectedFilePath = new File(getPath(_content_describer)); runOnUiThread(() -> { + progressDialog1.reset(); + if (VMManager.isADiskFile(selectedFilePath.getPath())) { startProcessingHardDriveFile(_content_describer, _addtodrive); } else { @@ -849,9 +891,17 @@ public class VMCreatorActivity extends AppCompatActivity { null); return; } - File selectedFilePath = new File(getPath(_content_describer)); - binding.drive.setText(selectedFilePath.getPath()); - binding.driveField.setEndIconDrawable(R.drawable.more_vert_24px); + + ProgressDialog progressDialog1 = new ProgressDialog(this); + progressDialog1.show(); + new Thread(() -> { + File selectedFilePath = new File(getPath(_content_describer)); + runOnUiThread(() -> { + progressDialog1.reset(); + binding.drive.setText(selectedFilePath.getPath()); + binding.driveField.setEndIconDrawable(R.drawable.more_vert_24px); + }); + }).start(); } } @@ -1030,7 +1080,7 @@ public class VMCreatorActivity extends AppCompatActivity { if (jObj.has("vmID")) { if (!jObj.isNull("vmID")) { if (!jObj.getString("vmID").isEmpty()) { - FileUtils.move(VmFileManager.getConfigFile(vmID), VmFileManager.getConfigFile( jObj.getString("vmID"))); + FileUtils.move(VmFileManager.getConfigFile(vmID), VmFileManager.getConfigFile(jObj.getString("vmID"))); vmID = jObj.getString("vmID"); } } diff --git a/app/src/main/java/com/vectras/vm/main/core/MainStartVM.java b/app/src/main/java/com/vectras/vm/main/core/MainStartVM.java index d88b188..1ba1124 100644 --- a/app/src/main/java/com/vectras/vm/main/core/MainStartVM.java +++ b/app/src/main/java/com/vectras/vm/main/core/MainStartVM.java @@ -18,6 +18,7 @@ import android.widget.Toast; import androidx.appcompat.app.AlertDialog; import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.vectras.qemu.Config; import com.vectras.qemu.MainSettingsManager; @@ -388,12 +389,16 @@ public class MainStartVM { .load(new File(thumbnailFile)) .placeholder(R.drawable.ic_computer_180dp_with_padding) .error(R.drawable.ic_computer_180dp_with_padding) + .skipMemoryCache(true) + .diskCacheStrategy(DiskCacheStrategy.NONE) .into(ivThumbnail); } else if (VmFileManager.isScreenshotPngExists(vmID)) { Glide.with(context.getApplicationContext()) .load(new File(VmFileManager.getScreenshotPng(vmID))) .placeholder(R.drawable.ic_computer_180dp_with_padding) .error(R.drawable.ic_computer_180dp_with_padding) + .skipMemoryCache(true) + .diskCacheStrategy(DiskCacheStrategy.NONE) .into(ivThumbnail); } else { VMManager.setIconWithName(ivThumbnail, _content); diff --git a/app/src/main/java/com/vectras/vm/manager/VmControllerDialog.java b/app/src/main/java/com/vectras/vm/manager/VmControllerDialog.java index b452e4d..23fa551 100644 --- a/app/src/main/java/com/vectras/vm/manager/VmControllerDialog.java +++ b/app/src/main/java/com/vectras/vm/manager/VmControllerDialog.java @@ -22,6 +22,7 @@ import com.vectras.qemu.VNCConfig; import com.vectras.vm.AppConfig; import com.vectras.vm.R; import com.vectras.vm.VMManager; +import com.vectras.vm.VectrasApp; import com.vectras.vm.creator.VMCreatorActivity; import com.vectras.vm.databinding.DialogChangeRemovableDevicesBinding; import com.vectras.vm.main.core.DisplaySystem; @@ -108,6 +109,7 @@ public class VmControllerDialog extends DialogFragment { }); if (isAdded() && (!(requireActivity() instanceof MainVNCActivity))) { + if (streamAudio == null) streamAudio = new StreamAudio(requireActivity().getApplicationContext()); if (!streamAudio.isPlaying()) VmAudioManager.set(Config.vmID); } @@ -143,7 +145,8 @@ public class VmControllerDialog extends DialogFragment { dismiss(); }); - if (!isHavingSecondaryOpticalDisc()) binding.tvCdrom.setText(R.string.cdrom); + if (!isHavingSecondaryOpticalDisc()) + binding.tvCdrom.setText(R.string.cdrom); } else { binding.lnCdrom.setVisibility(View.GONE); } @@ -156,7 +159,8 @@ public class VmControllerDialog extends DialogFragment { dismiss(); }); - if (!isHavingOpticalDisc()) binding.tvSecondaryCdrom.setText(R.string.cdrom); + if (!isHavingOpticalDisc()) + binding.tvSecondaryCdrom.setText(R.string.cdrom); } else { binding.lnSecondaryCdrom.setVisibility(View.GONE); } @@ -351,13 +355,20 @@ public class VmControllerDialog extends DialogFragment { registerForActivityResult(new ActivityResultContracts.GetContent(), uri -> { if (uri != null) { try { - File selectedFilePath = new File(Objects.requireNonNull(FileUtils.getPath(requireActivity(), uri))); - QmpSender.changeOpticalDisc(requireActivity(), selectedFilePath.getAbsolutePath(), infoBlock); + new Thread(() -> { + File selectedFilePath = new File(Objects.requireNonNull(FileUtils.getPath(getContext(), uri))); + + if (isAdded()) { + requireActivity().runOnUiThread(() -> { + QmpSender.changeOpticalDisc(requireActivity(), selectedFilePath.getAbsolutePath(), infoBlock); + dismiss(); + }); + } + }).start(); } catch (Exception e) { showErrorSelectedFileDialog(); + dismiss(); } - - dismiss(); } }); @@ -365,13 +376,20 @@ public class VmControllerDialog extends DialogFragment { registerForActivityResult(new ActivityResultContracts.GetContent(), uri -> { if (uri != null) { try { - File selectedFilePath = new File(Objects.requireNonNull(FileUtils.getPath(requireActivity(), uri))); - QmpSender.changeSecondaryOpticalDisc(requireActivity(), selectedFilePath.getAbsolutePath(), infoBlock); + new Thread(() -> { + File selectedFilePath = new File(Objects.requireNonNull(FileUtils.getPath(getContext(), uri))); + + if (isAdded()) { + requireActivity().runOnUiThread(() -> { + QmpSender.changeSecondaryOpticalDisc(requireActivity(), selectedFilePath.getAbsolutePath(), infoBlock); + dismiss(); + }); + } + }).start(); } catch (Exception e) { showErrorSelectedFileDialog(); + dismiss(); } - - dismiss(); } }); @@ -379,13 +397,20 @@ public class VmControllerDialog extends DialogFragment { registerForActivityResult(new ActivityResultContracts.GetContent(), uri -> { if (uri != null) { try { - File selectedFilePath = new File(Objects.requireNonNull(FileUtils.getPath(requireActivity(), uri))); - QmpSender.changeFloppyDiskA(requireActivity(), selectedFilePath.getAbsolutePath()); + new Thread(() -> { + File selectedFilePath = new File(Objects.requireNonNull(FileUtils.getPath(getContext(), uri))); + + if (isAdded()) { + requireActivity().runOnUiThread(() -> { + QmpSender.changeFloppyDiskA(requireActivity(), selectedFilePath.getAbsolutePath()); + dismiss(); + }); + } + }).start(); } catch (Exception e) { showErrorSelectedFileDialog(); + dismiss(); } - - dismiss(); } }); @@ -393,13 +418,20 @@ public class VmControllerDialog extends DialogFragment { registerForActivityResult(new ActivityResultContracts.GetContent(), uri -> { if (uri != null) { try { - File selectedFilePath = new File(Objects.requireNonNull(FileUtils.getPath(requireActivity(), uri))); - QmpSender.changeFloppyDiskB(requireActivity(), selectedFilePath.getAbsolutePath()); + new Thread(() -> { + File selectedFilePath = new File(Objects.requireNonNull(FileUtils.getPath(getContext(), uri))); + + if (isAdded()) { + requireActivity().runOnUiThread(() -> { + QmpSender.changeFloppyDiskB(requireActivity(), selectedFilePath.getAbsolutePath()); + dismiss(); + }); + } + }).start(); } catch (Exception e) { showErrorSelectedFileDialog(); + dismiss(); } - - dismiss(); } }); @@ -407,13 +439,20 @@ public class VmControllerDialog extends DialogFragment { registerForActivityResult(new ActivityResultContracts.GetContent(), uri -> { if (uri != null) { try { - File selectedFilePath = new File(Objects.requireNonNull(FileUtils.getPath(requireActivity(), uri))); - QmpSender.changeMemoryCard(requireActivity(), selectedFilePath.getAbsolutePath()); + new Thread(() -> { + File selectedFilePath = new File(Objects.requireNonNull(FileUtils.getPath(getContext(), uri))); + + if (isAdded()) { + requireActivity().runOnUiThread(() -> { + QmpSender.changeMemoryCard(requireActivity(), selectedFilePath.getAbsolutePath()); + dismiss(); + }); + } + }).start(); } catch (Exception e) { showErrorSelectedFileDialog(); + dismiss(); } - - dismiss(); } }); diff --git a/app/src/main/res/layout/activity_vnc.xml b/app/src/main/res/layout/activity_vnc.xml index fe7a69a..875afd2 100644 --- a/app/src/main/res/layout/activity_vnc.xml +++ b/app/src/main/res/layout/activity_vnc.xml @@ -30,70 +30,103 @@ android:keepScreenOn="true" /> - - - - - - - - - - + - + - + + + + + + + + + + + + + + - + - + - + + + + + + + + + + + + + + + + + + + - - - - - - 4.0.0\nBugs fixed.", "cancellable": true, - "versionCodeBeta":"105", - "versionNameBeta":"4.0.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,3.9.2,3.9.3,3.9.4,3.9.5,3.9.6,3.9.7,3.9.8,3.9.9,4.0.0,4.0.1", + "versionCodeBeta":"106", + "versionNameBeta":"4.0.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,3.9.3,3.9.4,3.9.5,3.9.6,3.9.7,3.9.8,3.9.9,4.0.0,4.0.1,4.0.2", "sizeBeta": "45 MB", "urlBeta": "https://github.com/AnBui2004/Vectras-VM-Emu-Android/releases", - "MessageBeta": "

4.0.1

Bugs fixed.", + "MessageBeta": "

4.0.2

Bugs fixed.", "cancellableBeta": true }