Compare commits

..

23 commits

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

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

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-22 08:17:17 +00:00
power690
1706c287c6 Complete Chinese (zh-CN) translation 2026-06-13 04:20:44 +00:00
107 changed files with 3540 additions and 790 deletions

View file

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

View file

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

View file

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

View file

@ -47,6 +47,16 @@
android:requestLegacyExternalStorage="true"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".settings.EqualizerActivity"
android:configChanges="orientation|screenSize|keyboardHidden|smallestScreenSize|screenLayout"
android:exported="false"
android:windowSoftInputMode="adjustResize" />
<activity
android:name=".settings.FilePickerSettingsActivity"
android:configChanges="orientation|screenSize|keyboardHidden|smallestScreenSize|screenLayout"
android:exported="false"
android:windowSoftInputMode="adjustResize" />
<activity
android:name=".settings.SearchSettingsActivity"
android:configChanges="orientation|screenSize|keyboardHidden|smallestScreenSize|screenLayout"

View file

@ -1331,11 +1331,13 @@ public class VncCanvas extends AppCompatImageView {
}
private static final String SHIFT_CHARS = "~!@#$%^&*()_+{}|:\"<>?";
private static final int LEFT_SHIFT_KEY_CODE = 0xFFE1;
public void sendText(String s) {
int l = s.length();
for (int i = 0; i < l; i++) {
char c = s.charAt(i);
int meta = 0;
int keysym = c;
if (Character.isISOControl(c)) {
if (c == '\n') {
@ -1344,9 +1346,20 @@ public class VncCanvas extends AppCompatImageView {
continue;
}
}
boolean isNeedShift = Character.isUpperCase(c) || SHIFT_CHARS.indexOf(c) >= 0;
try {
rfb.writeKeyEvent(keysym, meta, true);
rfb.writeKeyEvent(keysym, meta, false);
if (isNeedShift) {
rfb.writeKeyEvent(LEFT_SHIFT_KEY_CODE, 0, true);
}
rfb.writeKeyEvent(keysym, 0, true);
rfb.writeKeyEvent(keysym, 0, false);
if (isNeedShift) {
rfb.writeKeyEvent(LEFT_SHIFT_KEY_CODE, 0, false);
}
} catch (IOException ioe) {
Log.e(TAG, "sendText: ",ioe);
}

View file

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

View file

@ -73,7 +73,7 @@ import com.vectras.vm.utils.DialogUtils;
import com.vectras.vm.utils.FileUtils;
import com.vectras.vm.utils.ListUtils;
import com.vectras.vm.utils.SimulateKeyEvent;
import com.vectras.vm.utils.StreamAudio;
import com.vectras.vm.sound.StreamAudio;
import com.vectras.vm.utils.UIUtils;
import com.vectras.vm.view.DynamicBubble;
@ -397,7 +397,7 @@ public class MainVNCActivity extends VncCanvasActivity {
public void onDestroy() {
super.onDestroy();
this.stopTimeListener();
if (streamAudio != null) streamAudio.stop();
if (streamAudio != null) streamAudio.release();
//Terminal.killQemuProcess();
}
@ -774,8 +774,14 @@ public class MainVNCActivity extends VncCanvasActivity {
} catch (InterruptedException e) {
Log.e(TAG, "onMonitor: ", e);
}
monitorMode = true;
vncCanvas.sendMetaKey1(50, 6);
try {
vncCanvas.sendMetaKey1(50, 6);
monitorMode = true;
} catch (NullPointerException e) {
monitorMode = false;
Log.e(TAG, "onMonitor: ", e);
}
});
t.start();
@ -1473,8 +1479,9 @@ public class MainVNCActivity extends VncCanvasActivity {
});
bindingSendKey.sendtextButton.setOnClickListener(v -> {
vncCanvas.sendText(bindingSendKey.sendtextEdittext.getText().toString());
String text = bindingSendKey.sendtextEdittext.getText().toString();
bindingSendKey.sendtextEdittext.setText("");
new Thread(() -> vncCanvas.sendText(text)).start();
});
bindingSendKey.sendtextEdittext.addTextChangedListener(new TextWatcher() {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,12 +5,15 @@ import android.content.ComponentName;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback;
import androidx.activity.result.ActivityResultLauncher;
@ -29,6 +32,7 @@ import com.vectras.qemu.MainSettingsManager;
import com.vectras.vm.AppConfig;
import com.vectras.vm.Fragment.CreateImageDialogFragment;
import com.vectras.vm.R;
import com.vectras.vm.file.FilePickerDialog;
import com.vectras.vm.store.RomInfo;
import com.vectras.vm.SplashActivity;
import com.vectras.vm.VMManager;
@ -63,6 +67,7 @@ import java.util.concurrent.Executors;
import com.vectras.vm.utils.ZipUtils;
import com.vectras.vterm.Terminal;
import com.vectras.vm.utils.PermissionUtils;
import com.vectras.vterm.Terminal2;
public class VMCreatorActivity extends AppCompatActivity {
@ -99,13 +104,25 @@ public class VMCreatorActivity extends AppCompatActivity {
return true;
} else if (id == R.id.add_file) {
try {
filePicker.launch("*/*");
if (MainSettingsManager.getBuiltInFilePicker(this)) {
FilePickerDialog filePickerDialog = new FilePickerDialog();
filePickerDialog.pick(this, filePickerDialog.TYPE_FILE, (path -> handleAddFile(Uri.fromFile(new File (path)))));
} else {
filePicker.launch("*/*");
}
} catch (Exception e) {
IntentUtils.showErrorDialog(this);
}
return true;
} else if (id == R.id.show_in_folder) {
FileUtils.openFolder(this, VmFileManager.getPath(vmID));
if (MainSettingsManager.getBuiltInFilePicker(this)) {
FilePickerDialog filePickerDialog = new FilePickerDialog();
filePickerDialog.setHomeName(current.itemName);
filePickerDialog.setLockHome(true);
filePickerDialog.browse(this, VmFileManager.getPath(vmID));
} else {
FileUtils.openFolder(this, VmFileManager.getPath(vmID));
}
return true;
} else {
return super.onOptionsItemSelected(item);
@ -301,7 +318,12 @@ public class VMCreatorActivity extends AppCompatActivity {
} else if (getIntent().hasExtra("importcvbinow")) {
setDefault();
try {
cvbiPicker.launch("*/*");
if (MainSettingsManager.getBuiltInFilePicker(this)) {
FilePickerDialog filePickerDialog = new FilePickerDialog();
filePickerDialog.pick(this, filePickerDialog.ROM_FILE, (path -> importRom(null, path, new File(path).getName())));
} else {
cvbiPicker.launch("*/*");
}
} catch (Exception e) {
IntentUtils.showErrorDialog(this);
}
@ -309,9 +331,28 @@ public class VMCreatorActivity extends AppCompatActivity {
setDefault();
if (MainSettingsManager.autoCreateDisk(this)) {
if (createVMFolder(true)) {
Terminal vterm = new Terminal(this);
vterm.executeShellCommand2("qemu-img create -f qcow2 " + VmFileManager.getPath(vmID, "disk.qcow2") + " 128G", false, this);
binding.drive.setText(VmFileManager.getPath(vmID, "disk.qcow2"));
Terminal2 terminal2 = new Terminal2(this);
terminal2.setShowProgressDialog(true);
terminal2.execute("qemu-img create -f qcow2 " + VmFileManager.getPath(vmID, "disk.qcow2") + " 128G", new Terminal2.Terminal2Callback() {
@Override
public void onRunning(String command, String newLine) {
// Nothing to do.
}
@Override
public void onFinished(String command, String log, int status) {
new Handler(Looper.getMainLooper()).post(() -> {
if (status == terminal2.SUCCESS)
binding.drive.setText(VmFileManager.getPath(vmID, "disk.qcow2"));
});
}
@Override
public void onError(String command, Exception exception) {
new Handler(Looper.getMainLooper()).post(() -> Toast.makeText(getApplicationContext(), getString(R.string.an_error_occurred_while_creating_the_virtual_drive), Toast.LENGTH_SHORT).show());
}
});
}
} else {
setDrive(SELECT_DISK_0_FILE_MODE, null);
@ -482,44 +523,46 @@ public class VMCreatorActivity extends AppCompatActivity {
@SuppressLint("SetTextI18n")
private final ActivityResultLauncher<String> filePicker =
registerForActivityResult(new ActivityResultContracts.GetContent(), uri -> {
if (uri == null) return;
showProgressDialog(getString(R.string.copying_file));
registerForActivityResult(new ActivityResultContracts.GetContent(), this::handleAddFile);
executor.execute(() -> {
try {
isProcessingFile = true;
public void handleAddFile(Uri uri) {
if (uri == null) return;
showProgressDialog(getString(R.string.copying_file));
String filePath = copyToTemp(uri);
executor.execute(() -> {
try {
isProcessingFile = true;
runOnUiThread(() -> DialogUtils.twoDialog(this,
getString(R.string.file_added),
filePath,
getString(R.string.copy_full_path),
getString(R.string.close),
true,
R.drawable.check_24px,
true,
() -> ClipboardUltils.copyToClipboard(this, filePath),
null,
null));
} catch (Exception e) {
runOnUiThread(() -> DialogUtils.oneDialog(this,
getString(R.string.oops),
getString(R.string.adding_file_failed_content),
getString(R.string.ok),
true,
R.drawable.error_96px,
true,
null,
null));
Log.e(TAG, "filePicker: " + e.getMessage());
} finally {
isProcessingFile = false;
runOnUiThread(() -> DialogUtils.safeDismiss(this, progressDialog));
}
});
});
String filePath = copyToTemp(uri);
runOnUiThread(() -> DialogUtils.twoDialog(this,
getString(R.string.file_added),
filePath,
getString(R.string.copy_full_path),
getString(R.string.close),
true,
R.drawable.check_24px,
true,
() -> ClipboardUltils.copyToClipboard(this, filePath),
null,
null));
} catch (Exception e) {
runOnUiThread(() -> DialogUtils.oneDialog(this,
getString(R.string.oops),
getString(R.string.adding_file_failed_content),
getString(R.string.ok),
true,
R.drawable.error_96px,
true,
null,
null));
Log.e(TAG, "filePicker: " + e.getMessage());
} finally {
isProcessingFile = false;
runOnUiThread(() -> DialogUtils.safeDismiss(this, progressDialog));
}
});
}
private void loadConfig(DataMainRoms vmConfig) {
if (vmConfig != null) current = vmConfig;
@ -1156,7 +1199,7 @@ public class VMCreatorActivity extends AppCompatActivity {
private void showProgressDialog(String message) {
dialogProgressStyleBinding.progressText.setText(message);
dialogProgressStyleBinding.progressBar.setIndeterminate(true);
progressDialog.show();
if (!isFinishing() && !isDestroyed()) progressDialog.show();
}
private String copyToTemp(Uri uri) throws IOException {
@ -1272,13 +1315,26 @@ public class VMCreatorActivity extends AppCompatActivity {
try {
if (PENDING_SELECT_FILE_MODE == SELECT_CDROM_0_FILE_MODE || PENDING_SELECT_FILE_MODE == SELECT_CDROM_1_FILE_MODE) {
isoPicker.launch("*/*");
if (MainSettingsManager.getBuiltInFilePicker(this)) {
builtInFilePicker(PICK_OPTICAL_FILE_MODE);
} else {
isoPicker.launch("*/*");
}
} else if (PENDING_SELECT_FILE_MODE == SELECT_FLOPPY_A_FILE_MODE || PENDING_SELECT_FILE_MODE == SELECT_FLOPPY_B_FILE_MODE) {
if (MainSettingsManager.getBuiltInFilePicker(this)) {
builtInFilePicker(PICK_FLOPPY_FILE_MODE);
} else {
floppyPicker.launch("*/*");
}
} else {
diskPicker.launch("*/*");
if (MainSettingsManager.getBuiltInFilePicker(this)) {
builtInFilePicker(PICK_DRIVE_FILE_MODE);
} else {
diskPicker.launch("*/*");
}
}
} catch (Exception e) {
Log.e(TAG, "pickStorageFile: ", e);
IntentUtils.showErrorDialog(this);
}
}
@ -1452,4 +1508,23 @@ public class VMCreatorActivity extends AppCompatActivity {
});
}
}
final int PICK_DRIVE_FILE_MODE = 0;
final int PICK_OPTICAL_FILE_MODE = 1;
final int PICK_FLOPPY_FILE_MODE = 2;
private void builtInFilePicker(int mode) {
FilePickerDialog filePickerDialog = new FilePickerDialog();
int finalMode;
if (mode == PICK_DRIVE_FILE_MODE) {
finalMode = filePickerDialog.FIT_PICK_DISK_FILE_MODE;
} else if (mode == PICK_OPTICAL_FILE_MODE) {
finalMode = filePickerDialog.FIT_PICK_OPTICAL_FILE_MODE;
} else {
finalMode = filePickerDialog.FIT_PICK_FLOPPY_FILE_MODE;
}
filePickerDialog.pick(this, finalMode, (path -> handleFile(Uri.fromFile(new File(path)))));
}
}

View file

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

View file

@ -0,0 +1,9 @@
package com.vectras.vm.file;
public class FilePickerData {
String name = "";
String path = "";
int type = -1;
int icon = -1;
boolean enabled = true;
}

View file

@ -0,0 +1,469 @@
package com.vectras.vm.file;
import android.app.Activity;
import android.app.Dialog;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.MimeTypeMap;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.FileProvider;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.vectras.vm.R;
import com.vectras.vm.databinding.DialogFilePickerBinding;
import com.vectras.vm.databinding.ListFileBinding;
import com.vectras.vm.manager.FormatManager;
import com.vectras.vm.utils.ClipboardUltils;
import com.vectras.vm.utils.FileUtils;
import com.vectras.vm.utils.IntentUtils;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
public class FilePickerDialog extends DialogFragment {
DialogFilePickerBinding binding;
LinearLayoutManager layoutmanager;
RecyclerviewAdapter adapter;
FilePickerDialogCallback callback;
FilePickerSettings settings;
String currentPath = "";
String homeName;
String homePath;
boolean lockHome;
int pickType = -1;
boolean isShowHiddenFile;
boolean isShowHiddenDiviers;
boolean fixTextColor;
int textColor;
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
binding = DialogFilePickerBinding.inflate(getLayoutInflater());
settings = new FilePickerSettings(requireActivity());
isShowHiddenFile = settings.showHiddenFiles();
isShowHiddenDiviers = settings.divider();
if (fixTextColor) {
textColor = binding.tvTitle.getCurrentTextColor();
binding.tvFullPath.setTextColor(textColor);
binding.tvNothingHere.setTextColor(textColor);
}
AlertDialog dialog = new AlertDialog.Builder(requireActivity()).create();
dialog.setView(binding.getRoot());
layoutmanager = new LinearLayoutManager(requireActivity());
adapter = new RecyclerviewAdapter(requireActivity(), dialog, new ArrayList<>(), callback);
binding.list.setAdapter(adapter);
binding.list.setLayoutManager(layoutmanager);
binding.list.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView rv, int dx, int dy) {
boolean canScrollUp = rv.canScrollVertically(-1);
boolean canScrollDown = rv.canScrollVertically(1);
binding.dvTop.setVisibility(canScrollUp ? View.VISIBLE : View.INVISIBLE);
binding.dvBottom.setVisibility(canScrollDown ? View.VISIBLE : View.INVISIBLE);
}
});
String lastPath = new File(settings.lastPath()).exists() ? settings.lastPath() : Environment.getExternalStorageDirectory().getAbsolutePath();
adapter.load((homePath != null && new File(homePath).exists()) ? homePath : lastPath);
binding.btnHome.setOnClickListener(v -> adapter.load(homePath != null && new File(homePath).exists() ? homePath : Environment.getExternalStorageDirectory().getAbsolutePath()));
binding.btnUp.setOnClickListener(v -> adapter.load(new File(currentPath).getParent()));
if (pickType == BROWSE_MODE) {
binding.btnOpenInOtherApp.setOnClickListener(v -> {
if (!isAdded()) return;
FileUtils.openFolder(requireContext(), currentPath);
dismiss();
});
} else {
binding.btnOpenInOtherApp.setVisibility(View.GONE);
}
binding.tvFullPath.setOnClickListener(v -> {
if (!isAdded()) return;
ClipboardUltils.copyToClipboard(requireContext(), currentPath, false);
Toast.makeText(requireContext(), R.string.the_folder_path_has_been_copied_to_the_clipboard, Toast.LENGTH_SHORT).show();
});
binding.btnClose.setOnClickListener(v -> dismiss());
return dialog;
}
public void setFixTextColor(boolean isEnable) {
fixTextColor = isEnable;
}
public void setHomeName(String name) {
homeName = name;
}
public void setHomePath(String path) {
homePath = new File(path).getAbsolutePath();
}
public void setLockHome(boolean lock) {
lockHome = lock;
}
public void browse(Activity activity) {
pickType = BROWSE_MODE;
show(((FragmentActivity) activity).getSupportFragmentManager(), "file_picker");
}
public void browse(Activity activity, String path) {
setHomePath(path);
browse(activity);
}
public void pick(Activity activity, int type, FilePickerDialogCallback callback) {
this.callback = callback;
pickType = type;
show(((FragmentActivity) activity).getSupportFragmentManager(), "file_picker");
}
public void updateUpButton(String path) {
if (path.equals(Environment.getExternalStorageDirectory().getAbsolutePath())) {
binding.tvTitle.setText(R.string.internal_storage);
binding.btnHome.setVisibility(View.GONE);
binding.btnUp.setVisibility(View.INVISIBLE);
binding.btnUp.setEnabled(false);
} else if (lockHome && homePath.equals(path)) {
binding.tvTitle.setText(homeName.isEmpty() ? new File(path).getName() : homeName);
binding.btnHome.setVisibility(View.GONE);
binding.btnUp.setVisibility(View.INVISIBLE);
binding.btnUp.setEnabled(false);
} else {
binding.tvTitle.setText(new File(path).getName());
binding.btnHome.setVisibility(View.VISIBLE);
binding.btnUp.setVisibility(View.VISIBLE);
binding.btnUp.setEnabled(true);
}
binding.tvFullPath.setText(path);
}
boolean isLoading;
private ArrayList<FilePickerData> loadFileList(String path) {
currentPath = path;
new Handler(Looper.getMainLooper()).post(() -> updateUpButton(path));
ArrayList<FilePickerData> list = new ArrayList<>();
File dir = new File(path);
if (!dir.exists() || dir.isFile()) return list;
File[] listFiles = dir.listFiles();
if (listFiles == null || listFiles.length == 0) return list;
list.clear();
for (File file : listFiles) {
if (!isShowHiddenFile && file.getName().startsWith(".")) continue;
FilePickerData item = new FilePickerData();
item.name = file.getName();
item.path = file.getAbsolutePath();
item.type = file.isDirectory() ? TYPE_FOLDER : getType(file.getName());
item.icon = getIcon(item.type);
item.enabled = pickType == TYPE_FILE || pickType == BROWSE_MODE || item.type == TYPE_FOLDER || (pickType >= FIT_PICK_DISK_FILE_MODE ? checkFitPickFileMode(item.name) : item.type == pickType);
list.add(item);
}
Collections.sort(list, (o1, o2) -> {
if (o1.name == null || o2.name == null) {
return 0;
}
return o1.name.compareToIgnoreCase(o2.name);
});
return list;
}
public static final int BROWSE_MODE = -1;
public static final int TYPE_FILE = 0;
public static final int TYPE_FOLDER = 1;
public static final int AUDIO_FILE = 2;
public static final int VIDEO_FILE = 3;
public static final int IMAGE_FILE = 4;
public static final int DOCUMENT_FILE = 5;
public static final int APPLICATION_FILE = 6;
public static final int DISK_FILE = 7;
public static final int ISO_FILE = 8;
public static final int FLOPPY_FILE = 9;
public static final int ROM_FILE = 10;
public static final int SHELL_FILE = 11;
public static final int DB_FILE = 12;
public static final int LOG_FILE = 13;
public static final int WINDOWS_FILE = 14;
public static final int CONFIG_FILE = 15;
public static final int ZIP_FILE = 16;
public static final int DEV_FILE = 17;
public static final int SYSTEM_FILE = 18;
public static final int PACKAGE_FILE = 19;
public static final int FONT_FILE = 20;
public static final int KEY_FILE = 21;
public static final int IOS_FILE = 22;
private int getType(String fileName) {
if (!fileName.contains(".")) return TYPE_FILE;
String extension = fileName.substring(fileName.lastIndexOf(".")).toLowerCase();
return switch (extension) {
case ".mp3", ".wav", ".ogg", ".flac", ".m4a", ".aif", ".wma", ".smi" -> AUDIO_FILE;
case ".mp4", ".webm", ".mov", ".mkv", ".avi", ".wmv" -> VIDEO_FILE;
case ".jpg", ".jpeg", ".png", ".gif", ".webp", ".raw" -> IMAGE_FILE;
case ".pdf", ".txt", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".md", ".csv" -> DOCUMENT_FILE;
case ".apk", ".xapk", ".apkm", ".apks" -> APPLICATION_FILE;
case ".img", ".qcow2", ".vhd", ".vhdx", ".vdi", ".qcow", ".vmdk", ".vpc" -> DISK_FILE;
case ".iso", ".cdr", ".toast" -> ISO_FILE;
case ".flp", ".ima" -> FLOPPY_FILE;
case ".cvbi" -> ROM_FILE;
case ".sh", ".cmd", ".bat", ".ps1" -> SHELL_FILE;
case ".json", ".db", ".sqlite", ".sqlite3", ".dbf", ".accdb", ".mdf", ".ldf", ".ibd", ".frm", ".parquet", ".orc", ".kdbx" -> DB_FILE;
case ".log" -> LOG_FILE;
case ".exe", ".msi", ".msix", ".appx", ".msixbundle" -> WINDOWS_FILE;
case ".conf", ".ini" -> CONFIG_FILE;
case ".zip", ".7z", ".rar" -> ZIP_FILE;
case ".java", ".kt", ".js", ".py", ".c", ".cpp", ".h", ".xml", ".html", ".css", ".lua", ".go", ".asl" -> DEV_FILE;
case ".dll" -> SYSTEM_FILE;
case ".deb", ".rpm" -> PACKAGE_FILE;
case ".ttf", ".otf" -> FONT_FILE;
case ".jks" -> KEY_FILE;
case ".ipa", ".ipsw" -> IOS_FILE;
default -> TYPE_FILE;
};
}
private int getIcon(int type) {
return switch (type) {
case TYPE_FOLDER -> R.drawable.folder_24px;
case AUDIO_FILE -> R.drawable.music_note_24px;
case VIDEO_FILE -> R.drawable.movie_24px;
case IMAGE_FILE -> R.drawable.image_24px;
case DOCUMENT_FILE -> R.drawable.description_24px;
case APPLICATION_FILE -> R.drawable.android_24px;
case DISK_FILE -> R.drawable.hard_drive_24px;
case ISO_FILE -> R.drawable.album_24px;
case ROM_FILE -> R.drawable.vectras_vm_24px;
case SHELL_FILE -> R.drawable.terminal_24px;
case DB_FILE -> R.drawable.database_24px;
case LOG_FILE -> R.drawable.monitor_heart_24px;
case WINDOWS_FILE -> R.drawable.wysiwyg_24px;
case CONFIG_FILE -> R.drawable.tune_24px;
case ZIP_FILE -> R.drawable.folder_zip_24px;
case DEV_FILE -> R.drawable.code_24px;
case SYSTEM_FILE -> R.drawable.settings_24px;
case PACKAGE_FILE -> R.drawable.package_24px;
case FONT_FILE -> R.drawable.titlecase_24px;
case KEY_FILE -> R.drawable.key_24px;
case IOS_FILE -> R.drawable.ios_24px;
default -> R.drawable.draft_24px;
};
}
public static final int FIT_PICK_DISK_FILE_MODE = 20;
public static final int FIT_PICK_OPTICAL_FILE_MODE = 21;
public static final int FIT_PICK_FLOPPY_FILE_MODE = 22;
public boolean checkFitPickFileMode(String fileName) {
if (pickType == FIT_PICK_DISK_FILE_MODE) {
return FormatManager.isHardDriveFileFormat(fileName);
} else if (pickType == FIT_PICK_OPTICAL_FILE_MODE) {
return FormatManager.isOpticalFileFormat(fileName);
} else if (pickType == FIT_PICK_FLOPPY_FILE_MODE) {
return FormatManager.isFloppyFileFormat(fileName);
} else {
return false;
}
}
public interface FilePickerDialogCallback {
void onPicked(String path);
}
private class RecyclerviewAdapter extends RecyclerView.Adapter<RecyclerviewAdapter.ViewHolder> {
Activity activity;
ArrayList<FilePickerData> data;
AlertDialog dialog;
FilePickerDialogCallback callback;
public RecyclerviewAdapter(Activity activity, AlertDialog alertDialog, ArrayList<FilePickerData> arr, FilePickerDialogCallback callback) {
this.activity = activity;
data = arr;
dialog = alertDialog;
this.callback = callback;
}
@NonNull
@Override
public RecyclerviewAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = activity.getLayoutInflater();
ListFileBinding binding1 = ListFileBinding.inflate(inflater, parent, false);
return new RecyclerviewAdapter.ViewHolder(binding1.getRoot());
}
@Override
public void onBindViewHolder(RecyclerviewAdapter.ViewHolder holder, final int position) {
View view = holder.itemView;
TextView text = view.findViewById(R.id.text);
ImageView icon = view.findViewById(R.id.icon);
text.setText(data.get(position).name);
icon.setImageResource(data.get(position).icon);
if (fixTextColor) text.setTextColor(textColor);
LinearLayout main = view.findViewById(R.id.main);
if (data.get(position).enabled) {
main.setEnabled(true);
main.setAlpha(1f);
main.setOnClickListener(v -> {
if (data.get(position).type == TYPE_FOLDER) {
load(data.get(position).path);
} else {
if (activity.isFinishing() || activity.isDestroyed()) return;
if (pickType == BROWSE_MODE) {
openFile(data.get(position).path);
} else {
if (callback != null) callback.onPicked(data.get(position).path);
settings.lastPath(currentPath);
dialog.dismiss();
}
}
});
} else {
main.setAlpha(0.5f);
main.setOnClickListener(null);
main.setEnabled(false);
}
if (!isShowHiddenDiviers || position == data.size() - 1) {
view.findViewById(R.id.md_inset_bottom).setVisibility(View.GONE);
} else {
view.findViewById(R.id.md_inset_bottom).setVisibility(View.VISIBLE);
}
}
@Override
public int getItemCount() {
return data.size();
}
public void load(String path) {
isLoading = true;
new Handler(Looper.getMainLooper()).postDelayed(() -> {
if (isAdded() && isLoading) binding.lnLoading.setVisibility(View.VISIBLE);
}, 200);
new Thread(() -> {
ArrayList<FilePickerData> data1 = loadFileList(path);
new Handler(Looper.getMainLooper()).post(() -> {
if (!isAdded()) return;
if (data1.isEmpty()) {
binding.lnEmpty.setVisibility(View.VISIBLE);
binding.lnBrowse.setVisibility(View.INVISIBLE);
} else {
binding.lnEmpty.setVisibility(View.INVISIBLE);
binding.lnBrowse.setVisibility(View.VISIBLE);
data.clear();
data.addAll(data1);
notifyDataSetChanged();
}
binding.lnLoading.setVisibility(View.GONE);
isLoading = false;
});
}).start();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(View v) {
super(v);
}
}
}
public void openFile(String path) {
File file = new File(path);
if (!file.exists()) {
if (!isAdded()) return;
IntentUtils.showErrorDialog(requireActivity());
return;
}
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri fileUri;
if (!isAdded()) return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
String authority = requireActivity().getPackageName() + ".provider";
fileUri = FileProvider.getUriForFile(requireContext(), authority, file);
} else {
fileUri = Uri.fromFile(file);
}
String extension = MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(file).toString());
String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.toLowerCase());
if (mimeType == null) {
mimeType = "*/*";
}
intent.setDataAndType(fileUri, mimeType);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Intent chooserIntent = Intent.createChooser(intent, getString(R.string.open_with));
chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (!isAdded()) return;
if (intent.resolveActivity(requireActivity().getPackageManager()) != null) {
requireContext().startActivity(chooserIntent);
} else {
IntentUtils.showErrorDialog(requireActivity());
}
}
}

