mirror of
https://github.com/xoureldeen/Vectras-VM-Android.git
synced 2026-05-02 16:20:30 +00:00
update setup activity
This commit is contained in:
parent
ed08b9777c
commit
2e1f4e9256
159 changed files with 438 additions and 427 deletions
|
|
@ -27,13 +27,13 @@ public class AppConfig {
|
|||
public static final String vectrasPrivacy = vectrasRaw + "PRIVACYANDPOLICY.md";
|
||||
public static final String vectrasTerms = vectrasRaw + "TERMSOFSERVICE.md";
|
||||
public static final String vectrasInfo = vectrasRaw + "info.md";
|
||||
public static final String vectrasRepo = "https://github.com/epicstudios856/Vectras-windows-emulator/tree/main/";
|
||||
public static final String vectrasRepo = "https://github.com/epicstudios856/Vectras-VM-Android";
|
||||
public static final String updateJson = vectrasRaw + "UpdateConfig.json";
|
||||
public static final String blogJson = vectrasRaw + "news_list.json";
|
||||
public static final String storeJson = vectrasRaw + "store_list.json";
|
||||
public static final String vectrasPkg = vectrasWebsite + "download";
|
||||
|
||||
public static final String serverIP = "http://135.181.166.39:1234/";
|
||||
public static final String serverIP = "https://vectrasvm.blackstorm.cc/";
|
||||
|
||||
public static String getSetupFiles() {
|
||||
String abi = Build.SUPPORTED_ABIS[0];
|
||||
|
|
|
|||
|
|
@ -11,11 +11,13 @@ import android.net.NetworkRequest;
|
|||
import android.os.Bundle;
|
||||
import android.telephony.NetworkScanRequest;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
|
@ -38,6 +40,7 @@ import com.vectras.vm.utils.UIUtils;
|
|||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetAddress;
|
||||
|
|
@ -45,8 +48,11 @@ import java.net.UnknownHostException;
|
|||
import java.io.InputStreamReader;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Scanner;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
|
||||
|
|
|
|||
|
|
@ -355,7 +355,8 @@ public class MainActivity extends AppCompatActivity {
|
|||
startActivityForResult(intent, 1006);
|
||||
}
|
||||
} else if (id == R.id.navigation_terminal) {
|
||||
startActivity(new Intent(activity, ConnectionSettings.class));
|
||||
com.vectras.vterm.TerminalBottomSheetDialog VTERM = new com.vectras.vterm.TerminalBottomSheetDialog(activity);
|
||||
VTERM.showVterm();
|
||||
} else if (id == R.id.navigation_item_settings) {
|
||||
startActivity(new Intent(activity, MainSettingsManager.class));
|
||||
} else if (id == R.id.navigation_item_store) {
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ public class MainService extends Service {
|
|||
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
|
||||
.setContentTitle("Vectras VM")
|
||||
.setContentText(MACHINE_NAME + " running in background.")
|
||||
.setSmallIcon(R.mipmap.ic_launcher_foreground)
|
||||
.setSmallIcon(R.mipmap.ic_launcher)
|
||||
.addAction(R.drawable.round_logout_24, "Stop", pStopSelf)
|
||||
.build();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,11 @@
|
|||
package com.vectras.vm;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.view.View;
|
||||
import android.webkit.MimeTypeMap;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
|
@ -23,20 +19,20 @@ import com.vectras.vterm.view.ZoomableTextView;
|
|||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
public class SetupQemuActivity extends AppCompatActivity implements View.OnClickListener {
|
||||
SetupQemuActivity activity;
|
||||
private final String TAG = "SetupQemuActivity";
|
||||
ZoomableTextView vterm;
|
||||
MaterialButton dlBtn, hpBtn, slBtn, inBtn;
|
||||
TextView tvSelectedPath;
|
||||
MaterialButton inBtn;
|
||||
ProgressBar progressBar;
|
||||
|
||||
@Override
|
||||
|
|
@ -49,144 +45,33 @@ public class SetupQemuActivity extends AppCompatActivity implements View.OnClick
|
|||
|
||||
vterm = findViewById(R.id.tvTerminalOutput);
|
||||
|
||||
dlBtn = findViewById(R.id.btnDownload);
|
||||
hpBtn = findViewById(R.id.btnHelp);
|
||||
slBtn = findViewById(R.id.btnSelect);
|
||||
inBtn = findViewById(R.id.btnInstall);
|
||||
|
||||
dlBtn.setOnClickListener(activity);
|
||||
hpBtn.setOnClickListener(activity);
|
||||
slBtn.setOnClickListener(activity);
|
||||
inBtn.setOnClickListener(activity);
|
||||
|
||||
tvSelectedPath = findViewById(R.id.tarPath);
|
||||
tarPath = getExternalFilesDir("data") + "/data.tar.gz";
|
||||
|
||||
File tarGZ = new File(tarPath);
|
||||
if (tarGZ.exists()) {
|
||||
setupVectras();
|
||||
} else {
|
||||
startDownload();
|
||||
}
|
||||
}
|
||||
|
||||
public void onClick(View v) {
|
||||
int id = v.getId();
|
||||
if (id == R.id.btnDownload) {
|
||||
String qe = AppConfig.getSetupFiles();
|
||||
Intent q = new Intent(Intent.ACTION_VIEW);
|
||||
q.setData(Uri.parse(qe));
|
||||
startActivity(q);
|
||||
} else if (id == R.id.btnHelp) {
|
||||
String qe = AppConfig.vectrasHelp;
|
||||
Intent q = new Intent(Intent.ACTION_VIEW);
|
||||
q.setData(Uri.parse(qe));
|
||||
startActivity(q);
|
||||
} else if (id == R.id.btnSelect) {
|
||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
intent.setType("*/*"); // Allow the user to select any file type
|
||||
|
||||
// Optionally, specify a URI for the file that should appear in the system file picker when it loads
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
// Use the Downloads folder as the starting path
|
||||
Uri downloadsUri = Uri.parse(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString());
|
||||
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, downloadsUri);
|
||||
}
|
||||
|
||||
startActivityForResult(intent, 0);
|
||||
} else if (id == R.id.btnInstall) {
|
||||
if (tarPath != null) {
|
||||
inBtn.setEnabled(false);
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
|
||||
String setupFilesUrl = AppConfig.getSetupFiles();
|
||||
|
||||
String filesDir = activity.getFilesDir().getAbsolutePath();
|
||||
if (!com.vectras.vm.utils.FileUtils.readFromFile(activity, new File(filesDir + "/distro/etc/apk/repositories")).contains("http://dl-cdn.alpinelinux.org/alpine/edge/testing"))
|
||||
executeShellCommand("echo \"http://dl-cdn.alpinelinux.org/alpine/edge/testing\" >> /etc/apk/repositories");
|
||||
executeShellCommand("set -e;" +
|
||||
" apk update;" +
|
||||
" apk add libslirp libslirp-dev pulseaudio-dev glib-dev pixman-dev zlib-dev spice-dev libusbredirparser usbredir-dev libiscsi-dev sdl2 sdl2-dev libepoxy-dev virglrenderer-dev tar;" +
|
||||
" tar -xvzf " + tarPath.getAbsolutePath() + " -C " + filesDir + "/distro;" +
|
||||
" rm " + tarPath.getAbsolutePath() + ";" +
|
||||
" echo \"installation done! xssFjnj58Id\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File tarPath;
|
||||
|
||||
public String getPath(Uri uri) {
|
||||
return com.vectras.vm.utils.FileUtils.getPath(this, uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent ReturnedIntent) {
|
||||
super.onActivityResult(requestCode, resultCode, ReturnedIntent);
|
||||
|
||||
if (requestCode == 0 && resultCode == RESULT_OK) {
|
||||
Uri content_describer = ReturnedIntent.getData();
|
||||
|
||||
// Get the file extension from the URI
|
||||
String fileExtension = MimeTypeMap.getFileExtensionFromUrl(content_describer.toString());
|
||||
|
||||
File selectedFilePath = new File(getPath(content_describer));
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
String abi = Build.SUPPORTED_ABIS[0];
|
||||
if (selectedFilePath.getName().endsWith("tar.gz")) {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
FileInputStream File = null;
|
||||
try {
|
||||
File = (FileInputStream) getContentResolver().openInputStream(content_describer);
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
try {
|
||||
try {
|
||||
OutputStream out = new FileOutputStream(new File(com.vectras.vm.AppConfig.maindirpath + selectedFilePath.getName()));
|
||||
try {
|
||||
// Transfer bytes from in to out
|
||||
byte[] buf = new byte[1024];
|
||||
int len;
|
||||
while ((len = File.read(buf)) > 0) {
|
||||
out.write(buf, 0, len);
|
||||
}
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
} finally {
|
||||
Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
progressBar.setVisibility(View.GONE);
|
||||
inBtn.setEnabled(selectedFilePath.exists());
|
||||
tarPath = new File(com.vectras.vm.AppConfig.maindirpath + selectedFilePath.getName());
|
||||
|
||||
tvSelectedPath.setText(tarPath.getAbsolutePath());
|
||||
|
||||
tarPath.deleteOnExit();
|
||||
}
|
||||
};
|
||||
activity.runOnUiThread(runnable);
|
||||
File.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
progressBar.setVisibility(View.GONE);
|
||||
inBtn.setEnabled(true);
|
||||
com.vectras.vm.utils.UIUtils.UIAlert(activity, "error", e.toString());
|
||||
}
|
||||
};
|
||||
activity.runOnUiThread(runnable);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
if (id == R.id.btnInstall) {
|
||||
File tarGZ = new File(tarPath);
|
||||
if (tarGZ.exists()) {
|
||||
setupVectras();
|
||||
} else {
|
||||
com.vectras.vm.utils.UIUtils.UIAlert(activity, "File not supported", "please select supported tar.gz to continue.<br><br>required files:<br>vectras.tar<br><br>please download the required files from our official website.");
|
||||
progressBar.setVisibility(View.GONE);
|
||||
startDownload();
|
||||
}
|
||||
} else if (requestCode == 1000 && resultCode == RESULT_CANCELED) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
String tarPath;
|
||||
|
||||
// Function to append text and automatically scroll to bottom
|
||||
private void appendTextAndScroll(String textToAdd) {
|
||||
ScrollView scrollView = findViewById(R.id.scrollView);
|
||||
|
|
@ -194,7 +79,7 @@ public class SetupQemuActivity extends AppCompatActivity implements View.OnClick
|
|||
// Update the text
|
||||
vterm.append(textToAdd);
|
||||
|
||||
if (textToAdd.contains("installation done! xssFjnj58Id")) {
|
||||
if (textToAdd.contains("installation successful! xssFjnj58Id")) {
|
||||
//finish();
|
||||
startActivity(new Intent(this, SplashActivity.class));
|
||||
}
|
||||
|
|
@ -295,6 +180,7 @@ public class SetupQemuActivity extends AppCompatActivity implements View.OnClick
|
|||
// If exit value is not zero, display a toast message
|
||||
String toastMessage = "Command failed with exit code: " + exitValue;
|
||||
activity.runOnUiThread(() -> Toast.makeText(activity, toastMessage, Toast.LENGTH_LONG).show());
|
||||
inBtn.setVisibility(View.VISIBLE);
|
||||
}
|
||||
} catch (IOException | InterruptedException e) {
|
||||
// Handle exceptions by printing the stack trace in the terminal output
|
||||
|
|
@ -302,8 +188,126 @@ public class SetupQemuActivity extends AppCompatActivity implements View.OnClick
|
|||
activity.runOnUiThread(() -> {
|
||||
appendTextAndScroll("Error: " + errorMessage + "n");
|
||||
Toast.makeText(activity, "Error executing command: " + errorMessage, Toast.LENGTH_LONG).show();
|
||||
inBtn.setVisibility(View.VISIBLE);
|
||||
});
|
||||
}
|
||||
}).start(); // Execute the command in a separate thread to prevent blocking the UI thread
|
||||
}
|
||||
|
||||
private void startDownload() {
|
||||
new DownloadFileTask(activity).execute(AppConfig.getSetupFiles());
|
||||
}
|
||||
|
||||
private class DownloadFileTask extends AsyncTask<String, Integer, String> {
|
||||
|
||||
private Context context;
|
||||
private ProgressDialog progressDialog;
|
||||
private int fileLength;
|
||||
|
||||
public DownloadFileTask(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
progressDialog = new ProgressDialog(context, R.style.MainDialogTheme);
|
||||
progressDialog.setTitle("Downloading \"data.tar.gz\"...");
|
||||
progressDialog.setMessage(null);
|
||||
progressDialog.setIndeterminate(true);
|
||||
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||
progressDialog.setCancelable(false); // Allow canceling with back button
|
||||
progressDialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String doInBackground(String... sUrl) {
|
||||
InputStream input = null;
|
||||
OutputStream output = null;
|
||||
HttpURLConnection connection = null;
|
||||
try {
|
||||
URL url = new URL(sUrl[0]);
|
||||
connection = (HttpURLConnection) url.openConnection();
|
||||
connection.connect();
|
||||
|
||||
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
|
||||
return "Server returned HTTP " + connection.getResponseCode()
|
||||
+ " " + connection.getResponseMessage();
|
||||
}
|
||||
|
||||
fileLength = connection.getContentLength();
|
||||
input = connection.getInputStream();
|
||||
output = new FileOutputStream(tarPath);
|
||||
byte data[] = new byte[4096];
|
||||
long total = 0;
|
||||
int count;
|
||||
while ((count = input.read(data)) != -1) {
|
||||
if (isCancelled()) {
|
||||
input.close();
|
||||
return null;
|
||||
}
|
||||
total += count;
|
||||
if (fileLength > 0) {
|
||||
publishProgress((int) (total * 100 / fileLength));
|
||||
}
|
||||
output.write(data, 0, count);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return e.toString();
|
||||
} finally {
|
||||
try {
|
||||
if (output != null)
|
||||
output.close();
|
||||
if (input != null)
|
||||
input.close();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
|
||||
if (connection != null)
|
||||
connection.disconnect();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(Integer... progress) {
|
||||
super.onProgressUpdate(progress);
|
||||
|
||||
// If you get here, the length of the file is known.
|
||||
progressDialog.setIndeterminate(false);
|
||||
progressDialog.setMax(100);
|
||||
progressDialog.setProgress(progress[0]);
|
||||
|
||||
// Convert the bytes downloaded to MB and update the dialog message accordingly.
|
||||
int progressMB = (int) ((progress[0] / 100.0) * fileLength / (1024 * 1024));
|
||||
progressDialog.setMessage(progressMB + " MB/" + fileLength / (1024 * 1024) + " MB");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(String result) {
|
||||
progressDialog.dismiss(); // Dismiss the progress dialog
|
||||
|
||||
if (result != null) {
|
||||
Toast.makeText(context, "Download error: " + result, Toast.LENGTH_LONG).show();
|
||||
inBtn.setVisibility(View.VISIBLE);
|
||||
} else
|
||||
setupVectras();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void setupVectras() {
|
||||
inBtn.setVisibility(View.GONE);
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
String filesDir = activity.getFilesDir().getAbsolutePath();
|
||||
String cmd = "";
|
||||
cmd += "echo \"http://dl-cdn.alpinelinux.org/alpine/edge/testing\" >> /etc/apk/repositories;";
|
||||
executeShellCommand(cmd);
|
||||
executeShellCommand("set -e;" +
|
||||
" apk update;" +
|
||||
" apk add libslirp libslirp-dev pulseaudio-dev glib-dev pixman-dev zlib-dev spice-dev libusbredirparser usbredir-dev libiscsi-dev sdl2 sdl2-dev libepoxy-dev virglrenderer-dev tar;" +
|
||||
" tar -xvzf " + tarPath + " -C " + filesDir + "/distro;" +
|
||||
" rm " + tarPath + ";" +
|
||||
" echo \"installation successful! xssFjnj58Id\"");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import com.vectras.qemu.MainSettingsManager;
|
|||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
|
@ -93,6 +94,24 @@ public class SplashActivity extends AppCompatActivity implements Runnable {
|
|||
if (!distroDir.exists()) {
|
||||
distroDir.mkdirs();
|
||||
}
|
||||
|
||||
File jsonFile = new File(AppConfig.maindirpath
|
||||
+ "roms-data.json");
|
||||
if (!jsonFile.exists())
|
||||
try {
|
||||
|
||||
if (!jsonFile.exists()) {
|
||||
jsonFile.createNewFile();
|
||||
}
|
||||
|
||||
FileWriter writer = new FileWriter(jsonFile);
|
||||
writer.write("[]");
|
||||
writer.flush();
|
||||
writer.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
File binDir = new File(distroDir + "/bin");
|
||||
if (!binDir.exists()) {
|
||||
String CHANNEL_ID = "vectras";
|
||||
|
|
@ -108,7 +127,7 @@ public class SplashActivity extends AppCompatActivity implements Runnable {
|
|||
Notification notification = new NotificationCompat.Builder(activity, CHANNEL_ID) // Replace YOUR_CHANNEL_ID with your actual channel ID
|
||||
.setContentTitle("Extract Bootstrap")
|
||||
.setContentText("Error during file extraction.")
|
||||
.setSmallIcon(com.vectras.vterm.R.drawable.cursor) // Replace this with your notification icon.
|
||||
.setSmallIcon(R.mipmap.ic_launcher) // Replace this with your notification icon.
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(pendingIntent)
|
||||
.build();
|
||||
|
|
|
|||
159
app/src/main/java/com/vectras/vterm/Terminal.java
Normal file
159
app/src/main/java/com/vectras/vterm/Terminal.java
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
package com.vectras.vterm;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog;
|
||||
import com.vectras.vterm.view.ZoomableTextView;
|
||||
|
||||
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.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.util.Enumeration;
|
||||
|
||||
public class Terminal {
|
||||
private static final String TAG = "Vterm";
|
||||
private Context context;
|
||||
private String user = "root";
|
||||
|
||||
public static Process qemuProcess;
|
||||
|
||||
public Terminal(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
|
||||
private String getLocalIpAddress() {
|
||||
try {
|
||||
for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {
|
||||
NetworkInterface intf = en.nextElement();
|
||||
for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
|
||||
InetAddress inetAddress = enumIpAddr.nextElement();
|
||||
if (!inetAddress.isLoopbackAddress() && inetAddress.getHostAddress().toString().contains(".")) {
|
||||
return inetAddress.getHostAddress().toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (SocketException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Method to execute the shell command
|
||||
public void executeShellCommand(String userCommand) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
// Setup the qemuProcess builder to start PRoot with environmental variables and commands
|
||||
ProcessBuilder processBuilder = new ProcessBuilder();
|
||||
|
||||
// Adjust these environment variables as necessary for your app
|
||||
String filesDir = context.getFilesDir().getAbsolutePath();
|
||||
String nativeLibDir = context.getApplicationInfo().nativeLibraryDir;
|
||||
|
||||
// Setup environment for the PRoot qemuProcess
|
||||
processBuilder.environment().put("PROOT_TMP_DIR", filesDir + "/tmp");
|
||||
processBuilder.environment().put("PROOT_LOADER", nativeLibDir + "/libproot-loader.so");
|
||||
processBuilder.environment().put("PROOT_LOADER_32", nativeLibDir + "/libproot-loader32.so");
|
||||
|
||||
processBuilder.environment().put("HOME", "/root");
|
||||
processBuilder.environment().put("USER", user);
|
||||
processBuilder.environment().put("PATH", "/bin:/usr/bin:/sbin:/usr/sbin");
|
||||
processBuilder.environment().put("TERM", "xterm-256color");
|
||||
processBuilder.environment().put("TMPDIR", filesDir + "/tmp");
|
||||
processBuilder.environment().put("SHELL", "/bin/sh");
|
||||
processBuilder.environment().put("PULSE_SERVER", "/run/pulse/native");
|
||||
processBuilder.environment().put("XDG_RUNTIME_DIR", "/run");
|
||||
|
||||
// Example PRoot command; replace 'libproot.so' and other paths as needed
|
||||
String[] prootCommand = {
|
||||
nativeLibDir + "/libproot.so", // 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);
|
||||
qemuProcess = processBuilder.start();
|
||||
// Get the input and output streams of the qemuProcess
|
||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(qemuProcess.getOutputStream()));
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(qemuProcess.getInputStream()));
|
||||
BufferedReader errorReader = new BufferedReader(new InputStreamReader(qemuProcess.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) {
|
||||
Log.d(TAG, line);
|
||||
}
|
||||
|
||||
// Read any errors from the error stream
|
||||
while ((line = errorReader.readLine()) != null) {
|
||||
Log.w(TAG, line);
|
||||
}
|
||||
|
||||
// Clean up
|
||||
reader.close();
|
||||
errorReader.close();
|
||||
|
||||
// Wait for the qemuProcess to finish
|
||||
qemuProcess.waitFor();
|
||||
|
||||
} catch (IOException | InterruptedException e) {
|
||||
// Handle exceptions by printing the stack trace in the terminal output
|
||||
final String errorMessage = e.getMessage();
|
||||
Log.e("Vterm ERROR:", errorMessage);
|
||||
}
|
||||
}).start(); // Execute the command in a separate thread to prevent blocking the UI thread
|
||||
}
|
||||
|
||||
private boolean checkInstallation() {
|
||||
String filesDir = context.getFilesDir().getAbsolutePath();
|
||||
File distro = new File(filesDir, "distro");
|
||||
return distro.exists();
|
||||
}
|
||||
|
||||
public static void killQemuProcess() {
|
||||
if (qemuProcess != null) {
|
||||
qemuProcess.destroy();
|
||||
qemuProcess = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,240 @@
|
|||
package com.vectras.vterm;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog;
|
||||
import com.vectras.vm.R;
|
||||
import com.vectras.vterm.view.ZoomableTextView;
|
||||
|
||||
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.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.util.Enumeration;
|
||||
|
||||
public class TerminalBottomSheetDialog {
|
||||
private ZoomableTextView terminalOutput;
|
||||
private EditText commandInput;
|
||||
private View view;
|
||||
private Activity activity;
|
||||
private BottomSheetDialog bottomSheetDialog;
|
||||
|
||||
public TerminalBottomSheetDialog(Activity activity) {
|
||||
this.activity = activity;
|
||||
|
||||
bottomSheetDialog = new BottomSheetDialog(activity);
|
||||
view = activity.getLayoutInflater().inflate(R.layout.terminal_bottom_sheet, null);
|
||||
bottomSheetDialog.setContentView(view);
|
||||
|
||||
terminalOutput = view.findViewById(R.id.tvTerminalOutput);
|
||||
commandInput = view.findViewById(R.id.etCommandInput);
|
||||
|
||||
TextView tvPrompt = view.findViewById(R.id.tvPrompt);
|
||||
updateUserPrompt(tvPrompt);
|
||||
|
||||
// Show the keyboard
|
||||
InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(commandInput, InputMethodManager.SHOW_IMPLICIT);
|
||||
|
||||
commandInput.requestFocus();
|
||||
|
||||
// Whenever you modify the text of the EditText, do the following to ensure the cursor is at the end:
|
||||
commandInput.setSelection(commandInput.getText().length());
|
||||
|
||||
// when user click terminal view will open keyboard
|
||||
terminalOutput.setOnClickListener(view -> {
|
||||
// Request focus for the EditText
|
||||
commandInput.requestFocus();
|
||||
|
||||
// Show the keyboard
|
||||
InputMethodManager imm1 = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm1.showSoftInput(commandInput, InputMethodManager.SHOW_IMPLICIT);
|
||||
});
|
||||
// Configure the editor to handle the "Done" action on the soft keyboard
|
||||
commandInput.setOnEditorActionListener((v, actionId, event) -> {
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
executeShellCommand(commandInput.getText().toString());
|
||||
commandInput.setText("");
|
||||
commandInput.requestFocus();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
commandInput.setOnKeyListener(new View.OnKeyListener() {
|
||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||
// If the event is a key-down event on the "enter" button
|
||||
if ((event.getAction() == KeyEvent.ACTION_DOWN) &&
|
||||
(keyCode == KeyEvent.KEYCODE_ENTER)) {
|
||||
executeShellCommand(commandInput.getText().toString());
|
||||
commandInput.setText("");
|
||||
commandInput.requestFocus();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void showVterm() {
|
||||
bottomSheetDialog.show();
|
||||
}
|
||||
|
||||
private void updateUserPrompt(TextView promptView) {
|
||||
// Run this in a separate thread to not block UI
|
||||
new Thread(() -> {
|
||||
String username = null;
|
||||
// Update the prompt on the UI thread
|
||||
String finalUsername = username != null ? username : "user";
|
||||
activity.runOnUiThread(() -> {
|
||||
promptView.setText(finalUsername + "@localhost:~$ ");
|
||||
});
|
||||
}).start();
|
||||
}
|
||||
|
||||
// Function to append text and automatically scroll to bottom
|
||||
private void appendTextAndScroll(String textToAdd) {
|
||||
ScrollView scrollView = view.findViewById(R.id.scrollView);
|
||||
|
||||
// Update the text
|
||||
terminalOutput.append(textToAdd);
|
||||
|
||||
// Scroll to the bottom
|
||||
scrollView.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
scrollView.fullScroll(ScrollView.FOCUS_DOWN);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String getLocalIpAddress() {
|
||||
try {
|
||||
for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {
|
||||
NetworkInterface intf = en.nextElement();
|
||||
for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
|
||||
InetAddress inetAddress = enumIpAddr.nextElement();
|
||||
if (!inetAddress.isLoopbackAddress() && inetAddress.getHostAddress().toString().contains(".")) {
|
||||
return inetAddress.getHostAddress().toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (SocketException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Method to execute the shell command
|
||||
public void executeShellCommand(String userCommand) {
|
||||
if (checkInstallation())
|
||||
new Thread(() -> {
|
||||
try {
|
||||
// Setup the process builder to start PRoot with environmental variables and commands
|
||||
ProcessBuilder processBuilder = new ProcessBuilder();
|
||||
|
||||
// Adjust these environment variables as necessary for your app
|
||||
String filesDir = activity.getFilesDir().getAbsolutePath();
|
||||
String nativeLibDir = activity.getApplicationInfo().nativeLibraryDir;
|
||||
|
||||
// Setup environment for the PRoot process
|
||||
processBuilder.environment().put("PROOT_TMP_DIR", filesDir + "/tmp");
|
||||
processBuilder.environment().put("PROOT_LOADER", nativeLibDir + "/libproot-loader.so");
|
||||
processBuilder.environment().put("PROOT_LOADER_32", nativeLibDir + "/libproot-loader32.so");
|
||||
|
||||
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", "/tmp");
|
||||
processBuilder.environment().put("SHELL", "/bin/sh");
|
||||
processBuilder.environment().put("DISPLAY", getLocalIpAddress()+":0");
|
||||
|
||||
// Example PRoot command; replace 'libproot.so' and other paths as needed
|
||||
String[] prootCommand = {
|
||||
nativeLibDir + "/libproot.so", // 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;
|
||||
activity.runOnUiThread(() -> appendTextAndScroll(outputLine + "\n"));
|
||||
}
|
||||
|
||||
// Read any errors from the error stream
|
||||
while ((line = errorReader.readLine()) != null) {
|
||||
final String errorLine = line;
|
||||
activity.runOnUiThread(() -> appendTextAndScroll(errorLine + "\n"));
|
||||
}
|
||||
|
||||
// Clean up
|
||||
reader.close();
|
||||
errorReader.close();
|
||||
|
||||
// Wait for the process to finish
|
||||
process.waitFor();
|
||||
|
||||
} catch (IOException | InterruptedException e) {
|
||||
// Handle exceptions by printing the stack trace in the terminal output
|
||||
final String errorMessage = e.getMessage();
|
||||
activity.runOnUiThread(() -> appendTextAndScroll("Error: " + errorMessage + "n"));
|
||||
}
|
||||
}).start(); // Execute the command in a separate thread to prevent blocking the UI thread
|
||||
else
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle("Error!")
|
||||
.setMessage("Verify that \"setupFiles()\" is working properly in onCreate().")
|
||||
.setCancelable(false)
|
||||
.show();
|
||||
}
|
||||
|
||||
private boolean checkInstallation() {
|
||||
String filesDir = activity.getFilesDir().getAbsolutePath();
|
||||
File distro = new File(filesDir, "distro");
|
||||
return distro.exists();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
package com.vectras.vterm.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.ScaleGestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
import androidx.appcompat.widget.AppCompatTextView;
|
||||
|
||||
public class ZoomableTextView extends AppCompatTextView {
|
||||
private ScaleGestureDetector scaleGestureDetector;
|
||||
private float mScaleFactor = 1.0f;
|
||||
private float defaultSize;
|
||||
|
||||
public ZoomableTextView(Context context) {
|
||||
super(context);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public ZoomableTextView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public ZoomableTextView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context);
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
defaultSize = getTextSize();
|
||||
scaleGestureDetector = new ScaleGestureDetector(context, new ScaleGestureDetector.SimpleOnScaleGestureListener() {
|
||||
@Override
|
||||
public boolean onScale(ScaleGestureDetector scaleGestureDetector) {
|
||||
mScaleFactor *= scaleGestureDetector.getScaleFactor();
|
||||
// Don't let the object get too small or too large.
|
||||
mScaleFactor = Math.max(0.5f, Math.min(mScaleFactor, 2.0f));
|
||||
setTextSize(mScaleFactor * defaultSize);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
super.onTouchEvent(event);
|
||||
return scaleGestureDetector.onTouchEvent(event);
|
||||
}
|
||||
|
||||
public void setTextSize(float size){
|
||||
super.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
|
||||
}
|
||||
public interface OnTextSizeChangeListener {
|
||||
void onTextSizeChange(float textSize);
|
||||
}
|
||||
|
||||
private OnTextSizeChangeListener textSizeChangeListener;
|
||||
|
||||
public void setOnTextSizeChangeListener(OnTextSizeChangeListener textSizeChangeListener) {
|
||||
this.textSizeChangeListener = textSizeChangeListener;
|
||||
}
|
||||
|
||||
// In the code where the zoom and text size change happens:
|
||||
private void handleZoom(float scaleFactor) {
|
||||
// ... Existing zooming logic ...
|
||||
|
||||
float newTextSize = this.getTextSize() * scaleFactor;
|
||||
this.setTextSize(TypedValue.COMPLEX_UNIT_PX, newTextSize);
|
||||
|
||||
// Notify the listener about the text size change
|
||||
if (textSizeChangeListener != null) {
|
||||
textSizeChangeListener.onTextSizeChange(newTextSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue