diff --git a/.firebaserc b/.firebaserc
deleted file mode 100644
index d1ca34f..0000000
--- a/.firebaserc
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "projects": {
- "default": "vectras-1678b"
- }
-}
diff --git a/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/bug.yaml
index 1e7cf9c..a8b2edb 100644
--- a/.github/ISSUE_TEMPLATE/bug.yaml
+++ b/.github/ISSUE_TEMPLATE/bug.yaml
@@ -32,6 +32,7 @@ body:
options:
- Latest
- Beta
+ - 4.3.0
- 4.2.0
- 4.1.0
- 4.0.0
diff --git a/app/build.gradle b/app/build.gradle
index cf5dbc9..f5d72cf 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -12,8 +12,8 @@ android {
applicationId "com.vectras.vm"
minSdk minApi
targetSdk targetApi
- versionCode 134
- versionName "4.3.0"
+ versionCode 138
+ versionName "4.3.4"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 7a7b5d2..40938ae 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -47,6 +47,16 @@
android:requestLegacyExternalStorage="true"
android:supportsRtl="true"
android:theme="@style/AppTheme">
+
+
?";
+ private static final int LEFT_SHIFT_KEY_CODE = 0xFFE1;
+
public void sendText(String s) {
int l = s.length();
for (int i = 0; i < l; i++) {
char c = s.charAt(i);
- int meta = 0;
int keysym = c;
if (Character.isISOControl(c)) {
if (c == '\n') {
@@ -1344,9 +1346,20 @@ public class VncCanvas extends AppCompatImageView {
continue;
}
}
+
+ boolean isNeedShift = Character.isUpperCase(c) || SHIFT_CHARS.indexOf(c) >= 0;
+
try {
- rfb.writeKeyEvent(keysym, meta, true);
- rfb.writeKeyEvent(keysym, meta, false);
+ if (isNeedShift) {
+ rfb.writeKeyEvent(LEFT_SHIFT_KEY_CODE, 0, true);
+ }
+
+ rfb.writeKeyEvent(keysym, 0, true);
+ rfb.writeKeyEvent(keysym, 0, false);
+
+ if (isNeedShift) {
+ rfb.writeKeyEvent(LEFT_SHIFT_KEY_CODE, 0, false);
+ }
} catch (IOException ioe) {
Log.e(TAG, "sendText: ",ioe);
}
diff --git a/app/src/main/java/com/vectras/qemu/MainSettingsManager.java b/app/src/main/java/com/vectras/qemu/MainSettingsManager.java
index f10ad44..932e78e 100644
--- a/app/src/main/java/com/vectras/qemu/MainSettingsManager.java
+++ b/app/src/main/java/com/vectras/qemu/MainSettingsManager.java
@@ -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,18 @@ 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);
+ }
+
+ public static void setBuiltInFilePicker(Context context, Boolean _boolean) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ SharedPreferences.Editor edit = prefs.edit();
+ edit.putBoolean("builtInFilePicker", _boolean);
+ edit.apply();
+ }
+
+ public static Boolean getBuiltInFilePicker(Context context) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ return prefs.getBoolean("builtInFilePicker", false);
}
}
diff --git a/app/src/main/java/com/vectras/qemu/MainVNCActivity.java b/app/src/main/java/com/vectras/qemu/MainVNCActivity.java
index 1e38d2a..61c2571 100644
--- a/app/src/main/java/com/vectras/qemu/MainVNCActivity.java
+++ b/app/src/main/java/com/vectras/qemu/MainVNCActivity.java
@@ -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();
@@ -1473,8 +1479,9 @@ public class MainVNCActivity extends VncCanvasActivity {
});
bindingSendKey.sendtextButton.setOnClickListener(v -> {
- vncCanvas.sendText(bindingSendKey.sendtextEdittext.getText().toString());
+ String text = bindingSendKey.sendtextEdittext.getText().toString();
bindingSendKey.sendtextEdittext.setText("");
+ new Thread(() -> vncCanvas.sendText(text)).start();
});
bindingSendKey.sendtextEdittext.addTextChangedListener(new TextWatcher() {
diff --git a/app/src/main/java/com/vectras/vm/AboutActivity.java b/app/src/main/java/com/vectras/vm/AboutActivity.java
index df508b1..5b79239 100644
--- a/app/src/main/java/com/vectras/vm/AboutActivity.java
+++ b/app/src/main/java/com/vectras/vm/AboutActivity.java
@@ -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);
});
diff --git a/app/src/main/java/com/vectras/vm/AppConfig.java b/app/src/main/java/com/vectras/vm/AppConfig.java
index c52c500..60b3a6b 100644
--- a/app/src/main/java/com/vectras/vm/AppConfig.java
+++ b/app/src/main/java/com/vectras/vm/AppConfig.java
@@ -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;
}
diff --git a/app/src/main/java/com/vectras/vm/Fragment/CreateImageDialogFragment.java b/app/src/main/java/com/vectras/vm/Fragment/CreateImageDialogFragment.java
index 5ce7915..bd2ae3e 100644
--- a/app/src/main/java/com/vectras/vm/Fragment/CreateImageDialogFragment.java
+++ b/app/src/main/java/com/vectras/vm/Fragment/CreateImageDialogFragment.java
@@ -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(() -> {
diff --git a/app/src/main/java/com/vectras/vm/MainService.java b/app/src/main/java/com/vectras/vm/MainService.java
index c026fc5..0f5a56b 100644
--- a/app/src/main/java/com/vectras/vm/MainService.java
+++ b/app/src/main/java/com/vectras/vm/MainService.java
@@ -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));
+ }
+ });
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/vectras/vm/SplashActivity.java b/app/src/main/java/com/vectras/vm/SplashActivity.java
index cdcaace..af15f8e 100644
--- a/app/src/main/java/com/vectras/vm/SplashActivity.java
+++ b/app/src/main/java/com/vectras/vm/SplashActivity.java
@@ -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);
diff --git a/app/src/main/java/com/vectras/vm/VMManager.java b/app/src/main/java/com/vectras/vm/VMManager.java
index a013dd5..61425b0 100644
--- a/app/src/main/java/com/vectras/vm/VMManager.java
+++ b/app/src/main/java/com/vectras/vm/VMManager.java
@@ -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> vmList = new Gson().fromJson(vmJsonListContent, new TypeToken>>() {
- }.getType());
+ ArrayList> vmList;
+
+ try {
+ vmList = new Gson().fromJson(vmJsonListContent, new TypeToken>>() {
+ }.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() {
diff --git a/app/src/main/java/com/vectras/vm/VectrasApp.java b/app/src/main/java/com/vectras/vm/VectrasApp.java
index c2dd2dc..bc9b474 100644
--- a/app/src/main/java/com/vectras/vm/VectrasApp.java
+++ b/app/src/main/java/com/vectras/vm/VectrasApp.java
@@ -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() {
diff --git a/app/src/main/java/com/vectras/vm/creator/VMCreatorActivity.java b/app/src/main/java/com/vectras/vm/creator/VMCreatorActivity.java
index 480a019..22ce9da 100644
--- a/app/src/main/java/com/vectras/vm/creator/VMCreatorActivity.java
+++ b/app/src/main/java/com/vectras/vm/creator/VMCreatorActivity.java
@@ -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;
@@ -29,6 +32,7 @@ import com.vectras.qemu.MainSettingsManager;
import com.vectras.vm.AppConfig;
import com.vectras.vm.Fragment.CreateImageDialogFragment;
import com.vectras.vm.R;
+import com.vectras.vm.file.FilePickerDialog;
import com.vectras.vm.store.RomInfo;
import com.vectras.vm.SplashActivity;
import com.vectras.vm.VMManager;
@@ -63,6 +67,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 {
@@ -99,13 +104,25 @@ public class VMCreatorActivity extends AppCompatActivity {
return true;
} else if (id == R.id.add_file) {
try {
- filePicker.launch("*/*");
+ if (MainSettingsManager.getBuiltInFilePicker(this)) {
+ FilePickerDialog filePickerDialog = new FilePickerDialog();
+ filePickerDialog.pick(this, filePickerDialog.TYPE_FILE, (path -> handleAddFile(Uri.fromFile(new File (path)))));
+ } else {
+ filePicker.launch("*/*");
+ }
} catch (Exception e) {
IntentUtils.showErrorDialog(this);
}
return true;
} else if (id == R.id.show_in_folder) {
- FileUtils.openFolder(this, VmFileManager.getPath(vmID));
+ if (MainSettingsManager.getBuiltInFilePicker(this)) {
+ FilePickerDialog filePickerDialog = new FilePickerDialog();
+ filePickerDialog.setHomeName(current.itemName);
+ filePickerDialog.setLockHome(true);
+ filePickerDialog.browse(this, VmFileManager.getPath(vmID));
+ } else {
+ FileUtils.openFolder(this, VmFileManager.getPath(vmID));
+ }
return true;
} else {
return super.onOptionsItemSelected(item);
@@ -301,7 +318,12 @@ public class VMCreatorActivity extends AppCompatActivity {
} else if (getIntent().hasExtra("importcvbinow")) {
setDefault();
try {
- cvbiPicker.launch("*/*");
+ if (MainSettingsManager.getBuiltInFilePicker(this)) {
+ FilePickerDialog filePickerDialog = new FilePickerDialog();
+ filePickerDialog.pick(this, filePickerDialog.ROM_FILE, (path -> importRom(null, path, new File(path).getName())));
+ } else {
+ cvbiPicker.launch("*/*");
+ }
} catch (Exception e) {
IntentUtils.showErrorDialog(this);
}
@@ -309,9 +331,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);
@@ -482,44 +523,46 @@ public class VMCreatorActivity extends AppCompatActivity {
@SuppressLint("SetTextI18n")
private final ActivityResultLauncher filePicker =
- registerForActivityResult(new ActivityResultContracts.GetContent(), uri -> {
- if (uri == null) return;
- showProgressDialog(getString(R.string.copying_file));
+ registerForActivityResult(new ActivityResultContracts.GetContent(), this::handleAddFile);
- executor.execute(() -> {
- try {
- isProcessingFile = true;
+ public void handleAddFile(Uri uri) {
+ if (uri == null) return;
+ showProgressDialog(getString(R.string.copying_file));
- String filePath = copyToTemp(uri);
+ executor.execute(() -> {
+ try {
+ isProcessingFile = true;
- runOnUiThread(() -> DialogUtils.twoDialog(this,
- getString(R.string.file_added),
- filePath,
- getString(R.string.copy_full_path),
- getString(R.string.close),
- true,
- R.drawable.check_24px,
- true,
- () -> ClipboardUltils.copyToClipboard(this, filePath),
- null,
- null));
- } catch (Exception e) {
- runOnUiThread(() -> DialogUtils.oneDialog(this,
- getString(R.string.oops),
- getString(R.string.adding_file_failed_content),
- getString(R.string.ok),
- true,
- R.drawable.error_96px,
- true,
- null,
- null));
- Log.e(TAG, "filePicker: " + e.getMessage());
- } finally {
- isProcessingFile = false;
- runOnUiThread(() -> DialogUtils.safeDismiss(this, progressDialog));
- }
- });
- });
+ String filePath = copyToTemp(uri);
+
+ runOnUiThread(() -> DialogUtils.twoDialog(this,
+ getString(R.string.file_added),
+ filePath,
+ getString(R.string.copy_full_path),
+ getString(R.string.close),
+ true,
+ R.drawable.check_24px,
+ true,
+ () -> ClipboardUltils.copyToClipboard(this, filePath),
+ null,
+ null));
+ } catch (Exception e) {
+ runOnUiThread(() -> DialogUtils.oneDialog(this,
+ getString(R.string.oops),
+ getString(R.string.adding_file_failed_content),
+ getString(R.string.ok),
+ true,
+ R.drawable.error_96px,
+ true,
+ null,
+ null));
+ Log.e(TAG, "filePicker: " + e.getMessage());
+ } finally {
+ isProcessingFile = false;
+ runOnUiThread(() -> DialogUtils.safeDismiss(this, progressDialog));
+ }
+ });
+ }
private void loadConfig(DataMainRoms vmConfig) {
if (vmConfig != null) current = vmConfig;
@@ -1156,7 +1199,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 {
@@ -1272,13 +1315,26 @@ public class VMCreatorActivity extends AppCompatActivity {
try {
if (PENDING_SELECT_FILE_MODE == SELECT_CDROM_0_FILE_MODE || PENDING_SELECT_FILE_MODE == SELECT_CDROM_1_FILE_MODE) {
- isoPicker.launch("*/*");
+ if (MainSettingsManager.getBuiltInFilePicker(this)) {
+ builtInFilePicker(PICK_OPTICAL_FILE_MODE);
+ } else {
+ isoPicker.launch("*/*");
+ }
} else if (PENDING_SELECT_FILE_MODE == SELECT_FLOPPY_A_FILE_MODE || PENDING_SELECT_FILE_MODE == SELECT_FLOPPY_B_FILE_MODE) {
+ if (MainSettingsManager.getBuiltInFilePicker(this)) {
+ builtInFilePicker(PICK_FLOPPY_FILE_MODE);
+ } else {
floppyPicker.launch("*/*");
+ }
} else {
- diskPicker.launch("*/*");
+ if (MainSettingsManager.getBuiltInFilePicker(this)) {
+ builtInFilePicker(PICK_DRIVE_FILE_MODE);
+ } else {
+ diskPicker.launch("*/*");
+ }
}
} catch (Exception e) {
+ Log.e(TAG, "pickStorageFile: ", e);
IntentUtils.showErrorDialog(this);
}
}
@@ -1452,4 +1508,23 @@ public class VMCreatorActivity extends AppCompatActivity {
});
}
}
+
+ final int PICK_DRIVE_FILE_MODE = 0;
+ final int PICK_OPTICAL_FILE_MODE = 1;
+ final int PICK_FLOPPY_FILE_MODE = 2;
+
+ private void builtInFilePicker(int mode) {
+ FilePickerDialog filePickerDialog = new FilePickerDialog();
+
+ int finalMode;
+ if (mode == PICK_DRIVE_FILE_MODE) {
+ finalMode = filePickerDialog.FIT_PICK_DISK_FILE_MODE;
+ } else if (mode == PICK_OPTICAL_FILE_MODE) {
+ finalMode = filePickerDialog.FIT_PICK_OPTICAL_FILE_MODE;
+ } else {
+ finalMode = filePickerDialog.FIT_PICK_FLOPPY_FILE_MODE;
+ }
+
+ filePickerDialog.pick(this, finalMode, (path -> handleFile(Uri.fromFile(new File(path)))));
+ }
}
diff --git a/app/src/main/java/com/vectras/vm/fcm/FCMManager.java b/app/src/main/java/com/vectras/vm/fcm/FCMManager.java
index 429a0fd..40d068a 100644
--- a/app/src/main/java/com/vectras/vm/fcm/FCMManager.java
+++ b/app/src/main/java/com/vectras/vm/fcm/FCMManager.java
@@ -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 -> {
diff --git a/app/src/main/java/com/vectras/vm/file/FilePickerData.java b/app/src/main/java/com/vectras/vm/file/FilePickerData.java
new file mode 100644
index 0000000..7a732cb
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/file/FilePickerData.java
@@ -0,0 +1,9 @@
+package com.vectras.vm.file;
+
+public class FilePickerData {
+ String name = "";
+ String path = "";
+ int type = -1;
+ int icon = -1;
+ boolean enabled = true;
+}
diff --git a/app/src/main/java/com/vectras/vm/file/FilePickerDialog.java b/app/src/main/java/com/vectras/vm/file/FilePickerDialog.java
new file mode 100644
index 0000000..7ed3ab7
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/file/FilePickerDialog.java
@@ -0,0 +1,469 @@
+package com.vectras.vm.file;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.webkit.MimeTypeMap;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
+import androidx.core.content.FileProvider;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.vectras.vm.R;
+import com.vectras.vm.databinding.DialogFilePickerBinding;
+import com.vectras.vm.databinding.ListFileBinding;
+import com.vectras.vm.manager.FormatManager;
+import com.vectras.vm.utils.ClipboardUltils;
+import com.vectras.vm.utils.FileUtils;
+import com.vectras.vm.utils.IntentUtils;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+
+public class FilePickerDialog extends DialogFragment {
+
+ DialogFilePickerBinding binding;
+ LinearLayoutManager layoutmanager;
+ RecyclerviewAdapter adapter;
+ FilePickerDialogCallback callback;
+ FilePickerSettings settings;
+
+ String currentPath = "";
+ String homeName;
+ String homePath;
+ boolean lockHome;
+ int pickType = -1;
+
+ boolean isShowHiddenFile;
+ boolean isShowHiddenDiviers;
+
+ boolean fixTextColor;
+
+ int textColor;
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ binding = DialogFilePickerBinding.inflate(getLayoutInflater());
+
+ settings = new FilePickerSettings(requireActivity());
+
+ isShowHiddenFile = settings.showHiddenFiles();
+ isShowHiddenDiviers = settings.divider();
+
+ if (fixTextColor) {
+ textColor = binding.tvTitle.getCurrentTextColor();
+ binding.tvFullPath.setTextColor(textColor);
+ binding.tvNothingHere.setTextColor(textColor);
+ }
+
+ AlertDialog dialog = new AlertDialog.Builder(requireActivity()).create();
+ dialog.setView(binding.getRoot());
+
+ layoutmanager = new LinearLayoutManager(requireActivity());
+ adapter = new RecyclerviewAdapter(requireActivity(), dialog, new ArrayList<>(), callback);
+ binding.list.setAdapter(adapter);
+ binding.list.setLayoutManager(layoutmanager);
+
+ binding.list.addOnScrollListener(new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrolled(@NonNull RecyclerView rv, int dx, int dy) {
+ boolean canScrollUp = rv.canScrollVertically(-1);
+ boolean canScrollDown = rv.canScrollVertically(1);
+
+ binding.dvTop.setVisibility(canScrollUp ? View.VISIBLE : View.INVISIBLE);
+ binding.dvBottom.setVisibility(canScrollDown ? View.VISIBLE : View.INVISIBLE);
+ }
+ });
+
+ String lastPath = new File(settings.lastPath()).exists() ? settings.lastPath() : Environment.getExternalStorageDirectory().getAbsolutePath();
+
+ adapter.load((homePath != null && new File(homePath).exists()) ? homePath : lastPath);
+
+ binding.btnHome.setOnClickListener(v -> adapter.load(homePath != null && new File(homePath).exists() ? homePath : Environment.getExternalStorageDirectory().getAbsolutePath()));
+ binding.btnUp.setOnClickListener(v -> adapter.load(new File(currentPath).getParent()));
+
+ if (pickType == BROWSE_MODE) {
+ binding.btnOpenInOtherApp.setOnClickListener(v -> {
+ if (!isAdded()) return;
+ FileUtils.openFolder(requireContext(), currentPath);
+ dismiss();
+ });
+ } else {
+ binding.btnOpenInOtherApp.setVisibility(View.GONE);
+ }
+ binding.tvFullPath.setOnClickListener(v -> {
+ if (!isAdded()) return;
+ ClipboardUltils.copyToClipboard(requireContext(), currentPath, false);
+ Toast.makeText(requireContext(), R.string.the_folder_path_has_been_copied_to_the_clipboard, Toast.LENGTH_SHORT).show();
+ });
+ binding.btnClose.setOnClickListener(v -> dismiss());
+
+ return dialog;
+ }
+
+ public void setFixTextColor(boolean isEnable) {
+ fixTextColor = isEnable;
+ }
+
+ public void setHomeName(String name) {
+ homeName = name;
+ }
+
+ public void setHomePath(String path) {
+ homePath = new File(path).getAbsolutePath();
+ }
+
+ public void setLockHome(boolean lock) {
+ lockHome = lock;
+ }
+
+ public void browse(Activity activity) {
+ pickType = BROWSE_MODE;
+ show(((FragmentActivity) activity).getSupportFragmentManager(), "file_picker");
+ }
+
+ public void browse(Activity activity, String path) {
+ setHomePath(path);
+ browse(activity);
+ }
+
+ public void pick(Activity activity, int type, FilePickerDialogCallback callback) {
+ this.callback = callback;
+ pickType = type;
+ show(((FragmentActivity) activity).getSupportFragmentManager(), "file_picker");
+ }
+
+ public void updateUpButton(String path) {
+ if (path.equals(Environment.getExternalStorageDirectory().getAbsolutePath())) {
+ binding.tvTitle.setText(R.string.internal_storage);
+ binding.btnHome.setVisibility(View.GONE);
+ binding.btnUp.setVisibility(View.INVISIBLE);
+ binding.btnUp.setEnabled(false);
+ } else if (lockHome && homePath.equals(path)) {
+ binding.tvTitle.setText(homeName.isEmpty() ? new File(path).getName() : homeName);
+ binding.btnHome.setVisibility(View.GONE);
+ binding.btnUp.setVisibility(View.INVISIBLE);
+ binding.btnUp.setEnabled(false);
+ } else {
+ binding.tvTitle.setText(new File(path).getName());
+ binding.btnHome.setVisibility(View.VISIBLE);
+ binding.btnUp.setVisibility(View.VISIBLE);
+ binding.btnUp.setEnabled(true);
+ }
+
+ binding.tvFullPath.setText(path);
+ }
+
+ boolean isLoading;
+
+ private ArrayList loadFileList(String path) {
+ currentPath = path;
+
+ new Handler(Looper.getMainLooper()).post(() -> updateUpButton(path));
+
+ ArrayList list = new ArrayList<>();
+
+ File dir = new File(path);
+ if (!dir.exists() || dir.isFile()) return list;
+
+ File[] listFiles = dir.listFiles();
+ if (listFiles == null || listFiles.length == 0) return list;
+
+ list.clear();
+ for (File file : listFiles) {
+ if (!isShowHiddenFile && file.getName().startsWith(".")) continue;
+
+ FilePickerData item = new FilePickerData();
+
+ item.name = file.getName();
+ item.path = file.getAbsolutePath();
+ item.type = file.isDirectory() ? TYPE_FOLDER : getType(file.getName());
+ item.icon = getIcon(item.type);
+ item.enabled = pickType == TYPE_FILE || pickType == BROWSE_MODE || item.type == TYPE_FOLDER || (pickType >= FIT_PICK_DISK_FILE_MODE ? checkFitPickFileMode(item.name) : item.type == pickType);
+ list.add(item);
+ }
+
+ Collections.sort(list, (o1, o2) -> {
+ if (o1.name == null || o2.name == null) {
+ return 0;
+ }
+ return o1.name.compareToIgnoreCase(o2.name);
+ });
+
+ return list;
+ }
+
+ public static final int BROWSE_MODE = -1;
+ public static final int TYPE_FILE = 0;
+ public static final int TYPE_FOLDER = 1;
+ public static final int AUDIO_FILE = 2;
+ public static final int VIDEO_FILE = 3;
+ public static final int IMAGE_FILE = 4;
+ public static final int DOCUMENT_FILE = 5;
+ public static final int APPLICATION_FILE = 6;
+ public static final int DISK_FILE = 7;
+ public static final int ISO_FILE = 8;
+ public static final int FLOPPY_FILE = 9;
+ public static final int ROM_FILE = 10;
+ public static final int SHELL_FILE = 11;
+ public static final int DB_FILE = 12;
+ public static final int LOG_FILE = 13;
+ public static final int WINDOWS_FILE = 14;
+ public static final int CONFIG_FILE = 15;
+ public static final int ZIP_FILE = 16;
+ public static final int DEV_FILE = 17;
+ public static final int SYSTEM_FILE = 18;
+ public static final int PACKAGE_FILE = 19;
+ public static final int FONT_FILE = 20;
+ public static final int KEY_FILE = 21;
+ public static final int IOS_FILE = 22;
+
+ private int getType(String fileName) {
+ if (!fileName.contains(".")) return TYPE_FILE;
+
+ String extension = fileName.substring(fileName.lastIndexOf(".")).toLowerCase();
+ return switch (extension) {
+ case ".mp3", ".wav", ".ogg", ".flac", ".m4a", ".aif", ".wma", ".smi" -> AUDIO_FILE;
+ case ".mp4", ".webm", ".mov", ".mkv", ".avi", ".wmv" -> VIDEO_FILE;
+ case ".jpg", ".jpeg", ".png", ".gif", ".webp", ".raw" -> IMAGE_FILE;
+ case ".pdf", ".txt", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".md", ".csv" -> DOCUMENT_FILE;
+ case ".apk", ".xapk", ".apkm", ".apks" -> APPLICATION_FILE;
+ case ".img", ".qcow2", ".vhd", ".vhdx", ".vdi", ".qcow", ".vmdk", ".vpc" -> DISK_FILE;
+ case ".iso", ".cdr", ".toast" -> ISO_FILE;
+ case ".flp", ".ima" -> FLOPPY_FILE;
+ case ".cvbi" -> ROM_FILE;
+ case ".sh", ".cmd", ".bat", ".ps1" -> SHELL_FILE;
+ case ".json", ".db", ".sqlite", ".sqlite3", ".dbf", ".accdb", ".mdf", ".ldf", ".ibd", ".frm", ".parquet", ".orc", ".kdbx" -> DB_FILE;
+ case ".log" -> LOG_FILE;
+ case ".exe", ".msi", ".msix", ".appx", ".msixbundle" -> WINDOWS_FILE;
+ case ".conf", ".ini" -> CONFIG_FILE;
+ case ".zip", ".7z", ".rar" -> ZIP_FILE;
+ case ".java", ".kt", ".js", ".py", ".c", ".cpp", ".h", ".xml", ".html", ".css", ".lua", ".go", ".asl" -> DEV_FILE;
+ case ".dll" -> SYSTEM_FILE;
+ case ".deb", ".rpm" -> PACKAGE_FILE;
+ case ".ttf", ".otf" -> FONT_FILE;
+ case ".jks" -> KEY_FILE;
+ case ".ipa", ".ipsw" -> IOS_FILE;
+ default -> TYPE_FILE;
+ };
+ }
+
+ private int getIcon(int type) {
+ return switch (type) {
+ case TYPE_FOLDER -> R.drawable.folder_24px;
+ case AUDIO_FILE -> R.drawable.music_note_24px;
+ case VIDEO_FILE -> R.drawable.movie_24px;
+ case IMAGE_FILE -> R.drawable.image_24px;
+ case DOCUMENT_FILE -> R.drawable.description_24px;
+ case APPLICATION_FILE -> R.drawable.android_24px;
+ case DISK_FILE -> R.drawable.hard_drive_24px;
+ case ISO_FILE -> R.drawable.album_24px;
+ case ROM_FILE -> R.drawable.vectras_vm_24px;
+ case SHELL_FILE -> R.drawable.terminal_24px;
+ case DB_FILE -> R.drawable.database_24px;
+ case LOG_FILE -> R.drawable.monitor_heart_24px;
+ case WINDOWS_FILE -> R.drawable.wysiwyg_24px;
+ case CONFIG_FILE -> R.drawable.tune_24px;
+ case ZIP_FILE -> R.drawable.folder_zip_24px;
+ case DEV_FILE -> R.drawable.code_24px;
+ case SYSTEM_FILE -> R.drawable.settings_24px;
+ case PACKAGE_FILE -> R.drawable.package_24px;
+ case FONT_FILE -> R.drawable.titlecase_24px;
+ case KEY_FILE -> R.drawable.key_24px;
+ case IOS_FILE -> R.drawable.ios_24px;
+ default -> R.drawable.draft_24px;
+ };
+ }
+
+ public static final int FIT_PICK_DISK_FILE_MODE = 20;
+ public static final int FIT_PICK_OPTICAL_FILE_MODE = 21;
+ public static final int FIT_PICK_FLOPPY_FILE_MODE = 22;
+
+ public boolean checkFitPickFileMode(String fileName) {
+ if (pickType == FIT_PICK_DISK_FILE_MODE) {
+ return FormatManager.isHardDriveFileFormat(fileName);
+ } else if (pickType == FIT_PICK_OPTICAL_FILE_MODE) {
+ return FormatManager.isOpticalFileFormat(fileName);
+ } else if (pickType == FIT_PICK_FLOPPY_FILE_MODE) {
+ return FormatManager.isFloppyFileFormat(fileName);
+ } else {
+ return false;
+ }
+ }
+
+ public interface FilePickerDialogCallback {
+ void onPicked(String path);
+ }
+
+ private class RecyclerviewAdapter extends RecyclerView.Adapter {
+
+ Activity activity;
+ ArrayList data;
+ AlertDialog dialog;
+ FilePickerDialogCallback callback;
+
+ public RecyclerviewAdapter(Activity activity, AlertDialog alertDialog, ArrayList arr, FilePickerDialogCallback callback) {
+ this.activity = activity;
+ data = arr;
+ dialog = alertDialog;
+ this.callback = callback;
+ }
+
+ @NonNull
+ @Override
+ public RecyclerviewAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ LayoutInflater inflater = activity.getLayoutInflater();
+ ListFileBinding binding1 = ListFileBinding.inflate(inflater, parent, false);
+ return new RecyclerviewAdapter.ViewHolder(binding1.getRoot());
+ }
+
+ @Override
+ public void onBindViewHolder(RecyclerviewAdapter.ViewHolder holder, final int position) {
+ View view = holder.itemView;
+ TextView text = view.findViewById(R.id.text);
+ ImageView icon = view.findViewById(R.id.icon);
+ text.setText(data.get(position).name);
+ icon.setImageResource(data.get(position).icon);
+
+ if (fixTextColor) text.setTextColor(textColor);
+
+ LinearLayout main = view.findViewById(R.id.main);
+
+ if (data.get(position).enabled) {
+ main.setEnabled(true);
+ main.setAlpha(1f);
+ main.setOnClickListener(v -> {
+ if (data.get(position).type == TYPE_FOLDER) {
+ load(data.get(position).path);
+ } else {
+ if (activity.isFinishing() || activity.isDestroyed()) return;
+ if (pickType == BROWSE_MODE) {
+ openFile(data.get(position).path);
+ } else {
+ if (callback != null) callback.onPicked(data.get(position).path);
+
+ settings.lastPath(currentPath);
+ dialog.dismiss();
+ }
+ }
+ });
+ } else {
+ main.setAlpha(0.5f);
+ main.setOnClickListener(null);
+ main.setEnabled(false);
+ }
+
+ if (!isShowHiddenDiviers || position == data.size() - 1) {
+ view.findViewById(R.id.md_inset_bottom).setVisibility(View.GONE);
+ } else {
+ view.findViewById(R.id.md_inset_bottom).setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return data.size();
+ }
+
+ public void load(String path) {
+ isLoading = true;
+
+ new Handler(Looper.getMainLooper()).postDelayed(() -> {
+ if (isAdded() && isLoading) binding.lnLoading.setVisibility(View.VISIBLE);
+ }, 200);
+
+ new Thread(() -> {
+ ArrayList data1 = loadFileList(path);
+ new Handler(Looper.getMainLooper()).post(() -> {
+ if (!isAdded()) return;
+
+ if (data1.isEmpty()) {
+ binding.lnEmpty.setVisibility(View.VISIBLE);
+ binding.lnBrowse.setVisibility(View.INVISIBLE);
+ } else {
+ binding.lnEmpty.setVisibility(View.INVISIBLE);
+ binding.lnBrowse.setVisibility(View.VISIBLE);
+ data.clear();
+ data.addAll(data1);
+ notifyDataSetChanged();
+ }
+
+ binding.lnLoading.setVisibility(View.GONE);
+ isLoading = false;
+ });
+ }).start();
+ }
+
+ public static class ViewHolder extends RecyclerView.ViewHolder {
+ public ViewHolder(View v) {
+ super(v);
+ }
+ }
+ }
+
+ public void openFile(String path) {
+ File file = new File(path);
+
+ if (!file.exists()) {
+ if (!isAdded()) return;
+ IntentUtils.showErrorDialog(requireActivity());
+ return;
+ }
+
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ Uri fileUri;
+
+ if (!isAdded()) return;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ String authority = requireActivity().getPackageName() + ".provider";
+ fileUri = FileProvider.getUriForFile(requireContext(), authority, file);
+ } else {
+ fileUri = Uri.fromFile(file);
+ }
+
+
+ String extension = MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(file).toString());
+ String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.toLowerCase());
+ if (mimeType == null) {
+ mimeType = "*/*";
+ }
+
+ intent.setDataAndType(fileUri, mimeType);
+
+
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+
+ Intent chooserIntent = Intent.createChooser(intent, getString(R.string.open_with));
+ chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+
+ if (!isAdded()) return;
+ if (intent.resolveActivity(requireActivity().getPackageManager()) != null) {
+ requireContext().startActivity(chooserIntent);
+ } else {
+ IntentUtils.showErrorDialog(requireActivity());
+ }
+ }
+}
diff --git a/app/src/main/java/com/vectras/vm/file/FilePickerSettings.java b/app/src/main/java/com/vectras/vm/file/FilePickerSettings.java
new file mode 100644
index 0000000..0cfc573
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/file/FilePickerSettings.java
@@ -0,0 +1,47 @@
+package com.vectras.vm.file;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Environment;
+
+public class FilePickerSettings {
+ Context context;
+ SharedPreferences sharedPreferences;
+
+ public FilePickerSettings(Context context) {
+ this.context = context;
+ sharedPreferences = context.getSharedPreferences("built_in_file_picker_settings", Context.MODE_PRIVATE);
+ }
+
+ public void lastPath(String path) {
+ sharedPreferences.edit().putString("lastPath", path).apply();
+ }
+
+ public String lastPath() {
+ return sharedPreferences.getString("lastPath", Environment.getExternalStorageDirectory().getAbsolutePath());
+ }
+
+ public void resume(boolean enabled) {
+ sharedPreferences.edit().putBoolean("resume", enabled).apply();
+ }
+
+ public boolean resume() {
+ return sharedPreferences.getBoolean("resume", true);
+ }
+
+ public void showHiddenFiles(boolean enabled) {
+ sharedPreferences.edit().putBoolean("showHiddenFiles", enabled).apply();
+ }
+
+ public boolean showHiddenFiles() {
+ return sharedPreferences.getBoolean("showHiddenFiles", true);
+ }
+
+ public void divider(boolean enabled) {
+ sharedPreferences.edit().putBoolean("divider", enabled).apply();
+ }
+
+ public boolean divider() {
+ return sharedPreferences.getBoolean("divider", false);
+ }
+}
diff --git a/app/src/main/java/com/vectras/vm/main/MainActivity.java b/app/src/main/java/com/vectras/vm/main/MainActivity.java
index 54efe39..f099cb9 100644
--- a/app/src/main/java/com/vectras/vm/main/MainActivity.java
+++ b/app/src/main/java/com/vectras/vm/main/MainActivity.java
@@ -46,6 +46,7 @@ import com.vectras.qemu.MainSettingsManager;
import com.vectras.vm.AboutActivity;
import com.vectras.vm.AppConfig;
import com.vectras.vm.creator.VMCreatorActivity;
+import com.vectras.vm.file.FilePickerDialog;
import com.vectras.vm.settings.Minitools;
import com.vectras.vm.R;
import com.vectras.vm.WebViewActivity;
@@ -522,7 +523,14 @@ public class MainActivity extends AppCompatActivity implements RomStoreFragment.
startActivity(new Intent(this, Settings2Activity.class));
} else if (id == R.id.navigation_data_explorer) {
// startActivity(new Intent(this, DataExplorerActivity.class));
- FileUtils.openFolder(this, AppConfig.maindirpath);
+ if (MainSettingsManager.getBuiltInFilePicker(this)) {
+ FilePickerDialog filePickerDialog = new FilePickerDialog();
+ filePickerDialog.setHomeName(getString(R.string.app_name));
+ filePickerDialog.setLockHome(true);
+ filePickerDialog.browse(this, AppConfig.maindirpath);
+ } else {
+ FileUtils.openFolder(this, AppConfig.maindirpath);
+ }
} else if (id == R.id.navigation_item_donate) {
String tw = "https://www.patreon.com/VectrasTeam";
Intent w = new Intent(ACTION_VIEW);
diff --git a/app/src/main/java/com/vectras/vm/main/core/DisplaySystem.java b/app/src/main/java/com/vectras/vm/main/core/DisplaySystem.java
index 4f5abfb..0dc4b63 100644
--- a/app/src/main/java/com/vectras/vm/main/core/DisplaySystem.java
+++ b/app/src/main/java/com/vectras/vm/main/core/DisplaySystem.java
@@ -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) {
diff --git a/app/src/main/java/com/vectras/vm/main/core/MainStartVM.java b/app/src/main/java/com/vectras/vm/main/core/MainStartVM.java
index 64b25fa..2073dc9 100644
--- a/app/src/main/java/com/vectras/vm/main/core/MainStartVM.java
+++ b/app/src/main/java/com/vectras/vm/main/core/MainStartVM.java
@@ -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";
}
}
diff --git a/app/src/main/java/com/vectras/vm/main/core/PendingCommand.java b/app/src/main/java/com/vectras/vm/main/core/PendingCommand.java
index c30a936..153fe9e 100644
--- a/app/src/main/java/com/vectras/vm/main/core/PendingCommand.java
+++ b/app/src/main/java/com/vectras/vm/main/core/PendingCommand.java
@@ -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 = "";
diff --git a/app/src/main/java/com/vectras/vm/main/core/StartVmDialog.java b/app/src/main/java/com/vectras/vm/main/core/StartVmDialog.java
index 2b1f029..8ffc3d8 100644
--- a/app/src/main/java/com/vectras/vm/main/core/StartVmDialog.java
+++ b/app/src/main/java/com/vectras/vm/main/core/StartVmDialog.java
@@ -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) {
diff --git a/app/src/main/java/com/vectras/vm/main/monitor/SystemMonitorFragment.java b/app/src/main/java/com/vectras/vm/main/monitor/SystemMonitorFragment.java
index 9ac30ff..6cf5305 100644
--- a/app/src/main/java/com/vectras/vm/main/monitor/SystemMonitorFragment.java
+++ b/app/src/main/java/com/vectras/vm/main/monitor/SystemMonitorFragment.java
@@ -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(() -> {
diff --git a/app/src/main/java/com/vectras/vm/manager/BatteryEmulatorManager.java b/app/src/main/java/com/vectras/vm/manager/BatteryEmulatorManager.java
index 170550b..9de414f 100644
--- a/app/src/main/java/com/vectras/vm/manager/BatteryEmulatorManager.java
+++ b/app/src/main/java/com/vectras/vm/manager/BatteryEmulatorManager.java
@@ -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 {
diff --git a/app/src/main/java/com/vectras/vm/manager/HostSharedFolder.java b/app/src/main/java/com/vectras/vm/manager/HostSharedFolder.java
index 2189547..2d675f2 100644
--- a/app/src/main/java/com/vectras/vm/manager/HostSharedFolder.java
+++ b/app/src/main/java/com/vectras/vm/manager/HostSharedFolder.java
@@ -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);
- }
}
diff --git a/app/src/main/java/com/vectras/vm/manager/ProcessManager.java b/app/src/main/java/com/vectras/vm/manager/ProcessManager.java
index 484d636..c7c8e3b 100644
--- a/app/src/main/java/com/vectras/vm/manager/ProcessManager.java
+++ b/app/src/main/java/com/vectras/vm/manager/ProcessManager.java
@@ -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-");
}
}
diff --git a/app/src/main/java/com/vectras/vm/manager/QemuManager.java b/app/src/main/java/com/vectras/vm/manager/QemuManager.java
index 88b50d8..d548978 100644
--- a/app/src/main/java/com/vectras/vm/manager/QemuManager.java
+++ b/app/src/main/java/com/vectras/vm/manager/QemuManager.java
@@ -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) {
diff --git a/app/src/main/java/com/vectras/vm/manager/QmpSender.java b/app/src/main/java/com/vectras/vm/manager/QmpSender.java
index 861cc89..76a8e5e 100644
--- a/app/src/main/java/com/vectras/vm/manager/QmpSender.java
+++ b/app/src/main/java/com/vectras/vm/manager/QmpSender.java
@@ -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) {
diff --git a/app/src/main/java/com/vectras/vm/manager/VmAudioManager.java b/app/src/main/java/com/vectras/vm/manager/VmAudioManager.java
index 97bd772..ebf41b7 100644
--- a/app/src/main/java/com/vectras/vm/manager/VmAudioManager.java
+++ b/app/src/main/java/com/vectras/vm/manager/VmAudioManager.java
@@ -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";
diff --git a/app/src/main/java/com/vectras/vm/manager/VmControllerDialog.java b/app/src/main/java/com/vectras/vm/manager/VmControllerDialog.java
index 54b2540..affd2a9 100644
--- a/app/src/main/java/com/vectras/vm/manager/VmControllerDialog.java
+++ b/app/src/main/java/com/vectras/vm/manager/VmControllerDialog.java
@@ -5,6 +5,7 @@ import android.androidVNC.VncCanvas;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -26,13 +27,14 @@ import com.vectras.vm.AppConfig;
import com.vectras.vm.R;
import com.vectras.vm.VMManager;
import com.vectras.vm.databinding.DialogChangeRemovableDevicesBinding;
+import com.vectras.vm.file.FilePickerDialog;
import com.vectras.vm.main.core.DisplaySystem;
import com.vectras.vm.main.vms.DataMainRoms;
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;
@@ -226,7 +228,16 @@ public class VmControllerDialog extends DialogFragment {
if (isHavingOpticalDisc() || isHavingSecondaryOpticalDisc()) {
if (isHavingOpticalDisc()) {
- binding.lnCdrom.setOnClickListener(v -> isoPicker.launch("*/*"));
+ binding.lnCdrom.setOnClickListener(v -> {
+ if (MainSettingsManager.getBuiltInFilePicker(requireContext())) {
+ FilePickerDialog filePickerDialog = new FilePickerDialog();
+ if (requireActivity() instanceof MainVNCActivity) filePickerDialog.setFixTextColor(true);
+ filePickerDialog.pick(requireActivity(), filePickerDialog.FIT_PICK_OPTICAL_FILE_MODE, (path -> handleIsoFile(Uri.fromFile(new File(path)))));
+ } else {
+ isoPicker.launch("*/*");
+ }
+
+ });
binding.ivEjectcdrom.setOnClickListener(v -> {
QmpSender.ejectOpticalDisc(requireActivity(), infoBlock);
@@ -240,7 +251,15 @@ public class VmControllerDialog extends DialogFragment {
}
if (isHavingSecondaryOpticalDisc()) {
- binding.lnSecondaryCdrom.setOnClickListener(v -> isoPickerSecondary.launch("*/*"));
+ binding.lnSecondaryCdrom.setOnClickListener(v -> {
+ if (MainSettingsManager.getBuiltInFilePicker(requireContext())) {
+ FilePickerDialog filePickerDialog = new FilePickerDialog();
+ if (requireActivity() instanceof MainVNCActivity) filePickerDialog.setFixTextColor(true);
+ filePickerDialog.pick(requireActivity(), filePickerDialog.FIT_PICK_OPTICAL_FILE_MODE, (path -> handleIsoFile2(Uri.fromFile(new File(path)))));
+ } else {
+ isoPickerSecondary.launch("*/*");
+ }
+ });
binding.ivSecondaryEjectcdrom.setOnClickListener(v -> {
QmpSender.ejectSecondaryOpticalDisc(requireActivity(), infoBlock);
@@ -282,7 +301,15 @@ public class VmControllerDialog extends DialogFragment {
}
if (infoBlock.contains(QmpSender.DEFAULT_FLOPPY_DISK_0_ID)) {
- binding.lnFda.setOnClickListener(v -> floppyAPicker.launch("*/*"));
+ binding.lnFda.setOnClickListener(v -> {
+ if (MainSettingsManager.getBuiltInFilePicker(requireContext())) {
+ FilePickerDialog filePickerDialog = new FilePickerDialog();
+ if (requireActivity() instanceof MainVNCActivity) filePickerDialog.setFixTextColor(true);
+ filePickerDialog.pick(requireActivity(), filePickerDialog.FIT_PICK_FLOPPY_FILE_MODE, (path -> handleFloppyAFile(Uri.fromFile(new File(path)))));
+ } else {
+ floppyAPicker.launch("*/*");
+ }
+ });
binding.ivEjectfda.setOnClickListener(v -> {
QmpSender.ejectFloppyDiskA(requireActivity());
@@ -297,7 +324,15 @@ public class VmControllerDialog extends DialogFragment {
}
if (infoBlock.contains(QmpSender.DEFAULT_FLOPPY_DISK_1_ID)) {
- binding.lnFdb.setOnClickListener(v -> floppyBPicker.launch("*/*"));
+ binding.lnFdb.setOnClickListener(v -> {
+ if (MainSettingsManager.getBuiltInFilePicker(requireContext())) {
+ FilePickerDialog filePickerDialog = new FilePickerDialog();
+ if (requireActivity() instanceof MainVNCActivity) filePickerDialog.setFixTextColor(true);
+ filePickerDialog.pick(requireActivity(), filePickerDialog.FIT_PICK_FLOPPY_FILE_MODE, (path -> handleFloppyBFile(Uri.fromFile(new File(path)))));
+ } else {
+ floppyBPicker.launch("*/*");
+ }
+ });
binding.ivEjectfdb.setOnClickListener(v -> {
QmpSender.ejectFloppyDiskB(requireActivity());
@@ -312,7 +347,15 @@ public class VmControllerDialog extends DialogFragment {
}
if (infoBlock.contains(QmpSender.DEFAULT_MEMORY_CARD_ID)) {
- binding.lnSd.setOnClickListener(v -> memoryCardPicker.launch("*/*"));
+ binding.lnSd.setOnClickListener(v -> {
+ if (MainSettingsManager.getBuiltInFilePicker(requireContext())) {
+ FilePickerDialog filePickerDialog = new FilePickerDialog();
+ if (requireActivity() instanceof MainVNCActivity) filePickerDialog.setFixTextColor(true);
+ filePickerDialog.pick(requireActivity(), filePickerDialog.DISK_FILE, (path -> handleMemoryCardFile(Uri.fromFile(new File(path)))));
+ } else {
+ memoryCardPicker.launch("*/*");
+ }
+ });
binding.ivEjectsd.setOnClickListener(v -> {
QmpSender.ejectMemoryCard(requireActivity());
@@ -571,117 +614,134 @@ public class VmControllerDialog extends DialogFragment {
}
private final ActivityResultLauncher isoPicker =
- registerForActivityResult(new ActivityResultContracts.GetContent(), uri -> {
- if (uri != null) {
- try {
- new Thread(() -> {
- String path = FileUtils.getPath(getContext(), uri);
+ registerForActivityResult(new ActivityResultContracts.GetContent(), this::handleIsoFile);
- if (path == null) {
- showErrorSelectedFileDialog();
- dismiss();
- return;
- }
+ public void handleIsoFile(Uri uri) {
+ if (uri != null) {
+ try {
+ new Thread(() -> {
+ String path = FileUtils.getPath(getContext(), uri);
- File selectedFilePath = new File(path);
-
- if (isAdded()) {
- requireActivity().runOnUiThread(() -> {
- QmpSender.changeOpticalDisc(requireActivity(), selectedFilePath.getAbsolutePath(), infoBlock);
- dismiss();
- });
- }
- }).start();
- } catch (Exception e) {
+ if (path == null) {
showErrorSelectedFileDialog();
dismiss();
+ return;
}
- }
- });
+
+ File selectedFilePath = new File(path);
+
+ if (isAdded()) {
+ requireActivity().runOnUiThread(() -> {
+ QmpSender.changeOpticalDisc(requireActivity(), selectedFilePath.getAbsolutePath(), infoBlock);
+ dismiss();
+ });
+ }
+ }).start();
+ } catch (Exception e) {
+ showErrorSelectedFileDialog();
+ dismiss();
+ }
+ }
+ }
private final ActivityResultLauncher isoPickerSecondary =
- registerForActivityResult(new ActivityResultContracts.GetContent(), uri -> {
- if (uri != null) {
- try {
- new Thread(() -> {
- File selectedFilePath = new File(Objects.requireNonNull(FileUtils.getPath(getContext(), uri)));
+ registerForActivityResult(new ActivityResultContracts.GetContent(), this::handleIsoFile2);
- if (isAdded()) {
- requireActivity().runOnUiThread(() -> {
- QmpSender.changeSecondaryOpticalDisc(requireActivity(), selectedFilePath.getAbsolutePath(), infoBlock);
- dismiss();
- });
- }
- }).start();
- } catch (Exception e) {
- showErrorSelectedFileDialog();
- dismiss();
+ public void handleIsoFile2(Uri uri) {
+ if (uri != null) {
+ try {
+ new Thread(() -> {
+ File selectedFilePath = new File(Objects.requireNonNull(FileUtils.getPath(getContext(), uri)));
+
+ if (isAdded()) {
+ requireActivity().runOnUiThread(() -> {
+ QmpSender.changeSecondaryOpticalDisc(requireActivity(), selectedFilePath.getAbsolutePath(), infoBlock);
+ dismiss();
+ });
}
- }
- });
+ }).start();
+ } catch (Exception e) {
+ showErrorSelectedFileDialog();
+ dismiss();
+ }
+ }
+ }
private final ActivityResultLauncher floppyAPicker =
- registerForActivityResult(new ActivityResultContracts.GetContent(), uri -> {
- if (uri != null) {
- try {
- new Thread(() -> {
- File selectedFilePath = new File(Objects.requireNonNull(FileUtils.getPath(getContext(), uri)));
+ registerForActivityResult(new ActivityResultContracts.GetContent(), this::handleFloppyAFile);
- if (isAdded()) {
- requireActivity().runOnUiThread(() -> {
- QmpSender.changeFloppyDiskA(requireActivity(), selectedFilePath.getAbsolutePath());
- dismiss();
- });
- }
- }).start();
- } catch (Exception e) {
- showErrorSelectedFileDialog();
- dismiss();
+ public void handleFloppyAFile(Uri uri) {
+ if (uri != null) {
+ try {
+ new Thread(() -> {
+ File selectedFilePath = new File(Objects.requireNonNull(FileUtils.getPath(getContext(), uri)));
+
+ if (isAdded()) {
+ requireActivity().runOnUiThread(() -> {
+ QmpSender.changeFloppyDiskA(requireActivity(), selectedFilePath.getAbsolutePath());
+ dismiss();
+ });
}
- }
- });
+ }).start();
+ } catch (Exception e) {
+ showErrorSelectedFileDialog();
+ dismiss();
+ }
+ }
+ }
private final ActivityResultLauncher floppyBPicker =
- registerForActivityResult(new ActivityResultContracts.GetContent(), uri -> {
- if (uri != null) {
- try {
- new Thread(() -> {
- File selectedFilePath = new File(Objects.requireNonNull(FileUtils.getPath(getContext(), uri)));
+ registerForActivityResult(new ActivityResultContracts.GetContent(), this::handleFloppyBFile);
- if (isAdded()) {
- requireActivity().runOnUiThread(() -> {
- QmpSender.changeFloppyDiskB(requireActivity(), selectedFilePath.getAbsolutePath());
- dismiss();
- });
- }
- }).start();
- } catch (Exception e) {
- showErrorSelectedFileDialog();
- dismiss();
+ public void handleFloppyBFile(Uri uri) {
+ if (uri != null) {
+ try {
+ new Thread(() -> {
+ File selectedFilePath = new File(Objects.requireNonNull(FileUtils.getPath(getContext(), uri)));
+
+ if (isAdded()) {
+ requireActivity().runOnUiThread(() -> {
+ QmpSender.changeFloppyDiskB(requireActivity(), selectedFilePath.getAbsolutePath());
+ dismiss();
+ });
}
- }
- });
+ }).start();
+ } catch (Exception e) {
+ showErrorSelectedFileDialog();
+ dismiss();
+ }
+ }
+ }
private final ActivityResultLauncher memoryCardPicker =
- registerForActivityResult(new ActivityResultContracts.GetContent(), uri -> {
- if (uri != null) {
- try {
- new Thread(() -> {
- File selectedFilePath = new File(Objects.requireNonNull(FileUtils.getPath(getContext(), uri)));
+ registerForActivityResult(new ActivityResultContracts.GetContent(), this::handleMemoryCardFile);
- if (isAdded()) {
- requireActivity().runOnUiThread(() -> {
- QmpSender.changeMemoryCard(requireActivity(), selectedFilePath.getAbsolutePath());
- dismiss();
- });
- }
- }).start();
- } catch (Exception e) {
+ public void handleMemoryCardFile(Uri uri) {
+ if (uri != null) {
+ try {
+ new Thread(() -> {
+ String filePath = FileUtils.getPath(getContext(), uri);
+
+ if (filePath == null) {
showErrorSelectedFileDialog();
- dismiss();
+ return;
}
- }
- });
+
+ File selectedFilePath = new File(filePath);
+
+ if (isAdded()) {
+ requireActivity().runOnUiThread(() -> {
+ QmpSender.changeMemoryCard(requireActivity(), selectedFilePath.getAbsolutePath());
+ dismiss();
+ });
+ }
+ }).start();
+ } catch (Exception e) {
+ showErrorSelectedFileDialog();
+ dismiss();
+ }
+ }
+ }
private void showErrorSelectedFileDialog() {
DialogUtils.oneDialog(requireActivity(),
diff --git a/app/src/main/java/com/vectras/vm/settings/EqualizerActivity.java b/app/src/main/java/com/vectras/vm/settings/EqualizerActivity.java
new file mode 100644
index 0000000..7fe1328
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/settings/EqualizerActivity.java
@@ -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";
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/vectras/vm/settings/FilePickerSettingsActivity.java b/app/src/main/java/com/vectras/vm/settings/FilePickerSettingsActivity.java
new file mode 100644
index 0000000..0ebd709
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/settings/FilePickerSettingsActivity.java
@@ -0,0 +1,65 @@
+package com.vectras.vm.settings;
+
+import android.os.Bundle;
+
+import androidx.activity.EdgeToEdge;
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.vectras.qemu.MainSettingsManager;
+import com.vectras.vm.R;
+import com.vectras.vm.databinding.ActivityFilePickerSettingsBinding;
+import com.vectras.vm.file.FilePickerSettings;
+
+import java.util.Objects;
+
+public class FilePickerSettingsActivity extends AppCompatActivity {
+
+ ActivityFilePickerSettingsBinding binding;
+ FilePickerSettings settings;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ EdgeToEdge.enable(this);
+ setContentView(R.layout.activity_external_vnc_settings);
+ binding = ActivityFilePickerSettingsBinding.inflate(getLayoutInflater());
+ setContentView(binding.getRoot());
+ setSupportActionBar(binding.toolbar);
+ Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true);
+ binding.toolbar.setNavigationOnClickListener(view -> finish());
+ initialize();
+ }
+
+ private void initialize() {
+ settings = new FilePickerSettings(this);
+
+ binding.swEnabled.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ MainSettingsManager.setBuiltInFilePicker(this, isChecked);
+ uiController(isChecked);
+ });
+ binding.lnEnabled.setOnClickListener(v -> binding.swEnabled.toggle());
+
+ binding.swResume.setOnCheckedChangeListener((buttonView, isChecked) -> settings.resume(isChecked));
+ binding.lnResume.setOnClickListener(v -> binding.swResume.toggle());
+
+ binding.swDivider.setOnCheckedChangeListener((buttonView, isChecked) -> settings.divider(isChecked));
+ binding.lnDivider.setOnClickListener(v -> binding.swDivider.toggle());
+
+ binding.swShowHiddenFiles.setOnCheckedChangeListener((buttonView, isChecked) -> settings.showHiddenFiles(isChecked));
+ binding.lnShowHiddenFiles.setOnClickListener(v -> binding.swShowHiddenFiles.toggle());
+
+ binding.swResume.setChecked(settings.resume());
+ binding.swDivider.setChecked(settings.divider());
+ binding.swShowHiddenFiles.setChecked(settings.showHiddenFiles());
+ binding.swEnabled.setChecked(MainSettingsManager.getBuiltInFilePicker(this));
+
+ uiController(binding.swEnabled.isChecked());
+ }
+
+ private void uiController(boolean isEnabled) {
+ binding.lnAllOptions.setAlpha(isEnabled ? 1f : 0.5f);
+ binding.lnResume.setEnabled(isEnabled);
+ binding.lnDivider.setEnabled(isEnabled);
+ binding.lnShowHiddenFiles.setEnabled(isEnabled);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/vectras/vm/settings/Minitools.java b/app/src/main/java/com/vectras/vm/settings/Minitools.java
index 67a3a77..901c54c 100644
--- a/app/src/main/java/com/vectras/vm/settings/Minitools.java
+++ b/app/src/main/java/com/vectras/vm/settings/Minitools.java
@@ -287,7 +287,7 @@ public class Minitools extends AppCompatActivity {
FileUtils.delete(new File(getFilesDir().getAbsolutePath() + "/usr"));
runOnUiThread(() -> {
- progressDialog.dismiss();
+ DialogUtils.safeDismiss(this, progressDialog);
Intent intent = new Intent();
intent.setClass(this, SetupWizard2Activity.class);
startActivity(intent);
diff --git a/app/src/main/java/com/vectras/vm/settings/Settings2Activity.java b/app/src/main/java/com/vectras/vm/settings/Settings2Activity.java
index 9dd2bf4..688883e 100644
--- a/app/src/main/java/com/vectras/vm/settings/Settings2Activity.java
+++ b/app/src/main/java/com/vectras/vm/settings/Settings2Activity.java
@@ -31,6 +31,7 @@ public class Settings2Activity extends AppCompatActivity {
binding.lnTheme.setOnClickListener(v -> startActivity(new Intent(this, ThemeActivity.class)));
binding.lnNotification.setOnClickListener(v -> startActivity(new Intent(this, NotificationSettingsActivity.class)));
binding.lnSearch.setOnClickListener(v -> startActivity(new Intent(this, SearchSettingsActivity.class)));
+ binding.lnFilePicker.setOnClickListener(v -> startActivity(new Intent(this, FilePickerSettingsActivity.class)));
binding.lnSystem.setOnClickListener(v -> startActivity(new Intent(this, SystemSettingsActivity.class)));
binding.lnQemu.setOnClickListener(v -> {
@@ -42,5 +43,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)));
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/vectras/vm/setupwizard/SetupFeatureCore.java b/app/src/main/java/com/vectras/vm/setupwizard/SetupFeatureCore.java
index 3867716..d5bb1a9 100644
--- a/app/src/main/java/com/vectras/vm/setupwizard/SetupFeatureCore.java
+++ b/app/src/main/java/com/vectras/vm/setupwizard/SetupFeatureCore.java
@@ -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;
diff --git a/app/src/main/java/com/vectras/vm/setupwizard/SetupWizard2Activity.java b/app/src/main/java/com/vectras/vm/setupwizard/SetupWizard2Activity.java
index 54cc9bb..684c073 100644
--- a/app/src/main/java/com/vectras/vm/setupwizard/SetupWizard2Activity.java
+++ b/app/src/main/java/com/vectras/vm/setupwizard/SetupWizard2Activity.java
@@ -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> data;
- private final LayoutInflater inflater;
- private final int selectedPosition;
-
- public SpinnerSelectMirrorAdapter(Context context, ArrayList> arr) {
- this.data = arr;
- this.inflater = LayoutInflater.from(context);
- this.selectedPosition = MainSettingsManager.getSelectedMirror(context);
- }
-
- @Override
- public int getCount() {
- return data.size();
- }
-
- @Override
- public HashMap 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 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);
}
diff --git a/app/src/main/java/com/vectras/vm/sound/AudioSettingsData.java b/app/src/main/java/com/vectras/vm/sound/AudioSettingsData.java
new file mode 100644
index 0000000..d7a53f1
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/sound/AudioSettingsData.java
@@ -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);
+ }
+}
diff --git a/app/src/main/java/com/vectras/vm/sound/SoundEffect.java b/app/src/main/java/com/vectras/vm/sound/SoundEffect.java
new file mode 100644
index 0000000..2eaec7b
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/sound/SoundEffect.java
@@ -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;
+ }
+}
diff --git a/app/src/main/java/com/vectras/vm/utils/StreamAudio.java b/app/src/main/java/com/vectras/vm/sound/StreamAudio.java
similarity index 66%
rename from app/src/main/java/com/vectras/vm/utils/StreamAudio.java
rename to app/src/main/java/com/vectras/vm/sound/StreamAudio.java
index a513eed..f5a4599 100644
--- a/app/src/main/java/com/vectras/vm/utils/StreamAudio.java
+++ b/app/src/main/java/com/vectras/vm/sound/StreamAudio.java
@@ -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();
diff --git a/app/src/main/java/com/vectras/vm/store/RomInfo.java b/app/src/main/java/com/vectras/vm/store/RomInfo.java
index 3ca96fd..5a8b188 100644
--- a/app/src/main/java/com/vectras/vm/store/RomInfo.java
+++ b/app/src/main/java/com/vectras/vm/store/RomInfo.java
@@ -22,11 +22,13 @@ import com.anbui.elephant.interaction.Interaction;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition;
+import com.vectras.qemu.MainSettingsManager;
import com.vectras.vm.ImagePrvActivity;
import com.vectras.vm.R;
import com.vectras.vm.VMManager;
import com.vectras.vm.creator.VMCreatorActivity;
import com.vectras.vm.databinding.ActivityRomInfoBinding;
+import com.vectras.vm.file.FilePickerDialog;
import com.vectras.vm.utils.DialogUtils;
import com.vectras.vm.utils.FileUtils;
import com.vectras.vm.utils.ImageUtils;
@@ -72,89 +74,90 @@ public class RomInfo extends AppCompatActivity {
}
private final ActivityResultLauncher romPicker =
- registerForActivityResult(new ActivityResultContracts.GetContent(), uri -> {
+ registerForActivityResult(new ActivityResultContracts.GetContent(), this::handlePickedRom);
- if (uri == null) return;
+ public void handlePickedRom(Uri uri) {
+ if (uri == null) return;
- executor.execute(() -> {
- String filePath;
- try {
- File selectedFilePath = new File(getPath(uri));
- filePath = selectedFilePath.getPath();
- } catch (Exception e) {
- filePath = "";
+ executor.execute(() -> {
+ String filePath;
+ try {
+ File selectedFilePath = new File(getPath(uri));
+ filePath = selectedFilePath.getPath();
+ } catch (Exception e) {
+ filePath = "";
+ }
+
+ String selectedFileName = FileUtils.getFileNameFromUri(this, uri);
+
+ if (isFinishing() || isDestroyed()) return;
+
+ String finalFilePath = filePath;
+ runOnUiThread(() -> {
+ if (selectedFileName.equals(getIntent().getStringExtra("filename")) ||
+ (selectedFileName.endsWith(".cvbi.zip") && selectedFileName.equals(getIntent().getStringExtra("filename") + ".zip"))) {
+ Intent intent = new Intent();
+
+ if (getIntent().hasExtra("icon") &&
+ !Objects.requireNonNull(getIntent().getStringExtra("icon")).isEmpty() &&
+ (!selectedFileName.endsWith(".cvbi")
+ || !selectedFileName.endsWith(".cvbi.zip"))) {
+
+ Glide.with(this)
+ .asBitmap()
+ .load(getIntent().getStringExtra("icon"))
+ .into(new CustomTarget() {
+ @Override
+ public void onResourceReady(@NonNull Bitmap resource,
+ @Nullable Transition super Bitmap> transition) {
+ File cacheDir = getExternalCacheDir();
+
+ if (cacheDir != null)
+ ImageUtils.saveBitmapToPNGFile(resource, cacheDir.getAbsolutePath(), "thumbnail.png");
+
+ intent.putExtra("romicon", cacheDir != null ? cacheDir.getAbsolutePath() + "/thumbnail.png" : "");
+ }
+
+ @Override
+ public void onLoadCleared(@Nullable Drawable placeholder) {
+ intent.putExtra("romicon", "");
+ }
+ });
+ } else {
+ intent.putExtra("romicon", "");
}
- String selectedFileName = FileUtils.getFileNameFromUri(this, uri);
+ intent.setClass(getApplicationContext(), VMCreatorActivity.class);
+ intent.putExtra("addromnow", "");
+ intent.putExtra("romname", getIntent().getStringExtra("title"));
+ intent.putExtra("romfilename", getIntent().getStringExtra("filename"));
+ intent.putExtra("finalromfilename", getIntent().getStringExtra("finalromfilename"));
+ intent.putExtra("rompath", finalFilePath);
+ intent.putExtra("romuri", uri.toString());
+ if (Objects.requireNonNull(getIntent().getStringExtra("extra")).contains(selectedFileName)) {
+ intent.putExtra("addtodrive", "");
+ intent.putExtra("romextra", getIntent().getStringExtra("extra"));
+ } else {
+ intent.putExtra("addtodrive", "1");
+ intent.putExtra("romextra", getIntent().getStringExtra("extra"));
+ }
- if (isFinishing() || isDestroyed()) return;
+ VMManager.setArch(getIntent().hasExtra("arch") && getIntent().getStringExtra("arch") != null ? Objects.requireNonNull(getIntent().getStringExtra("arch")) : "", this);
- String finalFilePath = filePath;
- runOnUiThread(() -> {
- if (selectedFileName.equals(getIntent().getStringExtra("filename")) ||
- (selectedFileName.endsWith(".cvbi.zip") && selectedFileName.equals(getIntent().getStringExtra("filename") + ".zip"))) {
- Intent intent = new Intent();
-
- if (getIntent().hasExtra("icon") &&
- !Objects.requireNonNull(getIntent().getStringExtra("icon")).isEmpty() &&
- (!selectedFileName.endsWith(".cvbi")
- || !selectedFileName.endsWith(".cvbi.zip"))) {
-
- Glide.with(this)
- .asBitmap()
- .load(getIntent().getStringExtra("icon"))
- .into(new CustomTarget() {
- @Override
- public void onResourceReady(@NonNull Bitmap resource,
- @Nullable Transition super Bitmap> transition) {
- File cacheDir = getExternalCacheDir();
-
- if (cacheDir != null)
- ImageUtils.saveBitmapToPNGFile(resource, cacheDir.getAbsolutePath(), "thumbnail.png");
-
- intent.putExtra("romicon", cacheDir != null ? cacheDir.getAbsolutePath() + "/thumbnail.png" : "");
- }
-
- @Override
- public void onLoadCleared(@Nullable Drawable placeholder) {
- intent.putExtra("romicon", "");
- }
- });
- } else {
- intent.putExtra("romicon", "");
- }
-
- intent.setClass(getApplicationContext(), VMCreatorActivity.class);
- intent.putExtra("addromnow", "");
- intent.putExtra("romname", getIntent().getStringExtra("title"));
- intent.putExtra("romfilename", getIntent().getStringExtra("filename"));
- intent.putExtra("finalromfilename", getIntent().getStringExtra("finalromfilename"));
- intent.putExtra("rompath", finalFilePath);
- intent.putExtra("romuri", uri.toString());
- if (Objects.requireNonNull(getIntent().getStringExtra("extra")).contains(selectedFileName)) {
- intent.putExtra("addtodrive", "");
- intent.putExtra("romextra", getIntent().getStringExtra("extra"));
- } else {
- intent.putExtra("addtodrive", "1");
- intent.putExtra("romextra", getIntent().getStringExtra("extra"));
- }
-
- VMManager.setArch(getIntent().hasExtra("arch") && getIntent().getStringExtra("arch") != null ? Objects.requireNonNull(getIntent().getStringExtra("arch")) : "", this);
-
- startActivity(intent);
- } else {
- DialogUtils.oneDialog(RomInfo.this,
- getString(R.string.problem_has_been_detected),
- getString(R.string.please_select) + " " + getIntent().getStringExtra("filename"),
- getString(R.string.ok),
- true, R.drawable.warning_48px,
- true,
- null,
- null);
- }
- });
- });
+ startActivity(intent);
+ } else {
+ DialogUtils.oneDialog(RomInfo.this,
+ getString(R.string.problem_has_been_detected),
+ getString(R.string.please_select) + " " + getIntent().getStringExtra("filename"),
+ getString(R.string.ok),
+ true, R.drawable.warning_48px,
+ true,
+ null,
+ null);
+ }
});
+ });
+ }
public String getPath(Uri uri) {
return FileUtils.getPath(RomInfo.this, uri);
@@ -168,7 +171,12 @@ public class RomInfo extends AppCompatActivity {
if (getIntent().hasExtra("isRomInfo") && getIntent().getBooleanExtra("isRomInfo", false)) {
binding.btnPick.setOnClickListener(v -> {
try {
- romPicker.launch("*/*");
+ if (MainSettingsManager.getBuiltInFilePicker(this)) {
+ FilePickerDialog filePickerDialog = new FilePickerDialog();
+ filePickerDialog.pick(this, filePickerDialog.ROM_FILE, (path -> handlePickedRom(Uri.fromFile(new File(path)))));
+ } else {
+ romPicker.launch("*/*");
+ }
} catch (Exception e) {
IntentUtils.showErrorDialog(this);
}
diff --git a/app/src/main/java/com/vectras/vm/utils/ClipboardUltils.java b/app/src/main/java/com/vectras/vm/utils/ClipboardUltils.java
index 9300750..5261306 100644
--- a/app/src/main/java/com/vectras/vm/utils/ClipboardUltils.java
+++ b/app/src/main/java/com/vectras/vm/utils/ClipboardUltils.java
@@ -11,10 +11,14 @@ import com.vectras.vm.R;
public class ClipboardUltils {
public static void copyToClipboard(Context context, String text) {
+ copyToClipboard(context, text, true);
+ }
+
+ public static void copyToClipboard(Context context, String text, boolean showToast) {
ClipboardManager clipboard = (ClipboardManager) context.getSystemService(CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("From Vectras VM", text);
assert clipboard != null;
clipboard.setPrimaryClip(clip);
- Toast.makeText(context, context.getString(R.string.copied), Toast.LENGTH_SHORT).show();
+ if (showToast) Toast.makeText(context, context.getString(R.string.copied), Toast.LENGTH_SHORT).show();
}
}
diff --git a/app/src/main/java/com/vectras/vm/utils/CommandUtils.java b/app/src/main/java/com/vectras/vm/utils/CommandUtils.java
index e577ff7..c2321da 100644
--- a/app/src/main/java/com/vectras/vm/utils/CommandUtils.java
+++ b/app/src/main/java/com/vectras/vm/utils/CommandUtils.java
@@ -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");
}
}
diff --git a/app/src/main/java/com/vectras/vm/utils/DeviceUtils.java b/app/src/main/java/com/vectras/vm/utils/DeviceUtils.java
index 1216e11..f90aad6 100644
--- a/app/src/main/java/com/vectras/vm/utils/DeviceUtils.java
+++ b/app/src/main/java/com/vectras/vm/utils/DeviceUtils.java
@@ -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");
diff --git a/app/src/main/java/com/vectras/vm/utils/GmsChecker.java b/app/src/main/java/com/vectras/vm/utils/GmsChecker.java
new file mode 100644
index 0000000..4e8a5ab
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/utils/GmsChecker.java
@@ -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;
+ }
+}
diff --git a/app/src/main/java/com/vectras/vm/utils/JSONUtils.java b/app/src/main/java/com/vectras/vm/utils/JSONUtils.java
index 6953428..7a7c3fa 100644
--- a/app/src/main/java/com/vectras/vm/utils/JSONUtils.java
+++ b/app/src/main/java/com/vectras/vm/utils/JSONUtils.java
@@ -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;
}
}
diff --git a/app/src/main/java/com/vectras/vm/utils/LibraryChecker.java b/app/src/main/java/com/vectras/vm/utils/LibraryChecker.java
index 87c5126..81f2da3 100644
--- a/app/src/main/java/com/vectras/vm/utils/LibraryChecker.java
+++ b/app/src/main/java/com/vectras/vm/utils/LibraryChecker.java
@@ -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 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();
- }
}
diff --git a/app/src/main/java/com/vectras/vm/view/DynamicBubble.java b/app/src/main/java/com/vectras/vm/view/DynamicBubble.java
index 285d470..8836331 100644
--- a/app/src/main/java/com/vectras/vm/view/DynamicBubble.java
+++ b/app/src/main/java/com/vectras/vm/view/DynamicBubble.java
@@ -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 {
diff --git a/app/src/main/java/com/vectras/vm/x11/X11Activity.java b/app/src/main/java/com/vectras/vm/x11/X11Activity.java
index ec41fe8..18a73f3 100644
--- a/app/src/main/java/com/vectras/vm/x11/X11Activity.java
+++ b/app/src/main/java/com/vectras/vm/x11/X11Activity.java
@@ -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();
}
diff --git a/app/src/main/java/com/vectras/vterm/Terminal2.java b/app/src/main/java/com/vectras/vterm/Terminal2.java
new file mode 100644
index 0000000..cb64f69
--- /dev/null
+++ b/app/src/main/java/com/vectras/vterm/Terminal2.java
@@ -0,0 +1,252 @@
+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 = 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 (Exception 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;
+ }
+}
diff --git a/app/src/main/res/drawable/android_24px.xml b/app/src/main/res/drawable/android_24px.xml
new file mode 100644
index 0000000..be1c5e3
--- /dev/null
+++ b/app/src/main/res/drawable/android_24px.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/arrow_upward_alt_24px.xml b/app/src/main/res/drawable/arrow_upward_alt_24px.xml
new file mode 100644
index 0000000..16f5cae
--- /dev/null
+++ b/app/src/main/res/drawable/arrow_upward_alt_24px.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/code_24px.xml b/app/src/main/res/drawable/code_24px.xml
new file mode 100644
index 0000000..8e7fc92
--- /dev/null
+++ b/app/src/main/res/drawable/code_24px.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/database_24px.xml b/app/src/main/res/drawable/database_24px.xml
new file mode 100644
index 0000000..92d0650
--- /dev/null
+++ b/app/src/main/res/drawable/database_24px.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/description_24px.xml b/app/src/main/res/drawable/description_24px.xml
new file mode 100644
index 0000000..9e37e87
--- /dev/null
+++ b/app/src/main/res/drawable/description_24px.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/draft_24px.xml b/app/src/main/res/drawable/draft_24px.xml
new file mode 100644
index 0000000..eacfbbc
--- /dev/null
+++ b/app/src/main/res/drawable/draft_24px.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/app/src/main/res/drawable/folder_zip_24px.xml b/app/src/main/res/drawable/folder_zip_24px.xml
new file mode 100644
index 0000000..4f69cd7
--- /dev/null
+++ b/app/src/main/res/drawable/folder_zip_24px.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/graphic_eq_24px.xml b/app/src/main/res/drawable/graphic_eq_24px.xml
new file mode 100644
index 0000000..ee498e8
--- /dev/null
+++ b/app/src/main/res/drawable/graphic_eq_24px.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/graphic_eq_off_96px.xml b/app/src/main/res/drawable/graphic_eq_off_96px.xml
new file mode 100644
index 0000000..d532fad
--- /dev/null
+++ b/app/src/main/res/drawable/graphic_eq_off_96px.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/image_24px.xml b/app/src/main/res/drawable/image_24px.xml
new file mode 100644
index 0000000..8f9ee7f
--- /dev/null
+++ b/app/src/main/res/drawable/image_24px.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ios_24px.xml b/app/src/main/res/drawable/ios_24px.xml
new file mode 100644
index 0000000..df9a936
--- /dev/null
+++ b/app/src/main/res/drawable/ios_24px.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/key_24px.xml b/app/src/main/res/drawable/key_24px.xml
new file mode 100644
index 0000000..6fda034
--- /dev/null
+++ b/app/src/main/res/drawable/key_24px.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/movie_24px.xml b/app/src/main/res/drawable/movie_24px.xml
new file mode 100644
index 0000000..1d00939
--- /dev/null
+++ b/app/src/main/res/drawable/movie_24px.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/music_note_24px.xml b/app/src/main/res/drawable/music_note_24px.xml
new file mode 100644
index 0000000..02a46d9
--- /dev/null
+++ b/app/src/main/res/drawable/music_note_24px.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/titlecase_24px.xml b/app/src/main/res/drawable/titlecase_24px.xml
new file mode 100644
index 0000000..85c2b3a
--- /dev/null
+++ b/app/src/main/res/drawable/titlecase_24px.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/tune_24px.xml b/app/src/main/res/drawable/tune_24px.xml
new file mode 100644
index 0000000..c37eb33
--- /dev/null
+++ b/app/src/main/res/drawable/tune_24px.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/undo_24px.xml b/app/src/main/res/drawable/undo_24px.xml
new file mode 100644
index 0000000..a3f745b
--- /dev/null
+++ b/app/src/main/res/drawable/undo_24px.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/app/src/main/res/drawable/vectras_vm_24px.xml b/app/src/main/res/drawable/vectras_vm_24px.xml
new file mode 100644
index 0000000..16f468e
--- /dev/null
+++ b/app/src/main/res/drawable/vectras_vm_24px.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/wysiwyg_24px.xml b/app/src/main/res/drawable/wysiwyg_24px.xml
new file mode 100644
index 0000000..f3c3073
--- /dev/null
+++ b/app/src/main/res/drawable/wysiwyg_24px.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/app/src/main/res/layout/activity_equalizer.xml b/app/src/main/res/layout/activity_equalizer.xml
new file mode 100644
index 0000000..6f51acb
--- /dev/null
+++ b/app/src/main/res/layout/activity_equalizer.xml
@@ -0,0 +1,308 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_file_picker_settings.xml b/app/src/main/res/layout/activity_file_picker_settings.xml
new file mode 100644
index 0000000..becb6ca
--- /dev/null
+++ b/app/src/main/res/layout/activity_file_picker_settings.xml
@@ -0,0 +1,217 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index cc07d5e..7d5b74d 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -6,9 +6,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
-
+ app:menu="@menu/home_drawer_menu"
+ app:itemTextAppearanceActiveBoldEnabled="false" />
diff --git a/app/src/main/res/layout/activity_settings2.xml b/app/src/main/res/layout/activity_settings2.xml
index c4a2769..b2d4e71 100644
--- a/app/src/main/res/layout/activity_settings2.xml
+++ b/app/src/main/res/layout/activity_settings2.xml
@@ -150,6 +150,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
@@ -334,6 +371,43 @@
android:textSize="14sp" />
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/dialog_file_picker.xml b/app/src/main/res/layout/dialog_file_picker.xml
new file mode 100644
index 0000000..7ccc09c
--- /dev/null
+++ b/app/src/main/res/layout/dialog_file_picker.xml
@@ -0,0 +1,202 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/list_file.xml b/app/src/main/res/layout/list_file.xml
new file mode 100644
index 0000000..08dd4d2
--- /dev/null
+++ b/app/src/main/res/layout/list_file.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/equalizer_menu.xml b/app/src/main/res/menu/equalizer_menu.xml
new file mode 100644
index 0000000..00f3199
--- /dev/null
+++ b/app/src/main/res/menu/equalizer_menu.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml
index 9dea90b..cc41481 100644
--- a/app/src/main/res/values-vi/strings.xml
+++ b/app/src/main/res/values-vi/strings.xml
@@ -609,6 +609,8 @@
Bạn cần tắt tất cả máy ảo đang chạy để thực hiện hành động này.
Tự động chuyển sang chuột ngoài
Tự động chuyển đổi khi kết nối và ngắt chuột rời.
+ Các gói cần thiết đã được cài đặt.
+ Đã xảy ra lỗi khi tạo ổ đĩa ảo.
Vterm
@@ -768,6 +770,29 @@
Hiệu ứng mờ
Làm mờ xung quanh để tăng khả năng tập trung vào một vùng cần được chú ý.
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.
+ Bộ chỉnh âm
+ Bổng, trung và trầm.
+ 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.
+ Dùng bộ chỉnh âm
+ Bổng cao
+ Bổng
+ Trung
+ Trầm
+ Trầm sâu
+ Thiết bị của bạn không được hỗ trợ.
+ Hoàn tác
+ Bộ nhớ trong
+ Mở bằng
+ Trình chọn tệp
+ Trình chọn và duyệt tệp tích hợp sẵn.
+ Dùng trình chọn tệp
+ Tiếp tục
+ Tiếp tục ở thư mục cuối cùng mà bạn đã chọn tệp trong lần dùng trước đó.
+ Hiện các tệp ẩn
+ Áp dụng với các tệp và thư mục có tên bắt đầu bằng \".\".
+ Đã sap chép đường dẫn thư mục vào bảng tạm.
+ Đường phân cách
+ Thêm các đường phân cách giữa các mục.
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 6fcf5fd..0f70f53 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -1,16 +1,11 @@
-
+
Vectras VM
-
-
-
虚拟机已启动!
虚拟机已关闭!
无进程!
未知状态!
-
要使用外接鼠标:\n\t请在虚拟机中关闭鼠标加速.\n\t\t对于X-Window,执行: xset m 1\n\t\t对于其它操作系统在 Vectras主选项中关闭\"增强指针准确性\"或者在鼠标选项中选择\"usb-tablet\" (usb-tablet为QEMU中的一个选项)\n\t接入外接鼠标后点击OK\n\t最后通过让鼠标滑至屏幕的各个边缘以校准
-
打开抽屉
收起抽屉
地址
@@ -30,13 +25,10 @@
用户名
保存
内存使用量
-
允许外部VNC连接
显示
移除
查看日志
-
-
硬件鼠标模式
ROM 名称
ROM驱动(qcow2,img等)
@@ -47,9 +39,7 @@
鼠标模式
导出
退出
-
新建虚拟机
-
下箭头
左箭头
右箭头
@@ -99,19 +89,14 @@
作为副本保存
缩放
可缩放
-
Patreon
-
-
再次发送按键
发送按键
用户名
用于 Windows 身份验证
终端
-
安装
修改
-
再次尝试
大小(GB)
创建 QCOW2
@@ -227,9 +212,6 @@
运行虚拟机时出现问题: 虚拟机所需要的文件或文件夹不存在
创建新虚拟机时自动新建硬盘
它的大小为128GB
-
-
-
新建会话
键盘
重置
@@ -237,56 +219,39 @@
终端显示文本
保持屏幕打开
自动填充密码
-
安装中…
无法安装
Vterm 无法安装初始化包
停止
再次尝试
Vterm 只能安装在主用户帐户上
-
已达到的最大终端数
在开新的之前关闭现有项
-
重置终端
-
选择 URL
点击 URL 进行复制或长按打开
分享终端输出
在终端中找不到 URL
URL 已复制到剪贴板
发送文本至:
-
终止进程(%d)
真的要终止此会话吗?
-
设置会话名称
确认
新命名会话
新建
-
Vterm:Style 插件尚未安装
安装
-
退出
加上唤醒锁
释放唤醒锁
-
文件保存至 ~/downloads/
编辑
打开文件夹
-
-
-
-
-
-
未连接
帮助
首选项
在这里您可以像Termux一样设置一些额外按键 (可以看看 这个 ) 以及其余项 (e.g. META).\n 留空则使用默认值
-
-
取消
返回显示界面
X11
@@ -319,7 +284,6 @@
需要初始化!
您可以选择让软件自动下载或者选择初始化包完成手动设置
手动安装
-
Alpine Xfce4
分辨率模式
缩放值(%)
@@ -619,7 +583,7 @@
删除文件时出错,稍后重试
文件已删除
打开主文件夹
- 您正在使用 X11而非 VNC。要使用 VNC,请进入设置-> QEMU,向下滚动到底部,然后在用户界面中选择"VNC"。
+ 您正在使用 X11而非 VNC。要使用 VNC,请进入设置-> QEMU,向下滚动到底部,然后在用户界面中选择"VNC"。
没有可用于进行该操作的APP
文件夹不存在
共享文件夹体积不应超过516MB,请删除部分文件
@@ -732,4 +696,177 @@
前往 Termux:X11 设置。
使用 SDL
如果您想使用 3dfx 并玩老游戏,请开启此选项(仅适用于 x86_64 和 i386)。
-
+ 解放巴勒斯坦
+ x86_64
+ 发生错误。ROM信息已损坏,已应用部分默认设置。错误代码:CR_CVBI4。
+ 文件格式不受支持,可能无法正常工作。常见支持格式包括 qcow2、img、vhd、vhdx、vdi、qcow、vmdk 和 vpc。是否继续?
+ 文件格式不受支持,可能无法正常工作。常见支持格式包括 iso、img、cdr 和 toast。是否继续?
+ 文件格式不受支持,可能无法正常工作。常见支持格式包括 img、flp 和 ima。是否继续?
+ 镜像源
+ 开始吧
+ 无法复制此文件,请尝试从其他位置选择该文件。
+ 使用VNC
+ 正在安装系统文件
+ 连接中…
+ 您想如何安装?
+ 标准安装还是手动安装?
+ 标准安装
+ 推荐方式,安装程序将自动完成所有设置。
+ 自定义安装
+ 如果您想手动选择引导文件并安装所需的Qemu版本。
+ 选择离您最近的镜像源以加快安装速度。
+ 请勿断开网络连接,这可能需要几分钟时间。
+ 出了点问题
+ 设置未能完成,以下是日志信息。
+ 由于无法连接服务器,此选项暂时不可用。
+ Vectras VM 无法在此设备上运行。
+ 存储空间不足。
+ 没有日志。
+ 所需的软件包未安装,您需要先安装它才能使用此功能。
+ 未安装 Termux:X11 应用,您需要安装它才能继续。
+ 解压前检查
+ 快速检测错误,避免浪费时间解压后才发现问题。
+ 检查中…
+ 快速编辑
+ 写点什么…
+ 切换到VNC
+ 首选项
+ 大小 (GB)
+ 创建新虚拟磁盘
+ 将创建在
+ 共享者
+ 系统更新
+ 有系统更新可用,立即更新以获得完整体验。
+ QEMU 文档
+ 您需要切换到SDL才能使用3dfx。
+ 设备管理器
+ 在文件夹中显示
+ 退出
+ 3dfx
+ 刷新
+ 非常抱歉,应用之前发生了崩溃。
+ 虚拟鼠标
+ 显示虚拟鼠标
+ 隐藏虚拟鼠标
+ 此引导文件无效。
+ 虚拟机已创建。
+ 虚拟机已编辑。
+ 发生错误,虚拟机未创建。
+ 发生错误,虚拟机未修改。
+ 使用参数编辑器编辑
+ 您可以使用参数编辑器应用来编辑此虚拟机。
+ 无法保存,请稍后重试。
+ 高级
+ 启动
+ 显示启动菜单
+ 默认
+ 硬盘
+ 软盘
+ 网络
+ ROM 导出成功。
+ 选项
+ 添加文件
+ 文件已添加
+ 复制完整路径
+ 添加文件失败,请稍后重试。
+ 项目已清空。
+ 显示回收站
+ 删除虚拟机时发生错误。
+ 虚拟机文件已被保留,因为它们仍在其他地方使用。
+ 暂停
+ 虚拟机的状态将被保存并关闭以节省资源。下次启动时将恢复到此状态。请不要更改、移动或删除此虚拟机使用的文件。
+ 虚拟机正在暂停…
+请不要离开此页面。
+ 正在恢复…
+ 恢复之前暂停的虚拟机状态时出现问题。如果您在启动前修改了此虚拟机或Qemu设置,请检查是否将其更改为与上次开机时相同的设置。您想保留还是移除保存的状态?
+ 保留并继续
+ 移除并继续
+ 发生错误,无法保存虚拟机状态。请稍后重试。
+ 无法打开此文件夹。
+ 新通知
+ 点击查看。
+ 在 An Bui 应用中查看
+ 您可以在此处查看和获取。
+ 工具
+ 3dfx 包装器
+ Windows VirtIO 工具
+ 需要下载
+ 此工具需要先下载才能使用。下载完成后将通知您。
+ 下载失败,请稍后重试。
+ Windows VirtIO 工具现已就绪
+ 是否立即将其插入光驱?
+ 试试 Play 商店版
+ 输入命令
+ 控制台
+ 截图
+ 无法截图。
+ 已保存到相册。
+ 正在截图…
+ 此虚拟机已在运行。
+ 服务器正在运行
+ 在虚拟机中访问此链接以访问您设备上的下载文件夹:
+
+%s
+
+您也可以从虚拟机发送文件到您设备的下载文件夹,以下是使用 curl 的示例:
+
+%s
+ 启动服务器时发生错误,请稍后重试。
+ 复制链接
+ 复制上传命令
+ 文件夹共享服务器
+ 静音
+ 取消静音
+ 光驱 1
+ 光驱 2
+ 设备已锁定,暂时无法弹出。请稍后重试。您可以尝试在此虚拟机运行的操作系统中打开设备管理器或文件管理器并请求弹出。
+ 设备已锁定,暂时无法更换。请稍后重试。您可以尝试先弹出设备,然后再次尝试更换。
+ 管理
+ 连接
+ 正在关机…
+ 无法保存到此处,请选择其他位置。
+ 处理器
+ 主板
+ 核心
+ 核心
+ 线程
+ 线程
+ 刷新率
+ 低刷新率
+ 如果您的设备因绘制过多帧而过载,可以启用此功能以提升虚拟机性能并将刷新率降低至30。
+ 全面屏
+ 禁用全面屏
+ 双指缩放
+ 虚拟机选择器
+ 切换
+ 切换到
+ 没有可用的虚拟机。
+ 硬盘 0
+ 硬盘 1
+ 光驱
+ 光驱 0
+ 光驱 1
+ 此文件已在虚拟机的其他地方使用。您需要找到并移除或重命名它,然后重新选择。
+ 更高质量
+ 图像质量将会提高,但如果使用外部连接,延迟会更大。
+ 电池
+ 准备中…
+ 通知
+ 管理通知类型和主题。
+ 建议和提示
+ 接收关于我们的建议和提示的通知。
+ 在系统设置中。
+ 缺少的软件包
+ 以下是您需要安装的缺少软件包:
+ 在内置和外部显示之间切换时需要重启应用。
+ 包含广告
+ 安全模式
+ 使用 Termux:X11 应用代替内置屏幕。
+ 模糊效果
+ 模糊周围区域以增加对需要注意的特定区域的聚焦。
+ G4 (高性能)
+ G4 (基本)
+ G2 (工作站)
+ G2 (低功耗)
+ 空白片段
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index f7263e9..f18d8bf 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -627,6 +627,31 @@
You need to shut down all running virtual machines to perform this action.
Auto switch to external mouse
Automatically switches when the mouse is connected and disconnected.
+ The necessary packages have been installed.
+ An error occurred while creating the virtual drive.
+ Equalizer
+ Treble, mid and bass.
+ 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.
+ Use equalizer
+ Upper treble
+ Treble
+ Mid
+ Bass
+ Low bass
+ Your device is not supported.
+ Undo
+ Internal storage
+ Open with
+ File picker
+ Built-in file picker and browser.
+ Use file picker
+ Resume
+ Continue in the last folder where you selected the file in the previous use.
+ Show hidden files
+ This applies to files and folders whose names begin with \".\".
+ The folder path has been copied to the clipboard.
+ Divider
+ Add dividers between items.
diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt
deleted file mode 100644
index 4f8d23f..0000000
--- a/fastlane/metadata/android/en-US/full_description.txt
+++ /dev/null
@@ -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.
diff --git a/fastlane/metadata/android/en-US/images/featureGraphic.jpg b/fastlane/metadata/android/en-US/images/featureGraphic.jpg
deleted file mode 100644
index c0044d6..0000000
Binary files a/fastlane/metadata/android/en-US/images/featureGraphic.jpg and /dev/null differ
diff --git a/fastlane/metadata/android/en-US/images/icon.png b/fastlane/metadata/android/en-US/images/icon.png
deleted file mode 100644
index 29d6c32..0000000
Binary files a/fastlane/metadata/android/en-US/images/icon.png and /dev/null differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.jpg
deleted file mode 100644
index 511bf4c..0000000
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.jpg and /dev/null differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/2.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/2.jpg
deleted file mode 100644
index 9b2390b..0000000
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/2.jpg and /dev/null differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/3.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/3.jpg
deleted file mode 100644
index 3bb8e90..0000000
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/3.jpg and /dev/null differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/4.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/4.jpg
deleted file mode 100644
index 032d9d9..0000000
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/4.jpg and /dev/null differ
diff --git a/fastlane/metadata/android/en-US/short_description.txt b/fastlane/metadata/android/en-US/short_description.txt
deleted file mode 100644
index b8712fe..0000000
--- a/fastlane/metadata/android/en-US/short_description.txt
+++ /dev/null
@@ -1 +0,0 @@
-It's a Virtual Machine App for Android Which is Based on QEMU
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index bbbcc1e..fac00a7 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -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"
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 4b271a6..208d731 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -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
diff --git a/gradlew b/gradlew
index b9bb139..249efbb 100755
--- a/gradlew
+++ b/gradlew
@@ -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:
diff --git a/gradlew.bat b/gradlew.bat
index aa5f10b..8508ef6 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -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
diff --git a/terminal-view/src/main/res/values-zh-rCN/strings.xml b/terminal-view/src/main/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..85dba47
--- /dev/null
+++ b/terminal-view/src/main/res/values-zh-rCN/strings.xml
@@ -0,0 +1,6 @@
+
+
+ 粘贴
+ 复制
+ 更多…
+
diff --git a/web/1024.png b/web/1024.png
deleted file mode 100644
index fa0daa4..0000000
Binary files a/web/1024.png and /dev/null differ
diff --git a/web/README.txt b/web/README.txt
deleted file mode 100644
index 2daf47f..0000000
--- a/web/README.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-Add this to your HTML :
-
-
-
-
-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" }
- ]
- }
- ...
diff --git a/web/apple-touch-icon.png b/web/apple-touch-icon.png
deleted file mode 100644
index 8aa2f07..0000000
Binary files a/web/apple-touch-icon.png and /dev/null differ
diff --git a/web/coffee.html b/web/coffee.html
deleted file mode 100644
index d26f911..0000000
--- a/web/coffee.html
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-Just a moment... Home
-
-
-
-
-
-
- Just a moment...
-
-
-
-
-
\ No newline at end of file
diff --git a/web/data/UpdateConfig.json b/web/data/UpdateConfig.json
index e0b11a7..a884e99 100644
--- a/web/data/UpdateConfig.json
+++ b/web/data/UpdateConfig.json
@@ -5,11 +5,11 @@
"url": "https://github.com/xoureldeen/Vectras-VM-Android/releases",
"Message": "4.3.0 \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":"138",
+ "versionNameBeta":"4.3.4",
+ "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,4.3.4",
"sizeBeta": "45 MB",
"urlBeta": "https://github.com/AnBui2004/Vectras-VM-Emu-Android/releases",
- "MessageBeta": "4.3.0 Bugs fixed.",
+ "MessageBeta": "4.3.4 Bugs fixed.",
"cancellableBeta": true
}
diff --git a/web/data/vroms-store.json b/web/data/vroms-store.json
index 96362da..907905f 100644
--- a/web/data/vroms-store.json
+++ b/web/data/vroms-store.json
@@ -674,6 +674,26 @@
"vecid": "macosxleopardcvbi",
"id": ""
},
+ {
+ "rom_name": "macOS High Sierra",
+ "rom_icon": "https://static.wikia.nocookie.net/ipod/images/e/ec/MacOSHighSierraCircle.png/revision/latest/thumbnail/width/360/height/450?cb=20241004222256",
+ "rom_url": "https://youtu.be/ZvMxdVennwU",
+ "rom_path": "macOS High Sierra.cvbi",
+ "rom_avail": true,
+ "gui": true,
+ "rom_size": "10.13",
+ "rom_arch": "X86_64",
+ "rom_kernel": "apple",
+ "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\nmacOS High Sierra (version 10.13) is the fourteenth major release of macOS, Apple's desktop operating system for Macintosh computers. macOS High Sierra was announced at the WWDC 2017 on June 5, 2017 and was released on September 25, 2017. The name \"High Sierra\" refers to the High Sierra region in California. Its name signified its goal to be a refinement of the previous macOS version, macOS Sierra, focused on performance improvements and technical updates rather than features. This makes it similar to previous macOS releases Snow Leopard, Mavericks and El Capitan. Among the apps with notable changes are Photos and Safari.",
+ "file_size": "16 GB",
+ "containsAds": true,
+ "creator": "Nguyen Bao An Bui",
+ "verified": true,
+ "vecid": "macoshighsierracvbi",
+ "id": "99624"
+ },
{
"rom_name": "macOS Mojave",
"rom_icon": "https://cdsassets.apple.com/live/SZLF0YNV/images/sp/111930_mojave-roundel-240.png",
@@ -1541,6 +1561,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",
diff --git a/web/favicon.ico b/web/favicon.ico
deleted file mode 100644
index 3b43dc6..0000000
Binary files a/web/favicon.ico and /dev/null differ
diff --git a/web/how.html b/web/how.html
deleted file mode 100644
index d26f911..0000000
--- a/web/how.html
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-Just a moment... Home
-
-
-
-
-
-
- Just a moment...
-
-
-
-
-
\ No newline at end of file
diff --git a/web/icon-192-maskable.png b/web/icon-192-maskable.png
deleted file mode 100644
index d0c1503..0000000
Binary files a/web/icon-192-maskable.png and /dev/null differ
diff --git a/web/icon-192.png b/web/icon-192.png
deleted file mode 100644
index b468d14..0000000
Binary files a/web/icon-192.png and /dev/null differ
diff --git a/web/icon-512-maskable.png b/web/icon-512-maskable.png
deleted file mode 100644
index 94161f5..0000000
Binary files a/web/icon-512-maskable.png and /dev/null differ
diff --git a/web/icon-512.png b/web/icon-512.png
deleted file mode 100644
index 33ddc40..0000000
Binary files a/web/icon-512.png and /dev/null differ
diff --git a/web/index.html b/web/index.html
deleted file mode 100644
index d26f911..0000000
--- a/web/index.html
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-Just a moment... Home
-
-
-
-
-
-
- Just a moment...
-
-
-
-
-
\ No newline at end of file
diff --git a/web/logo.png b/web/logo.png
deleted file mode 100644
index b16de6a..0000000
Binary files a/web/logo.png and /dev/null differ