View file

@ -0,0 +1,47 @@
package com.vectras.vm.file;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Environment;
public class FilePickerSettings {
Context context;
SharedPreferences sharedPreferences;
public FilePickerSettings(Context context) {
this.context = context;
sharedPreferences = context.getSharedPreferences("built_in_file_picker_settings", Context.MODE_PRIVATE);
}
public void lastPath(String path) {
sharedPreferences.edit().putString("lastPath", path).apply();
}
public String lastPath() {
return sharedPreferences.getString("lastPath", Environment.getExternalStorageDirectory().getAbsolutePath());
}
public void resume(boolean enabled) {
sharedPreferences.edit().putBoolean("resume", enabled).apply();
}
public boolean resume() {
return sharedPreferences.getBoolean("resume", true);
}
public void showHiddenFiles(boolean enabled) {
sharedPreferences.edit().putBoolean("showHiddenFiles", enabled).apply();
}
public boolean showHiddenFiles() {
return sharedPreferences.getBoolean("showHiddenFiles", true);
}
public void divider(boolean enabled) {
sharedPreferences.edit().putBoolean("divider", enabled).apply();
}
public boolean divider() {
return sharedPreferences.getBoolean("divider", false);
}
}

View file

@ -46,6 +46,7 @@ import com.vectras.qemu.MainSettingsManager;
import com.vectras.vm.AboutActivity;
import com.vectras.vm.AppConfig;
import com.vectras.vm.creator.VMCreatorActivity;
import com.vectras.vm.file.FilePickerDialog;
import com.vectras.vm.settings.Minitools;
import com.vectras.vm.R;
import com.vectras.vm.WebViewActivity;
@ -522,7 +523,14 @@ public class MainActivity extends AppCompatActivity implements RomStoreFragment.
startActivity(new Intent(this, Settings2Activity.class));
} else if (id == R.id.navigation_data_explorer) {
// startActivity(new Intent(this, DataExplorerActivity.class));
FileUtils.openFolder(this, AppConfig.maindirpath);
if (MainSettingsManager.getBuiltInFilePicker(this)) {
FilePickerDialog filePickerDialog = new FilePickerDialog();
filePickerDialog.setHomeName(getString(R.string.app_name));
filePickerDialog.setLockHome(true);
filePickerDialog.browse(this, AppConfig.maindirpath);
} else {
FileUtils.openFolder(this, AppConfig.maindirpath);
}
} else if (id == R.id.navigation_item_donate) {
String tw = "https://www.patreon.com/VectrasTeam";
Intent w = new Intent(ACTION_VIEW);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,6 +5,7 @@ import android.androidVNC.VncCanvas;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@ -26,13 +27,14 @@ import com.vectras.vm.AppConfig;
import com.vectras.vm.R;
import com.vectras.vm.VMManager;
import com.vectras.vm.databinding.DialogChangeRemovableDevicesBinding;
import com.vectras.vm.file.FilePickerDialog;
import com.vectras.vm.main.core.DisplaySystem;
import com.vectras.vm.main.vms.DataMainRoms;
import com.vectras.vm.settings.VNCSettingsActivity;
import com.vectras.vm.utils.DialogUtils;
import com.vectras.vm.utils.FileUtils;
import com.vectras.vm.utils.ProgressDialog;
import com.vectras.vm.utils.StreamAudio;
import com.vectras.vm.sound.StreamAudio;
import com.vectras.vm.x11.X11Activity;
import java.io.File;
@ -226,7 +228,16 @@ public class VmControllerDialog extends DialogFragment {
if (isHavingOpticalDisc() || isHavingSecondaryOpticalDisc()) {
if (isHavingOpticalDisc()) {
binding.lnCdrom.setOnClickListener(v -> isoPicker.launch("*/*"));
binding.lnCdrom.setOnClickListener(v -> {
if (MainSettingsManager.getBuiltInFilePicker(requireContext())) {
FilePickerDialog filePickerDialog = new FilePickerDialog();
if (requireActivity() instanceof MainVNCActivity) filePickerDialog.setFixTextColor(true);
filePickerDialog.pick(requireActivity(), filePickerDialog.FIT_PICK_OPTICAL_FILE_MODE, (path -> handleIsoFile(Uri.fromFile(new File(path)))));
} else {
isoPicker.launch("*/*");
}
});
binding.ivEjectcdrom.setOnClickListener(v -> {
QmpSender.ejectOpticalDisc(requireActivity(), infoBlock);
@ -240,7 +251,15 @@ public class VmControllerDialog extends DialogFragment {
}
if (isHavingSecondaryOpticalDisc()) {
binding.lnSecondaryCdrom.setOnClickListener(v -> isoPickerSecondary.launch("*/*"));
binding.lnSecondaryCdrom.setOnClickListener(v -> {
if (MainSettingsManager.getBuiltInFilePicker(requireContext())) {
FilePickerDialog filePickerDialog = new FilePickerDialog();
if (requireActivity() instanceof MainVNCActivity) filePickerDialog.setFixTextColor(true);
filePickerDialog.pick(requireActivity(), filePickerDialog.FIT_PICK_OPTICAL_FILE_MODE, (path -> handleIsoFile2(Uri.fromFile(new File(path)))));
} else {
isoPickerSecondary.launch("*/*");
}
});
binding.ivSecondaryEjectcdrom.setOnClickListener(v -> {
QmpSender.ejectSecondaryOpticalDisc(requireActivity(), infoBlock);
@ -282,7 +301,15 @@ public class VmControllerDialog extends DialogFragment {
}
if (infoBlock.contains(QmpSender.DEFAULT_FLOPPY_DISK_0_ID)) {
binding.lnFda.setOnClickListener(v -> floppyAPicker.launch("*/*"));
binding.lnFda.setOnClickListener(v -> {
if (MainSettingsManager.getBuiltInFilePicker(requireContext())) {
FilePickerDialog filePickerDialog = new FilePickerDialog();
if (requireActivity() instanceof MainVNCActivity) filePickerDialog.setFixTextColor(true);
filePickerDialog.pick(requireActivity(), filePickerDialog.FIT_PICK_FLOPPY_FILE_MODE, (path -> handleFloppyAFile(Uri.fromFile(new File(path)))));
} else {
floppyAPicker.launch("*/*");
}
});
binding.ivEjectfda.setOnClickListener(v -> {
QmpSender.ejectFloppyDiskA(requireActivity());
@ -297,7 +324,15 @@ public class VmControllerDialog extends DialogFragment {
}
if (infoBlock.contains(QmpSender.DEFAULT_FLOPPY_DISK_1_ID)) {
binding.lnFdb.setOnClickListener(v -> floppyBPicker.launch("*/*"));
binding.lnFdb.setOnClickListener(v -> {
if (MainSettingsManager.getBuiltInFilePicker(requireContext())) {
FilePickerDialog filePickerDialog = new FilePickerDialog();
if (requireActivity() instanceof MainVNCActivity) filePickerDialog.setFixTextColor(true);
filePickerDialog.pick(requireActivity(), filePickerDialog.FIT_PICK_FLOPPY_FILE_MODE, (path -> handleFloppyBFile(Uri.fromFile(new File(path)))));
} else {
floppyBPicker.launch("*/*");
}
});
binding.ivEjectfdb.setOnClickListener(v -> {
QmpSender.ejectFloppyDiskB(requireActivity());
@ -312,7 +347,15 @@ public class VmControllerDialog extends DialogFragment {
}
if (infoBlock.contains(QmpSender.DEFAULT_MEMORY_CARD_ID)) {
binding.lnSd.setOnClickListener(v -> memoryCardPicker.launch("*/*"));
binding.lnSd.setOnClickListener(v -> {
if (MainSettingsManager.getBuiltInFilePicker(requireContext())) {
FilePickerDialog filePickerDialog = new FilePickerDialog();
if (requireActivity() instanceof MainVNCActivity) filePickerDialog.setFixTextColor(true);
filePickerDialog.pick(requireActivity(), filePickerDialog.DISK_FILE, (path -> handleMemoryCardFile(Uri.fromFile(new File(path)))));
} else {
memoryCardPicker.launch("*/*");
}
});
binding.ivEjectsd.setOnClickListener(v -> {
QmpSender.ejectMemoryCard(requireActivity());
@ -571,117 +614,134 @@ public class VmControllerDialog extends DialogFragment {
}
private final ActivityResultLauncher<String> isoPicker =
registerForActivityResult(new ActivityResultContracts.GetContent(), uri -> {
if (uri != null) {
try {
new Thread(() -> {
String path = FileUtils.getPath(getContext(), uri);
registerForActivityResult(new ActivityResultContracts.GetContent(), this::handleIsoFile);
if (path == null) {
showErrorSelectedFileDialog();
dismiss();
return;
}
public void handleIsoFile(Uri uri) {
if (uri != null) {
try {
new Thread(() -> {
String path = FileUtils.getPath(getContext(), uri);
File selectedFilePath = new File(path);
if (isAdded()) {
requireActivity().runOnUiThread(() -> {
QmpSender.changeOpticalDisc(requireActivity(), selectedFilePath.getAbsolutePath(), infoBlock);
dismiss();
});
}
}).start();
} catch (Exception e) {
if (path == null) {
showErrorSelectedFileDialog();
dismiss();
return;
}
}
});
File selectedFilePath = new File(path);
if (isAdded()) {
requireActivity().runOnUiThread(() -> {
QmpSender.changeOpticalDisc(requireActivity(), selectedFilePath.getAbsolutePath(), infoBlock);
dismiss();
});
}
}).start();
} catch (Exception e) {
showErrorSelectedFileDialog();
dismiss();
}
}
}
private final ActivityResultLauncher<String> isoPickerSecondary =
registerForActivityResult(new ActivityResultContracts.GetContent(), uri -> {
if (uri != null) {
try {
new Thread(() -> {
File selectedFilePath = new File(Objects.requireNonNull(FileUtils.getPath(getContext(), uri)));
registerForActivityResult(new ActivityResultContracts.GetContent(), this::handleIsoFile2);
if (isAdded()) {
requireActivity().runOnUiThread(() -> {
QmpSender.changeSecondaryOpticalDisc(requireActivity(), selectedFilePath.getAbsolutePath(), infoBlock);
dismiss();
});
}
}).start();
} catch (Exception e) {
showErrorSelectedFileDialog();
dismiss();
public void handleIsoFile2(Uri uri) {
if (uri != null) {
try {
new Thread(() -> {
File selectedFilePath = new File(Objects.requireNonNull(FileUtils.getPath(getContext(), uri)));
if (isAdded()) {
requireActivity().runOnUiThread(() -> {
QmpSender.changeSecondaryOpticalDisc(requireActivity(), selectedFilePath.getAbsolutePath(), infoBlock);
dismiss();
});
}
}
});
}).start();
} catch (Exception e) {
showErrorSelectedFileDialog();
dismiss();
}
}
}
private final ActivityResultLauncher<String> floppyAPicker =
registerForActivityResult(new ActivityResultContracts.GetContent(), uri -> {
if (uri != null) {
try {
new Thread(() -> {
File selectedFilePath = new File(Objects.requireNonNull(FileUtils.getPath(getContext(), uri)));
registerForActivityResult(new ActivityResultContracts.GetContent(), this::handleFloppyAFile);
if (isAdded()) {
requireActivity().runOnUiThread(() -> {
QmpSender.changeFloppyDiskA(requireActivity(), selectedFilePath.getAbsolutePath());
dismiss();
});
}
}).start();
} catch (Exception e) {
showErrorSelectedFileDialog();
dismiss();
public void handleFloppyAFile(Uri uri) {
if (uri != null) {
try {
new Thread(() -> {
File selectedFilePath = new File(Objects.requireNonNull(FileUtils.getPath(getContext(), uri)));
if (isAdded()) {
requireActivity().runOnUiThread(() -> {
QmpSender.changeFloppyDiskA(requireActivity(), selectedFilePath.getAbsolutePath());
dismiss();
});
}
}
});
}).start();
} catch (Exception e) {
showErrorSelectedFileDialog();
dismiss();
}
}
}
private final ActivityResultLauncher<String> floppyBPicker =
registerForActivityResult(new ActivityResultContracts.GetContent(), uri -> {
if (uri != null) {
try {
new Thread(() -> {
File selectedFilePath = new File(Objects.requireNonNull(FileUtils.getPath(getContext(), uri)));
registerForActivityResult(new ActivityResultContracts.GetContent(), this::handleFloppyBFile);
if (isAdded()) {
requireActivity().runOnUiThread(() -> {
QmpSender.changeFloppyDiskB(requireActivity(), selectedFilePath.getAbsolutePath());
dismiss();
});
}
}).start();
} catch (Exception e) {
showErrorSelectedFileDialog();
dismiss();
public void handleFloppyBFile(Uri uri) {
if (uri != null) {
try {
new Thread(() -> {
File selectedFilePath = new File(Objects.requireNonNull(FileUtils.getPath(getContext(), uri)));
if (isAdded()) {
requireActivity().runOnUiThread(() -> {
QmpSender.changeFloppyDiskB(requireActivity(), selectedFilePath.getAbsolutePath());
dismiss();
});
}
}
});
}).start();
} catch (Exception e) {
showErrorSelectedFileDialog();
dismiss();
}
}
}
private final ActivityResultLauncher<String> memoryCardPicker =
registerForActivityResult(new ActivityResultContracts.GetContent(), uri -> {
if (uri != null) {
try {
new Thread(() -> {
File selectedFilePath = new File(Objects.requireNonNull(FileUtils.getPath(getContext(), uri)));
registerForActivityResult(new ActivityResultContracts.GetContent(), this::handleMemoryCardFile);
if (isAdded()) {
requireActivity().runOnUiThread(() -> {
QmpSender.changeMemoryCard(requireActivity(), selectedFilePath.getAbsolutePath());
dismiss();
});
}
}).start();
} catch (Exception e) {
public void handleMemoryCardFile(Uri uri) {
if (uri != null) {
try {
new Thread(() -> {
String filePath = FileUtils.getPath(getContext(), uri);
if (filePath == null) {
showErrorSelectedFileDialog();
dismiss();
return;
}
}
});
File selectedFilePath = new File(filePath);
if (isAdded()) {
requireActivity().runOnUiThread(() -> {
QmpSender.changeMemoryCard(requireActivity(), selectedFilePath.getAbsolutePath());
dismiss();
});
}
}).start();
} catch (Exception e) {
showErrorSelectedFileDialog();
dismiss();
}
}
}
private void showErrorSelectedFileDialog() {
DialogUtils.oneDialog(requireActivity(),

View file

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

View file

@ -0,0 +1,65 @@
package com.vectras.vm.settings;
import android.os.Bundle;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import com.vectras.qemu.MainSettingsManager;
import com.vectras.vm.R;
import com.vectras.vm.databinding.ActivityFilePickerSettingsBinding;
import com.vectras.vm.file.FilePickerSettings;
import java.util.Objects;
public class FilePickerSettingsActivity extends AppCompatActivity {
ActivityFilePickerSettingsBinding binding;
FilePickerSettings settings;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_external_vnc_settings);
binding = ActivityFilePickerSettingsBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true);
binding.toolbar.setNavigationOnClickListener(view -> finish());
initialize();
}
private void initialize() {
settings = new FilePickerSettings(this);
binding.swEnabled.setOnCheckedChangeListener((buttonView, isChecked) -> {
MainSettingsManager.setBuiltInFilePicker(this, isChecked);
uiController(isChecked);
});
binding.lnEnabled.setOnClickListener(v -> binding.swEnabled.toggle());
binding.swResume.setOnCheckedChangeListener((buttonView, isChecked) -> settings.resume(isChecked));
binding.lnResume.setOnClickListener(v -> binding.swResume.toggle());
binding.swDivider.setOnCheckedChangeListener((buttonView, isChecked) -> settings.divider(isChecked));
binding.lnDivider.setOnClickListener(v -> binding.swDivider.toggle());
binding.swShowHiddenFiles.setOnCheckedChangeListener((buttonView, isChecked) -> settings.showHiddenFiles(isChecked));
binding.lnShowHiddenFiles.setOnClickListener(v -> binding.swShowHiddenFiles.toggle());
binding.swResume.setChecked(settings.resume());
binding.swDivider.setChecked(settings.divider());
binding.swShowHiddenFiles.setChecked(settings.showHiddenFiles());
binding.swEnabled.setChecked(MainSettingsManager.getBuiltInFilePicker(this));
uiController(binding.swEnabled.isChecked());
}
private void uiController(boolean isEnabled) {
binding.lnAllOptions.setAlpha(isEnabled ? 1f : 0.5f);
binding.lnResume.setEnabled(isEnabled);
binding.lnDivider.setEnabled(isEnabled);
binding.lnShowHiddenFiles.setEnabled(isEnabled);
}
}

View file

@ -287,7 +287,7 @@ public class Minitools extends AppCompatActivity {
FileUtils.delete(new File(getFilesDir().getAbsolutePath() + "/usr"));
runOnUiThread(() -> {
progressDialog.dismiss();
DialogUtils.safeDismiss(this, progressDialog);
Intent intent = new Intent();
intent.setClass(this, SetupWizard2Activity.class);
startActivity(intent);

View file

@ -31,6 +31,7 @@ public class Settings2Activity extends AppCompatActivity {
binding.lnTheme.setOnClickListener(v -> startActivity(new Intent(this, ThemeActivity.class)));
binding.lnNotification.setOnClickListener(v -> startActivity(new Intent(this, NotificationSettingsActivity.class)));
binding.lnSearch.setOnClickListener(v -> startActivity(new Intent(this, SearchSettingsActivity.class)));
binding.lnFilePicker.setOnClickListener(v -> startActivity(new Intent(this, FilePickerSettingsActivity.class)));
binding.lnSystem.setOnClickListener(v -> startActivity(new Intent(this, SystemSettingsActivity.class)));
binding.lnQemu.setOnClickListener(v -> {
@ -42,5 +43,7 @@ public class Settings2Activity extends AppCompatActivity {
binding.lnVnc.setOnClickListener(v -> startActivity(new Intent(this, VNCSettingsActivity.class)));
binding.lnX11.setOnClickListener(v -> startActivity(new Intent(this, X11DisplaySettingsActivity.class)));
binding.lnEqualizer.setOnClickListener(v -> startActivity(new Intent(this, EqualizerActivity.class)));
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -22,11 +22,13 @@ import com.anbui.elephant.interaction.Interaction;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition;
import com.vectras.qemu.MainSettingsManager;
import com.vectras.vm.ImagePrvActivity;
import com.vectras.vm.R;
import com.vectras.vm.VMManager;
import com.vectras.vm.creator.VMCreatorActivity;
import com.vectras.vm.databinding.ActivityRomInfoBinding;
import com.vectras.vm.file.FilePickerDialog;
import com.vectras.vm.utils.DialogUtils;
import com.vectras.vm.utils.FileUtils;
import com.vectras.vm.utils.ImageUtils;
@ -72,89 +74,90 @@ public class RomInfo extends AppCompatActivity {
}
private final ActivityResultLauncher<String> romPicker =
registerForActivityResult(new ActivityResultContracts.GetContent(), uri -> {
registerForActivityResult(new ActivityResultContracts.GetContent(), this::handlePickedRom);
if (uri == null) return;
public void handlePickedRom(Uri uri) {
if (uri == null) return;
executor.execute(() -> {
String filePath;
try {
File selectedFilePath = new File(getPath(uri));
filePath = selectedFilePath.getPath();
} catch (Exception e) {
filePath = "";
executor.execute(() -> {
String filePath;
try {
File selectedFilePath = new File(getPath(uri));
filePath = selectedFilePath.getPath();
} catch (Exception e) {
filePath = "";
}
String selectedFileName = FileUtils.getFileNameFromUri(this, uri);
if (isFinishing() || isDestroyed()) return;
String finalFilePath = filePath;
runOnUiThread(() -> {
if (selectedFileName.equals(getIntent().getStringExtra("filename")) ||
(selectedFileName.endsWith(".cvbi.zip") && selectedFileName.equals(getIntent().getStringExtra("filename") + ".zip"))) {
Intent intent = new Intent();
if (getIntent().hasExtra("icon") &&
!Objects.requireNonNull(getIntent().getStringExtra("icon")).isEmpty() &&
(!selectedFileName.endsWith(".cvbi")
|| !selectedFileName.endsWith(".cvbi.zip"))) {
Glide.with(this)
.asBitmap()
.load(getIntent().getStringExtra("icon"))
.into(new CustomTarget<Bitmap>() {
@Override
public void onResourceReady(@NonNull Bitmap resource,
@Nullable Transition<? super Bitmap> transition) {
File cacheDir = getExternalCacheDir();
if (cacheDir != null)
ImageUtils.saveBitmapToPNGFile(resource, cacheDir.getAbsolutePath(), "thumbnail.png");
intent.putExtra("romicon", cacheDir != null ? cacheDir.getAbsolutePath() + "/thumbnail.png" : "");
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
intent.putExtra("romicon", "");
}
});
} else {
intent.putExtra("romicon", "");
}
String selectedFileName = FileUtils.getFileNameFromUri(this, uri);
intent.setClass(getApplicationContext(), VMCreatorActivity.class);
intent.putExtra("addromnow", "");
intent.putExtra("romname", getIntent().getStringExtra("title"));
intent.putExtra("romfilename", getIntent().getStringExtra("filename"));
intent.putExtra("finalromfilename", getIntent().getStringExtra("finalromfilename"));
intent.putExtra("rompath", finalFilePath);
intent.putExtra("romuri", uri.toString());
if (Objects.requireNonNull(getIntent().getStringExtra("extra")).contains(selectedFileName)) {
intent.putExtra("addtodrive", "");
intent.putExtra("romextra", getIntent().getStringExtra("extra"));
} else {
intent.putExtra("addtodrive", "1");
intent.putExtra("romextra", getIntent().getStringExtra("extra"));
}
if (isFinishing() || isDestroyed()) return;
VMManager.setArch(getIntent().hasExtra("arch") && getIntent().getStringExtra("arch") != null ? Objects.requireNonNull(getIntent().getStringExtra("arch")) : "", this);
String finalFilePath = filePath;
runOnUiThread(() -> {
if (selectedFileName.equals(getIntent().getStringExtra("filename")) ||
(selectedFileName.endsWith(".cvbi.zip") && selectedFileName.equals(getIntent().getStringExtra("filename") + ".zip"))) {
Intent intent = new Intent();
if (getIntent().hasExtra("icon") &&
!Objects.requireNonNull(getIntent().getStringExtra("icon")).isEmpty() &&
(!selectedFileName.endsWith(".cvbi")
|| !selectedFileName.endsWith(".cvbi.zip"))) {
Glide.with(this)
.asBitmap()
.load(getIntent().getStringExtra("icon"))
.into(new CustomTarget<Bitmap>() {
@Override
public void onResourceReady(@NonNull Bitmap resource,
@Nullable Transition<? super Bitmap> transition) {
File cacheDir = getExternalCacheDir();
if (cacheDir != null)
ImageUtils.saveBitmapToPNGFile(resource, cacheDir.getAbsolutePath(), "thumbnail.png");
intent.putExtra("romicon", cacheDir != null ? cacheDir.getAbsolutePath() + "/thumbnail.png" : "");
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
intent.putExtra("romicon", "");
}
});
} else {
intent.putExtra("romicon", "");
}
intent.setClass(getApplicationContext(), VMCreatorActivity.class);
intent.putExtra("addromnow", "");
intent.putExtra("romname", getIntent().getStringExtra("title"));
intent.putExtra("romfilename", getIntent().getStringExtra("filename"));
intent.putExtra("finalromfilename", getIntent().getStringExtra("finalromfilename"));
intent.putExtra("rompath", finalFilePath);
intent.putExtra("romuri", uri.toString());
if (Objects.requireNonNull(getIntent().getStringExtra("extra")).contains(selectedFileName)) {
intent.putExtra("addtodrive", "");
intent.putExtra("romextra", getIntent().getStringExtra("extra"));
} else {
intent.putExtra("addtodrive", "1");
intent.putExtra("romextra", getIntent().getStringExtra("extra"));
}
VMManager.setArch(getIntent().hasExtra("arch") && getIntent().getStringExtra("arch") != null ? Objects.requireNonNull(getIntent().getStringExtra("arch")) : "", this);
startActivity(intent);
} else {
DialogUtils.oneDialog(RomInfo.this,
getString(R.string.problem_has_been_detected),
getString(R.string.please_select) + " " + getIntent().getStringExtra("filename"),
getString(R.string.ok),
true, R.drawable.warning_48px,
true,
null,
null);
}
});
});
startActivity(intent);
} else {
DialogUtils.oneDialog(RomInfo.this,
getString(R.string.problem_has_been_detected),
getString(R.string.please_select) + " " + getIntent().getStringExtra("filename"),
getString(R.string.ok),
true, R.drawable.warning_48px,
true,
null,
null);
}
});
});
}
public String getPath(Uri uri) {
return FileUtils.getPath(RomInfo.this, uri);
@ -168,7 +171,12 @@ public class RomInfo extends AppCompatActivity {
if (getIntent().hasExtra("isRomInfo") && getIntent().getBooleanExtra("isRomInfo", false)) {
binding.btnPick.setOnClickListener(v -> {
try {
romPicker.launch("*/*");
if (MainSettingsManager.getBuiltInFilePicker(this)) {
FilePickerDialog filePickerDialog = new FilePickerDialog();
filePickerDialog.pick(this, filePickerDialog.ROM_FILE, (path -> handlePickedRom(Uri.fromFile(new File(path)))));
} else {
romPicker.launch("*/*");
}
} catch (Exception e) {
IntentUtils.showErrorDialog(this);
}

View file

@ -11,10 +11,14 @@ import com.vectras.vm.R;
public class ClipboardUltils {
public static void copyToClipboard(Context context, String text) {
copyToClipboard(context, text, true);
}
public static void copyToClipboard(Context context, String text, boolean showToast) {
ClipboardManager clipboard = (ClipboardManager) context.getSystemService(CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("From Vectras VM", text);
assert clipboard != null;
clipboard.setPrimaryClip(clip);
Toast.makeText(context, context.getString(R.string.copied), Toast.LENGTH_SHORT).show();
if (showToast) Toast.makeText(context, context.getString(R.string.copied), Toast.LENGTH_SHORT).show();
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M40,720Q50,615 104,529Q158,443 243,389L166,260Q157,246 161.5,230Q166,214 180,206Q194,197 210,201Q226,205 234,219L314,352Q352,336 393.5,328Q435,320 480,320Q525,320 566.5,328Q608,336 646,352L726,219Q734,205 750,201Q766,197 780,206Q794,214 798,230Q802,246 794,260L717,389Q802,443 856,529Q910,615 920,720L40,720ZM720.5,568.5Q722,551 711,534Q700,517 684,512Q668,507 655,516Q642,525 640,542Q638,559 649,576Q660,593 676.5,598.5Q693,604 706,595Q719,586 720.5,568.5ZM284,598Q300,593 311,576Q322,559 320.5,541.5Q319,524 306,515Q293,506 276.5,511.5Q260,517 249,534Q238,551 240,568Q242,585 255,594Q268,603 284,598Z"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M440,720L440,352L296,496L240,440L480,200L720,440L664,496L520,352L520,720L440,720Z"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M320,720L80,480L320,240L377,297L193,481L376,664L320,720ZM640,720L583,663L767,479L584,296L640,240L880,480L640,720Z"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M480,840Q329,840 224.5,793.5Q120,747 120,680L120,280Q120,214 225.5,167Q331,120 480,120Q629,120 734.5,167Q840,214 840,280L840,680Q840,747 735.5,793.5Q631,840 480,840ZM480,361Q569,361 659,335.5Q749,310 760,281Q749,252 659.5,226Q570,200 480,200Q389,200 301.5,225.5Q214,251 200,281Q214,311 301.5,336Q389,361 480,361ZM480,560Q522,560 561,556Q600,552 635.5,544.5Q671,537 702.5,526Q734,515 760,501L760,381Q734,395 702.5,406Q671,417 635.5,424.5Q600,432 561,436Q522,440 480,440Q438,440 398,436Q358,432 322.5,424.5Q287,417 256,406Q225,395 200,381L200,501Q225,515 256,526Q287,537 322.5,544.5Q358,552 398,556Q438,560 480,560ZM480,760Q526,760 573.5,753Q621,746 661,734.5Q701,723 728,708.5Q755,694 760,679L760,581Q734,595 702.5,606Q671,617 635.5,624.5Q600,632 561,636Q522,640 480,640Q438,640 398,636Q358,632 322.5,624.5Q287,617 256,606Q225,595 200,581L200,680Q205,695 231.5,709Q258,723 298,734.5Q338,746 386,753Q434,760 480,760Z"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M320,720L640,720L640,640L320,640L320,720ZM320,560L640,560L640,480L320,480L320,560ZM240,880Q207,880 183.5,856.5Q160,833 160,800L160,160Q160,127 183.5,103.5Q207,80 240,80L560,80L800,320L800,800Q800,833 776.5,856.5Q753,880 720,880L240,880ZM520,360L520,160L240,160Q240,160 240,160Q240,160 240,160L240,800Q240,800 240,800Q240,800 240,800L720,800Q720,800 720,800Q720,800 720,800L720,360L520,360ZM240,160L240,160L240,360L240,360L240,160L240,360L240,360L240,800Q240,800 240,800Q240,800 240,800L240,800Q240,800 240,800Q240,800 240,800L240,160Q240,160 240,160Q240,160 240,160Z"/>
</vector>

View file

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal"
android:autoMirrored="true">
<path
android:fillColor="@android:color/white"
android:pathData="M240,880Q207,880 183.5,856.5Q160,833 160,800L160,160Q160,127 183.5,103.5Q207,80 240,80L560,80L800,320L800,800Q800,833 776.5,856.5Q753,880 720,880L240,880ZM520,360L520,160L240,160Q240,160 240,160Q240,160 240,160L240,800Q240,800 240,800Q240,800 240,800L720,800Q720,800 720,800Q720,800 720,800L720,360L520,360ZM240,160L240,160L240,360L240,360L240,160L240,360L240,360L240,800Q240,800 240,800Q240,800 240,800L240,800Q240,800 240,800Q240,800 240,800L240,160Q240,160 240,160Q240,160 240,160Z"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M640,480L640,400L720,400L720,480L640,480ZM640,560L560,560L560,480L640,480L640,560ZM640,640L640,560L720,560L720,640L640,640ZM447,320L367,240L160,240Q160,240 160,240Q160,240 160,240L160,720Q160,720 160,720Q160,720 160,720L560,720L560,640L640,640L640,720L800,720Q800,720 800,720Q800,720 800,720L800,320Q800,320 800,320Q800,320 800,320L640,320L640,400L560,400L560,320L447,320ZM160,800Q127,800 103.5,776.5Q80,753 80,720L80,240Q80,207 103.5,183.5Q127,160 160,160L400,160L480,240L800,240Q833,240 856.5,263.5Q880,287 880,320L880,720Q880,753 856.5,776.5Q833,800 800,800L160,800ZM160,720L160,720Q160,720 160,720Q160,720 160,720L160,320Q160,320 160,320Q160,320 160,320L160,320L160,240L160,240Q160,240 160,240Q160,240 160,240L160,720Q160,720 160,720Q160,720 160,720Z"/>
</vector>

View file

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

View file

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

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L760,120Q793,120 816.5,143.5Q840,167 840,200L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM200,760L760,760Q760,760 760,760Q760,760 760,760L760,200Q760,200 760,200Q760,200 760,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760ZM240,680L720,680L570,480L450,640L360,520L240,680ZM200,760Q200,760 200,760Q200,760 200,760L200,200Q200,200 200,200Q200,200 200,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760Z"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M160,360L160,280L240,280L240,360L160,360ZM160,680L160,440L240,440L240,680L160,680ZM440,680L360,680Q327,680 303.5,656.5Q280,633 280,600L280,360Q280,327 303.5,303.5Q327,280 360,280L440,280Q473,280 496.5,303.5Q520,327 520,360L520,600Q520,633 496.5,656.5Q473,680 440,680ZM360,600L440,600Q440,600 440,600Q440,600 440,600L440,360Q440,360 440,360Q440,360 440,360L360,360Q360,360 360,360Q360,360 360,360L360,600Q360,600 360,600Q360,600 360,600ZM560,680L560,600L720,600Q720,600 720,600Q720,600 720,600L720,520Q720,520 720,520Q720,520 720,520L640,520Q607,520 583.5,496.5Q560,473 560,440L560,360Q560,327 583.5,303.5Q607,280 640,280L800,280L800,360L640,360Q640,360 640,360Q640,360 640,360L640,440Q640,440 640,440Q640,440 640,440L720,440Q753,440 776.5,463.5Q800,487 800,520L800,600Q800,633 776.5,656.5Q753,680 720,680L560,680Z"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M223.5,536.5Q200,513 200,480Q200,447 223.5,423.5Q247,400 280,400Q313,400 336.5,423.5Q360,447 360,480Q360,513 336.5,536.5Q313,560 280,560Q247,560 223.5,536.5ZM280,720Q180,720 110,650Q40,580 40,480Q40,380 110,310Q180,240 280,240Q347,240 401.5,273Q456,306 488,360L840,360L960,480L780,660L700,600L620,660L535,600L488,600Q456,654 401.5,687Q347,720 280,720ZM280,640Q336,640 378.5,606Q421,572 435,520L560,520L618,561L700,500L771,555L846,480L806,440L435,440Q421,388 378.5,354Q336,320 280,320Q214,320 167,367Q120,414 120,480Q120,546 167,593Q214,640 280,640Z"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M160,160L240,320L360,320L280,160L360,160L440,320L560,320L480,160L560,160L640,320L760,320L680,160L800,160Q833,160 856.5,183.5Q880,207 880,240L880,720Q880,753 856.5,776.5Q833,800 800,800L160,800Q127,800 103.5,776.5Q80,753 80,720L80,240Q80,207 103.5,183.5Q127,160 160,160ZM160,400L160,720Q160,720 160,720Q160,720 160,720L800,720Q800,720 800,720Q800,720 800,720L800,400L160,400ZM160,400L160,400L160,720Q160,720 160,720Q160,720 160,720L160,720Q160,720 160,720Q160,720 160,720L160,400Z"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M287,793Q240,746 240,680Q240,614 287,567Q334,520 400,520Q423,520 442.5,525.5Q462,531 480,542L480,120L720,120L720,280L560,280L560,680Q560,746 513,793Q466,840 400,840Q334,840 287,793Z"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M344,710L344,344L224,344L224,280L532,280L532,344L412,344L412,710L344,710ZM688,720Q644,720 619,694.5Q594,669 594,624L594,462L540,462L540,404L594,404L594,317L660,317L660,404L734,404L734,462L660,462L660,610Q660,633 670.5,646Q681,659 699,659Q708,659 717.5,655.5Q727,652 736,646L736,711Q726,716 714,718Q702,720 688,720Z"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M440,840L440,600L520,600L520,680L840,680L840,760L520,760L520,840L440,840ZM120,760L120,680L360,680L360,760L120,760ZM280,600L280,520L120,520L120,440L280,440L280,360L360,360L360,600L280,600ZM440,520L440,440L840,440L840,520L440,520ZM600,360L600,120L680,120L680,200L840,200L840,280L680,280L680,360L600,360ZM120,280L120,200L520,200L520,280L120,280Z"/>
</vector>

View file

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

View file

@ -0,0 +1,67 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="3000"
android:viewportHeight="3000"
android:tint="?attr/colorControlNormal">
<path
android:pathData="M1143.5 935L1154 935.5L1136.5 937Q1130 940 1120.5 940Q1097.2 945.2 1077.5 954Q1029.9 975.4 997 1011.5Q970.8 1039.8 954 1077.5Q945.2 1097.2 940 1120.5L937 1136.5L935.5 1155L935 1143.5L936 1142.5L936 1135.5L939 1119.5L944 1100.5Q952 1075.5 964 1054.5Q982.6 1021.6 1009.5 997Q1038.1 970.1 1076.5 953L1107.5 942L1143.5 935Z"
android:fillColor="#FFFFFF"
android:fillAlpha="0.9960784"
android:strokeColor="#FFFFFF"
android:strokeAlpha="0.9960784"
android:strokeWidth="1" />
<path
android:pathData="M1459.5 935L1460 1460L935 1459.5L1459 1459L1459.5 935Z"
android:fillColor="#FFFFFF"
android:fillAlpha="0.9960784"
android:strokeColor="#FFFFFF"
android:strokeAlpha="0.9960784"
android:strokeWidth="1" />
<path
android:pathData="M1540.5 935L1541 1459L2064 1459L2064 1154.5L2063 1153.5L2063 1144.5L2062 1143.5L2062 1136.5L2059 1120.5Q2053.8 1097.2 2045 1077.5Q2024 1030.5 1988.5 998Q1959.9 971.1 1921.5 954L1900.5 946L1878.5 940Q1869 940 1862.5 937L1845 935.5L1855.5 935L1856.5 936L1863.5 936Q1870 939 1879.5 939L1901.5 945Q1924.8 952.7 1944.5 964Q1977.4 982.6 2002 1009.5Q2028.9 1038.1 2046 1076.5Q2054.8 1096.2 2060 1119.5L2063 1135.5L2063 1142.5L2064 1143.5L2064 1153L2066 1153.5L2066 1459L2063.5 1460L1540 1460L1540.5 935Z"
android:fillColor="#FFFFFF"
android:fillAlpha="0.9960784"
android:strokeColor="#FFFFFF"
android:strokeAlpha="0.9960784"
android:strokeWidth="1" />
<path
android:pathData="M935.5 1540L1460 1540L1460 2063.5L1459 2066L1153.5 2066L1152.5 2064L1143.5 2064L1142.5 2063L1135.5 2063L1119.5 2060L1100.5 2055Q1075.5 2047 1054.5 2035Q1021.6 2016.4 997 1989.5Q970.1 1960.9 953 1922.5Q944.2 1902.8 939 1879.5L936 1863.5L936 1856.5L935 1855.5L935 1845.5L936 1845.5L936 1854.5L937 1855.5L937 1862.5L940 1878.5Q945.2 1901.8 954 1921.5Q975 1968.5 1010.5 2001Q1039.1 2027.9 1077.5 2045L1101.5 2054L1136.5 2062L1153.5 2063L1154.5 2064L1459 2064L1459 1541L935.5 1541L935.5 1540Z"
android:fillColor="#FFFFFF"
android:fillAlpha="0.9960784"
android:strokeColor="#FFFFFF"
android:strokeAlpha="0.9960784"
android:strokeWidth="1" />
<path
android:pathData="M1540 1540L2063.5 1540L2066 1541L2066 1845.5L2064 1846.5L2064 1855.5L2063 1856.5L2063 1863.5L2060 1879.5L2055 1898.5Q2047 1923.5 2035 1944.5Q2016.4 1977.4 1989.5 2002Q1960.9 2028.9 1922.5 2046L1901.5 2054L1879.5 2060L1863.5 2063L1856.5 2063L1855.5 2064L1846 2064L1845.5 2066L1541 2066L1540 2063.5L1540 1540ZM1541 1541L1541 2064L1845 2064L1846 2063L1863 2062L1891 2056L1922 2045Q1960 2028 1989 2001Q2024 1968 2045 1922Q2054 1902 2059 1879L2062 1863L2062 1856L2063 1855L2063 1846L2064 1845L2064 1541L1541 1541Z"
android:fillColor="#FFFFFF"
android:fillAlpha="0.9960784"
android:strokeColor="#FFFFFF"
android:strokeAlpha="0.9960784"
android:strokeWidth="1" />
<path
android:pathData="M540.5 348L2459.5 348L2460.5 349L2469.5 349L2470.5 350L2482.5 351L2508.5 357Q2530.5 363.5 2549.5 373Q2592.9 395.1 2624 429.5Q2651.6 459.4 2669 499.5Q2677.8 519.7 2683 543.5L2687 566.5L2687 575.5L2688 576.5L2688 2423.5L2687 2424.5L2685 2446.5L2679 2472.5L2664 2511.5Q2641.7 2556.2 2606.5 2588Q2576.6 2615.6 2536.5 2633Q2516.3 2641.8 2492.5 2647L2469.5 2651L2460.5 2651L2459.5 2652L540.5 2652L539.5 2651L530.5 2651L529.5 2650L517.5 2649L491.5 2643Q468.3 2636.2 448.5 2626Q406.4 2604.1 376 2570.5Q348.4 2540.6 331 2500.5Q322.2 2480.3 317 2456.5L313 2433.5L313 2424.5L312 2423.5L312 576.5L313 575.5L313 566.5L314 565.5L315 553.5L321 527.5Q327.5 505.5 337 486.5Q359.1 443.1 393.5 412L406.5 401L410 400Q408.9 397.3 411.5 398Q438.2 376.2 473.5 363L507.5 353L530.5 349L539.5 349L540.5 348ZM950 841L927 845L907 852Q884 862 867 877L853 892Q854 894 852 893L838 913L831 927L824 950L824 952L822 956L821 971L820 972L820 2029L821 2030L821 2038L823 2048L823 2050L825 2053L825 2057L831 2074L838 2088L850 2106L853 2107L857 2114Q888 2149 946 2159L2055 2159L2079 2154L2082 2152L2091 2150L2114 2138L2131 2125Q2154 2105 2168 2075L2177 2047L2178 2036L2179 2035L2179 2026L2180 2025L2180 976L2179 975L2179 965L2177 954L2168 926Q2159 905 2145 890Q2130 872 2110 860L2084 848L2065 843L2052 842L2051 841L950 841Z"
android:fillColor="#FFFFFF"
android:strokeColor="#FFFFFF"
android:strokeWidth="1" />
<path
android:pathData="M1154.5 935L1459 935L1459 1459L935 1459L935 1155.5L936 1154.5L936 1144.5L937 1143.5L937 1136.5L940 1120.5Q945.2 1097.2 954 1077.5Q975 1030.5 1010.5 998Q1039.1 971.1 1077.5 954Q1097.2 945.2 1120.5 940Q1130 940 1136.5 937L1143.5 937L1144.5 936L1153.5 936L1154.5 935Z"
android:fillColor="#FFFFFF"
android:strokeColor="#FFFFFF"
android:strokeWidth="1" />
<path
android:pathData="M1541 935L1844.5 935L1845.5 936L1862.5 937Q1869 940 1878.5 940L1906.5 948L1943.5 965Q1976.4 983.6 2001 1010.5Q2027.9 1039.1 2045 1077.5L2054 1101.5L2062 1136.5L2063 1153.5L2064 1154.5L2064 1459L1541 1459L1541 935Z"
android:fillColor="#FFFFFF"
android:strokeColor="#FFFFFF"
android:strokeWidth="1" />
<path
android:pathData="M935 1541L1459 1541L1459 2064L1154.5 2064L1153.5 2063L1136.5 2062L1120.5 2059L1092.5 2051L1055.5 2034Q1022.6 2015.4 998 1988.5Q971.1 1959.9 954 1921.5L945 1897.5L937 1862.5L936 1845.5L935 1844.5L935 1541Z"
android:fillColor="#FFFFFF"
android:strokeColor="#FFFFFF"
android:strokeWidth="1" />
<path
android:pathData="M1541 1541L2064 1541L2064 1844.5L2063 1845.5L2063 1854.5L2062 1855.5L2062 1862.5L2059 1878.5Q2053.8 1901.8 2045 1921.5Q2024 1968.5 1988.5 2001Q1959.9 2027.9 1921.5 2045L1890.5 2056L1862.5 2062L1845.5 2063L1844.5 2064L1541 2064L1541 1541Z"
android:fillColor="#FFFFFF"
android:strokeColor="#FFFFFF"
android:strokeWidth="1" />
</vector>

View file

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal"
android:autoMirrored="true">
<path
android:fillColor="@android:color/white"
android:pathData="M200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L760,120Q793,120 816.5,143.5Q840,167 840,200L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM200,760L760,760Q760,760 760,760Q760,760 760,760L760,280L200,280L200,760Q200,760 200,760Q200,760 200,760ZM280,480L280,400L680,400L680,480L280,480ZM280,640L280,560L520,560L520,640L280,640Z"/>
</vector>

View file

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

View file

@ -0,0 +1,217 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:fitsSystemWindows="true"
android:orientation="vertical"
tools:context=".settings.FilePickerSettingsActivity">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$Behavior">
<com.google.android.material.appbar.CollapsingToolbarLayout
style="@style/App.CollapsingToolbarLarge"
app:title="@string/file_picker">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
style="@style/App.CollapsingMaterialToolbar" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:id="@+id/ln_all_options"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="108dp"
android:paddingHorizontal="16dp"
android:paddingBottom="16dp"
android:orientation="vertical"
android:animateLayoutChanges="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/ln_resume"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/object_shape_top_high"
android:padding="16dp"
android:layout_marginBottom="2dp" >
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/sw_resume"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/resume"
android:textSize="18sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/file_picker_resume_note"
android:textColor="?android:attr/textColorSecondary"
android:textSize="14sp" />
</LinearLayout>
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/sw_resume"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:clickable="false"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/ln_divider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/object_shape_middle_high"
android:padding="16dp"
android:layout_marginBottom="2dp" >
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/sw_divider"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/divider"
android:textSize="18sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/file_picker_divier_note"
android:textColor="?android:attr/textColorSecondary"
android:textSize="14sp" />
</LinearLayout>
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/sw_divider"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:clickable="false"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/ln_show_hidden_files"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/object_shape_bottom_high"
android:padding="16dp"
android:layout_marginBottom="2dp" >
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/sw_show_hidden_files"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/show_hidden_files"
android:textSize="18sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/show_hidden_files_note"
android:textColor="?android:attr/textColorSecondary"
android:textSize="14sp" />
</LinearLayout>
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/sw_show_hidden_files"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:clickable="false"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<View
android:layout_width="match_parent"
android:layout_height="55dp"
android:background="?attr/colorSurfaceContainerLow"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<com.google.android.material.card.MaterialCardView
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:backgroundTint="?attr/colorSecondaryContainer"
app:cardCornerRadius="50dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:id="@+id/ln_enabled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/selectableItemBackground"
android:gravity="center_vertical"
android:paddingVertical="16dp"
android:paddingLeft="32dp"
android:paddingRight="20dp" >
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/use_file_picker"
android:textSize="16sp">
</TextView>
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/sw_enabled"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?attr/colorAccent"
android:clickable="false"/>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

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

View file

@ -150,6 +150,43 @@
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/ln_file_picker"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="2dp"
android:background="@drawable/object_shape_middle_high"
android:padding="16dp"
android:gravity="center_vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp"
android:background="@drawable/settings_system_background_icon"
android:src="@drawable/folder_24px" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/file_picker"
android:textSize="18sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/file_picker_settings_note"
android:textColor="?android:attr/textColorSecondary"
android:textSize="14sp" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/ln_system"
android:layout_width="match_parent"
@ -302,7 +339,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 +371,43 @@
android:textSize="14sp" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/ln_equalizer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="2dp"
android:background="@drawable/object_shape_single_high"
android:padding="16dp"
android:gravity="center_vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp"
android:background="@drawable/settings_system_background_icon"
android:src="@drawable/graphic_eq_24px" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/equalizer"
android:textSize="18sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/equalizer_settings_note"
android:textColor="?android:attr/textColorSecondary"
android:textSize="14sp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View file

@ -0,0 +1,202 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:background="?attr/colorSurfaceContainer"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingTop="16dp"
android:paddingHorizontal="16dp">
<TextView
android:id="@+id/tv_title"
style="@style/MaterialAlertDialog.Material3Expressive.Title.Text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingEnd="16dp"
android:singleLine="true"
android:text="@string/internal_storage" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_home"
style="@style/Widget.Material3Expressive.Button.IconButton.Tonal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:icon="@drawable/home_24px"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_up"
style="@style/Widget.Material3Expressive.Button.IconButton.Tonal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="invisible"
app:icon="@drawable/arrow_upward_24px"
tools:visibility="visible" />
</LinearLayout>
<TextView
android:id="@+id/tv_full_path"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingVertical="8dp"
android:singleLine="true"
android:ellipsize="start"
android:text="@string/internal_storage"
android:background="?attr/selectableItemBackground"/>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1">
<LinearLayout
android:id="@+id/ln_loading"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="?attr/colorSurfaceContainer"
android:visibility="gone">
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<com.google.android.material.progressindicator.CircularProgressIndicator
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="16dp"
android:text="@string/nothing_here"
android:textSize="18sp" />
</LinearLayout>
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:id="@+id/ln_empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="gone">
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/folder_96px" />
<TextView
android:id="@+id/tv_nothing_here"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="16dp"
android:text="@string/nothing_here"
android:textSize="18sp" />
</LinearLayout>
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:id="@+id/ln_browse"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.divider.MaterialDivider
android:id="@+id/dv_top"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="invisible"
tools:visibility="visible" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:fadeScrollbars="true"
android:scrollbars="vertical" />
<com.google.android.material.divider.MaterialDivider
android:id="@+id/dv_bottom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="invisible"
tools:visibility="visible" />
</LinearLayout>
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:padding="16dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_open_in_other_app"
style="@style/Widget.Material3Expressive.Button.IconButton.Tonal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:icon="@drawable/open_in_new_24px" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_close"
style="@style/Widget.Material3Expressive.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/close" />
</LinearLayout>
</LinearLayout>

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="16dp"
android:paddingHorizontal="16dp"
android:orientation="vertical"
android:background="@drawable/dialog_shape_click_effect_button">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:gravity="center_vertical">
<ImageView
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp"
android:background="@drawable/settings_system_background_icon"
android:src="@drawable/folder_24px"/>
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"/>
</LinearLayout>
<com.google.android.material.divider.MaterialDivider
android:id="@+id/md_inset_bottom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="56dp"/>
</LinearLayout>

View file

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

View file

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

View file

@ -1,16 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="app_name">Vectras VM</string>
<!--======================VECTRAS STRINGS====================-->
<!-- logger -->
<string name="startvm">虚拟机已启动!</string>
<string name="stopvm">虚拟机已关闭!</string>
<string name="noproccesses">无进程!</string>
<string name="unknownstate">未知状态!</string>
<string name="desktopInstructions">要使用外接鼠标:\n\t请在虚拟机中关闭鼠标加速.\n\t\t对于X-Window执行: xset m 1\n\t\t对于其它操作系统在 Vectras主选项中关闭\"增强指针准确性\"或者在鼠标选项中选择\"usb-tablet\" (usb-tablet为QEMU中的一个选项)\n\t接入外接鼠标后点击OK\n\t最后通过让鼠标滑至屏幕的各个边缘以校准</string>
<string name="navigation_drawer_open">打开抽屉</string>
<string name="navigation_drawer_close">收起抽屉</string>
<string name="address_caption">地址</string>
@ -30,13 +25,10 @@
<string name="username">用户名</string>
<string name="save">保存</string>
<string name="memory_usage">内存使用量</string>
<!-- TODO: Remove or change this placeholder text -->
<string name="external_vnc">允许外部VNC连接</string>
<string name="display">显示</string>
<string name="remove">移除</string>
<string name="view_logs">查看日志</string>
<!-- Strings used for fragments for navigation -->
<string name="input_mode_hardware_mouse">硬件鼠标模式</string>
<string name="rom_name">ROM 名称</string>
<string name="rom_drive_qcow2_img_etc">ROM驱动qcow2,img等</string>
@ -47,9 +39,7 @@
<string name="mouse_mode">鼠标模式</string>
<string name="export">导出</string>
<string name="sign_out">退出</string>
<string name="create_a_machine">新建虚拟机</string>
<string name="arrow_down">下箭头</string>
<string name="arrow_left">左箭头</string>
<string name="arrow_right">右箭头</string>
@ -99,19 +89,14 @@
<string name="save_as_copy">作为副本保存</string>
<string name="scaling">缩放</string>
<string name="scaling_zoomable">可缩放</string>
<string name="patreon">Patreon</string>
<!-- Send selected metakey -->
<string name="send_key_again">再次发送按键</string>
<string name="special_keys">发送按键</string>
<string name="username_caption">用户名</string>
<string name="username_hint">用于 Windows 身份验证</string>
<string name="terminal">终端</string>
<string name="install">安装</string>
<string name="modify">修改</string>
<string name="try_again">再次尝试</string>
<string name="size_in_gb">大小GB</string>
<string name="create_qcow2">创建 QCOW2</string>
@ -227,9 +212,6 @@
<string name="error_NO_SUCH_FILE_OR_DIRECTORY">运行虚拟机时出现问题: 虚拟机所需要的文件或文件夹不存在</string>
<string name="auto_create_new_hard_drive_when_creating_new_vm">创建新虚拟机时自动新建硬盘</string>
<string name="with_size_is_128GB">它的大小为128GB</string>
<!--======================TERMUX STRINGS====================-->
<string name="new_session">新建会话</string>
<string name="toggle_soft_keyboard">键盘</string>
<string name="reset_terminal">重置</string>
@ -237,56 +219,39 @@
<string name="share_transcript_title">终端显示文本</string>
<string name="toggle_keep_screen_on">保持屏幕打开</string>
<string name="autofill_password">自动填充密码</string>
<string name="bootstrap_installer_body">安装中…</string>
<string name="bootstrap_error_title">无法安装</string>
<string name="bootstrap_error_body">Vterm 无法安装初始化包</string>
<string name="bootstrap_error_abort">停止</string>
<string name="bootstrap_error_try_again">再次尝试</string>
<string name="bootstrap_error_not_primary_user_message">Vterm 只能安装在主用户帐户上</string>
<string name="max_terminals_reached_title">已达到的最大终端数</string>
<string name="max_terminals_reached_message">在开新的之前关闭现有项</string>
<string name="reset_toast_notification">重置终端</string>
<string name="select_url">选择 URL</string>
<string name="select_url_dialog_title">点击 URL 进行复制或长按打开</string>
<string name="select_all_and_share">分享终端输出</string>
<string name="select_url_no_found">在终端中找不到 URL</string>
<string name="select_url_copied_to_clipboard">URL 已复制到剪贴板</string>
<string name="share_transcript_chooser_title">发送文本至:</string>
<string name="kill_process">终止进程(%d</string>
<string name="confirm_kill_process">真的要终止此会话吗?</string>
<string name="session_rename_title">设置会话名称</string>
<string name="session_rename_positive_button">确认</string>
<string name="session_new_named_title">新命名会话</string>
<string name="session_new_named_positive_button">新建</string>
<string name="styling_not_installed">Vterm:Style 插件尚未安装</string>
<string name="styling_install">安装</string>
<string name="notification_action_exit">退出</string>
<string name="notification_action_wake_lock">加上唤醒锁</string>
<string name="notification_action_wake_unlock">释放唤醒锁</string>
<string name="file_received_title">文件保存至 ~/downloads/</string>
<string name="file_received_edit_button">编辑</string>
<string name="file_received_open_folder_button">打开文件夹</string>
<!--======================TERMUX-X11 STRINGS====================-->
<!-- TermuxX11Activity -->
<string name="not_connected">未连接</string>
<string name="help_button_text">   帮助   </string>
<string name="preferences_button_text">   首选项  </string>
<string name="extra_keys_config_desc">在这里您可以像Termux一样设置一些额外按键 (可以看看 <a href="http://wiki.termux.com/wiki/Touch_Keyboard#Extra_Keys_Row">这个</a>) 以及其余项 (e.g. META).\n 留空则使用默认值</string>
<!-- Log Level -->
<string name="cancel">取消</string>
<string name="back_to_the_display">返回显示界面</string>
<string name="x11">X11</string>
@ -319,7 +284,6 @@
<string name="bootstrap_required">需要初始化!</string>
<string name="you_can_choose_between_auto_download_and_setup_or_manual_setup_by_choosing_bootstrap_file">您可以选择让软件自动下载或者选择初始化包完成手动设置</string>
<string name="manual_setup">手动安装</string>
<string name="alpine_desktop">Alpine Xfce4</string>
<string name="settings_x11_display_resolution_mode">分辨率模式</string>
<string name="settings_x11_display_scale">缩放值(%</string>
@ -619,7 +583,7 @@
<string name="delete_file_failed_content">删除文件时出错,稍后重试</string>
<string name="file_deleted">文件已删除</string>
<string name="open_main_folder">打开主文件夹</string>
<string name="you_are_using_x11_instead_of_vnc_content">您正在使用 X11而非 VNC。要使用 VNC请进入设置-> QEMU向下滚动到底部然后在用户界面中选择"VNC"。</string>
<string name="you_are_using_x11_instead_of_vnc_content">您正在使用 X11而非 VNC。要使用 VNC请进入设置-&gt; QEMU向下滚动到底部然后在用户界面中选择"VNC"。</string>
<string name="there_is_no_app_to_perform_this_action">没有可用于进行该操作的APP</string>
<string name="directory_does_not_exist">文件夹不存在</string>
<string name="shared_folder_is_too_large_content">共享文件夹体积不应超过516MB请删除部分文件</string>
@ -732,4 +696,177 @@
<string name="go_to_termux_x11_settings">前往 Termux:X11 设置。</string>
<string name="use_sdl">使用 SDL</string>
<string name="use_sdl_note">如果您想使用 3dfx 并玩老游戏,请开启此选项(仅适用于 x86_64 和 i386</string>
</resources>
<string name="free_palistine">解放巴勒斯坦</string>
<string name="x86_64">x86_64</string>
<string name="error_CR_CVBI4">发生错误。ROM信息已损坏已应用部分默认设置。错误代码CR_CVBI4。</string>
<string name="file_format_is_not_supported_hard_drive">文件格式不受支持,可能无法正常工作。常见支持格式包括 qcow2、img、vhd、vhdx、vdi、qcow、vmdk 和 vpc。是否继续</string>
<string name="file_format_is_not_supported_optical_drive">文件格式不受支持,可能无法正常工作。常见支持格式包括 iso、img、cdr 和 toast。是否继续</string>
<string name="file_format_is_not_supported_floppy_drive">文件格式不受支持,可能无法正常工作。常见支持格式包括 img、flp 和 ima。是否继续</string>
<string name="mirrors">镜像源</string>
<string name="letstart">开始吧</string>
<string name="unable_to_copy_file_content">无法复制此文件,请尝试从其他位置选择该文件。</string>
<string name="use_vnc">使用VNC</string>
<string name="installing_system_files">正在安装系统文件</string>
<string name="connecting">连接中…</string>
<string name="how_do_you_want_to_install">您想如何安装?</string>
<string name="how_do_you_want_to_install_note">标准安装还是手动安装?</string>
<string name="standard">标准安装</string>
<string name="standard_note">推荐方式,安装程序将自动完成所有设置。</string>
<string name="custom">自定义安装</string>
<string name="custom_note">如果您想手动选择引导文件并安装所需的Qemu版本。</string>
<string name="mirror_note">选择离您最近的镜像源以加快安装速度。</string>
<string name="please_do_not_disconnect_the_network_this_may_take_a_few_minutes">请勿断开网络连接,这可能需要几分钟时间。</string>
<string name="something_went_wrong">出了点问题</string>
<string name="the_setup_could_not_be_completed_and_below_is_the_log">设置未能完成,以下是日志信息。</string>
<string name="this_option_is_temporarily_unavailable_because_the_server_cannot_be_connected">由于无法连接服务器,此选项暂时不可用。</string>
<string name="vectras_vm_cannot_run_on_this_device">Vectras VM 无法在此设备上运行。</string>
<string name="not_enough_storage_space">存储空间不足。</string>
<string name="there_are_no_logs">没有日志。</string>
<string name="the_required_package_is_not_installed_content">所需的软件包未安装,您需要先安装它才能使用此功能。</string>
<string name="need_install_termux_x11_content">未安装 Termux:X11 应用,您需要安装它才能继续。</string>
<string name="check_before_extract">解压前检查</string>
<string name="check_before_extract_note">快速检测错误,避免浪费时间解压后才发现问题。</string>
<string name="checking">检查中…</string>
<string name="quick_edit">快速编辑</string>
<string name="write_something">写点什么…</string>
<string name="switch_to_vnc">切换到VNC</string>
<string name="preferences">首选项</string>
<string name="size_gb">大小 (GB)</string>
<string name="create_a_new_virtual_disk">创建新虚拟磁盘</string>
<string name="it_will_be_created_in">将创建在</string>
<string name="shared_by">共享者</string>
<string name="system_update">系统更新</string>
<string name="system_update_note">有系统更新可用,立即更新以获得完整体验。</string>
<string name="qemu_documentation">QEMU 文档</string>
<string name="you_need_to_switch_to_sdl_to_use_3dfx">您需要切换到SDL才能使用3dfx。</string>
<string name="device_manager">设备管理器</string>
<string name="show_in_folder">在文件夹中显示</string>
<string name="exit">退出</string>
<string name="threedfx">3dfx</string>
<string name="refresh">刷新</string>
<string name="last_crash_note">非常抱歉,应用之前发生了崩溃。</string>
<string name="virtual_mouse">虚拟鼠标</string>
<string name="show_virtual_mouse">显示虚拟鼠标</string>
<string name="hide_virtual_mouse">隐藏虚拟鼠标</string>
<string name="this_bootstrap_file_is_invalid">此引导文件无效。</string>
<string name="vm_has_been_created">虚拟机已创建。</string>
<string name="vm_has_been_edited">虚拟机已编辑。</string>
<string name="an_error_occurred_and_vm_was_not_created">发生错误,虚拟机未创建。</string>
<string name="an_error_occurred_and_vm_was_not_modified">发生错误,虚拟机未修改。</string>
<string name="edit_with_create_command">使用参数编辑器编辑</string>
<string name="edit_with_create_command_content">您可以使用参数编辑器应用来编辑此虚拟机。</string>
<string name="unable_to_save_please_try_again_later">无法保存,请稍后重试。</string>
<string name="advanced">高级</string>
<string name="boot">启动</string>
<string name="show_boot_menu">显示启动菜单</string>
<string name="defaulttext">默认</string>
<string name="hard_disk">硬盘</string>
<string name="floppy_disk">软盘</string>
<string name="network">网络</string>
<string name="rom_successfully_exported">ROM 导出成功。</string>
<string name="options">选项</string>
<string name="add_file">添加文件</string>
<string name="file_added">文件已添加</string>
<string name="copy_full_path">复制完整路径</string>
<string name="adding_file_failed_content">添加文件失败,请稍后重试。</string>
<string name="items_have_been_cleared">项目已清空。</string>
<string name="show_recycle_bin">显示回收站</string>
<string name="an_error_occurred_while_deleting_the_vm">删除虚拟机时发生错误。</string>
<string name="the_vm_files_were_retained_because_they_are_still_being_used_elsewhere">虚拟机文件已被保留,因为它们仍在其他地方使用。</string>
<string name="pause">暂停</string>
<string name="pause_vm_note">虚拟机的状态将被保存并关闭以节省资源。下次启动时将恢复到此状态。请不要更改、移动或删除此虚拟机使用的文件。</string>
<string name="pausing_vm_note">虚拟机正在暂停…
请不要离开此页面。</string>
<string name="resuming">正在恢复…</string>
<string name="vm_resume_error_note">恢复之前暂停的虚拟机状态时出现问题。如果您在启动前修改了此虚拟机或Qemu设置请检查是否将其更改为与上次开机时相同的设置。您想保留还是移除保存的状态</string>
<string name="keep_and_continue">保留并继续</string>
<string name="remove_and_continue">移除并继续</string>
<string name="vm_state_save_failed_note">发生错误,无法保存虚拟机状态。请稍后重试。</string>
<string name="this_folder_cannot_be_opened">无法打开此文件夹。</string>
<string name="new_notification">新通知</string>
<string name="tap_to_view">点击查看。</string>
<string name="view_in_an_bui_app">在 An Bui 应用中查看</string>
<string name="view_in_an_bui_app_note">您可以在此处查看和获取。</string>
<string name="tools">工具</string>
<string name="threedfx_wrappers">3dfx 包装器</string>
<string name="virtio_tools_for_windows">Windows VirtIO 工具</string>
<string name="download_required">需要下载</string>
<string name="this_tool_needs_to_be_downloaded_before_use">此工具需要先下载才能使用。下载完成后将通知您。</string>
<string name="download_failed_note">下载失败,请稍后重试。</string>
<string name="virtio_tools_for_windows_is_now_ready_to_use">Windows VirtIO 工具现已就绪</string>
<string name="do_you_want_to_insert_it_into_the_optical_drive_right_now">是否立即将其插入光驱?</string>
<string name="try_play_store_version">试试 Play 商店版</string>
<string name="enter_a_command">输入命令</string>
<string name="console">控制台</string>
<string name="take_screenshot">截图</string>
<string name="unable_to_take_a_screenshot">无法截图。</string>
<string name="saved_to_the_gallery">已保存到相册。</string>
<string name="taking_a_screenshot">正在截图…</string>
<string name="this_vm_is_already_running">此虚拟机已在运行。</string>
<string name="the_server_is_running">服务器正在运行</string>
<string name="host_shared_folder_format_content">在虚拟机中访问此链接以访问您设备上的下载文件夹:
%s
您也可以从虚拟机发送文件到您设备的下载文件夹,以下是使用 curl 的示例:
%s</string>
<string name="host_shared_folder_error_content">启动服务器时发生错误,请稍后重试。</string>
<string name="copy_link">复制链接</string>
<string name="copy_upload_command">复制上传命令</string>
<string name="folder_sharing_server">文件夹共享服务器</string>
<string name="mute">静音</string>
<string name="unmute">取消静音</string>
<string name="cdrom_1">光驱 1</string>
<string name="cdrom_2">光驱 2</string>
<string name="locked_and_cannot_be_ejected_content">设备已锁定,暂时无法弹出。请稍后重试。您可以尝试在此虚拟机运行的操作系统中打开设备管理器或文件管理器并请求弹出。</string>
<string name="locked_and_cannot_be_changed_content">设备已锁定,暂时无法更换。请稍后重试。您可以尝试先弹出设备,然后再次尝试更换。</string>
<string name="manage">管理</string>
<string name="connect">连接</string>
<string name="shutting_down">正在关机…</string>
<string name="cannot_save_here_please_choose_another_location">无法保存到此处,请选择其他位置。</string>
<string name="processor">处理器</string>
<string name="board">主板</string>
<string name="core">核心</string>
<string name="cores">核心</string>
<string name="thread">线程</string>
<string name="threads">线程</string>
<string name="refresh_rate">刷新率</string>
<string name="low_refresh_rate">低刷新率</string>
<string name="low_refresh_rate_note">如果您的设备因绘制过多帧而过载可以启用此功能以提升虚拟机性能并将刷新率降低至30。</string>
<string name="edge_to_edge">全面屏</string>
<string name="disable_edge_to_edge">禁用全面屏</string>
<string name="pinch_to_zoom">双指缩放</string>
<string name="vm_picker">虚拟机选择器</string>
<string name="switchtext">切换</string>
<string name="switch_to">切换到</string>
<string name="no_vms_are_available">没有可用的虚拟机。</string>
<string name="hard_drive_0">硬盘 0</string>
<string name="hard_drive_1">硬盘 1</string>
<string name="optical_drive">光驱</string>
<string name="optical_drive_0">光驱 0</string>
<string name="optical_drive_1">光驱 1</string>
<string name="vm_creator_this_file_is_already_in_use_content">此文件已在虚拟机的其他地方使用。您需要找到并移除或重命名它,然后重新选择。</string>
<string name="higher_quality">更高质量</string>
<string name="vnc_higher_quality_note">图像质量将会提高,但如果使用外部连接,延迟会更大。</string>
<string name="battery">电池</string>
<string name="preparing">准备中…</string>
<string name="notification">通知</string>
<string name="notification_system_description">管理通知类型和主题。</string>
<string name="suggestions_and_tips">建议和提示</string>
<string name="suggestions_and_tips_settings_description">接收关于我们的建议和提示的通知。</string>
<string name="in_system_settings">在系统设置中。</string>
<string name="missing_packages">缺少的软件包</string>
<string name="missing_packages_content">以下是您需要安装的缺少软件包:</string>
<string name="x11_general_setting_note">在内置和外部显示之间切换时需要重启应用。</string>
<string name="contains_ads">包含广告</string>
<string name="new_session_failsafe">安全模式</string>
<string name="use_external_x11_note">使用 Termux:X11 应用代替内置屏幕。</string>
<string name="blur_effect">模糊效果</string>
<string name="blur_effect_note">模糊周围区域以增加对需要注意的特定区域的聚焦。</string>
<string name="g4_cpu_turbo">G4 (高性能)</string>
<string name="g4_cpu_basic">G4 (基本)</string>
<string name="g2_cpu_workstation">G2 (工作站)</string>
<string name="g2_cpu_low_power">G2 (低功耗)</string>
<string name="hello_blank_fragment">空白片段</string>
</resources>

View file

@ -627,6 +627,31 @@
<string name="need_to_shut_down_all_running_vm_note">You need to shut down all running virtual machines to perform this action.</string>
<string name="auto_switch_to_external_mouse">Auto switch to external mouse</string>
<string name="auto_switch_to_external_mouse_note">Automatically switches when the mouse is connected and disconnected.</string>
<string name="the_necessary_packages_have_been_installed">The necessary packages have been installed.</string>
<string name="an_error_occurred_while_creating_the_virtual_drive">An error occurred while creating the virtual drive.</string>
<string name="equalizer">Equalizer</string>
<string name="equalizer_settings_note">Treble, mid and bass.</string>
<string name="equalizer_note">You may need to restart the virtual machine for the changes to take effect. Sound effects may be affected if you use accessories such as headphones.</string>
<string name="use_equalizer">Use equalizer</string>
<string name="upper_treble">Upper treble</string>
<string name="treble">Treble</string>
<string name="mid">Mid</string>
<string name="bass">Bass</string>
<string name="low_bass">Low bass</string>
<string name="your_device_is_not_supported">Your device is not supported.</string>
<string name="undo">Undo</string>
<string name="internal_storage">Internal storage</string>
<string name="open_with">Open with</string>
<string name="file_picker">File picker</string>
<string name="file_picker_settings_note">Built-in file picker and browser.</string>
<string name="use_file_picker">Use file picker</string>
<string name="resume">Resume</string>
<string name="file_picker_resume_note">Continue in the last folder where you selected the file in the previous use.</string>
<string name="show_hidden_files">Show hidden files</string>
<string name="show_hidden_files_note">This applies to files and folders whose names begin with \".\".</string>
<string name="the_folder_path_has_been_copied_to_the_clipboard">The folder path has been copied to the clipboard.</string>
<string name="divider">Divider</string>
<string name="file_picker_divier_note">Add dividers between items.</string>
<!--======================TERMUX STRINGS====================-->

View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

View file

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

View file

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

View file

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

4
gradlew vendored
View file

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

4
gradlew.bat vendored
View file

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

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="paste_text">粘贴</string>
<string name="copy_text">复制</string>
<string name="text_selection_more">更多…</string>
</resources>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

View file

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

View file

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

View file

@ -674,6 +674,26 @@
"vecid": "macosxleopardcvbi",
"id": ""
},
{
"rom_name": "macOS High Sierra",
"rom_icon": "https://static.wikia.nocookie.net/ipod/images/e/ec/MacOSHighSierraCircle.png/revision/latest/thumbnail/width/360/height/450?cb=20241004222256",
"rom_url": "https://youtu.be/ZvMxdVennwU",
"rom_path": "macOS High Sierra.cvbi",
"rom_avail": true,
"gui": true,
"rom_size": "10.13",
"rom_arch": "X86_64",
"rom_kernel": "apple",
"rom_extra": "",
"final_rom_file_name": "",
"desc": "This Rom is from Nguyen Bao An Bui. You can get it on An Bui app: https://play.google.com/store/apps/details?id=com.anbui.app\n\nmacOS High Sierra (version 10.13) is the fourteenth major release of macOS, Apple's desktop operating system for Macintosh computers. macOS High Sierra was announced at the WWDC 2017 on June 5, 2017 and was released on September 25, 2017. The name \"High Sierra\" refers to the High Sierra region in California. Its name signified its goal to be a refinement of the previous macOS version, macOS Sierra, focused on performance improvements and technical updates rather than features. This makes it similar to previous macOS releases Snow Leopard, Mavericks and El Capitan. Among the apps with notable changes are Photos and Safari.",
"file_size": "16 GB",
"containsAds": true,
"creator": "Nguyen Bao An Bui",
"verified": true,
"vecid": "macoshighsierracvbi",
"id": "99624"
},
{
"rom_name": "macOS Mojave",
"rom_icon": "https://cdsassets.apple.com/live/SZLF0YNV/images/sp/111930_mojave-roundel-240.png",
@ -1541,6 +1561,26 @@
"vecid": "windows10mobilecvbi",
"id": "99672"
},
{
"rom_name": "Windows 10 S 1709 ARM64 build 16299",
"rom_icon": "https://vuetiwuvbyxywfukompp.supabase.co/storage/v1/object/public/getmyos/v1/files/2018/09/17/windows-logo_1_926ed76111646acbbe332bc5af0cf2ce.png",
"rom_url": "https://youtu.be/2csyaU2w8dk",
"rom_path": "Windows 10 S ARM64 build 16299.cvbi",
"rom_avail": true,
"gui": true,
"rom_size": "Fall Creators Update",
"rom_arch": "ARM64",
"rom_kernel": "windows",
"rom_extra": "",
"final_rom_file_name": "",
"desc": "This Rom is from Nguyen Bao An Bui. You can get it on An Bui app: https://play.google.com/store/apps/details?id=com.anbui.app\n\nWindows 10 is a major release of the Windows NT operating system developed by Microsoft. The successor to Windows 8.1, it was released to manufacturing on July 15, 2015, became generally available on July 29, 2015, and was a free upgrade to users of Windows 7, 8, and 8.1. Its server counterparts are Windows Server 2016, 2019, and 2022. It was succeeded by Windows 11 in October 2021.",
"file_size": "16 GB",
"containsAds": true,
"creator": "Nguyen Bao An Bui",
"verified": true,
"vecid": "windows10sarm64build16299cvbi",
"id": "99627"
},
{
"rom_name": "Windows 11 21H2",
"rom_icon": "https://www.getmyos.com/app_public/files/t/1/2021/06/windows_11_logo_by_getmyos.png",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Some files were not shown because too many files have changed in this diff Show more