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..4a074e8 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 137 + versionName "4.3.3" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" multiDexEnabled true diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7a7b5d2..7c09edb 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -299,6 +299,11 @@ + + \ No newline at end of file diff --git a/app/src/main/java/com/vectras/qemu/MainSettingsManager.java b/app/src/main/java/com/vectras/qemu/MainSettingsManager.java index f10ad44..59d51bc 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,6 @@ public class MainSettingsManager extends AppCompatActivity public static Boolean getAutoSwitchToExternalMouse(Context context) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - return prefs.getBoolean("autoSwitchToExternalMouse", true); + return prefs.getBoolean("autoSwitchToExternalMouse", false); } } diff --git a/app/src/main/java/com/vectras/qemu/MainVNCActivity.java b/app/src/main/java/com/vectras/qemu/MainVNCActivity.java index 1e38d2a..617ea55 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(); 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..c947f18 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; @@ -63,6 +66,7 @@ import java.util.concurrent.Executors; import com.vectras.vm.utils.ZipUtils; import com.vectras.vterm.Terminal; import com.vectras.vm.utils.PermissionUtils; +import com.vectras.vterm.Terminal2; public class VMCreatorActivity extends AppCompatActivity { @@ -309,9 +313,28 @@ public class VMCreatorActivity extends AppCompatActivity { setDefault(); if (MainSettingsManager.autoCreateDisk(this)) { if (createVMFolder(true)) { - Terminal vterm = new Terminal(this); - vterm.executeShellCommand2("qemu-img create -f qcow2 " + VmFileManager.getPath(vmID, "disk.qcow2") + " 128G", false, this); - binding.drive.setText(VmFileManager.getPath(vmID, "disk.qcow2")); + Terminal2 terminal2 = new Terminal2(this); + terminal2.setShowProgressDialog(true); + terminal2.execute("qemu-img create -f qcow2 " + VmFileManager.getPath(vmID, "disk.qcow2") + " 128G", new Terminal2.Terminal2Callback() { + @Override + public void onRunning(String command, String newLine) { + // Nothing to do. + } + + @Override + public void onFinished(String command, String log, int status) { + new Handler(Looper.getMainLooper()).post(() -> { + if (status == terminal2.SUCCESS) + binding.drive.setText(VmFileManager.getPath(vmID, "disk.qcow2")); + }); + } + + @Override + public void onError(String command, Exception exception) { + new Handler(Looper.getMainLooper()).post(() -> Toast.makeText(getApplicationContext(), getString(R.string.an_error_occurred_while_creating_the_virtual_drive), Toast.LENGTH_SHORT).show()); + + } + }); } } else { setDrive(SELECT_DISK_0_FILE_MODE, null); @@ -1156,7 +1179,7 @@ public class VMCreatorActivity extends AppCompatActivity { private void showProgressDialog(String message) { dialogProgressStyleBinding.progressText.setText(message); dialogProgressStyleBinding.progressBar.setIndeterminate(true); - progressDialog.show(); + if (!isFinishing() && !isDestroyed()) progressDialog.show(); } private String copyToTemp(Uri uri) throws IOException { 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/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..b8b522d 100644 --- a/app/src/main/java/com/vectras/vm/manager/VmControllerDialog.java +++ b/app/src/main/java/com/vectras/vm/manager/VmControllerDialog.java @@ -32,7 +32,7 @@ import com.vectras.vm.settings.VNCSettingsActivity; import com.vectras.vm.utils.DialogUtils; import com.vectras.vm.utils.FileUtils; import com.vectras.vm.utils.ProgressDialog; -import com.vectras.vm.utils.StreamAudio; +import com.vectras.vm.sound.StreamAudio; import com.vectras.vm.x11.X11Activity; import java.io.File; @@ -667,7 +667,14 @@ public class VmControllerDialog extends DialogFragment { if (uri != null) { try { new Thread(() -> { - File selectedFilePath = new File(Objects.requireNonNull(FileUtils.getPath(getContext(), uri))); + String filePath = FileUtils.getPath(getContext(), uri); + + if (filePath == null) { + showErrorSelectedFileDialog(); + return; + } + + File selectedFilePath = new File(filePath); if (isAdded()) { requireActivity().runOnUiThread(() -> { 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/Settings2Activity.java b/app/src/main/java/com/vectras/vm/settings/Settings2Activity.java index 9dd2bf4..30bb1d3 100644 --- a/app/src/main/java/com/vectras/vm/settings/Settings2Activity.java +++ b/app/src/main/java/com/vectras/vm/settings/Settings2Activity.java @@ -42,5 +42,7 @@ public class Settings2Activity extends AppCompatActivity { binding.lnVnc.setOnClickListener(v -> startActivity(new Intent(this, VNCSettingsActivity.class))); binding.lnX11.setOnClickListener(v -> startActivity(new Intent(this, X11DisplaySettingsActivity.class))); + + binding.lnEqualizer.setOnClickListener(v -> startActivity(new Intent(this, EqualizerActivity.class))); } } \ 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/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..3d2d811 --- /dev/null +++ b/app/src/main/java/com/vectras/vterm/Terminal2.java @@ -0,0 +1,253 @@ +package com.vectras.vterm; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; + +import com.vectras.vm.AppConfig; +import com.vectras.vm.R; +import com.vectras.vm.logger.VectrasStatus; +import com.vectras.vm.utils.FileUtils; +import com.vectras.vm.utils.ProgressDialog; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +public class Terminal2 { + final String TAG = "Terminal2"; + public final int SUCCESS = 0; + public final int ERROR = 1; + Context context; + String user = "root"; + String home = "/root"; + String display = ":0"; + String shell = "/bin/sh"; + String startup = ""; + String logs = ""; + String lastLog = ""; + boolean isShowProgressDialog; + + ProgressDialog progressDialog = null; + + public interface Terminal2Callback { + void onRunning(String command, String newLine); + void onFinished(String command, String log, int status); + void onError(String command, Exception exception); + } + + public Terminal2(Context context) { + this.context = context; + } + + public void setUserName(String user) { + this.user = user; + home = user.equals("root") ? "/root" : "/home/" + user; + } + + public void setRootMode() { + this.user = "root"; + home = "/root"; + } + + public void setDefaultShell() { + this.shell = "/bin/sh"; + } + + public void setDefaultShellBash() { + this.shell = "/bin/bash"; + } + + public void setStartup(String command) { + this.startup = command; + } + + public String getLogs() { + return logs; + } + + public void clearLog() { + logs = ""; + } + + public void setShowProgressDialog(boolean isShow) { + this.isShowProgressDialog = isShow; + } + + public String executeOnThisThread(String command) { + execute(command, null, false); + return lastLog; + } + + public void execute(String command) { + execute(command, null); + } + + public void execute(String command, Terminal2Callback callback) { + execute(command, callback, true); + } + + public void execute(String command, Terminal2Callback callback, boolean isNewThread) { + AtomicReference process = new AtomicReference<>(); + + Runnable runnable = () -> { + + if (isShowProgressDialog) { + progressDialog = new ProgressDialog(context); + progressDialog.setText(context.getString(R.string.executing_command_please_wait)); + new Handler(Looper.getMainLooper()).post((progressDialog::show)); + } + + try { + ProcessBuilder processBuilder = new ProcessBuilder(); + processBuilder.redirectErrorStream(true); + + + String filesDir = Objects.requireNonNull(context.getFilesDir().getAbsolutePath()); + String prootPath = new File(filesDir, "usr/bin/proot").getAbsolutePath(); + String tmpDir = new File(filesDir, "usr/tmp").getAbsolutePath(); + + if (!FileUtils.isFileExists(filesDir + "/distro" + shell)) { + setDefaultShell(); + } + + if (!FileUtils.isFileExists(filesDir + "/distro" + home)) { + FileUtils.createDirectory(filesDir + "/distro" + home); + } + + processBuilder.environment().put("PROOT_TMP_DIR", tmpDir); + processBuilder.environment().put("HOME", home); + processBuilder.environment().put("USER", user); + processBuilder.environment().put("TERM", "xterm-256color"); + processBuilder.environment().put("TMPDIR", "/tmp"); + processBuilder.environment().put("SHELL", shell); + processBuilder.environment().put("DISPLAY", display); + processBuilder.environment().put("XDG_RUNTIME_DIR", "/tmp"); + processBuilder.environment().put("SDL_VIDEODRIVER", "x11"); + + String[] prootCommand = { + prootPath, + "--kill-on-exit", + "--link2symlink", + "-0", + "-r", filesDir + "/distro", + "-b", "/dev", + "-b", "/proc", + "-b", "/sys", + "-b", AppConfig.internalDataDirPath + "distro" + home + ":/dev/shm", + "-b", "/sdcard", + "-b", "/storage", + "-b", "/data", + "-b", AppConfig.internalDataDirPath + "usr/tmp:/tmp", + "-w", home, + shell, + "--login" + }; + + processBuilder.command(prootCommand); + process.set(processBuilder.start()); + + resultData data = startProcess((startup.isEmpty() ? "" : startup + " && ") + command, process.get(), callback); + + if (callback != null) callback.onFinished(command, data.log.toString(), data.status); + } catch (IOException e) { + if (callback != null) callback.onError(command, e); + + addToLogs(command, e.getMessage()); + } finally { + if (progressDialog != null) { + new Handler(Looper.getMainLooper()).post(() -> { + progressDialog.reset(); + progressDialog = null; + }); + } + } + }; + + if (isNewThread) { + new Thread(runnable).start(); + } else { + runnable.run(); + } + } + + private resultData startProcess(String command, Process process, Terminal2Callback callback) { + resultData data = new resultData(); + AtomicInteger exitCode = new AtomicInteger(); + + int MAX_LOG_SIZE = 200_000; // ~200KB text + StringBuilder output = new StringBuilder(); + try { + BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(process.getOutputStream())); + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + + writer.write(command); + writer.newLine(); + writer.flush(); + writer.close(); + + String line; + while ((line = reader.readLine()) != null) { + Log.d(TAG, line); + VectrasStatus.logError(line); + output.append(line).append("\n"); + if (callback != null) callback.onRunning(command, line); + + if (output.length() > MAX_LOG_SIZE) { + output.delete(0, output.length() - MAX_LOG_SIZE); + } + + if (progressDialog != null) { + String finalLine = line; + new Handler(Looper.getMainLooper()).post(() -> progressDialog.setText(finalLine)); + } + } + + try { + exitCode.set(process.waitFor()); + } catch (InterruptedException e) { + exitCode.set(ERROR); + } + data.status = exitCode.get(); + + reader.close(); + } catch (Exception e) { + output.append(e.getMessage()); + Log.e(TAG, "streamLog: ", e); + } + + data.status = exitCode.get(); + data.log = output; + + addToLogs(command, output.toString()); + + return data; + } + + private void addToLogs(String command,String log) { + lastLog = log; + + if (!logs.isEmpty()) { + logs += "\n\n----------\n\n"; + } + + logs += "$ " + command + "\n\n"; + + logs += log; + + if (logs.contains("\n") && logs.lastIndexOf("\n") > 1000) + logs = logs.substring(logs.lastIndexOf("\n") + 1); + } + + private class resultData { + StringBuilder log; + int status; + } +} 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/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/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_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..98587cc 100644 --- a/app/src/main/res/layout/activity_settings2.xml +++ b/app/src/main/res/layout/activity_settings2.xml @@ -302,7 +302,7 @@ android:id="@+id/ln_x11" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="2dp" + android:layout_marginBottom="18dp" android:background="@drawable/object_shape_bottom_high" android:padding="16dp" android:gravity="center_vertical"> @@ -334,6 +334,43 @@ android:textSize="14sp" /> + + + + + + + + + + + + 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..a9818ac 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,17 @@ 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 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f7263e9..f8956c3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -627,6 +627,19 @@ 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 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/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..b814b06 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":"137", + "versionNameBeta":"4.3.3", + "versionNameBetas":"4.0.1,4.0.2,4.0.3,4.0.4,4.0.5,4.0.6,4.0.7,4.0.8,4.0.9,4.1.1,4.1.2,4.1.3,4.1.4,4.1.5,4.1.6,4.1.7,4.1.8,4.1.9,4.2.1,4.2.2,4.2.3,4.2.4,4.2.5,4.2.6,4.2.7,4.2.8,4.2.9,4.3.0,4.3.1,4.3.2,4.3.3", "sizeBeta": "45 MB", "urlBeta": "https://github.com/AnBui2004/Vectras-VM-Emu-Android/releases", - "MessageBeta": "

4.3.0

Bugs fixed.", + "MessageBeta": "

4.3.3

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