Compare commits

...

16 commits

Author SHA1 Message Date
An Bui
0f99c74ecd
Merge pull request #678 from AnBui2004/master
4.3.3
2026-07-09 23:39:39 +07:00
An Bui
3990bbb4c8 4.3.3
- Fixed the issue of being unable to insert or change optical discs, floppy drives, and memory cards when filenames contained spaces.
- Added equalizer.
- Bubbles on the built-in VNC screen will automatically fade when there is no interaction.
- The default setting for automatically switching to external mouse mode on the built-in VNC screen is now off.
2026-07-09 23:38:17 +07:00
An Bui
5074600e8b
Merge pull request #676 from AnBui2004/master
4.3.2
2026-07-08 22:22:07 +07:00
An Bui
24a63b4311 4.3.2
- Fixed Terminal2 not collecting error logs.
- Improved detection of whether QEMU returns an error.
2026-07-08 22:20:50 +07:00
An Bui
fcdcab3b15
Merge pull request #675 from AnBui2004/master
4.3.1
2026-07-07 15:44:33 +07:00
An Bui
582d1d9845 4.3.1
- Improved system update.
- Added error reporting when virtual drive creation fails.
- Automatically resumes opening the X11 screen after missing packages have been installed.
- Fixed an issue with the virtual machine startup dialog getting stuck.
- Fixed an issue when selecting an invalid file in the virtual machine control dialog.
- Fixed a failure to check if the device supports 64-bit.
- Added a check for available Google Play services.
- Improved checking for broken virtual machine listings.
- New Terminal2.
- Cleaned up unnecessary resources and assets.
- Fixed other minor issues.
2026-07-07 15:43:01 +07:00
An Bui
661be39c3b
Merge pull request #90 from AnBui2004/dependabot/gradle/com.google.firebase-firebase-messaging-25.1.0
Bump com.google.firebase:firebase-messaging from 25.0.2 to 25.1.0
2026-07-06 11:36:49 +07:00
An Bui
30c98c3c17
Merge pull request #91 from AnBui2004/dependabot/gradle/com.google.firebase-firebase-bom-34.15.0
Bump com.google.firebase:firebase-bom from 34.14.1 to 34.15.0
2026-07-06 11:36:37 +07:00
An Bui
cb32dffb89
Merge pull request #92 from AnBui2004/dependabot/gradle/gradle-wrapper-9.6.0
Bump gradle-wrapper from 9.5.0 to 9.6.0
2026-07-06 11:36:26 +07:00
An Bui
08dcd03614
Merge pull request #674 from AnBui2004/master
Update vroms-store.json
2026-07-01 20:44:37 +07:00
An Bui
1735664e6c Update vroms-store.json 2026-07-01 20:43:58 +07:00
An Bui
809bd1a3c3
Merge pull request #673 from AnBui2004/master
Update bug.yaml
2026-06-29 14:07:12 +07:00
An Bui
a531e2331b Update bug.yaml 2026-06-29 14:06:49 +07:00
dependabot[bot]
6b00df5785
Bump gradle-wrapper from 9.5.0 to 9.6.0
Bumps [gradle-wrapper](https://github.com/gradle/gradle) from 9.5.0 to 9.6.0.
- [Release notes](https://github.com/gradle/gradle/releases)
- [Commits](https://github.com/gradle/gradle/compare/v9.5.0...v9.6.0)

---
updated-dependencies:
- dependency-name: gradle-wrapper
  dependency-version: 9.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-22 08:17:43 +00:00
dependabot[bot]
56dd71a355
Bump com.google.firebase:firebase-bom from 34.14.1 to 34.15.0
Bumps com.google.firebase:firebase-bom from 34.14.1 to 34.15.0.

---
updated-dependencies:
- dependency-name: com.google.firebase:firebase-bom
  dependency-version: 34.15.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-22 08:17:20 +00:00
dependabot[bot]
71614fa6ff
Bump com.google.firebase:firebase-messaging from 25.0.2 to 25.1.0
Bumps [com.google.firebase:firebase-messaging](https://github.com/firebase/firebase-android-sdk) from 25.0.2 to 25.1.0.
- [Commits](https://github.com/firebase/firebase-android-sdk/commits)

---
updated-dependencies:
- dependency-name: com.google.firebase:firebase-messaging
  dependency-version: 25.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-22 08:17:17 +00:00
77 changed files with 1639 additions and 534 deletions

View file

@ -1,5 +0,0 @@
{
"projects": {
"default": "vectras-1678b"
}
}

View file

@ -32,6 +32,7 @@ body:
options:
- Latest
- Beta
- 4.3.0
- 4.2.0
- 4.1.0
- 4.0.0

View file

@ -12,8 +12,8 @@ android {
applicationId "com.vectras.vm"
minSdk minApi
targetSdk targetApi
versionCode 134
versionName "4.3.0"
versionCode 137
versionName "4.3.3"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true

View file

@ -299,6 +299,11 @@
<meta-data
android:name="android.max_aspect"
android:value="10.0" />
<activity
android:name=".settings.EqualizerActivity"
android:exported="false"
android:windowSoftInputMode="adjustResize" />
</application>
</manifest>

View file

@ -1188,6 +1188,18 @@ public class MainSettingsManager extends AppCompatActivity
return prefs.getInt("standardSetupVersion", 0);
}
public static void setCoreSetupVersion(Context context, int value) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor edit = prefs.edit();
edit.putInt("coreSetupVersion", value);
edit.apply();
}
public static int getCoreSetupVersion(Context context) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
return prefs.getInt("coreSetupVersion", 0);
}
public static void setShowLastCrashLog(Context context, Boolean _boolean) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor edit = prefs.edit();
@ -1329,6 +1341,6 @@ public class MainSettingsManager extends AppCompatActivity
public static Boolean getAutoSwitchToExternalMouse(Context context) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
return prefs.getBoolean("autoSwitchToExternalMouse", true);
return prefs.getBoolean("autoSwitchToExternalMouse", false);
}
}

View file

@ -73,7 +73,7 @@ import com.vectras.vm.utils.DialogUtils;
import com.vectras.vm.utils.FileUtils;
import com.vectras.vm.utils.ListUtils;
import com.vectras.vm.utils.SimulateKeyEvent;
import com.vectras.vm.utils.StreamAudio;
import com.vectras.vm.sound.StreamAudio;
import com.vectras.vm.utils.UIUtils;
import com.vectras.vm.view.DynamicBubble;
@ -397,7 +397,7 @@ public class MainVNCActivity extends VncCanvasActivity {
public void onDestroy() {
super.onDestroy();
this.stopTimeListener();
if (streamAudio != null) streamAudio.stop();
if (streamAudio != null) streamAudio.release();
//Terminal.killQemuProcess();
}
@ -774,8 +774,14 @@ public class MainVNCActivity extends VncCanvasActivity {
} catch (InterruptedException e) {
Log.e(TAG, "onMonitor: ", e);
}
monitorMode = true;
vncCanvas.sendMetaKey1(50, 6);
try {
vncCanvas.sendMetaKey1(50, 6);
monitorMode = true;
} catch (NullPointerException e) {
monitorMode = false;
Log.e(TAG, "onMonitor: ", e);
}
});
t.start();

View file

@ -94,7 +94,7 @@ public class AboutActivity extends AppCompatActivity implements View.OnClickList
TextView qemuVersion = findViewById(R.id.qemuVersion);
executor.execute(() -> {
String qemuVersionName = CommandUtils.getQemuVersionName();
String qemuVersionName = CommandUtils.getQemuVersionName(this);
runOnUiThread(() -> {
if (!qemuVersionName.isEmpty()) qemuVersion.setText(qemuVersionName); else getString(R.string.unknow);
});

View file

@ -19,6 +19,7 @@ public class AppConfig {
public static String vectrasVersion;
public static int vectrasVersionCode;
public static final int standardSetupVersion = 922202606;
public static final int coreSetupVersion = 0;
public static String vectrasWebsite = "https://vectras.vercel.app/";
public static String vectrasWebsiteRaw = "https://raw.githubusercontent.com/AnBui2004/Vectras-VM-Emu-Android/refs/heads/master/web/";
public static String bootstrapfileslink = vectrasWebsiteRaw + "/data/setupfiles3.json";
@ -78,13 +79,13 @@ public class AppConfig {
public static String neededPkgs() {
if (DeviceUtils.isArm()) {
return "bash aria2 tar dwm xterm libslirp libslirp-dev pulseaudio-dev glib-dev pixman-dev zlib-dev spice-dev" +
return "sudo bash aria2 tar dwm xterm libslirp libslirp-dev pulseaudio-dev glib-dev pixman-dev zlib-dev spice-dev" +
" libusbredirparser usbredir-dev sdl2 sdl2-dev sdl2_image-dev libepoxy-dev virglrenderer-dev rdma-core fluxbox" +
" libusb libaio ncurses-libs curl libnfs gtk+3.0 gtk+3.0-dev fuse libpulse libseccomp jack pipewire liburing pulseaudio pulseaudio-alsa alsa-plugins-pulse" +
" mesa-dri-gallium mesa-vulkan-swrast vulkan-loader mesa-utils mesa-egl mesa-gbm mesa-vulkan-ati mesa-vulkan-broadcom mesa-vulkan-freedreno mesa-vulkan-panfrost" +
" qemu-audio-sdl capstone libcbor snappy lzo ndctl keyutils-libs vde2-libs libdw libbpf sndio-libs linux-pam fuse3-libs libssh vte3 iasl perl";
} else {
return "bash aria2 tar dwm xterm libslirp libslirp-dev pulseaudio-dev glib-dev pixman-dev zlib-dev spice-dev" +
return "sudo bash aria2 tar dwm xterm libslirp libslirp-dev pulseaudio-dev glib-dev pixman-dev zlib-dev spice-dev" +
" libusbredirparser usbredir-dev sdl2 sdl2-dev sdl2_image-dev libepoxy-dev virglrenderer-dev rdma-core fluxbox" +
" libusb libaio ncurses-libs curl libnfs gtk+3.0 gtk+3.0-dev fuse libpulse libseccomp jack pipewire liburing pulseaudio pulseaudio-alsa alsa-plugins-pulse" +
" mesa-dri-gallium mesa-vulkan-swrast vulkan-loader mesa-utils mesa-egl" +
@ -94,13 +95,13 @@ public class AppConfig {
public static String neededPkgs32bit() {
if (DeviceUtils.isArm()) {
return "bash aria2 tar dwm xterm libslirp libslirp-dev pulseaudio-dev glib-dev pixman-dev zlib-dev spice-dev" +
return "sudo bash aria2 tar dwm xterm libslirp libslirp-dev pulseaudio-dev glib-dev pixman-dev zlib-dev spice-dev" +
" libusbredirparser usbredir-dev sdl2 sdl2-dev sdl2_image-dev libepoxy-dev virglrenderer-dev rdma-core fluxbox" +
" libusb libaio ncurses-libs curl libnfs gtk+3.0 gtk+3.0-dev fuse libpulse libseccomp jack pipewire liburing pulseaudio pulseaudio-alsa alsa-plugins-pulse" +
" mesa-dri-gallium mesa-vulkan-swrast vulkan-loader mesa-utils mesa-egl" +
" qemu-audio-sdl capstone libcbor snappy lzo ndctl keyutils-libs vde2-libs libdw libbpf sndio-libs linux-pam fuse3-libs libssh vte3 iasl perl";
} else {
return "bash aria2 tar dwm xterm libslirp libslirp-dev pulseaudio-dev glib-dev pixman-dev zlib-dev spice-dev" +
return "sudo bash aria2 tar dwm xterm libslirp libslirp-dev pulseaudio-dev glib-dev pixman-dev zlib-dev spice-dev" +
" libusbredirparser usbredir-dev sdl2 sdl2-dev sdl2_image-dev libepoxy-dev virglrenderer-dev rdma-core fluxbox" +
" libusb libaio ncurses-libs curl libnfs gtk+3.0 gtk+3.0-dev fuse libpulse libseccomp jack pipewire liburing pulseaudio pulseaudio-alsa alsa-plugins-pulse" +
" mesa-dri-gallium mesa-vulkan-swrast vulkan-loader mesa-utils mesa-egl" +
@ -119,4 +120,5 @@ public class AppConfig {
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";
public static boolean isGmsAvailable = false;
}

View file

@ -23,7 +23,7 @@ import com.vectras.vm.R;
import com.vectras.vm.manager.VmFileManager;
import com.vectras.vm.utils.DialogUtils;
import com.vectras.vm.utils.ProgressDialog;
import com.vectras.vterm.Terminal;
import com.vectras.vterm.Terminal2;
import java.io.File;
import java.util.Objects;
@ -99,8 +99,8 @@ public class CreateImageDialogFragment extends DialogFragment {
progressDialog.show();
new Thread(() -> {
String result = Terminal.executeShellCommandWithResult("qemu-img create -f qcow2 \"" + folder + Objects.requireNonNull(imageName.getText()) + ".qcow2\" " +
Objects.requireNonNull(imageSize.getText()) + "G", requireActivity()).trim();
String result = new Terminal2(requireContext()).executeOnThisThread("qemu-img create -f qcow2 \"" + folder + Objects.requireNonNull(imageName.getText()) + ".qcow2\" " +
Objects.requireNonNull(imageSize.getText()) + "G").trim();
if (isMarkPendingAdd) VmFileManager.markPendingAdd(folder + Objects.requireNonNull(imageName.getText()) + ".qcow2");
requireActivity().runOnUiThread(() -> {

View file

@ -1,5 +1,6 @@
package com.vectras.vm;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
@ -8,21 +9,28 @@ import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.util.Log;
import androidx.core.app.NotificationCompat;
import com.vectras.vm.main.core.MainStartVM;
import com.vectras.vm.manager.VmServiceManager;
import com.vectras.vm.utils.ClipboardUltils;
import com.vectras.vm.utils.DialogUtils;
import com.vectras.vterm.Terminal;
import com.vectras.vterm.Terminal2;
import java.util.Objects;
public class MainService extends Service {
public static String CHANNEL_ID = "Vectras VM Service";
private final int NOTIFICATION_ID = 1;
private final String MACHINE_NAME = "Vectras VM";
public static String vmName = "Vectras VM";
public static String env = null;
private String TAG = "MainService";
private static String TAG = "MainService";
public static MainService service;
public static Context activityContext;
@ -42,7 +50,7 @@ public class MainService extends Service {
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle(getString(R.string.app_name))
.setContentText(MACHINE_NAME + " running in background.")
.setContentText("The virtual machines are running...")
.setSmallIcon(R.drawable.ic_vectras_vm_48)
.addAction(R.drawable.close_24px, getString(R.string.stop), pStopSelf)
.build();
@ -51,8 +59,7 @@ public class MainService extends Service {
if (env != null) {
if (service != null) {
Terminal vterm = new Terminal(activityContext);
vterm.executeShellCommand2(env, true, activityContext);
startCommand(vmName, env, activityContext);
}
} else {
Log.e(TAG, "env is null");
@ -72,10 +79,13 @@ public class MainService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null && Objects.equals(intent.getAction(), STOP_ACTION)) {
VMManager.killallqemuprocesses(this);
stopForeground(true);
stopSelf();
return START_NOT_STICKY;
new Thread(() -> {
VMManager.killallqemuprocesses(this);
new Handler(Looper.getMainLooper()).post(() -> {
stopForeground(true);
stopSelf();
});
}).start();
}
return START_NOT_STICKY;
}
@ -97,8 +107,55 @@ public class MainService extends Service {
}
}
public static void startCommand(String _env, Context _context) {
Terminal vterm = new Terminal(_context);
vterm.executeShellCommand2(_env, true, _context);
public static void startCommand(String vmName, String env, Context context) {
Terminal2 terminal2 = new Terminal2(activityContext);
terminal2.setDefaultShellBash();
terminal2.setStartup("export XDG_RUNTIME_DIR=/tmp && unset PULSE_SERVER");
terminal2.execute(env, new Terminal2.Terminal2Callback() {
@Override
public void onRunning(String command, String newLine) {
// Nothing to do.
}
@Override
public void onFinished(String command, String log, int status) {
if (context instanceof Activity activity) {
if (activity.isFinishing() || activity.isDestroyed()) {
return;
}
} else {
Log.e(TAG, "context is not an Activity");
return;
}
if (!(log.trim().isEmpty() || log.trim().equals(MainStartVM.TAG_FINISHED_WITHOUT_ERROR))) {
new Handler(Looper.getMainLooper()).post(() -> {
if (!VMManager.isExecutedCommandError(command, log, context)) {
String finalLog = log.contains(MainStartVM.TAG_FINISHED_WITHOUT_ERROR) ? log.substring(0, log.lastIndexOf(MainStartVM.TAG_FINISHED_WITHOUT_ERROR) - 1) : log;
DialogUtils.twoDialog(context, vmName, finalLog, context.getString(R.string.copy), context.getString(R.string.close), true, R.drawable.stack_24px, true,
() -> ClipboardUltils.copyToClipboard(context, log), null, null);
}
});
}
VmServiceManager.stopService(context);
}
@Override
public void onError(String command, Exception exception) {
if (context instanceof Activity activity) {
if (activity.isFinishing() || activity.isDestroyed()) {
return;
}
} else {
Log.e(TAG, "context is not an Activity");
return;
}
new Handler(Looper.getMainLooper()).post(() -> DialogUtils.twoDialog(context, "Execution Result", exception.getMessage(), context.getString(R.string.copy), context.getString(R.string.close), true, R.drawable.round_terminal_24, true,
() -> ClipboardUltils.copyToClipboard(context, exception.getMessage()), null, null));
}
});
}
}

View file

@ -126,7 +126,12 @@ public class SplashActivity extends AppCompatActivity {
if (MainSettingsManager.getShowLastCrashLog(this)) {
startActivity(new Intent(this, LastCrashActivity.class));
} else if (SetupFeatureCore.isInstalledQemu(this)) {
if (MainSettingsManager.getStandardSetupVersion(this) != AppConfig.standardSetupVersion &&
if (MainSettingsManager.getCoreSetupVersion(this) != AppConfig.coreSetupVersion) {
Intent intent = new Intent();
intent.putExtra("action", SetupWizard2Activity.ACTION_CORE_SYSTEM_UPDATE);
intent.setClass(this, SetupWizard2Activity.class);
startActivity(intent);
} else if (MainSettingsManager.getStandardSetupVersion(this) != AppConfig.standardSetupVersion &&
!MainSettingsManager.getsetUpWithManualSetupBefore(this)) {
Intent intent = new Intent();
intent.putExtra("action", SetupWizard2Activity.ACTION_SYSTEM_UPDATE);

View file

@ -19,6 +19,7 @@ import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
import com.vectras.qemu.Config;
import com.vectras.qemu.MainSettingsManager;
@ -37,6 +38,7 @@ import com.vectras.vm.utils.JSONUtils;
import com.vectras.vm.utils.ProgressDialog;
import com.vectras.vm.utils.TextUtils;
import com.vectras.vterm.Terminal;
import com.vectras.vterm.Terminal2;
import org.jetbrains.annotations.Contract;
@ -70,8 +72,14 @@ public class VMManager {
if (!JSONUtils.isValidFromString(vmJsonListContent) || vmId.isEmpty()) return false;
ArrayList<HashMap<String, Object>> vmList = new Gson().fromJson(vmJsonListContent, new TypeToken<ArrayList<HashMap<String, Object>>>() {
}.getType());
ArrayList<HashMap<String, Object>> vmList;
try {
vmList = new Gson().fromJson(vmJsonListContent, new TypeToken<ArrayList<HashMap<String, Object>>>() {
}.getType());
} catch (JsonSyntaxException e) {
return JSONUtils.isValidFromFile(VmFileManager.getConfigFile(vmId));
}
if (vmList == null) return false;
@ -658,8 +666,6 @@ public class VMManager {
return false;
}
VmServiceManager.stopService(_activity);
if (_command.contains("qemu-system") && _result.contains("Killed")) {
isQemuStopedWithError = true;
return true;
@ -727,7 +733,7 @@ public class VMManager {
},
null, null);
return false;
} else if (_command.contains("qemu-system") && _result.contains("qemu-system") && !_result.contains("warning:")) {
} else if (!_result.contains(MainStartVM.TAG_FINISHED_WITHOUT_ERROR)) {
//Error code: UNKNOW_ERROR
DialogUtils.oneDialog(_activity, _activity.getString(R.string.problem_has_been_detected), _activity.getString(R.string.vm_could_not_be_run_content) + "\n\n" + _result, R.drawable.error_96px);
_activity.stopService(new Intent(_activity, MainService.class));
@ -841,7 +847,7 @@ public class VMManager {
}
public static boolean isVMRunning(Context context, String vmID) {
String result = Terminal.executeShellCommandWithResult("ps -e", context);
String result = new Terminal2(context).executeOnThisThread("ps -e");
if (result.contains(" -qmp unix:" + Config.getLocalQMPSocketPath(vmID))) {
Log.d("VMManager.isThisVMRunning", "Yes.\n\n" + result);
return true;
@ -858,18 +864,6 @@ public class VMManager {
return exists;
}
public static boolean isQemuRunning(Activity activity) {
Terminal vterm = new Terminal(activity);
vterm.executeShellCommand2("ps -e", false, activity);
if (AppConfig.temporaryLastedTerminalOutput.contains("qemu-system")) {
Log.d("VMManager.isQemuRunning", "Yes");
return true;
} else {
Log.d("VMManager.isQemuRunning", "No");
return false;
}
}
public static boolean isHaveADisk(String env) {
return env.contains("-drive") || env.contains("-hda") || env.contains("-hdb") || env.contains("-cdrom") || env.contains("-fda") || env.contains("-fdb");
}
@ -902,20 +896,8 @@ public class VMManager {
}, null, null);
}
public static void killcurrentqemuprocess(Activity activity) {
Terminal vterm = new Terminal(activity);
String qemuProcess = switch (MainSettingsManager.getArch(activity)) {
case "ARM64" -> "qemu-system-aarch64";
case "PPC" -> "qemu-system-ppc";
case "I386" -> "qemu-system-i386";
default -> "qemu-system-x86_64";
};
vterm.executeShellCommand2("killall -15 " + qemuProcess + "; sleep 1; killall -9 " + qemuProcess, false, null);
}
public static void killallqemuprocesses(Context context) {
Terminal vterm = new Terminal(context);
vterm.executeShellCommand2("pkill -15 -f qemu-system-; sleep 1; pkill -9 -f qemu-system-", false, null);
new Terminal2(context).executeOnThisThread("pkill -15 -f qemu-system-; sleep 1; pkill -9 -f qemu-system-");
}
public static String startMigrate() {

View file

@ -19,11 +19,11 @@ import com.google.firebase.crashlytics.FirebaseCrashlytics;
import com.vectras.qemu.Config;
import com.vectras.qemu.MainSettingsManager;
import com.vectras.vm.utils.FileUtils;
import com.vectras.vm.utils.GmsChecker;
import com.vectras.vm.utils.PackageUtils;
import com.vectras.vm.utils.UIUtils;
import java.lang.ref.WeakReference;
import java.util.Locale;
public class VectrasApp extends Application {
public static VectrasApp vectrasapp;
@ -97,8 +97,14 @@ public class VectrasApp extends Application {
}
});
FirebaseAnalytics.getInstance(this).logEvent(FirebaseAnalytics.Event.APP_OPEN, null);
FirebaseCrashlytics.getInstance().log("App started: " + AppConfig.vectrasVersion);
if (GmsChecker.isAvailable(this)) {
AppConfig.isGmsAvailable = true;
FirebaseAnalytics.getInstance(this).logEvent(FirebaseAnalytics.Event.APP_OPEN, null);
} else {
FirebaseCrashlytics.getInstance().log("Device does not support GMS.");
}
}
private void setupTheme() {

View file

@ -5,12 +5,15 @@ import android.content.ComponentName;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback;
import androidx.activity.result.ActivityResultLauncher;
@ -63,6 +66,7 @@ import java.util.concurrent.Executors;
import com.vectras.vm.utils.ZipUtils;
import com.vectras.vterm.Terminal;
import com.vectras.vm.utils.PermissionUtils;
import com.vectras.vterm.Terminal2;
public class VMCreatorActivity extends AppCompatActivity {
@ -309,9 +313,28 @@ public class VMCreatorActivity extends AppCompatActivity {
setDefault();
if (MainSettingsManager.autoCreateDisk(this)) {
if (createVMFolder(true)) {
Terminal vterm = new Terminal(this);
vterm.executeShellCommand2("qemu-img create -f qcow2 " + VmFileManager.getPath(vmID, "disk.qcow2") + " 128G", false, this);
binding.drive.setText(VmFileManager.getPath(vmID, "disk.qcow2"));
Terminal2 terminal2 = new Terminal2(this);
terminal2.setShowProgressDialog(true);
terminal2.execute("qemu-img create -f qcow2 " + VmFileManager.getPath(vmID, "disk.qcow2") + " 128G", new Terminal2.Terminal2Callback() {
@Override
public void onRunning(String command, String newLine) {
// Nothing to do.
}
@Override
public void onFinished(String command, String log, int status) {
new Handler(Looper.getMainLooper()).post(() -> {
if (status == terminal2.SUCCESS)
binding.drive.setText(VmFileManager.getPath(vmID, "disk.qcow2"));
});
}
@Override
public void onError(String command, Exception exception) {
new Handler(Looper.getMainLooper()).post(() -> Toast.makeText(getApplicationContext(), getString(R.string.an_error_occurred_while_creating_the_virtual_drive), Toast.LENGTH_SHORT).show());
}
});
}
} else {
setDrive(SELECT_DISK_0_FILE_MODE, null);
@ -1156,7 +1179,7 @@ public class VMCreatorActivity extends AppCompatActivity {
private void showProgressDialog(String message) {
dialogProgressStyleBinding.progressText.setText(message);
dialogProgressStyleBinding.progressBar.setIndeterminate(true);
progressDialog.show();
if (!isFinishing() && !isDestroyed()) progressDialog.show();
}
private String copyToTemp(Uri uri) throws IOException {

View file

@ -3,11 +3,14 @@ package com.vectras.vm.fcm;
import android.util.Log;
import com.google.firebase.messaging.FirebaseMessaging;
import com.vectras.vm.AppConfig;
public class FCMManager {
private static final String TAG = "FCMManager";
public static void subscribe() {
if (!AppConfig.isGmsAvailable) return;
FirebaseMessaging.getInstance()
.subscribeToTopic("vectrasvmandroidgithub")
.addOnCompleteListener(task -> {
@ -20,6 +23,8 @@ public class FCMManager {
}
public static void unSubscribe() {
if (!AppConfig.isGmsAvailable) return;
FirebaseMessaging.getInstance()
.unsubscribeFromTopic("vectrasvmandroidgithub")
.addOnCompleteListener(task -> {

View file

@ -6,6 +6,8 @@ import static com.vectras.vm.utils.LibraryChecker.isPackageInstalled2;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import com.termux.app.TermuxService;
@ -21,6 +23,7 @@ import com.vectras.vm.utils.FileUtils;
import com.vectras.vm.utils.PackageUtils;
import com.vectras.vm.x11.X11Activity;
import com.vectras.vterm.Terminal;
import com.vectras.vterm.Terminal2;
import java.io.File;
import java.util.HashSet;
@ -86,8 +89,32 @@ public class DisplaySystem {
R.drawable.desktop_24px,
true,
() -> {
String installCommand = "apk add " + necessaryPackage;
new Terminal(context).executeShellCommand(installCommand, true, true, context.getString(R.string.just_a_moment), context);
String installCommand = "apk add " + necessaryPackage + " && echo \"Installed: " + necessaryPackage + "\"";
Terminal2 terminal2 = new Terminal2(context);
terminal2.setShowProgressDialog(true);
terminal2.execute(installCommand, new Terminal2.Terminal2Callback() {
@Override
public void onRunning(String command, String newLine) {
// Nothing to do.
}
@Override
public void onFinished(String command, String log, int status) {
new Handler(Looper.getMainLooper()).post(() -> {
if (status == terminal2.SUCCESS) {
launchX11(context, isKill);
} else {
DialogUtils.oopsDialog(context, log);
}
});
}
@Override
public void onError(String command, Exception exception) {
new Handler(Looper.getMainLooper()).post(() -> DialogUtils.oopsDialog(context, exception.getMessage()));
}
});
},
null,
null
@ -116,7 +143,9 @@ public class DisplaySystem {
}
public static void startDesktop(Context context) {
new Terminal(context).executeShellCommand2("export DISPLAY=:0 && fluxbox > /dev/null", false, context);
Terminal2 terminal2 = new Terminal2(context);
terminal2.setDefaultShellBash();
terminal2.execute("export DISPLAY=:0 && fluxbox > /dev/null");
}
public static void startTermuxX11(Context context) {

View file

@ -35,6 +35,7 @@ import java.io.File;
public class MainStartVM {
public static final String TAG = "HomeStartVM";
public static final String TAG_FINISHED_WITHOUT_ERROR = "This VM has shut down.";
public static boolean skipIDEwithARM64DialogInStartVM = false;
public static boolean breakNow = false;
public static final Handler handlerForLaunch = new Handler(Looper.getMainLooper());
@ -50,7 +51,7 @@ public class MainStartVM {
public static String pendingVMID = "";
public static String pendingThumbnailFile = "";
public static boolean isLaunchFromPending = false;
public static String runCommandFormat = "export TMPDIR=/tmp && mkdir -p $TMPDIR/pulse && export XDG_RUNTIME_DIR=/tmp && chmod -R 775 $TMPDIR/pulse && pulseaudio --start --exit-idle-time=-1 > /dev/null 2>&1 && %s";
public static String runCommandFormat = "%s";
private static StartVmDialog dialog;
@ -297,7 +298,7 @@ public class MainStartVM {
) {
VMManager.isQemuStopedWithError = false;
String cleanUpCommand = "; rm -r " + Config.getCacheVMPath(vmID);
String cleanUpCommand = " && echo \"" + TAG_FINISHED_WITHOUT_ERROR + "\"\nrm -r " + Config.getCacheVMPath(vmID);
String finalCommand = VMManager.addAudioDevWav(vmID, String.format(runCommandFormat, env + cleanUpCommand));
@ -314,11 +315,12 @@ public class MainStartVM {
}
if (ServiceUtils.isServiceRunning(context, MainService.class)) {
MainService.startCommand(finalCommand, context);
MainService.startCommand(vmName, finalCommand, context);
} else {
Intent serviceIntent = new Intent(context, MainService.class);
MainService.activityContext = context;
MainService.env = finalCommand;
MainService.vmName = vmName;
MainService.CHANNEL_ID = vmName;
if (SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(serviceIntent);
@ -488,6 +490,6 @@ public class MainStartVM {
}
public static void setDefault() {
runCommandFormat = "export TMPDIR=/tmp && mkdir -p $TMPDIR/pulse && export XDG_RUNTIME_DIR=/tmp && chmod -R 775 $TMPDIR/pulse && pulseaudio --start --exit-idle-time=-1 > /dev/null 2>&1 && %s";
runCommandFormat = "%s";
}
}

View file

@ -1,6 +1,8 @@
package com.vectras.vm.main.core;
import android.app.Activity;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;
@ -13,6 +15,7 @@ import com.vectras.vm.manager.VmFileManager;
import com.vectras.vm.utils.DialogUtils;
import com.vectras.vm.utils.FileUtils;
import com.vectras.vterm.Terminal;
import com.vectras.vterm.Terminal2;
public class PendingCommand {
private static final String TAG = "PendingCommand";
@ -49,9 +52,30 @@ public class PendingCommand {
null
);
} else {
Terminal _vterm = new Terminal(activity);
_vterm.executeShellCommand2(command, false, activity);
Toast.makeText(activity, activity.getResources().getString(R.string.done), Toast.LENGTH_LONG).show();
Terminal2 terminal2 = new Terminal2(activity);
terminal2.setShowProgressDialog(true);
terminal2.execute(command, new Terminal2.Terminal2Callback() {
@Override
public void onRunning(String command, String newLine) {
// Nothing to do.
}
@Override
public void onFinished(String command, String log, int status) {
new Handler(Looper.getMainLooper()).post(() -> {
if (status == terminal2.SUCCESS) {
Toast.makeText(activity, activity.getResources().getString(R.string.done), Toast.LENGTH_LONG).show();
} else {
Toast.makeText(activity, activity.getResources().getString(R.string.an_error_occurred_while_creating_the_virtual_drive), Toast.LENGTH_LONG).show();
}
});
}
@Override
public void onError(String command, Exception exception) {
new Handler(Looper.getMainLooper()).post(() -> Toast.makeText(activity, activity.getResources().getString(R.string.an_error_occurred_while_creating_the_virtual_drive), Toast.LENGTH_LONG).show());
}
});
}
command = "";

View file

@ -3,6 +3,7 @@ package com.vectras.vm.main.core;
import android.app.Activity;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
@ -92,31 +93,38 @@ public class StartVmDialog {
boolean isVmRuning = VMManager.isVMRunning(activity, vmId);
while (isVmRuning) {
Config.vmID = vmId;
if (!isVmRuning) {
new Handler(Looper.getMainLooper()).post(() -> {
isShuttingDown = false;
dismiss();
});
} else {
while (true) {
Config.vmID = vmId;
isVmRuning = VMManager.isVMRunning(activity, vmId);
isVmRuning = VMManager.isVMRunning(activity, vmId);
QmpSender.shutdown();
QmpSender.shutdown();
if (!isVmRuning || VMManager.isQemuStopedWithError || triedCount == MAX_TRY) {
if (triedCount < MAX_TRY) FileUtils.delete(Config.getLocalQMPSocketPath(vmId));
if (!isVmRuning || VMManager.isQemuStopedWithError || triedCount >= MAX_TRY) {
if (triedCount < MAX_TRY) FileUtils.delete(Config.getLocalQMPSocketPath(vmId));
new Handler(Looper.getMainLooper()).post(() -> {
isShuttingDown = false;
dismiss();
});
new Handler(Looper.getMainLooper()).post(() -> {
isShuttingDown = false;
dismiss();
});
break;
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException ignored) {
}
triedCount++;
}
try {
Thread.sleep(1000);
} catch (InterruptedException ignored) {
}
triedCount++;
}
}).start();
});
@ -126,7 +134,7 @@ public class StartVmDialog {
.setCancelable(false)
.create();
progressDialog.show();
if (!activity.isFinishing() && !activity.isDestroyed()) progressDialog.show();
}
public void setStatus(String status) {

View file

@ -34,6 +34,7 @@ import com.vectras.vm.manager.QmpSender;
import com.vectras.vm.utils.CommandUtils;
import com.vectras.vm.utils.DialogUtils;
import com.vectras.vterm.Terminal;
import com.vectras.vterm.Terminal2;
import java.io.File;
import java.util.Locale;
@ -229,10 +230,10 @@ public class SystemMonitorFragment extends Fragment {
binding.tvQemuarch.setText(getString(R.string.arch) + " " + currentArch + ".");
executor.execute(() -> {
String qemuVersionName = CommandUtils.getQemuVersionName();
String qemuVersionName = CommandUtils.getQemuVersionName(requireContext());
if (!isAdded()) return;
String result = Terminal.executeShellCommandWithResult("ps -e command && echo \"psendhere\" && cat /proc/cpuinfo", requireActivity());
String result = new Terminal2(requireContext()).executeOnThisThread("ps -e command && echo \"psendhere\" && cat /proc/cpuinfo");
if (!isAdded()) return;
requireActivity().runOnUiThread(() -> {

View file

@ -6,6 +6,7 @@ import com.vectras.vm.setupwizard.SetupFeatureCore;
import com.vectras.vm.utils.DeviceUtils;
import com.vectras.vm.utils.FileUtils;
import com.vectras.vterm.Terminal;
import com.vectras.vterm.Terminal2;
import java.io.File;
@ -51,7 +52,7 @@ public class BatteryEmulatorManager {
FileUtils.writeToFile(vmTemp.getAbsolutePath(), "battery.asl", batteryAsl);
if (FileUtils.isFileExists(VmFileManager.getTempPath(context, vmId, "battery.asl"))) {
String respond = Terminal.executeShellCommandWithResult("cd " + vmTemp.getAbsolutePath() + " && iasl battery.asl && echo Compiled", context);
String respond = new Terminal2(context).executeOnThisThread("cd " + vmTemp.getAbsolutePath() + " && iasl battery.asl && echo Compiled");
//Log.d(TAG, respond);
return respond.contains("Compiled");
} else {

View file

@ -10,6 +10,7 @@ import com.vectras.vm.utils.ClipboardUltils;
import com.vectras.vm.utils.DialogUtils;
import com.vectras.vm.utils.ProgressDialog;
import com.vectras.vterm.Terminal;
import com.vectras.vterm.Terminal2;
public class HostSharedFolder {
public static final String TAG = "HostSharedFolder";
@ -25,13 +26,16 @@ public class HostSharedFolder {
new Thread(() -> {
boolean isRuning;
if (Terminal.executeShellCommandWithResult("pgrep -f 'http.server 19000'", context).isEmpty()) {
if (!Terminal.executeShellCommandWithResult("which python3", context).contains("python3"))
Terminal.executeShellCommandWithResult("apk add python3", context);
startCommand(context);
Terminal2 terminal2 = new Terminal2(context);
isRuning = !Terminal.executeShellCommandWithResult("sleep 1; pgrep -f 'http.server 19000'", context).isEmpty();
if (terminal2.executeOnThisThread("pgrep -f 'http.server 19000'").isEmpty()) {
if (!terminal2.executeOnThisThread("which python3").contains("python3"))
terminal2.executeOnThisThread("apk add python3");
new Terminal2(context).execute("python3 -m http.server 19000 --bind 127.0.0.1 --directory " + SHARED_DIR);
isRuning = !terminal2.executeOnThisThread("sleep 1; pgrep -f 'http.server 19000'").isEmpty();
} else {
isRuning = true;
}
@ -61,9 +65,4 @@ public class HostSharedFolder {
});
}).start();
}
public static void startCommand(Context _context) {
Terminal vterm = new Terminal(_context);
vterm.executeShellCommand2("python3 -m http.server 19000 --bind 127.0.0.1 --directory " + SHARED_DIR, false, null);
}
}

View file

@ -2,10 +2,10 @@ package com.vectras.vm.manager;
import android.content.Context;
import com.vectras.vterm.Terminal;
import com.vectras.vterm.Terminal2;
public class ProcessManager {
public static boolean isQemuRunning(Context context) {
return Terminal.executeShellCommandWithResult("ps -e", context).contains("qemu-system-");
return new Terminal2(context).executeOnThisThread("ps -e").contains("qemu-system-");
}
}

View file

@ -4,14 +4,14 @@ import android.content.Context;
import com.vectras.qemu.MainSettingsManager;
import com.vectras.vm.settings.ItemSettingsSelector;
import com.vectras.vterm.Terminal;
import com.vectras.vterm.Terminal2;
public class QemuManager {
public static final int DEFAULT_REFRESH_RATE = 60;
public static final int DEFAULT_LOW_REFRESH_RATE = 30;
public static boolean isSupportSetRefreshRate(Context context) {
return Terminal.executeShellCommandWithResult(getQemuExecutableFile(context) + " -vnc help", context).contains("refresh-rate");
return new Terminal2(context).executeOnThisThread(getQemuExecutableFile(context) + " -vnc help").contains("refresh-rate");
}
public static boolean isSupportAcpiTable(Context context) {

View file

@ -363,7 +363,7 @@ public class QmpSender {
public static String changeDevice(String deviceId, String filePath) {
if (filePath.trim().isEmpty()) return ejectDevice(deviceId);
return send("change " + deviceId + " " + filePath);
return send("change " + deviceId + " \"" + filePath + "\"");
}
public static void quickEjectDevice(Context context, String deviceId) {

View file

@ -5,7 +5,7 @@ import android.util.Log;
import com.vectras.qemu.Config;
import com.vectras.vm.VectrasApp;
import com.vectras.vm.utils.FileUtils;
import com.vectras.vm.utils.StreamAudio;
import com.vectras.vm.sound.StreamAudio;
public class VmAudioManager {
private static final String TAG = "VmAudioManager";

View file

@ -32,7 +32,7 @@ import com.vectras.vm.settings.VNCSettingsActivity;
import com.vectras.vm.utils.DialogUtils;
import com.vectras.vm.utils.FileUtils;
import com.vectras.vm.utils.ProgressDialog;
import com.vectras.vm.utils.StreamAudio;
import com.vectras.vm.sound.StreamAudio;
import com.vectras.vm.x11.X11Activity;
import java.io.File;
@ -667,7 +667,14 @@ public class VmControllerDialog extends DialogFragment {
if (uri != null) {
try {
new Thread(() -> {
File selectedFilePath = new File(Objects.requireNonNull(FileUtils.getPath(getContext(), uri)));
String filePath = FileUtils.getPath(getContext(), uri);
if (filePath == null) {
showErrorSelectedFileDialog();
return;
}
File selectedFilePath = new File(filePath);
if (isAdded()) {
requireActivity().runOnUiThread(() -> {

View file

@ -0,0 +1,195 @@
package com.vectras.vm.settings;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import com.vectras.vm.R;
import com.vectras.vm.databinding.ActivityEqualizerBinding;
import com.vectras.vm.sound.AudioSettingsData;
import com.vectras.vm.sound.SoundEffect;
import java.text.DecimalFormat;
import java.util.Objects;
public class EqualizerActivity extends AppCompatActivity {
ActivityEqualizerBinding binding;
AudioSettingsData audioSettingsData;
float[] lastValues = new float[5];
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.undo) {
binding.sliderUpperTreble.setValue(lastValues[0]);
binding.sliderTreble.setValue(lastValues[1]);
binding.sliderMid.setValue(lastValues[2]);
binding.sliderBass.setValue(lastValues[3]);
binding.sliderLowBass.setValue(lastValues[4]);
return true;
} else if (id == R.id.reset) {
binding.sliderUpperTreble.setValue(0);
binding.sliderTreble.setValue(0);
binding.sliderMid.setValue(0);
binding.sliderBass.setValue(0);
binding.sliderLowBass.setValue(0);
return true;
} else {
return super.onOptionsItemSelected(item);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.equalizer_menu, menu);
return true;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
binding = ActivityEqualizerBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true);
binding.toolbar.setNavigationOnClickListener(view -> finish());
initialize();
}
@Override
public void onPause() {
super.onPause();
if (audioSettingsData != null) {
audioSettingsData.setEqualizerEnabled(binding.swEnabled.isChecked());
audioSettingsData.setUpperTreble(binding.sliderUpperTreble.getValue());
audioSettingsData.setTreble(binding.sliderTreble.getValue());
audioSettingsData.setMid(binding.sliderMid.getValue());
audioSettingsData.setBass(binding.sliderBass.getValue());
audioSettingsData.setLowBass(binding.sliderLowBass.getValue());
}
}
private void initialize() {
SoundEffect soundEffect = new SoundEffect(this, 0);
short bands = soundEffect.equalizer.getNumberOfBands();
if (bands < 2) {
binding.lnUnsupport.setVisibility(View.VISIBLE);
binding.appbar.setVisibility(View.GONE);
binding.lnEnabled.setVisibility(View.GONE);
binding.lnAllOptions.setVisibility(View.GONE);
binding.btnExit.setOnClickListener(v -> finish());
return;
}
audioSettingsData = new AudioSettingsData(this);
lastValues[0] = audioSettingsData.getUpperTreble();
lastValues[1] = audioSettingsData.getTreble();
lastValues[2] = audioSettingsData.getMid();
lastValues[3] = audioSettingsData.getBass();
lastValues[4] = audioSettingsData.getLowBass();
// 1dB = 100mB
int minBandLevelRange = soundEffect.getMinBandLevelRange() / 100;
int maxBandLevelRange = soundEffect.getMaxBandLevelRange() / 100;
if (bands > 4) {
binding.sliderLowBass.setValueFrom(minBandLevelRange);
binding.sliderLowBass.setValueTo(maxBandLevelRange);
binding.sliderLowBass.setValue(audioSettingsData.getLowBass());
binding.tvLowBassValue.setText(getFormattedValue(binding.sliderLowBass.getValue()));
binding.sliderLowBass.addOnChangeListener((slider, value, fromUser) -> binding.tvLowBassValue.setText(getFormattedValue(value)));
} else {
binding.lnLowBass.setVisibility(View.GONE);
binding.tvUpperTreble.setText(R.string.upper_treble);
binding.tvTreble.setText(R.string.treble);
binding.tvMid.setText(R.string.bass);
binding.tvBass.setText(R.string.low_bass);
}
if (bands > 3) {
binding.sliderBass.setValueFrom(minBandLevelRange);
binding.sliderBass.setValueTo(maxBandLevelRange);
binding.sliderBass.setValue(audioSettingsData.getBass());
binding.tvBassValue.setText(getFormattedValue(binding.sliderBass.getValue()));
binding.sliderBass.addOnChangeListener((slider, value, fromUser) -> binding.tvBassValue.setText(getFormattedValue(value)));
} else {
binding.lnBass.setVisibility(View.GONE);
binding.tvUpperTreble.setText(R.string.treble);
binding.tvTreble.setText(R.string.mid);
binding.tvMid.setText(R.string.bass);
}
if (bands > 2) {
binding.sliderMid.setValueFrom(minBandLevelRange);
binding.sliderMid.setValueTo(maxBandLevelRange);
binding.sliderMid.setValue(audioSettingsData.getMid());
binding.tvMidValue.setText(getFormattedValue(binding.sliderMid.getValue()));
binding.sliderMid.addOnChangeListener((slider, value, fromUser) -> binding.tvMidValue.setText(getFormattedValue(value)));
} else {
binding.lnMid.setVisibility(View.GONE);
binding.tvUpperTreble.setText(R.string.treble);
binding.tvTreble.setText(R.string.bass);
}
binding.sliderUpperTreble.setValueFrom(minBandLevelRange);
binding.sliderUpperTreble.setValueTo(maxBandLevelRange);
binding.sliderTreble.setValueFrom(minBandLevelRange);
binding.sliderTreble.setValueTo(maxBandLevelRange);
binding.sliderUpperTreble.setValue(audioSettingsData.getUpperTreble());
binding.sliderTreble.setValue(audioSettingsData.getTreble());
binding.tvUpperTrebleValue.setText(getFormattedValue(binding.sliderUpperTreble.getValue()));
binding.tvTrebleValue.setText(getFormattedValue(binding.sliderTreble.getValue()));
binding.sliderUpperTreble.addOnChangeListener((slider, value, fromUser) -> binding.tvUpperTrebleValue.setText(getFormattedValue(value)));
binding.sliderTreble.addOnChangeListener((slider, value, fromUser) -> binding.tvTrebleValue.setText(getFormattedValue(value)));
binding.lnEnabled.setOnClickListener(v -> binding.swEnabled.toggle());
binding.swEnabled.setChecked(audioSettingsData.isEqualizerEnabled());
binding.swEnabled.setOnCheckedChangeListener((buttonView, isChecked) -> setEnabled(isChecked));
setEnabled(binding.swEnabled.isChecked());
}
private void setEnabled(boolean isEnabled) {
binding.lnAllOptions.setAlpha(isEnabled ? 1 : 0.5f);
binding.sliderUpperTreble.setEnabled(isEnabled);
binding.sliderTreble.setEnabled(isEnabled);
binding.sliderMid.setEnabled(isEnabled);
binding.sliderBass.setEnabled(isEnabled);
binding.sliderLowBass.setEnabled(isEnabled);
}
DecimalFormat decimalFormat = new DecimalFormat("#.#");
private String getFormattedValue(float value) {
return decimalFormat.format(value) + " dB";
}
}

View file

@ -42,5 +42,7 @@ public class Settings2Activity extends AppCompatActivity {
binding.lnVnc.setOnClickListener(v -> startActivity(new Intent(this, VNCSettingsActivity.class)));
binding.lnX11.setOnClickListener(v -> startActivity(new Intent(this, X11DisplaySettingsActivity.class)));
binding.lnEqualizer.setOnClickListener(v -> startActivity(new Intent(this, EqualizerActivity.class)));
}
}

View file

@ -3,7 +3,6 @@ package com.vectras.vm.setupwizard;
import android.app.Activity;
import android.content.Context;
import android.os.Build;
import android.os.Environment;
import android.util.Log;
import com.vectras.vm.R;

View file

@ -1,9 +1,6 @@
package com.vectras.vm.setupwizard;
import static android.content.Intent.ACTION_VIEW;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
@ -13,10 +10,8 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.transition.TransitionManager;
import android.view.LayoutInflater;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import androidx.activity.OnBackPressedCallback;
import androidx.activity.result.ActivityResultLauncher;
@ -29,7 +24,6 @@ import com.anbui.elephant.retrofit2utils.Retrofit2Utils;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.termux.app.TermuxActivity;
import com.termux.app.TermuxService;
import com.vectras.qemu.MainSettingsManager;
import com.vectras.vm.AppConfig;
import com.vectras.vm.R;
@ -37,7 +31,6 @@ import com.vectras.vm.VMManager;
import com.vectras.vm.creator.VMCreatorSelector;
import com.vectras.vm.databinding.ActivitySetupWizard2Binding;
import com.vectras.vm.databinding.SetupQemuDoneBinding;
import com.vectras.vm.databinding.SimpleLayoutListViewWithCheckBinding;
import com.vectras.vm.main.MainActivity;
import com.vectras.vm.utils.DeviceUtils;
import com.vectras.vm.utils.DialogUtils;
@ -48,14 +41,9 @@ import com.vectras.vm.utils.ListUtils;
import com.vectras.vm.utils.PermissionUtils;
import com.vectras.vm.utils.TarUtils;
import com.vectras.vm.utils.UIUtils;
import com.vectras.vterm.Terminal2;
import com.vectras.vterm.TerminalBottomSheetDialog;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Objects;
@ -66,6 +54,8 @@ public class SetupWizard2Activity extends AppCompatActivity {
ActivitySetupWizard2Binding binding;
SetupQemuDoneBinding bindingFinalSteps;
public static final int ACTION_SYSTEM_UPDATE = 1;
public static final int ACTION_CORE_SYSTEM_UPDATE = 2;
int ACTION;
final int STEP_REQUEST_PERMISSION = 1;
final int STEP_EXTRACTING_SYSTEM_FILES = 2;
final int STEP_GETTING_DATA = 3;
@ -84,7 +74,6 @@ public class SetupWizard2Activity extends AppCompatActivity {
String downloadBootstrapsCommand = "";
String tarPath = "";
String progressText ="0%";
boolean isSystemUpdateMode = false;
boolean isExecutingCommand = false;
boolean isLibProotError = false;
boolean aria2Error = false;
@ -188,7 +177,7 @@ public class SetupWizard2Activity extends AppCompatActivity {
});
binding.btnTryAgain.setOnClickListener(v -> {
if (isSystemUpdateMode) {
if (ACTION == ACTION_SYSTEM_UPDATE) {
uiController(STEP_SYSTEM_UPDATE);
binding.btnSkipSystemUpdate.setVisibility(View.GONE);
} else if (isLibProotError) {
@ -232,16 +221,22 @@ public class SetupWizard2Activity extends AppCompatActivity {
uiController(STEP_EXTRACTING_SYSTEM_FILES);
new Thread(() -> {
VMManager.killallqemuprocesses(this);
FileUtils.delete(getFilesDir().getAbsolutePath() + "/data");
FileUtils.delete(getFilesDir().getAbsolutePath() + "/distro");
FileUtils.delete(getFilesDir().getAbsolutePath() + "/usr");
if (ACTION == ACTION_CORE_SYSTEM_UPDATE) {
FileUtils.delete(getFilesDir().getAbsolutePath() + "/data");
FileUtils.delete(getFilesDir().getAbsolutePath() + "/distro");
FileUtils.delete(getFilesDir().getAbsolutePath() + "/usr");
}
runOnUiThread(this::extractSystemFiles);
}).start();
});
if (getIntent().hasExtra("action")) {
if (getIntent().getIntExtra("action", -1) == ACTION_SYSTEM_UPDATE) {
isSystemUpdateMode = true;
ACTION = getIntent().getIntExtra("action", -1);
if (ACTION == ACTION_CORE_SYSTEM_UPDATE) {
uiController(STEP_SYSTEM_UPDATE);
binding.btnSkipSystemUpdate.setVisibility(View.GONE);
} else if (ACTION == ACTION_SYSTEM_UPDATE) {
uiController(STEP_SYSTEM_UPDATE);
}
}
@ -379,6 +374,11 @@ public class SetupWizard2Activity extends AppCompatActivity {
}
private void extractSystemFiles() {
if (ACTION == ACTION_SYSTEM_UPDATE) {
getDataForStandardSetup();
return;
}
uiController(STEP_EXTRACTING_SYSTEM_FILES);
executor.execute(() -> {
@ -423,7 +423,7 @@ public class SetupWizard2Activity extends AppCompatActivity {
}
}
new Handler(Looper.getMainLooper()).postDelayed(() -> {
if (isSystemUpdateMode) {
if (ACTION == ACTION_SYSTEM_UPDATE) {
startSetup();
} else {
uiController(STEP_SETUP_OPTIONS);
@ -431,6 +431,7 @@ public class SetupWizard2Activity extends AppCompatActivity {
}, 1000);
} else {
new Handler(Looper.getMainLooper()).postDelayed(() -> uiController(STEP_SETUP_OPTIONS), 1000);
Log.e("SetupWizard2Activity", "getDataForStandardSetup: " + error);
}
}));
}
@ -465,32 +466,46 @@ public class SetupWizard2Activity extends AppCompatActivity {
" apk update;" +
" echo \"Installing packages...\";" +
" apk add " + (DeviceUtils.is64bit() ? AppConfig.neededPkgs()
: AppConfig.neededPkgs32bit()) + ";" +
" echo \"Downloading Qemu...\";";
: AppConfig.neededPkgs32bit()) + ";";
if (isCustomSetupMode) {
cmd += " tar -xzvf " + tarPath + " -C /;" +
" rm " + tarPath + ";" +
" chmod 775 /usr/local/bin/*;";
} else {
if (FileUtils.isFileExists(getFilesDir().getAbsolutePath() + "/distro/root/setup.tar.gz"))
FileUtils.delete(getFilesDir().getAbsolutePath() + "/distro/root/setup.tar.gz");
cmd += downloadBootstrapsCommand + ";" +
if (ACTION == ACTION_SYSTEM_UPDATE) {
cmd += "echo \"Uninstalling...\";" +
" rm -f /usr/local/bin/qemu-*;" +
" rm -f /usr/share/applications/qemu.desktop;" +
" rm -f /usr/share/icons/hicolor/*/qemu.png;" +
" rm -rf /usr/share/qemu;" +
downloadBootstrapsCommand + ";" +
" echo \"Installing Qemu...\";" +
" tar -xzvf setup.tar.gz -C /;" +
" rm setup.tar.gz;" +
" chmod 775 /usr/local/bin/*;";
} else {
if (isCustomSetupMode) {
cmd += " echo \"Installing Qemu...\";" +
" tar -xzvf " + tarPath + " -C /;" +
" rm " + tarPath + ";" +
" chmod 775 /usr/local/bin/*;" +
" echo \"Just a sec...\";" +
" mkdir -p ~/.vnc && echo -e \"555555\\n555555\" | vncpasswd -f > ~/.vnc/passwd && chmod 0600 ~/.vnc/passwd;";
} else {
if (FileUtils.isFileExists(getFilesDir().getAbsolutePath() + "/distro/root/setup.tar.gz"))
FileUtils.delete(getFilesDir().getAbsolutePath() + "/distro/root/setup.tar.gz");
cmd += " echo \"Downloading Qemu...\";" +
downloadBootstrapsCommand + ";" +
" echo \"Installing Qemu...\";" +
" tar -xzvf setup.tar.gz -C /;" +
" rm setup.tar.gz;" +
" chmod 775 /usr/local/bin/*;" +
" echo \"Just a sec...\";" +
" mkdir -p ~/.vnc && echo -e \"555555\\n555555\" | vncpasswd -f > ~/.vnc/passwd && chmod 0600 ~/.vnc/passwd;";
}
}
cmd += " echo \"Just a sec...\";" +
" echo export TMPDIR=/tmp >> /etc/profile;" +
" mkdir -p $TMPDIR/pulse;" +
" echo export PULSE_SERVER=127.0.0.1 >> /etc/profile;" +
" mkdir -p ~/.vnc && echo -e \"555555\\n555555\" | vncpasswd -f > ~/.vnc/passwd && chmod 0600 ~/.vnc/passwd;" +
" echo \"Installation successful! xssFjnj58Id\"";
cmd += " echo \"Installation successful! xssFjnj58Id\"";
executeShellCommand(cmd);
execute(cmd);
});
}).start();
}
@ -526,83 +541,17 @@ public class SetupWizard2Activity extends AppCompatActivity {
}
});
public void executeShellCommand(String userCommand) {
isExecutingCommand = true;
new Thread(() -> {
try {
// Set up the process builder to start PRoot with environmental variables and commands
ProcessBuilder processBuilder = new ProcessBuilder();
private void execute(String command) {
Terminal2 terminal2 = new Terminal2(this);
terminal2.execute(command, new Terminal2.Terminal2Callback() {
@Override
public void onRunning(String command, String newLine) {
runOnUiThread(() -> appendTextAndScroll(newLine + "\n"));
}
// Adjust these environment variables as necessary for your app
String filesDir = getFilesDir().getAbsolutePath();
File tmpDir = new File(getFilesDir(), "usr/tmp");
// Setup environment for the PRoot process
processBuilder.environment().put("PROOT_TMP_DIR", tmpDir.getAbsolutePath());
processBuilder.environment().put("HOME", "/root");
processBuilder.environment().put("USER", "root");
processBuilder.environment().put("PATH", "/bin:/usr/bin:/sbin:/usr/sbin");
processBuilder.environment().put("TERM", "xterm-256color");
processBuilder.environment().put("TMPDIR", tmpDir.getAbsolutePath());
processBuilder.environment().put("SHELL", "/bin/sh");
String[] prootCommand = {
TermuxService.PREFIX_PATH + "/bin/proot", // PRoot binary path
"--kill-on-exit",
"--link2symlink",
"-0",
"-r", filesDir + "/distro", // Path to the rootfs
"-b", "/dev",
"-b", "/proc",
"-b", "/sys",
"-b", "/sdcard",
"-b", "/storage",
"-b", "/data",
"-w", "/root",
"/bin/sh",
"--login"// The shell to execute inside PRoot
};
processBuilder.command(prootCommand);
Process process = processBuilder.start();
// Get the input and output streams of the process
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
// Send user command to PRoot
writer.write(userCommand);
writer.newLine();
writer.flush();
writer.close();
// Read the input stream for the output of the command
String line;
while ((line = reader.readLine()) != null) {
final String outputLine = line;
runOnUiThread(() -> appendTextAndScroll(outputLine + "\n"));
}
// Read any errors from the error stream
while ((line = errorReader.readLine()) != null) {
final String errorLine = line;
runOnUiThread(() -> appendTextAndScroll(errorLine + "\n"));
}
// Clean up
reader.close();
errorReader.close();
// Wait for the process to finish
process.waitFor();
// Wait for the process to finish
int exitValue = process.waitFor();
// Check if the exit value indicates an error
if (exitValue != 0) {
@Override
public void onFinished(String command, String log, int status) {
if (status != terminal2.SUCCESS) {
isExecutingCommand = false;
if (aria2Error && downloadBootstrapsCommand.contains("aria2c")) {
runOnUiThread(() -> {
@ -611,22 +560,22 @@ public class SetupWizard2Activity extends AppCompatActivity {
});
} else {
runOnUiThread(() -> {
String toastMessage = "Command failed with exit code: " + exitValue;
String toastMessage = "Command failed with exit code: " + status;
appendTextAndScroll("Error: " + toastMessage + "\n");
uiController(STEP_ERROR, logs);
});
}
}
} catch (IOException | InterruptedException e) {
isExecutingCommand = false;
// Handle exceptions by printing the stack trace in the terminal output
final String errorMessage = e.getMessage();
}
@Override
public void onError(String command, Exception exception) {
runOnUiThread(() -> {
appendTextAndScroll("Error: " + errorMessage + "\n");
appendTextAndScroll("Error: " + exception.getMessage() + "\n");
uiController(STEP_ERROR, logs);
});
}
}).start(); // Execute the command in a separate thread to prevent blocking the UI thread
});
}
@SuppressLint("SetTextI18n")
@ -636,9 +585,10 @@ public class SetupWizard2Activity extends AppCompatActivity {
if (newLog.contains("xssFjnj58Id")) {
isExecutingCommand = false;
MainSettingsManager.setStandardSetupVersion(this, AppConfig.standardSetupVersion);
MainSettingsManager.setCoreSetupVersion(this, AppConfig.coreSetupVersion);
MainSettingsManager.setsetUpWithManualSetupBefore(this, isCustomSetupMode);
uiController(STEP_JOIN_COMMUNITY);
if (isSystemUpdateMode) {
if (ACTION == ACTION_SYSTEM_UPDATE) {
uiControllerFinalSteps(STEP_FINISH);
}
} else if (newLog.contains("libproot.so --help") || newLog.contains("/bin/sh: can't fork:")) {
@ -694,65 +644,6 @@ public class SetupWizard2Activity extends AppCompatActivity {
}), getString(R.string.mirrors));
}
public static class SpinnerSelectMirrorAdapter extends BaseAdapter {
private final ArrayList<HashMap<String, String>> data;
private final LayoutInflater inflater;
private final int selectedPosition;
public SpinnerSelectMirrorAdapter(Context context, ArrayList<HashMap<String, String>> arr) {
this.data = arr;
this.inflater = LayoutInflater.from(context);
this.selectedPosition = MainSettingsManager.getSelectedMirror(context);
}
@Override
public int getCount() {
return data.size();
}
@Override
public HashMap<String, String> getItem(int position) {
return data.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
SetupWizard2Activity.SpinnerSelectMirrorAdapter.ViewHolder holder;
if (convertView == null) {
// Inflate binding only once for each new item
SimpleLayoutListViewWithCheckBinding simpleLayoutListViewWithCheckBinding =
SimpleLayoutListViewWithCheckBinding.inflate(inflater, parent, false);
// Create ViewHolder to hold binding
holder = new SetupWizard2Activity.SpinnerSelectMirrorAdapter.ViewHolder(simpleLayoutListViewWithCheckBinding);
convertView = simpleLayoutListViewWithCheckBinding.getRoot();
convertView.setTag(holder);
} else {
// Get back the saved ViewHolder
holder = (SetupWizard2Activity.SpinnerSelectMirrorAdapter.ViewHolder) convertView.getTag();
}
// Assign data
HashMap<String, String> item = data.get(position);
holder.simpleLayoutListViewWithCheckBinding.textview.setText(item.get("location"));
holder.simpleLayoutListViewWithCheckBinding.ivCheck.setVisibility(position == selectedPosition ? View.VISIBLE : View.INVISIBLE);
return convertView;
}
// ViewHolder holds binding for reuse
private record ViewHolder(SimpleLayoutListViewWithCheckBinding simpleLayoutListViewWithCheckBinding) {
}
}
public String getPath(Uri uri) {
return FileUtils.getPath(this, uri);
}

View file

@ -0,0 +1,62 @@
package com.vectras.vm.sound;
import android.content.Context;
import android.content.SharedPreferences;
public class AudioSettingsData {
Context context;
SharedPreferences sharedPreferences;
public AudioSettingsData(Context context) {
this.context = context;
sharedPreferences = context.getSharedPreferences("audio_settings", Context.MODE_PRIVATE);
}
public void setEqualizerEnabled(boolean enabled) {
sharedPreferences.edit().putBoolean("isEnabled", enabled).apply();
}
public boolean isEqualizerEnabled() {
return sharedPreferences.getBoolean("isEnabled", false);
}
public void setUpperTreble(float value) {
sharedPreferences.edit().putFloat("upperTreble", value).apply();
}
public float getUpperTreble() {
return sharedPreferences.getFloat("upperTreble", 0);
}
public void setTreble(float value) {
sharedPreferences.edit().putFloat("treble", value).apply();
}
public float getTreble() {
return sharedPreferences.getFloat("treble", 0);
}
public void setMid(float value) {
sharedPreferences.edit().putFloat("mid", value).apply();
}
public float getMid() {
return sharedPreferences.getFloat("mid", 0);
}
public void setBass(float value) {
sharedPreferences.edit().putFloat("bass", value).apply();
}
public float getBass() {
return sharedPreferences.getFloat("bass", 0);
}
public void setLowBass(float value) {
sharedPreferences.edit().putFloat("lowBass", value).apply();
}
public float getLowBass() {
return sharedPreferences.getFloat("lowBass", 0);
}
}

View file

@ -0,0 +1,118 @@
package com.vectras.vm.sound;
import android.content.Context;
import android.media.audiofx.Equalizer;
import android.util.Log;
public class SoundEffect {
final String TAG = "SoundEffect";
Context context;
public int session;
public Equalizer equalizer;
public SoundEffect(Context context, int session) {
this.context = context;
this.session = session;
equalizer = new Equalizer(1000, session);
}
public AudioFrequencyData getDeviceAudioFrequencies() {
AudioFrequencyData data = new AudioFrequencyData();
int[] targets = { 60, 230, 910, 4000, 14000 };
short bands = equalizer.getNumberOfBands();
int[] hzList = new int[bands];
for (short i = 0; i < bands; i++) {
int center = equalizer.getCenterFreq(i);
hzList[i] = center / 1000;
}
if (bands <= targets.length) {
int[] finalIds = new int[bands];
for (int i = 0; i < bands; i++) {
finalIds[i] = i;
}
data.frequencies = hzList;
data.ids = finalIds;
Log.d(TAG, "Equalizer bands: " + bands);
Log.d(TAG, "Equalizer frequencies: " + hzList[0] + ", " + hzList[1] + ", " + hzList[2] + ", " + hzList[3] + ", " + hzList[4]);
Log.d(TAG, "Equalizer ids: " + finalIds[0] + ", " + finalIds[1] + ", " + finalIds[2] + ", " + finalIds[3] + ", " + finalIds[4]);
return data;
}
int[] finalHzList = new int[5];
int[] finalIds = new int[5];
for (int i = 0; i < targets.length; i++) {
int losest = Integer.MAX_VALUE;
int bandId = 0;
for (int ii = 0; ii < hzList.length; ii++) {
if (Math.abs(targets[i] - hzList[ii]) < losest) {
losest = hzList[ii];
bandId = ii;
}
finalHzList[i] = losest;
finalIds[i] = bandId;
}
}
data.frequencies = finalHzList;
data.ids = finalIds;
Log.d(TAG, "Equalizer bands: " + bands);
Log.d(TAG, "Equalizer frequencies: " + hzList[0] + ", " + hzList[1] + ", " + hzList[2] + ", " + hzList[3] + ", " + hzList[4]);
Log.d(TAG, "Equalizer ids: " + finalIds[0] + ", " + finalIds[1] + ", " + finalIds[2] + ", " + finalIds[3] + ", " + finalIds[4]);
return data;
}
public void applyEffect(float[] dBData) {
short bands = equalizer.getNumberOfBands();
if (bands < 2) {
Log.d(TAG, "Equalizer bands: " + bands);
return;
}
AudioFrequencyData data = getDeviceAudioFrequencies();
equalizer.setBandLevel((short) data.ids[0], (short) dBData[0]);
equalizer.setBandLevel((short) data.ids[1], (short) dBData[1]);
if (bands > 2) equalizer.setBandLevel((short) data.ids[2], (short) dBData[2]);
if (bands > 3) equalizer.setBandLevel((short) data.ids[3], (short) dBData[3]);
if (bands > 4) equalizer.setBandLevel((short) data.ids[4], (short) dBData[4]);
Log.d(TAG, "Equalizer dB: " + dBData[0] + ", " + dBData[1] + ", " + dBData[2] + ", " + dBData[3] + ", " + dBData[4]);
}
public void release() {
if (equalizer != null) {
equalizer.release();
}
}
public void setEnabled(boolean isEnabled) {
equalizer.setEnabled(isEnabled);
}
public short getMinBandLevelRange() {
return equalizer.getBandLevelRange()[0];
}
public short getMaxBandLevelRange() {
return equalizer.getBandLevelRange()[1];
}
public static class AudioFrequencyData {
int[] frequencies;
int[] ids;
}
}

View file

@ -1,12 +1,15 @@
package com.vectras.vm.utils;
package com.vectras.vm.sound;
import android.app.Activity;
import android.content.Context;
import android.media.AudioAttributes;
import android.media.AudioFormat;
import android.media.AudioTrack;
import android.media.audiofx.Equalizer;
import android.util.Log;
import com.vectras.vm.utils.FileUtils;
import java.io.RandomAccessFile;
public class StreamAudio {
@ -17,6 +20,7 @@ public class StreamAudio {
private int sampleRate = 48000;
public boolean isDestroyed;
private StreamAudio cross;
private SoundEffect soundEffect;
public StreamAudio(Context context) {
this.context = context;
@ -54,6 +58,10 @@ public class StreamAudio {
sampleRate = 48000;
}
public void release() {
isPlay = false;
releaseSoundEffect();
}
public void streamFromFile() {
if (isPlay) return;
@ -100,6 +108,8 @@ public class StreamAudio {
.setTransferMode(AudioTrack.MODE_STREAM)
.build();
applyEffect(audioTrack);
audioTrack.play();
if (cross != null && !cross.isDestroyed && cross.isPlaying()) cross.stop();
@ -133,6 +143,63 @@ public class StreamAudio {
}).start();
}
float[] currentdBData = new float[5];
public void applyEffect(AudioTrack audioTrack) {
if (soundEffect != null && soundEffect.equalizer.getNumberOfBands() < 2) return;
AudioSettingsData audioSettingsData = new AudioSettingsData(context);
if (!audioSettingsData.isEqualizerEnabled()) {
releaseSoundEffect();
return;
}
if (soundEffect == null || soundEffect.session != audioTrack.getAudioSessionId())
soundEffect = new SoundEffect(context, audioTrack.getAudioSessionId());
if (soundEffect.equalizer.getNumberOfBands() < 2) return;
float[] dBData = new float[5];
//Bass -> Mid -> Treble
dBData[0] = audioSettingsData.getLowBass();
dBData[1] = audioSettingsData.getBass();
dBData[2] = audioSettingsData.getMid();
dBData[3] = audioSettingsData.getTreble();
dBData[4] = audioSettingsData.getUpperTreble();
if (
currentdBData[0] == dBData[0] &&
currentdBData[1] == dBData[1] &&
currentdBData[2] == dBData[2] &&
currentdBData[3] == dBData[3] &&
currentdBData[4] == dBData[4]) {
return;
}
this.currentdBData = dBData;
// Convert dB to mB
for (int i = 0; i < dBData.length; i++) {
dBData[i] = dBData[i] * 100;
}
soundEffect.applyEffect(dBData);
soundEffect.setEnabled(audioSettingsData.isEqualizerEnabled());
Log.d(TAG, "Equalizer enabled: " + audioSettingsData.isEqualizerEnabled());
}
public void releaseSoundEffect() {
if (soundEffect != null) {
soundEffect.setEnabled(false);
soundEffect.release();
soundEffect = null;
}
}
private boolean isContextDestroyed(Context context) {
if (context instanceof Activity activity) {
isDestroyed = activity.isDestroyed() || activity.isFinishing();

View file

@ -1,9 +1,11 @@
package com.vectras.vm.utils;
import android.app.Activity;
import android.content.Context;
import com.vectras.vm.VectrasApp;
import com.vectras.vterm.Terminal;
import com.vectras.vterm.Terminal2;
public class CommandUtils {
public static String createForSelectedMirror(boolean _https, String _url, String _beforemain) {
@ -18,24 +20,23 @@ public class CommandUtils {
}
public static void run(String _command, boolean _isShowResult, Activity _activity) {
Terminal vterm = new Terminal(_activity);
vterm.executeShellCommand2(_command, _isShowResult, _activity);
new Terminal2(_activity).execute(_command);
}
public static String getQemuVersionName() {
String qemuVersion = getQemuVersion();
public static String getQemuVersionName(Context context) {
String qemuVersion = getQemuVersion(context);
if (qemuVersion.toLowerCase().contains("failed") || qemuVersion.toLowerCase().contains("not found"))
return "";
return (qemuVersion.contains("Error") ? qemuVersion.substring(0, qemuVersion.indexOf("Error")) : qemuVersion) + (is3dfxVersion() ? " - 3dfx" : "");
return (qemuVersion.contains("Error") ? qemuVersion.substring(0, qemuVersion.indexOf("Error")) : qemuVersion) + (is3dfxVersion(context) ? " - 3dfx" : "");
}
public static String getQemuVersion() {
return VectrasApp.getContext() == null ? "Unknow" : Terminal.executeShellCommandWithResult("qemu-system-x86_64 --version | head -n1 | awk '{print $4}'", VectrasApp.getContext()).replaceAll("\n", "");
public static String getQemuVersion(Context context) {
return VectrasApp.getContext() == null ? "Unknow" : new Terminal2(context).executeOnThisThread("qemu-system-x86_64 --version | head -n1 | awk '{print $4}'").replaceAll("\n", "");
}
public static boolean is3dfxVersion() {
return VectrasApp.getContext() != null && Terminal.executeShellCommandWithResult("qemu-system-x86_64 --version", VectrasApp.getContext()).contains("3dfx");
public static boolean is3dfxVersion(Context context) {
return VectrasApp.getContext() != null && new Terminal2(context).executeOnThisThread("qemu-system-x86_64 --version").contains("3dfx");
}
}

View file

@ -70,7 +70,13 @@ public class DeviceUtils {
}
public static boolean is64bit() {
return new CpuHelper().is64Bit();
try {
return new CpuHelper().is64Bit();
} catch (UnsatisfiedLinkError e) {
return Build.SUPPORTED_ABIS[0].contains("64") &&
Build.SUPPORTED_64_BIT_ABIS != null &&
Build.SUPPORTED_64_BIT_ABIS.length > 0;
}
}
public static boolean isArm() {
return Build.SUPPORTED_ABIS[0].contains("arm");

View file

@ -0,0 +1,13 @@
package com.vectras.vm.utils;
import android.content.Context;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
public class GmsChecker {
public static boolean isAvailable(Context context) {
GoogleApiAvailability googleApiAvailability = GoogleApiAvailability.getInstance();
return googleApiAvailability.isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS;
}
}

View file

@ -1,5 +1,7 @@
package com.vectras.vm.utils;
import android.util.Log;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
@ -14,11 +16,14 @@ import java.util.HashMap;
import java.util.Objects;
public class JSONUtils {
private static final String TAG = "JSONUtils";
public static boolean isValidFromString(String _content) {
try {
JsonElement element = JsonParser.parseString(_content);
return element != null;
} catch (Exception e) {
Log.e(TAG, "isValidFromString: " + e.getMessage());
return false;
}
}

View file

@ -2,14 +2,16 @@ package com.vectras.vm.utils;
import android.app.Activity;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import androidx.appcompat.app.AlertDialog;
import com.vectras.vm.AppConfig;
import com.vectras.vm.R;
import com.vectras.vterm.Terminal;
import com.vectras.vterm.Terminal2;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
@ -66,30 +68,69 @@ public class LibraryChecker {
() -> {
// Create the installation command
String installCommand = "apk add " + missingLibraries.replace("\n", " ");
new Terminal(context).executeShellCommand(installCommand, true, true, activity);
Terminal2 terminal2 = new Terminal2(context);
terminal2.setShowProgressDialog(true);
terminal2.execute(installCommand, new Terminal2.Terminal2Callback() {
@Override
public void onRunning(String command, String newLine) {
// Nothing to do.
}
@Override
public void onFinished(String command, String log, int status) {
new Handler(Looper.getMainLooper()).post(() -> {
if (status == terminal2.SUCCESS) {
DialogUtils.oneDialog(
activity,
activity.getString(R.string.done),
activity.getString(R.string.the_necessary_packages_have_been_installed),
R.drawable.check_24px
);
} else {
DialogUtils.oopsDialog(context, log);
}
});
}
@Override
public void onError(String command, Exception exception) {
new Handler(Looper.getMainLooper()).post(() -> DialogUtils.oopsDialog(context, exception.getMessage()));
}
});
},
null,
null
);
}
// Method to show the "All Libraries Installed" dialog
private void showAllLibrariesInstalledDialog(Activity activity) {
new AlertDialog.Builder(activity, R.style.MainDialogTheme)
.setTitle("All Libraries Installed")
.setMessage("All required libraries are already installed.")
.setPositiveButton("OK", (dialog, which) -> dialog.dismiss())
.show();
}
// Method to check if the package is installed
public void isPackageInstalled(String packageName, Terminal.CommandCallback callback) {
String command = "apk info";
Terminal terminal = new Terminal(context);
terminal.executeShellCommand(command, (Activity) context, false, (output, errors) -> {
if (callback != null) {
callback.onCommandCompleted(output, errors);
Terminal2 terminal2 = new Terminal2(context);
terminal2.execute(command, new Terminal2.Terminal2Callback() {
@Override
public void onRunning(String command, String newLine) {
// Nothing to do.
}
@Override
public void onFinished(String command, String log, int status) {
new Handler(Looper.getMainLooper()).post(() -> {
if (callback != null) {
if (status == terminal2.SUCCESS) {
callback.onCommandCompleted(log, "");
} else {
callback.onCommandCompleted(log, log);
}
}
});
}
@Override
public void onError(String command, Exception exception) {
new Handler(Looper.getMainLooper()).post(() -> callback.onCommandCompleted("", exception.getMessage()));
}
});
}
@ -98,59 +139,30 @@ public class LibraryChecker {
public static void isPackageInstalled2(Context context, String packageName, Terminal.CommandCallback callback) {
String command = "apk info";
Terminal terminal = new Terminal(context);
terminal.executeShellCommand(command, context, false, (output, errors) -> {
if (callback != null) {
callback.onCommandCompleted(output, errors);
Terminal2 terminal2 = new Terminal2(context);
terminal2.execute(command, new Terminal2.Terminal2Callback() {
@Override
public void onRunning(String command, String newLine) {
// Nothing to do.
}
@Override
public void onFinished(String command, String log, int status) {
new Handler(Looper.getMainLooper()).post(() -> {
if (callback != null) {
if (status == terminal2.SUCCESS) {
callback.onCommandCompleted(log, "");
} else {
callback.onCommandCompleted(log, log);
}
}
});
}
@Override
public void onError(String command, Exception exception) {
new Handler(Looper.getMainLooper()).post(() -> callback.onCommandCompleted("", exception.getMessage()));
}
});
}
public void checkAndInstallXFCE4(Activity activity) {
// XFCE4 meta-package
String xfce4Package = "xfce4";
// Check if XFCE4 is installed
isPackageInstalled(xfce4Package, (output, errors) -> {
boolean isInstalled = false;
// Check if the package exists in the installed packages output
if (output != null) {
Set<String> installedPackages = new HashSet<>();
for (String installedPackage : output.split("\n")) {
installedPackages.add(installedPackage.trim());
}
isInstalled = installedPackages.contains(xfce4Package.trim());
}
// If not installed, show a dialog to install it
if (!isInstalled) {
showInstallDialog(activity, xfce4Package);
} else {
showAlreadyInstalledDialog(activity);
}
});
}
private void showInstallDialog(Activity activity, String packageName) {
new AlertDialog.Builder(activity, R.style.MainDialogTheme)
.setTitle("Install XFCE4")
.setMessage("XFCE4 is not installed. Would you like to install it?")
.setCancelable(false)
.setPositiveButton("Install", (dialog, which) -> {
String installCommand = "apk add " + packageName;
new Terminal(context).executeShellCommand(installCommand, true, true, activity);
})
.setNegativeButton("Cancel", (dialog, which) -> dialog.dismiss())
.show();
}
private void showAlreadyInstalledDialog(Activity activity) {
new AlertDialog.Builder(activity, R.style.MainDialogTheme)
.setTitle("XFCE4 Installed")
.setMessage("XFCE4 is already installed on this system.")
.setPositiveButton("OK", (dialog, which) -> dialog.dismiss())
.show();
}
}

View file

@ -23,6 +23,8 @@ public class DynamicBubble {
}
private void setup() {
bubble.animate().alpha(0.5f).setDuration(200);
bubble.setOnTouchListener((v, event) -> {
switch (event.getActionMasked()) {
@ -44,6 +46,9 @@ public class DynamicBubble {
// If the displacement exceeds the threshold, it is determined that pulling is occurring.
if (Math.abs(movedX) > CLICK_ACTION_THRESHOLD || Math.abs(movedY) > CLICK_ACTION_THRESHOLD) {
if (!isDragging) bubble.animate().alpha(1).setDuration(200);
isDragging = true;
// Move the view using your finger.
@ -75,6 +80,8 @@ public class DynamicBubble {
}
private void onDropped() {
bubble.animate().alpha(0.5f).setDuration(200);
if (bubble.getX() > (float) container.getWidth() / 2) {
bubble.animate().x(container.getWidth() - bubble.getWidth());
} else {

View file

@ -2,8 +2,6 @@ package com.vectras.vm.x11;
import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
import android.app.Dialog;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Build.VERSION.SDK_INT;
@ -16,8 +14,6 @@ import static android.view.InputDevice.KEYBOARD_TYPE_ALPHABETIC;
import static android.view.KeyEvent.*;
import static android.view.WindowManager.LayoutParams.*;
import android.view.WindowManager;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.fragment.app.FragmentTransaction;
@ -37,7 +33,7 @@ import com.vectras.vm.manager.VmFileManager;
import com.vectras.vm.manager.VmListManager;
import com.vectras.vm.manager.VmPicker;
import com.vectras.vm.utils.DialogUtils;
import com.vectras.vm.utils.StreamAudio;
import com.vectras.vm.sound.StreamAudio;
import com.vectras.vm.utils.UIUtils;
import static com.vectras.vm.x11.CmdEntryPoint.ACTION_START;
@ -80,7 +76,6 @@ import android.view.Window;
import android.view.WindowInsets;
import android.view.inputmethod.InputMethodManager;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
@ -98,7 +93,6 @@ import com.vectras.vm.x11.utils.KeyInterceptor;
import com.vectras.vm.x11.utils.TermuxX11ExtraKeys;
import com.vectras.vm.x11.utils.X11ToolbarViewPager;
import com.vectras.vm.R;
import com.vectras.vterm.Terminal;
import java.util.ArrayList;
import java.util.HashMap;
@ -758,7 +752,7 @@ public class X11Activity extends AppCompatActivity implements View.OnApplyWindow
@Override
protected void onDestroy() {
unregisterReceiver(receiver);
if (streamAudio != null) streamAudio.stop();
if (streamAudio != null) streamAudio.release();
super.onDestroy();
}

View file

@ -0,0 +1,253 @@
package com.vectras.vterm;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import com.vectras.vm.AppConfig;
import com.vectras.vm.R;
import com.vectras.vm.logger.VectrasStatus;
import com.vectras.vm.utils.FileUtils;
import com.vectras.vm.utils.ProgressDialog;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
public class Terminal2 {
final String TAG = "Terminal2";
public final int SUCCESS = 0;
public final int ERROR = 1;
Context context;
String user = "root";
String home = "/root";
String display = ":0";
String shell = "/bin/sh";
String startup = "";
String logs = "";
String lastLog = "";
boolean isShowProgressDialog;
ProgressDialog progressDialog = null;
public interface Terminal2Callback {
void onRunning(String command, String newLine);
void onFinished(String command, String log, int status);
void onError(String command, Exception exception);
}
public Terminal2(Context context) {
this.context = context;
}
public void setUserName(String user) {
this.user = user;
home = user.equals("root") ? "/root" : "/home/" + user;
}
public void setRootMode() {
this.user = "root";
home = "/root";
}
public void setDefaultShell() {
this.shell = "/bin/sh";
}
public void setDefaultShellBash() {
this.shell = "/bin/bash";
}
public void setStartup(String command) {
this.startup = command;
}
public String getLogs() {
return logs;
}
public void clearLog() {
logs = "";
}
public void setShowProgressDialog(boolean isShow) {
this.isShowProgressDialog = isShow;
}
public String executeOnThisThread(String command) {
execute(command, null, false);
return lastLog;
}
public void execute(String command) {
execute(command, null);
}
public void execute(String command, Terminal2Callback callback) {
execute(command, callback, true);
}
public void execute(String command, Terminal2Callback callback, boolean isNewThread) {
AtomicReference<Process> process = new AtomicReference<>();
Runnable runnable = () -> {
if (isShowProgressDialog) {
progressDialog = new ProgressDialog(context);
progressDialog.setText(context.getString(R.string.executing_command_please_wait));
new Handler(Looper.getMainLooper()).post((progressDialog::show));
}
try {
ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.redirectErrorStream(true);
String filesDir = Objects.requireNonNull(context.getFilesDir().getAbsolutePath());
String prootPath = new File(filesDir, "usr/bin/proot").getAbsolutePath();
String tmpDir = new File(filesDir, "usr/tmp").getAbsolutePath();
if (!FileUtils.isFileExists(filesDir + "/distro" + shell)) {
setDefaultShell();
}
if (!FileUtils.isFileExists(filesDir + "/distro" + home)) {
FileUtils.createDirectory(filesDir + "/distro" + home);
}
processBuilder.environment().put("PROOT_TMP_DIR", tmpDir);
processBuilder.environment().put("HOME", home);
processBuilder.environment().put("USER", user);
processBuilder.environment().put("TERM", "xterm-256color");
processBuilder.environment().put("TMPDIR", "/tmp");
processBuilder.environment().put("SHELL", shell);
processBuilder.environment().put("DISPLAY", display);
processBuilder.environment().put("XDG_RUNTIME_DIR", "/tmp");
processBuilder.environment().put("SDL_VIDEODRIVER", "x11");
String[] prootCommand = {
prootPath,
"--kill-on-exit",
"--link2symlink",
"-0",
"-r", filesDir + "/distro",
"-b", "/dev",
"-b", "/proc",
"-b", "/sys",
"-b", AppConfig.internalDataDirPath + "distro" + home + ":/dev/shm",
"-b", "/sdcard",
"-b", "/storage",
"-b", "/data",
"-b", AppConfig.internalDataDirPath + "usr/tmp:/tmp",
"-w", home,
shell,
"--login"
};
processBuilder.command(prootCommand);
process.set(processBuilder.start());
resultData data = startProcess((startup.isEmpty() ? "" : startup + " && ") + command, process.get(), callback);
if (callback != null) callback.onFinished(command, data.log.toString(), data.status);
} catch (IOException e) {
if (callback != null) callback.onError(command, e);
addToLogs(command, e.getMessage());
} finally {
if (progressDialog != null) {
new Handler(Looper.getMainLooper()).post(() -> {
progressDialog.reset();
progressDialog = null;
});
}
}
};
if (isNewThread) {
new Thread(runnable).start();
} else {
runnable.run();
}
}
private resultData startProcess(String command, Process process, Terminal2Callback callback) {
resultData data = new resultData();
AtomicInteger exitCode = new AtomicInteger();
int MAX_LOG_SIZE = 200_000; // ~200KB text
StringBuilder output = new StringBuilder();
try {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
writer.write(command);
writer.newLine();
writer.flush();
writer.close();
String line;
while ((line = reader.readLine()) != null) {
Log.d(TAG, line);
VectrasStatus.logError(line);
output.append(line).append("\n");
if (callback != null) callback.onRunning(command, line);
if (output.length() > MAX_LOG_SIZE) {
output.delete(0, output.length() - MAX_LOG_SIZE);
}
if (progressDialog != null) {
String finalLine = line;
new Handler(Looper.getMainLooper()).post(() -> progressDialog.setText(finalLine));
}
}
try {
exitCode.set(process.waitFor());
} catch (InterruptedException e) {
exitCode.set(ERROR);
}
data.status = exitCode.get();
reader.close();
} catch (Exception e) {
output.append(e.getMessage());
Log.e(TAG, "streamLog: ", e);
}
data.status = exitCode.get();
data.log = output;
addToLogs(command, output.toString());
return data;
}
private void addToLogs(String command,String log) {
lastLog = log;
if (!logs.isEmpty()) {
logs += "\n\n----------\n\n";
}
logs += "$ " + command + "\n\n";
logs += log;
if (logs.contains("\n") && logs.lastIndexOf("\n") > 1000)
logs = logs.substring(logs.lastIndexOf("\n") + 1);
}
private class resultData {
StringBuilder log;
int status;
}
}

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M280,720L280,240L360,240L360,720L280,720ZM440,880L440,80L520,80L520,880L440,880ZM120,560L120,400L200,400L200,560L120,560ZM600,720L600,240L680,240L680,720L600,720ZM760,560L760,400L840,400L840,560L760,560Z"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="96dp"
android:height="96dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M791,905L520,634L520,880L440,880L440,554L360,474L360,720L280,720L280,394L55,169L112,112L848,848L791,905ZM120,560L120,400L200,400L200,560L120,560ZM520,406L440,326L440,80L520,80L520,406ZM680,566L600,486L600,240L680,240L680,566ZM760,560L760,400L840,400L840,560L760,560Z"/>
</vector>

View file

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal"
android:autoMirrored="true">
<path
android:fillColor="@android:color/white"
android:pathData="M280,760L280,680L564,680Q627,680 673.5,640Q720,600 720,540Q720,480 673.5,440Q627,400 564,400L312,400L416,504L360,560L160,360L360,160L416,216L312,320L564,320Q661,320 730.5,383Q800,446 800,540Q800,634 730.5,697Q661,760 564,760L280,760Z"/>
</vector>

View file

@ -0,0 +1,308 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:fitsSystemWindows="true"
android:orientation="vertical"
tools:context=".settings.EqualizerActivity">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$Behavior">
<com.google.android.material.appbar.CollapsingToolbarLayout
style="@style/App.CollapsingToolbarLarge"
app:title="@string/equalizer">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
style="@style/App.CollapsingMaterialToolbar"
app:navigationIcon="@drawable/arrow_back_rounded_24px" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:id="@+id/ln_all_options"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingTop="100dp"
android:paddingHorizontal="16dp"
android:animateLayoutChanges="true">
<LinearLayout
android:id="@+id/ln_upper_treble"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:paddingHorizontal="16dp">
<TextView
android:id="@+id/tv_upper_treble"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/upper_treble"
android:textSize="18sp" />
<TextView
android:id="@+id/tv_upper_treble_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0 dB"
android:textSize="18sp" />
</LinearLayout>
<com.google.android.material.slider.Slider
android:id="@+id/slider_upper_treble"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:value="0.0"
android:valueFrom="-10"
android:valueTo="10" />
</LinearLayout>
<LinearLayout
android:id="@+id/ln_treble"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:paddingHorizontal="16dp">
<TextView
android:id="@+id/tv_treble"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/treble"
android:textSize="18sp" />
<TextView
android:id="@+id/tv_treble_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0 dB"
android:textSize="18sp" />
</LinearLayout>
<com.google.android.material.slider.Slider
android:id="@+id/slider_treble"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:value="0.0"
android:valueFrom="-10"
android:valueTo="10" />
</LinearLayout>
<LinearLayout
android:id="@+id/ln_mid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:paddingHorizontal="16dp">
<TextView
android:id="@+id/tv_mid"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/mid"
android:textSize="18sp" />
<TextView
android:id="@+id/tv_mid_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0 dB"
android:textSize="18sp" />
</LinearLayout>
<com.google.android.material.slider.Slider
android:id="@+id/slider_mid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:value="0.0"
android:valueFrom="-10"
android:valueTo="10" />
</LinearLayout>
<LinearLayout
android:id="@+id/ln_bass"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:paddingHorizontal="16dp">
<TextView
android:id="@+id/tv_bass"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/bass"
android:textSize="18sp" />
<TextView
android:id="@+id/tv_bass_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0 dB"
android:textSize="18sp" />
</LinearLayout>
<com.google.android.material.slider.Slider
android:id="@+id/slider_bass"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:value="0.0"
android:valueFrom="-10"
android:valueTo="10" />
</LinearLayout>
<LinearLayout
android:id="@+id/ln_low_bass"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:paddingHorizontal="16dp">
<TextView
android:id="@+id/tv_low_bass"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/low_bass"
android:textSize="18sp" />
<TextView
android:id="@+id/tv_low_bass_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0 dB"
android:textSize="18sp" />
</LinearLayout>
<com.google.android.material.slider.Slider
android:id="@+id/slider_low_bass"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:value="0.0"
android:valueFrom="-10"
android:valueTo="10" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginVertical="16dp" >
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="16dp"
android:src="@drawable/info_24px" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/equalizer_note" />
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<View
android:layout_width="match_parent"
android:layout_height="55dp"
android:background="?attr/colorSurfaceContainerLow"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<com.google.android.material.card.MaterialCardView
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:backgroundTint="?attr/colorSecondaryContainer"
app:cardCornerRadius="50dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:id="@+id/ln_enabled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/selectableItemBackground"
android:gravity="center_vertical"
android:paddingVertical="16dp"
android:paddingLeft="32dp"
android:paddingRight="20dp" >
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/use_equalizer"
android:textSize="16sp">
</TextView>
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/sw_enabled"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?attr/colorAccent"
android:clickable="false"/>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<LinearLayout
android:id="@+id/ln_unsupport"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:background="?attr/colorSurfaceContainerLow"
android:visibility="gone">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/graphic_eq_off_96px"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="16dp"
android:gravity="center"
android:text="@string/your_device_is_not_supported"
android:textSize="18sp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_exit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ok"/>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -6,9 +6,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
<androidx.drawerlayout.widget.DrawerLayout
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -28,7 +26,8 @@
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="@layout/nav_header_main"
app:menu="@menu/home_drawer_menu" />
app:menu="@menu/home_drawer_menu"
app:itemTextAppearanceActiveBoldEnabled="false" />
</androidx.drawerlayout.widget.DrawerLayout>

View file

@ -302,7 +302,7 @@
android:id="@+id/ln_x11"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="2dp"
android:layout_marginBottom="18dp"
android:background="@drawable/object_shape_bottom_high"
android:padding="16dp"
android:gravity="center_vertical">
@ -334,6 +334,43 @@
android:textSize="14sp" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/ln_equalizer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="2dp"
android:background="@drawable/object_shape_single_high"
android:padding="16dp"
android:gravity="center_vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp"
android:background="@drawable/settings_system_background_icon"
android:src="@drawable/graphic_eq_24px" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/equalizer"
android:textSize="18sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/equalizer_settings_note"
android:textColor="?android:attr/textColorSecondary"
android:textSize="14sp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View file

@ -0,0 +1,13 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/undo"
android:icon="@drawable/undo_24px"
android:title="@string/undo"
app:showAsAction="always"/>
<item
android:id="@+id/reset"
android:icon="@drawable/refresh_24px"
android:title="@string/reset"
app:showAsAction="always"/>
</menu>

View file

@ -609,6 +609,8 @@
<string name="need_to_shut_down_all_running_vm_note">Bạn cần tắt tất cả máy ảo đang chạy để thực hiện hành động này.</string>
<string name="auto_switch_to_external_mouse">Tự động chuyển sang chuột ngoài</string>
<string name="auto_switch_to_external_mouse_note">Tự động chuyển đổi khi kết nối và ngắt chuột rời.</string>
<string name="the_necessary_packages_have_been_installed">Các gói cần thiết đã được cài đặt.</string>
<string name="an_error_occurred_while_creating_the_virtual_drive">Đã xảy ra lỗi khi tạo ổ đĩa ảo.</string>
<!--======================TERMUX STRINGS====================-->
<string name="application_name" translatable="false">Vterm</string>
@ -768,6 +770,17 @@
<string name="blur_effect">Hiệu ứng mờ</string>
<string name="blur_effect_note">Làm mờ xung quanh để tăng khả năng tập trung vào một vùng cần được chú ý.</string>
<string name="x11_general_setting_note">Bạn cần khởi động lại ứng dụng khi chuyển đổi giữa tích hợp sẵn và ngoài.</string>
<string name="equalizer">Bộ chỉnh âm</string>
<string name="equalizer_settings_note">Bổng, trung và trầm.</string>
<string name="equalizer_note">Bạn có thể cần khởi động lại máy ảo để áp dụng các thay đổi. Hiệu ứng âm thanh có thể chỉ có tác dụng nếu bạn sử dụng các phụ kiện như tai nghe.</string>
<string name="use_equalizer">Dùng bộ chỉnh âm</string>
<string name="upper_treble">Bổng cao</string>
<string name="treble">Bổng</string>
<string name="mid">Trung</string>
<string name="bass">Trầm</string>
<string name="low_bass">Trầm sâu</string>
<string name="your_device_is_not_supported">Thiết bị của bạn không được hỗ trợ.</string>
<string name="undo">Hoàn tác</string>
<!-- CPUs -->

View file

@ -627,6 +627,19 @@
<string name="need_to_shut_down_all_running_vm_note">You need to shut down all running virtual machines to perform this action.</string>
<string name="auto_switch_to_external_mouse">Auto switch to external mouse</string>
<string name="auto_switch_to_external_mouse_note">Automatically switches when the mouse is connected and disconnected.</string>
<string name="the_necessary_packages_have_been_installed">The necessary packages have been installed.</string>
<string name="an_error_occurred_while_creating_the_virtual_drive">An error occurred while creating the virtual drive.</string>
<string name="equalizer">Equalizer</string>
<string name="equalizer_settings_note">Treble, mid and bass.</string>
<string name="equalizer_note">You may need to restart the virtual machine for the changes to take effect. Sound effects may be affected if you use accessories such as headphones.</string>
<string name="use_equalizer">Use equalizer</string>
<string name="upper_treble">Upper treble</string>
<string name="treble">Treble</string>
<string name="mid">Mid</string>
<string name="bass">Bass</string>
<string name="low_bass">Low bass</string>
<string name="your_device_is_not_supported">Your device is not supported.</string>
<string name="undo">Undo</string>
<!--======================TERMUX STRINGS====================-->

View file

@ -1,15 +0,0 @@
Welcome to Vectras VM! A virtual machine app for Android based on QEMU that lets you emulate various OSes including: Linux, Windows, Android, macOS and more.
Minimum System Requirements
- Android 6.0 and up.
- 3GB RAM (1GB of free RAM).
- A good processor.
Recommended System Requirements
- Android 8.1 and up.
- 8GB RAM (3GB of free RAM).
- CPU and Android OS support 64-bit.
- Snapdragon 855 CPU or better.
- Integrated or removable cooling system (if running operating systems from 2010 to present).
Tips: If the OS you are trying to emulate crashes, try using an older version.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

View file

@ -1 +0,0 @@
It's a Virtual Machine App for Android Which is Based on QEMU

View file

@ -11,14 +11,14 @@ coreKtx = "1.19.0"
documentfile = "1.1.0"
drawerlayout = "1.2.0"
espressoCore = "3.7.0"
firebaseBom = "34.14.1"
firebaseBom = "34.15.0"
firebaseCrashlyticsGradle = "3.0.7"
firebaseMessaging = "25.0.2"
firebaseMessaging = "25.1.0"
glide = "5.0.7"
googleFirebase = "firebase-analytics"
googleFirebaseCrashlytics = "3.0.7"
googleGmsGoogleServices = "4.4.4"
googleServices = "4.4.4"
googleGmsGoogleServices = "4.5.0"
googleServices = "4.5.0"
gradle = "9.2.1"
gradleToolchainsFoojayResolverConvention = "1.0.0"
gson = "2.14.0"

View file

@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=553c78f50dafcd54d65b9a444649057857469edf836431389695608536d6b746
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip
distributionSha256Sum=bbaeb2fef8710818cf0e261201dab964c572f92b942812df0c3620d62a529a01
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500

4
gradlew vendored
View file

@ -20,7 +20,7 @@
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
# gradlew start up script for POSIX generated by Gradle.
#
# Important for running:
#
@ -29,7 +29,7 @@
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
# ksh gradlew
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:

4
gradlew.bat vendored
View file

@ -19,7 +19,7 @@
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem gradlew startup script for Windows
@rem
@rem ##########################################################################
@ -72,7 +72,7 @@ echo location of your Java installation. 1>&2
@rem Execute Gradle
@rem Execute gradlew
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
@rem which allows us to clear the local environment before executing the java command
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

View file

@ -1,18 +0,0 @@
Add this to your HTML <head>:
<link rel="icon" href="/favicon.ico" sizes="any">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
Add this to your app's manifest.json:
...
{
"icons": [
{ "src": "/favicon.ico", "type": "image/x-icon", "sizes": "16x16 32x32" },
{ "src": "/icon-192.png", "type": "image/png", "sizes": "192x192" },
{ "src": "/icon-512.png", "type": "image/png", "sizes": "512x512" },
{ "src": "/icon-192-maskable.png", "type": "image/png", "sizes": "192x192", "purpose": "maskable" },
{ "src": "/icon-512-maskable.png", "type": "image/png", "sizes": "512x512", "purpose": "maskable" }
]
}
...

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

View file

@ -1,30 +0,0 @@
<html>
<head>
<meta content='#151e25' name='theme-color' />
<meta content='#151e25' name='msapplication-navbutton-color' />
<meta content='#151e25' name='apple-mobile-web-app-status-bar-style' />
<meta content='true' name='apple-mobile-web-app-capable' />
<meta charset="utf-8">
<meta name="description" content="Home">
<meta name="keywords" content="Windows,Linux,Emulator,Discord">
<meta name="author" content="Nguyen Bao An Bui">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta property="og:image" content="https://firebasestorage.googleapis.com/v0/b/baoanbui-ac174.appspot.com/o/Logo%2FInShot_20230228_181958299.jpg?alt=media&token=ea22e382-f35a-43ce-be02-799358aaea2b">
<title>Just a moment... Home</title>
<link rel="icon" type="image/x-icon" href="https://firebasestorage.googleapis.com/v0/b/baoanbui-ac174.appspot.com/o/Logo%2F1677640749718.png?alt=media&token=3ba289db-3a05-4ea5-b318-42ef5e8cf613">
<link href='https://fonts.googleapis.com/css?family=Open Sans' rel='stylesheet'>
<link rel="stylesheet" href="https://nguyenbaoanbui-86deb.web.app/style/style.css">
<style>
</style>
</head>
<body>
<h1>Just a moment...</h1>
<script>
document.location.href="https://anbui.ovh/vectrasvm";
</script>
</body>
</html>

View file

@ -5,11 +5,11 @@
"url": "https://github.com/xoureldeen/Vectras-VM-Android/releases",
"Message": "<h2>4.3.0</h2>\nBugs fixed.",
"cancellable": true,
"versionCodeBeta":"134",
"versionNameBeta":"4.3.0",
"versionNameBetas":"4.0.1,4.0.2,4.0.3,4.0.4,4.0.5,4.0.6,4.0.7,4.0.8,4.0.9,4.1.1,4.1.2,4.1.3,4.1.4,4.1.5,4.1.6,4.1.7,4.1.8,4.1.9,4.2.0,4.2.1,4.2.2,4.2.3,4.2.4,4.2.5,4.2.6,4.2.7,4.2.8,4.2.9,4.3.0",
"versionCodeBeta":"137",
"versionNameBeta":"4.3.3",
"versionNameBetas":"4.0.1,4.0.2,4.0.3,4.0.4,4.0.5,4.0.6,4.0.7,4.0.8,4.0.9,4.1.1,4.1.2,4.1.3,4.1.4,4.1.5,4.1.6,4.1.7,4.1.8,4.1.9,4.2.1,4.2.2,4.2.3,4.2.4,4.2.5,4.2.6,4.2.7,4.2.8,4.2.9,4.3.0,4.3.1,4.3.2,4.3.3",
"sizeBeta": "45 MB",
"urlBeta": "https://github.com/AnBui2004/Vectras-VM-Emu-Android/releases",
"MessageBeta": "<h2>4.3.0</h2>Bugs fixed.",
"MessageBeta": "<h2>4.3.3</h2>Bugs fixed.",
"cancellableBeta": true
}

View file

@ -1541,6 +1541,26 @@
"vecid": "windows10mobilecvbi",
"id": "99672"
},
{
"rom_name": "Windows 10 S 1709 ARM64 build 16299",
"rom_icon": "https://vuetiwuvbyxywfukompp.supabase.co/storage/v1/object/public/getmyos/v1/files/2018/09/17/windows-logo_1_926ed76111646acbbe332bc5af0cf2ce.png",
"rom_url": "https://youtu.be/2csyaU2w8dk",
"rom_path": "Windows 10 S ARM64 build 16299.cvbi",
"rom_avail": true,
"gui": true,
"rom_size": "Fall Creators Update",
"rom_arch": "ARM64",
"rom_kernel": "windows",
"rom_extra": "",
"final_rom_file_name": "",
"desc": "This Rom is from Nguyen Bao An Bui. You can get it on An Bui app: https://play.google.com/store/apps/details?id=com.anbui.app\n\nWindows 10 is a major release of the Windows NT operating system developed by Microsoft. The successor to Windows 8.1, it was released to manufacturing on July 15, 2015, became generally available on July 29, 2015, and was a free upgrade to users of Windows 7, 8, and 8.1. Its server counterparts are Windows Server 2016, 2019, and 2022. It was succeeded by Windows 11 in October 2021.",
"file_size": "16 GB",
"containsAds": true,
"creator": "Nguyen Bao An Bui",
"verified": true,
"vecid": "windows10sarm64build16299cvbi",
"id": "99627"
},
{
"rom_name": "Windows 11 21H2",
"rom_icon": "https://www.getmyos.com/app_public/files/t/1/2021/06/windows_11_logo_by_getmyos.png",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

View file

@ -1,30 +0,0 @@
<html>
<head>
<meta content='#151e25' name='theme-color' />
<meta content='#151e25' name='msapplication-navbutton-color' />
<meta content='#151e25' name='apple-mobile-web-app-status-bar-style' />
<meta content='true' name='apple-mobile-web-app-capable' />
<meta charset="utf-8">
<meta name="description" content="Home">
<meta name="keywords" content="Windows,Linux,Emulator,Discord">
<meta name="author" content="Nguyen Bao An Bui">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta property="og:image" content="https://firebasestorage.googleapis.com/v0/b/baoanbui-ac174.appspot.com/o/Logo%2FInShot_20230228_181958299.jpg?alt=media&token=ea22e382-f35a-43ce-be02-799358aaea2b">
<title>Just a moment... Home</title>
<link rel="icon" type="image/x-icon" href="https://firebasestorage.googleapis.com/v0/b/baoanbui-ac174.appspot.com/o/Logo%2F1677640749718.png?alt=media&token=3ba289db-3a05-4ea5-b318-42ef5e8cf613">
<link href='https://fonts.googleapis.com/css?family=Open Sans' rel='stylesheet'>
<link rel="stylesheet" href="https://nguyenbaoanbui-86deb.web.app/style/style.css">
<style>
</style>
</head>
<body>
<h1>Just a moment...</h1>
<script>
document.location.href="https://anbui.ovh/vectrasvm";
</script>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

View file

@ -1,30 +0,0 @@
<html>
<head>
<meta content='#151e25' name='theme-color' />
<meta content='#151e25' name='msapplication-navbutton-color' />
<meta content='#151e25' name='apple-mobile-web-app-status-bar-style' />
<meta content='true' name='apple-mobile-web-app-capable' />
<meta charset="utf-8">
<meta name="description" content="Home">
<meta name="keywords" content="Windows,Linux,Emulator,Discord">
<meta name="author" content="Nguyen Bao An Bui">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta property="og:image" content="https://firebasestorage.googleapis.com/v0/b/baoanbui-ac174.appspot.com/o/Logo%2FInShot_20230228_181958299.jpg?alt=media&token=ea22e382-f35a-43ce-be02-799358aaea2b">
<title>Just a moment... Home</title>
<link rel="icon" type="image/x-icon" href="https://firebasestorage.googleapis.com/v0/b/baoanbui-ac174.appspot.com/o/Logo%2F1677640749718.png?alt=media&token=3ba289db-3a05-4ea5-b318-42ef5e8cf613">
<link href='https://fonts.googleapis.com/css?family=Open Sans' rel='stylesheet'>
<link rel="stylesheet" href="https://nguyenbaoanbui-86deb.web.app/style/style.css">
<style>
</style>
</head>
<body>
<h1>Just a moment...</h1>
<script>
document.location.href="https://anbui.ovh/vectrasvm";
</script>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB