update size: " + obj.getString("size")))
+ .setCancelable(obj.getBoolean("cancellable"))
+ .setNegativeButton("Update", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ try {
+ startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(obj.getString("url"))));
+ } catch (JSONException e) {
+
+ }
+ }
+ }).show();
+
+ }
+ } else if(result.contains("Error on getting data") && showDialog){
+ errorUpdateDialog(result);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }).start(showDialog);
+ }
+
+ private void errorUpdateDialog(String error) {
+ VectrasStatus.logInfo(String.format(error));
+ }
+
+ private MenuItem vectrasInfo;
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.home_toolbar_menu, menu);
+ vectrasInfo = menu.findItem(R.id.vectrasInfo);
+ return true;
+
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+
+ // Menu items
+ int id = item.getItemId();
+ if (id == R.id.vectrasInfo) {
+ AppBarLayout nnl_appbar = findViewById(R.id.nnl_appbar);
+ if (nnl_appbar.getTop() < 0)
+ nnl_appbar.setExpanded(true);
+ else
+ nnl_appbar.setExpanded(false);
+
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void initNavigationMenu() {
+ BottomNavigationView bottomNavigationView = (BottomNavigationView) findViewById(R.id.bottom_navigation_view);
+
+ bottomNavigationView
+ .setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
+ @Override
+ public boolean onNavigationItemSelected(@NonNull MenuItem item) {
+ int id = item.getItemId();
+ if (id == R.id.menu_home) {
+ viewPager.setCurrentItem(0);
+ } else if (id == R.id.menu_logger) {
+ viewPager.setCurrentItem(1);
+ }
+ return false;
+ }
+ });
+ }
+
+ public class MyAdapter extends FragmentPagerAdapter {
+
+ MyAdapter(FragmentManager fm) {
+ super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
+ }
+
+ @Override
+ public Fragment getItem(int position) {
+
+ switch (position) {
+ case 0:
+ return new HomeFragment();
+ case 1:
+ return new LoggerFragment();
+ }
+ return null;
+ }
+
+ @Override
+ public int getCount() {
+ return pager_number;
+ }
+ }
+
+ public void setupNativeLibs() {
+ //Glib
+ System.loadLibrary("glib-2.0");
+ System.loadLibrary("gthread-2.0");
+ System.loadLibrary("gobject-2.0");
+ System.loadLibrary("gmodule-2.0");
+
+ //Pixman for qemu
+ System.loadLibrary("pixman");
+
+ // //Load SDL libraries
+ System.loadLibrary("SDL2");
+ System.loadLibrary("SDL2_image");
+
+ // System.loadLibrary("mikmod");
+ System.loadLibrary("SDL2_mixer");
+ // System.loadLibrary("SDL_ttf");
+
+ //main for SDL
+ System.loadLibrary("main");
+
+ //Limbo needed for vmexecutor
+ System.loadLibrary("vectras");
+
+ System.loadLibrary("qemu-system-x86_64");
+ }
+
+ public void checkUpdate() {
+
+ }
+
+ private void setupStrictMode() {
+
+ if (Config.debug) {
+ StrictMode.setThreadPolicy(
+ new StrictMode.ThreadPolicy.Builder().detectDiskReads().detectDiskWrites().detectNetwork()
+ //.penaltyDeath()
+ .penaltyLog().build());
+ StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectLeakedSqlLiteObjects()
+ .detectLeakedClosableObjects().penaltyLog()
+ // .penaltyDeath()
+ .build());
+ }
+
+ }
+
+ public void cleanup() {
+ MainActivity.vmexecutor = null;
+
+ }
+
+ public void exit() {
+ onStopButton(true);
+ }
+
+ // Main event function
+ // Retrives values from saved preferences
+ public static void onStartButton() {
+
+ if (vmexecutor == null) {
+
+ try {
+ vmexecutor = new VMExecutor(activity);
+ } catch (Exception ex) {
+ UIUtils.toastLong(activity, "Error: " + ex);
+ return;
+
+ }
+ }
+
+ output = "Starting VM...";
+ VectrasStatus.logInfo(String.format("Starting VM..."));
+ VectrasStatus.logInfo(String.format(imgPath));
+ vmexecutor.paused = 0;
+ startSDL();
+
+ if (vmStarted) {
+ //do nothing
+ } else if (vmexecutor.paused == 1) {
+ vmStarted = true;
+ }
+
+ }
+
+ public static void startSDL() {
+
+ Thread tsdl = new Thread(new Runnable() {
+ public void run() {
+ startsdl();
+ }
+ });
+ tsdl.setPriority(Thread.MAX_PRIORITY);
+ tsdl.start();
+ }
+
+ public static void onStopButton(boolean exit) {
+ stopVM(exit);
+ }
+
+ public static void onRestartButton() {
+
+ new AlertDialog.Builder(activity).setTitle("Reset VM")
+ .setMessage("VM will be reset and you may lose data. Continue?")
+ .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ if (MainActivity.vmexecutor != null) {
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ restartvm();
+ }
+ });
+ t.start();
+ } else if (activity.getParent() != null) {
+ activity.getParent().finish();
+ } else {
+ activity.finish();
+ }
+ }
+ }).setNegativeButton("No", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ }
+ }).show();
+
+ }
+
+ public static void onResumeButton() {
+
+ // TODO: This probably has no effect
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ resumevm();
+ }
+ });
+ t.start();
+ }
+
+ public void prepareParams() throws FileNotFoundException {
+ String libqemu = null;
+ libqemu = FileUtils.getDataDir() + "/lib/libqemu-system-x86_64.so";
+
+ SharedPreferences credentials = activity.getSharedPreferences(CREDENTIAL_SHARED_PREF, Context.MODE_PRIVATE);
+
+ params = null;
+ ArrayList paramsList = new ArrayList();
+
+ paramsList.add(libqemu);
+
+ paramsList.add("-L");
+ paramsList.add(Config.basefiledir);
+
+ if (FileUtils.fileValid(activity, imgPath)) {
+ paramsList.add("-hda");
+ paramsList.add(imgPath);
+ } else {
+ UIUtils.toastLong(activity, "please set the correct downloaded os");
+ startActivity(new Intent(activity, FirstActivity.class));
+ return;
+ }
+
+ paramsList.add("-boot");
+ paramsList.add("c");
+/*
+ paramsList.add("-drive");
+ String driveParams = "index=3";
+ driveParams += ",media=disk";
+ driveParams += ",if=ide";
+ driveParams += ",format=raw";
+ driveParams += ",file=fat:";
+ driveParams += "rw:";
+ driveParams += Config.sharedFolder;
+ paramsList.add(driveParams);
+*/
+ params = (String[]) paramsList.toArray(new String[paramsList.size()]);
+
+ }
+
+ // Setting up the UI
+ public void setupWidgets() {
+ viewPager = findViewById(R.id.viewPager);
+ viewPager.setAdapter(new MyAdapter(getSupportFragmentManager()));
+ viewPager.setOffscreenPageLimit(pager_number);
+ final BottomNavigationView bottomNavigationView = (BottomNavigationView) findViewById(R.id.bottom_navigation_view);
+ viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
+ @Override
+ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ }
+
+ @Override
+ public void onPageSelected(int position) {
+ if (prevMenuItem != null) {
+ prevMenuItem.setChecked(false);
+ } else {
+ bottomNavigationView.getMenu().getItem(0).setChecked(false);
+ }
+ bottomNavigationView.getMenu().getItem(position).setChecked(true);
+ prevMenuItem = bottomNavigationView.getMenu().getItem(position);
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int state) {
+
+ }
+ });
+
+ try {
+ prepareParams();
+ } catch (Exception ex) {
+ //UIUtils.toastLong(this, ex.getMessage());
+ }
+ AppBarLayout nnl_appbar = findViewById(R.id.nnl_appbar);
+ nnl_appbar.setExpanded(false);
+ mainToolbar = (Toolbar) findViewById(R.id.nnl_toolbar);
+ setSupportActionBar(mainToolbar);
+ mainDrawer = (DrawerLayout) findViewById(R.id.nnl_drawer_layout);
+ ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, mainDrawer, mainToolbar,
+ R.string.navigation_drawer_open, R.string.navigation_drawer_close);
+ mainDrawer.setDrawerListener(toggle);
+ toggle.syncState();
+
+ NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
+
+ //Setting Navigation View Item Selected Listener to handle the item click of the navigation menu
+ navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
+
+ // This method will trigger on item Click of navigation menu
+ @Override
+ public boolean onNavigationItemSelected(MenuItem menuItem) {
+ //Closing drawer on item click
+ mainDrawer.closeDrawers();
+
+ //Check to see which item was being clicked and perform appropriate action
+ int id = menuItem.getItemId();
+ if (id == R.id.navigation_item_info){
+ startActivity(new Intent(activity, AboutActivity.class));
+ } else if (id == R.id.navigation_item_website){
+ String tw = Config.vectrasWebsite;
+ Intent w = new Intent(Intent.ACTION_VIEW);
+ w.setData(Uri.parse(tw));
+ startActivity(w);
+ } else if (id == R.id.navigation_item_store){
+ startActivity(new Intent(activity, StoreActivity.class));
+ }
+ return false;
+ }
+ });
+
+ ipTxt = findViewById(R.id.ipTxt);
+
+ ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
+ ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
+ activityManager.getMemoryInfo(mi);
+
+ long freeMem = mi.availMem / 1048576L;
+ long totalMem = mi.totalMem / 1048576L;
+ long usedMem = totalMem - freeMem;
+ int freeRamInt = safeLongToInt(freeMem);
+ int totalRamInt = safeLongToInt(totalMem);
+ ipTxt.setText("Local Ip Address: " + getLocalIpAddress());
+
+ SharedPreferences credentials = activity.getSharedPreferences(CREDENTIAL_SHARED_PREF, Context.MODE_PRIVATE);
+
+ totalRam = findViewById(R.id.totalRam);
+ usedRam = findViewById(R.id.usedRam);
+ freeRam = findViewById(R.id.freeRam);
+ t = new TimerTask() {
+ @Override
+ public void run() {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ //update
+ ActivityManager.MemoryInfo miI = new ActivityManager.MemoryInfo();
+ ActivityManager activityManagerr = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
+ activityManagerr.getMemoryInfo(miI);
+ //update textview here
+ long freeMemory = miI.availMem / 1048576L;
+ long totalMemory = miI.totalMem / 1048576L;
+ long usedMemory = totalMemory - freeMemory;
+
+ totalRam.setText("Total Memory: " + totalMemory + " MB");
+ usedRam.setText("Used Memory: " + usedMemory + " MB");
+ freeRam.setText("Free Memory: " + freeMemory + " MB");
+ }
+ });
+ }
+ };
+ _timer.scheduleAtFixedRate(t, (int) (0), (int) (1000));
+
+ mStart = (FloatingActionButton) findViewById(R.id.nnl_fab);
+ mStart.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View view) {
+
+ FAB_Click(view);
+
+ }
+ });
+ AdView mAdView = findViewById(R.id.adView);
+ AdRequest adRequest = new AdRequest.Builder().build();
+ mAdView.loadAd(adRequest);
+ UIAlert(getString(R.string.app_version), "This is only the first beta version of Vectras. Please note that the application is still in the experimental stage so that it is available in the best condition. Do not be lazy and support the application with your opinion.", activity);
+ //File extra = new File(Config.basefiledir+"config_extra.txt");
+ //String extraParams = FileUtils.readFromFile(MainActivity.activity, extra);
+
+ //UIAlert("Args", imgPath+"\n\n"+vectrasMem, activity);
+ }
+
+ public static int safeLongToInt(long l) {
+ if (l < Integer.MIN_VALUE || l > Integer.MAX_VALUE) {
+ throw new IllegalArgumentException(l + " cannot be cast to int without changing its value.");
+ }
+ return (int) l;
+ }
+
+ @Override
+ public void onBackPressed() {
+ DrawerLayout drawer = (DrawerLayout) findViewById(R.id.nnl_drawer_layout);
+ if (drawer.isDrawerOpen(GravityCompat.START)) {
+ drawer.closeDrawer(GravityCompat.START);
+ } else {
+ super.onBackPressed();
+ }
+ }
+
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ moveTaskToBack(true);
+ return true; // return
+ }
+
+ return false;
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ this.stopTimeListener();
+
+ }
+
+ public static void startsdl() {
+
+ Intent intent = null;
+
+ intent = new Intent(activity, VectrasSDLActivity.class);
+
+ android.content.ContentValues values = new android.content.ContentValues();
+ activity.startActivityForResult(intent, Config.SDL_REQUEST_CODE);
+ }
+
+ public static void restartvm() {
+ if (vmexecutor != null) {
+
+ output = vmexecutor.stopvm(1);
+ vmStarted = true;
+
+ } else {
+
+ }
+
+ }
+
+ public void savevm(String name) {
+
+ }
+
+ public static void resumevm() {
+ if (vmexecutor != null) {
+ output = vmexecutor.resumevm();
+
+ } else {
+
+ }
+
+ }
+
+ public void onViewLog() {
+
+ }
+
+ private void goToURL(String url) {
+
+ Intent i = new Intent(Intent.ACTION_VIEW);
+ i.setData(Uri.parse(url));
+ activity.startActivity(i);
+
+ }
+
+ public static void stopVM(boolean exit) {
+
+ new AlertDialog.Builder(activity).setTitle("Shutdown VM")
+ .setMessage("To avoid any corrupt data make sure you "
+ + "have already shutdown the Operating system from within the VM. Continue?")
+ .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
+
+ public void onClick(DialogInterface dialog, int which) {
+ if (MainActivity.vmexecutor != null) {
+ MainActivity.vmexecutor.stopvm(0);
+ VectrasStatus.logInfo(String.format("VMStopped"));
+ } else if (activity.getParent() != null) {
+ activity.getParent().finish();
+ } else {
+ activity.finish();
+ }
+ }
+ }).setNegativeButton("No", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ }
+ }).show();
+ }
+
+ public void saveSnapshotDB(String snapshot_name) {
+
+ }
+
+ public void saveStateVMDB() {
+
+ }
+
+ public void stopTimeListener() {
+
+ synchronized (this.lockTime) {
+ this.timeQuit = true;
+ this.lockTime.notifyAll();
+ }
+ }
+
+ public void onPause() {
+ super.onPause();
+ }
+
+ public void onResume() {
+
+ super.onResume();
+
+ }
+
+ public void timeListener() {
+ while (timeQuit != true) {
+ if (vmexecutor != null) {
+ String status = checkStatus();
+ if (!status.equals(currStatus)) {
+ currStatus = status;
+
+ }
+ }
+
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException ex) {
+ ex.printStackTrace();
+ }
+ }
+
+ }
+
+ void execTimeListener() {
+
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ startTimeListener();
+ }
+ });
+ t.start();
+ }
+
+ public void startTimeListener() {
+ this.stopTimeListener();
+
+ timeQuit = false;
+ try {
+
+ timeListener();
+ synchronized (lockTime) {
+ while (timeQuit == false) {
+ lockTime.wait();
+ }
+ lockTime.notifyAll();
+ }
+ } catch (Exception ex) {
+ ex.printStackTrace();
+
+ }
+
+ }
+
+ private String checkStatus() {
+ String state = "READY";
+ if (vmexecutor != null && vmexecutor.libLoaded && vmexecutor.get_state().equals("RUNNING")) {
+ state = "RUNNING";
+ } else if (vmexecutor != null) {
+ //String save_state = vmexecutor.get_save_state();
+ String pause_state = vmexecutor.get_pause_state();
+
+ // Shutdown if paused done
+ if (pause_state.equals("SAVING")) {
+ return pause_state;
+ } else if (pause_state.equals("DONE")) {
+ if (MainActivity.vmexecutor != null) {
+ MainActivity.vmexecutor.stopvm(0);
+ }
+
+ } else {
+ state = "READY";
+ }
+ } else {
+ state = "READY";
+ }
+
+ return state;
+ }
+
+ public static class Installer extends AsyncTask {
+
+ @Override
+ protected Void doInBackground(Void... arg0) {
+ onInstall();
+ if (progDialog.isShowing()) {
+ progDialog.dismiss();
+ //activity.setupWidgets();
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void test) {
+
+ }
+ }
+
+ public class AutoScrollView extends ScrollView {
+
+ public AutoScrollView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public AutoScrollView(Context context) {
+ super(context);
+ }
+ }
+}
diff --git a/app/src/main/java/com/epicstudios/vectras/PostActivity.java b/app/src/main/java/com/epicstudios/vectras/PostActivity.java
new file mode 100644
index 0000000..2ea8b5e
--- /dev/null
+++ b/app/src/main/java/com/epicstudios/vectras/PostActivity.java
@@ -0,0 +1,110 @@
+package com.epicstudios.vectras;
+
+import android.text.Html;
+import android.util.Log;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.ImageView;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.widget.Toolbar;
+import com.bumptech.glide.Glide;
+import com.epicstudios.vectras.R;
+import com.epicstudios.vectras.utils.UIUtils;
+
+import android.os.Bundle;
+import androidx.appcompat.app.AppCompatActivity;
+import android.widget.TextView;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+public class PostActivity extends AppCompatActivity {
+
+ private Toolbar tb;
+ public static TextView postTitle;
+ public static TextView postContent;
+ public static TextView postDate;
+ public static ImageView postThumb;
+ public static String title, content, contentStr, date, thumb;
+
+ /**
+ * Called when the activity is first created.
+ */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+
+ super.onCreate(savedInstanceState);
+ this.setContentView(R.layout.post_content);
+ postTitle = findViewById(R.id.postTitle);
+ postContent = findViewById(R.id.postContent);
+ postDate = findViewById(R.id.postDate);
+ postThumb = findViewById(R.id.postThumb);
+ tb = (Toolbar) findViewById(R.id.nnl_toolbar);
+ setSupportActionBar(tb);
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setDisplayShowHomeEnabled(true);
+
+ postContent.setTextIsSelectable(true);
+
+ Glide.with(this).load(thumb).into(postThumb);
+ new Thread(new Runnable(){
+
+ public void run(){
+
+ BufferedReader reader = null;
+ final StringBuilder builder = new StringBuilder();
+
+ try {
+ // Create a URL for the desired page
+ URL url = new URL(content); //My text file location
+ //First open the connection
+ HttpURLConnection conn=(HttpURLConnection) url.openConnection();
+ conn.setConnectTimeout(60000); // timing out in a minute
+
+ BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
+
+ //t=(TextView)findViewById(R.id.TextView1); // ideally do this in onCreate()
+ String str;
+ while ((str = in.readLine()) != null) {
+ builder.append(str);
+ }
+ in.close();
+ } catch (Exception e) {
+ postContent.setText("no internet connection");
+ UIUtils.toastLong(PostActivity.this, "check your internet connection");
+ Log.d("VECTRAS",e.toString());
+ }
+
+ //since we are in background thread, to post results we have to go back to ui thread. do the following for that
+
+ PostActivity.this.runOnUiThread(new Runnable(){
+ public void run(){
+ contentStr = builder.toString(); // My TextFile has 3 lines
+ postContent.setText(Html.fromHtml(contentStr));
+ }
+ });
+
+ }
+ }).start();
+ postDate.setText(date);
+ postTitle.setText(title);
+ }
+ @Override
+ public boolean onOptionsItemSelected(@NonNull MenuItem item) {
+ if(item.getItemId()== android.R.id.home){
+ finish();
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public void onBackPressed() {
+ super.onBackPressed();
+ finish();
+ }
+
+}
diff --git a/app/src/main/java/com/epicstudios/vectras/Roms/AdapterRoms.java b/app/src/main/java/com/epicstudios/vectras/Roms/AdapterRoms.java
new file mode 100644
index 0000000..426083c
--- /dev/null
+++ b/app/src/main/java/com/epicstudios/vectras/Roms/AdapterRoms.java
@@ -0,0 +1,148 @@
+package com.epicstudios.vectras.Roms;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.graphics.Color;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.webkit.URLUtil;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.RadioButton;
+import android.widget.TextView;
+import androidx.appcompat.app.AlertDialog;
+import androidx.cardview.widget.CardView;
+import androidx.recyclerview.widget.RecyclerView;
+import com.epicstudios.vectras.Config;
+import com.bumptech.glide.Glide;
+import com.epicstudios.vectras.FirstActivity;
+import com.epicstudios.vectras.MainActivity;
+import com.epicstudios.vectras.R;
+import com.epicstudios.vectras.utils.FileUtils;
+import java.util.Collections;
+import java.util.List;
+import androidx.appcompat.app.AlertDialog;
+import android.content.DialogInterface;
+import android.app.Dialog;
+
+public class AdapterRoms extends RecyclerView.Adapter {
+
+ private Context context;
+ private LayoutInflater inflater;
+ List data = Collections.emptyList();
+ DataRoms current;
+ int currentPos = 0;
+ private int mSelectedItem = -1;
+
+ // create constructor to innitilize context and data sent from MainActivity
+ public AdapterRoms(Context context, List data) {
+ this.context = context;
+ inflater = LayoutInflater.from(context);
+ this.data = data;
+ }
+
+ // Inflate the layout when viewholder created
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View view = inflater.inflate(R.layout.container_roms, parent, false);
+ MyHolder holder = new MyHolder(view);
+ return holder;
+ }
+
+ // Bind data
+ @Override
+ public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
+
+ // Get current position of item in recyclerview to bind data and assign values from list
+ final MyHolder myHolder = (MyHolder) holder;
+ final DataRoms current = data.get(position);
+
+ Glide.with(FirstActivity.activity).load(current.itemIcon).into(myHolder.ivIcon);
+ myHolder.textName.setText(current.itemName + " " + current.itemArch);
+ myHolder.textSize.setText(current.itemSize);
+ myHolder.checkBox.setChecked(position == mSelectedItem);
+ if (current.itemAvail) {
+ myHolder.textAvail.setText("availability: available");
+ myHolder.textAvail.setTextColor(Color.GREEN);
+ } else if (!current.itemAvail) {
+ myHolder.textAvail.setText("availability: unavailable");
+ myHolder.textAvail.setTextColor(Color.RED);
+ myHolder.checkBox.setEnabled(false);
+ myHolder.cdItem.setEnabled(false);
+ }
+ if (current.itemAvail)
+ myHolder.checkBox.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mSelectedItem = position;
+ notifyItemRangeChanged(0, data.size());
+ FirstActivity.selected = true;
+ FirstActivity.selectedPath = current.itemPath;
+ FirstActivity.selectedExtra = current.itemExtra;
+ FirstActivity.selectedName = current.itemName+" "+current.itemArch;
+ FirstActivity.selectedLink = current.itemUrl;
+ FirstActivity.selectedIcon = current.itemIcon;
+ }
+ });
+ //Glide.with(MainActivity.activity).load(current.itemIcon).into(myHolder.ivIcon);
+ if (current.itemAvail)
+ myHolder.cdItem.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (FileUtils.fileValid(FirstActivity.activity, Config.maindirpath + current.itemPath)) {
+ mSelectedItem = position;
+ notifyItemRangeChanged(0, data.size());
+ FirstActivity.selected = true;
+ FirstActivity.selectedPath = current.itemPath;
+ FirstActivity.selectedExtra = current.itemExtra;
+ } else {
+ AlertDialog ad;
+ ad = new AlertDialog.Builder(FirstActivity.activity, R.style.MainDialogTheme).create();
+ ad.setTitle(current.itemName + " Not found");
+ ad.setMessage(current.itemName + " Rom not found please download from our official website");
+ ad.setButton(Dialog.BUTTON_POSITIVE, "DOWNLAOD WEBSITE", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ String gt = current.itemUrl;
+ Intent g = new Intent(Intent.ACTION_VIEW);
+ g.setData(Uri.parse(gt));
+ FirstActivity.activity.startActivity(g);
+ FirstActivity.activity.finish();
+ }
+ });
+ }
+ }
+ });
+
+ }
+
+ // return total item from List
+ @Override
+ public int getItemCount() {
+ return data.size();
+ }
+
+ class MyHolder extends RecyclerView.ViewHolder {
+
+ CardView cdItem;
+ TextView textName, textAvail, textSize;
+ ImageView ivIcon;
+
+ RadioButton checkBox;
+
+ // create constructor to get widget reference
+ public MyHolder(View itemView) {
+ super(itemView);
+ cdItem = (CardView) itemView.findViewById(R.id.cdItem);
+ textName = (TextView) itemView.findViewById(R.id.textName);
+ ivIcon = (ImageView) itemView.findViewById(R.id.ivIcon);
+ textSize = (TextView) itemView.findViewById(R.id.textSize);
+ textAvail = (TextView) itemView.findViewById(R.id.textAvail);
+
+ checkBox = (RadioButton) itemView.findViewById(R.id.checkBox);
+ }
+
+ }
+
+}
diff --git a/app/src/main/java/com/epicstudios/vectras/Roms/DataRoms.java b/app/src/main/java/com/epicstudios/vectras/Roms/DataRoms.java
new file mode 100644
index 0000000..8535f7c
--- /dev/null
+++ b/app/src/main/java/com/epicstudios/vectras/Roms/DataRoms.java
@@ -0,0 +1,13 @@
+package com.epicstudios.vectras.Roms;
+
+public class DataRoms {
+
+ public String itemIcon;
+ public String itemName;
+ public String itemArch;
+ public Boolean itemAvail;
+ public String itemSize;
+ public String itemUrl;
+ public String itemPath;
+ public String itemExtra;
+}
diff --git a/app/src/main/java/com/epicstudios/vectras/SplashActivity.java b/app/src/main/java/com/epicstudios/vectras/SplashActivity.java
new file mode 100644
index 0000000..caae6a6
--- /dev/null
+++ b/app/src/main/java/com/epicstudios/vectras/SplashActivity.java
@@ -0,0 +1,137 @@
+package com.epicstudios.vectras;
+
+import static android.os.Build.VERSION.SDK_INT;
+
+import android.Manifest;
+import android.app.Dialog;
+import android.content.*;
+import android.content.pm.*;
+import android.net.Uri;
+import android.os.*;
+import android.preference.PreferenceManager;
+import android.provider.Settings;
+import android.view.*;
+import android.graphics.*;
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+import com.epicstudios.vectras.R;
+import com.epicstudios.vectras.MainActivity;
+import com.epicstudios.vectras.utils.UIUtils;
+
+import java.io.File;
+
+public class SplashActivity extends AppCompatActivity implements Runnable {
+ public AlertDialog ad;
+ public static SplashActivity activity;
+ @Override
+ protected void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+ activity = this;
+ File baseDir = new File(Config.basefiledir);
+ if (!baseDir.exists()) {
+ baseDir.mkdirs();
+ }
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ setContentView(R.layout.activity_splash);
+ if (!checkPermission()) {
+ ad = new AlertDialog.Builder(this, R.style.MainDialogTheme).create();
+ ad.setTitle("permissions");
+ ad.setMessage("Vectras needs some permissions:\n-full storage access(shared folder - vectras bootable image '.vbi')");
+ ad.setCanceledOnTouchOutside(false);
+ ad.setButton(Dialog.BUTTON_POSITIVE, "Allow", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ try {
+ ActivityCompat.requestPermissions(SplashActivity.this,
+ new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
+ } catch (Exception e) {
+ UIUtils.toastLong(activity, e.toString());
+ throw new RuntimeException(e);
+ }
+ return;
+ }
+ });
+
+ ad.setButton(Dialog.BUTTON_NEGATIVE, "Learn More", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ String gt = Config.vectrasRepo;
+ Intent g = new Intent(Intent.ACTION_VIEW);
+ g.setData(Uri.parse(gt));
+ startActivity(g);
+ finish();
+ return;
+ }
+ });
+ ad.show();
+ } else {
+ File sharedDir = new File(Config.sharedFolder);
+ if (!sharedDir.exists()) {
+ sharedDir.mkdirs();
+ }
+ new Handler().postDelayed(this, 2000);
+ }
+
+ File sharedDir = new File(Config.sharedFolder);
+ if (!sharedDir.exists()) {
+ sharedDir.mkdirs();
+ }
+ File mainDir = new File(Config.maindirpath);
+ if (!mainDir.exists()) {
+ mainDir.mkdirs();
+ }
+
+ }
+ private boolean checkPermission() {
+ if (SDK_INT >= Build.VERSION_CODES.R) {
+ return Environment.isExternalStorageManager();
+ } else {
+ int result = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
+ int result1 = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
+ if (android.os.Build.VERSION.SDK_INT >= 30) {
+
+ int result2 = ContextCompat.checkSelfPermission(this, Manifest.permission.MANAGE_EXTERNAL_STORAGE);
+ return result == PackageManager.PERMISSION_GRANTED && result1 == PackageManager.PERMISSION_GRANTED && result2 == PackageManager.PERMISSION_GRANTED;
+
+ } else {
+
+ return result == PackageManager.PERMISSION_GRANTED && result1 == PackageManager.PERMISSION_GRANTED;
+
+ }
+ }
+ }
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ if (android.os.Build.VERSION.SDK_INT >= 30) {
+
+ Intent intent = new Intent();
+ intent.setAction(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
+ Uri uri = Uri.fromParts("package", this.getPackageName(), null);
+ intent.setData(uri);
+ startActivity(intent);
+ ad.cancel();
+ new Handler().postDelayed(this, 2000);
+ }
+ } else if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_DENIED) {
+ ActivityCompat.requestPermissions(SplashActivity.this,
+ new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
+ }
+ }
+ public static final String CREDENTIAL_SHARED_PREF = "settings_prefs";
+
+ @Override
+ public void run() {
+ SharedPreferences prefs = getSharedPreferences(CREDENTIAL_SHARED_PREF, Context.MODE_PRIVATE);
+
+ boolean isAccessed = prefs.getBoolean("isFirstLaunch", false);
+ if (!isAccessed) {
+ startActivity(new Intent(this, FirstActivity.class));
+ } else {
+ startActivity(new Intent(this, MainActivity.class));
+ }
+ finish();
+ }
+}
diff --git a/app/src/main/java/com/epicstudios/vectras/Store/AdapterStore.java b/app/src/main/java/com/epicstudios/vectras/Store/AdapterStore.java
new file mode 100644
index 0000000..f71d001
--- /dev/null
+++ b/app/src/main/java/com/epicstudios/vectras/Store/AdapterStore.java
@@ -0,0 +1,103 @@
+package com.epicstudios.vectras.Store;
+
+import android.content.Context;
+import android.content.Intent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.ImageView;
+import android.widget.TextView;
+import androidx.cardview.widget.CardView;
+import androidx.fragment.app.FragmentTransaction;
+import androidx.recyclerview.widget.RecyclerView;
+import com.bumptech.glide.Glide;
+import com.epicstudios.vectras.R;
+import com.epicstudios.vectras.Fragment.HomeFragment;
+import com.epicstudios.vectras.PostActivity;
+import java.util.Collections;
+import java.util.List;
+import com.epicstudios.vectras.MainActivity;
+import com.epicstudios.vectras.StoreActivity;
+import com.epicstudios.vectras.StoreItemActivity;
+
+public class AdapterStore extends RecyclerView.Adapter {
+
+ private Context context;
+ private LayoutInflater inflater;
+ List data = Collections.emptyList();
+ DataStore current;
+ int currentPos = 0;
+
+ // create constructor to innitilize context and data sent from MainActivity
+ public AdapterStore(Context context, List data) {
+ this.context = context;
+ inflater = LayoutInflater.from(context);
+ this.data = data;
+ }
+
+ // Inflate the layout when viewholder created
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View view = inflater.inflate(R.layout.container_store, parent, false);
+ MyHolder holder = new MyHolder(view);
+ return holder;
+ }
+
+ // Bind data
+ @Override
+ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+
+ // Get current position of item in recyclerview to bind data and assign values from list
+ MyHolder myHolder = (MyHolder) holder;
+ final DataStore current = data.get(position);
+ myHolder.textName.setText(current.itemName);
+ myHolder.textSize.setText("Size: " + current.itemSize);
+ Glide.with(MainActivity.activity).load(current.itemIcon).into(myHolder.ivIcon);
+ Animation animation;
+ animation = AnimationUtils.loadAnimation(MainActivity.activity, android.R.anim.slide_in_left);
+ animation.setDuration(300);
+
+ myHolder.cdItem.startAnimation(animation);
+ animation = null;
+ myHolder.cdItem.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View view) {
+ StoreItemActivity.name = current.itemName;
+ StoreItemActivity.icon = current.itemIcon;
+ StoreItemActivity.size = current.itemSize;
+ StoreItemActivity.desc = current.itemData;
+ StoreItemActivity.link = current.itemLink;
+ StoreItemActivity.prvMain = current.itemPreviewMain;
+ StoreItemActivity.prv1 = current.itemPreview1;
+ StoreItemActivity.prv2 = current.itemPreview2;
+ StoreActivity.activity.startActivity(new Intent(StoreActivity.activity, StoreItemActivity.class));
+ }
+ });
+ }
+
+ // return total item from List
+ @Override
+ public int getItemCount() {
+ return data.size();
+ }
+
+ class MyHolder extends RecyclerView.ViewHolder {
+
+ CardView cdItem;
+ TextView textName;
+ ImageView ivIcon;
+ TextView textSize;
+
+ // create constructor to get widget reference
+ public MyHolder(View itemView) {
+ super(itemView);
+ cdItem = (CardView) itemView.findViewById(R.id.cdItem);
+ textName = (TextView) itemView.findViewById(R.id.textName);
+ ivIcon = (ImageView) itemView.findViewById(R.id.ivIcon);
+ textSize = (TextView) itemView.findViewById(R.id.textSize);
+ }
+
+ }
+
+}
diff --git a/app/src/main/java/com/epicstudios/vectras/Store/DataStore.java b/app/src/main/java/com/epicstudios/vectras/Store/DataStore.java
new file mode 100644
index 0000000..9d06441
--- /dev/null
+++ b/app/src/main/java/com/epicstudios/vectras/Store/DataStore.java
@@ -0,0 +1,13 @@
+package com.epicstudios.vectras.Store;
+
+public class DataStore {
+
+ public String itemName;
+ public String itemSize;
+ public String itemData;
+ public String itemIcon;
+ public String itemLink;
+ public String itemPreviewMain;
+ public String itemPreview1;
+ public String itemPreview2;
+}
diff --git a/app/src/main/java/com/epicstudios/vectras/StoreActivity.java b/app/src/main/java/com/epicstudios/vectras/StoreActivity.java
new file mode 100644
index 0000000..0be536a
--- /dev/null
+++ b/app/src/main/java/com/epicstudios/vectras/StoreActivity.java
@@ -0,0 +1,199 @@
+package com.epicstudios.vectras;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.*;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
+
+import com.epicstudios.vectras.R;
+import com.epicstudios.vectras.Blog.AdapterBlog;
+import com.epicstudios.vectras.Blog.DataBlog;
+import com.epicstudios.vectras.Fragment.HomeFragment;
+import com.epicstudios.vectras.Store.AdapterStore;
+import com.epicstudios.vectras.Store.DataStore;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.net.ssl.HttpsURLConnection;
+
+public class StoreActivity extends AppCompatActivity{
+ private RecyclerView mRVStore;
+ private AdapterStore mAdapter;
+ public static LinearLayout noConnectionLayout;
+ public SwipeRefreshLayout pullToRefresh;
+ public static StoreActivity activity;
+ public String Data;
+ @Override
+ protected void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+ setContentView(R.layout.activity_store);
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setDisplayShowHomeEnabled(true);
+ toolbar.setTitle(getString(R.string.app_name));
+
+ activity = this;
+
+ noConnectionLayout = findViewById(R.id.noConnectionLayout);
+ mRVStore = findViewById(R.id.storeRv);
+
+ if (checkConnection(activity)) {
+ new StoreActivity.AsyncLogin().execute();
+ noConnectionLayout.setVisibility(View.GONE);
+ //mRVBlog.setVisibility(View.VISIBLE);
+ } else {
+ noConnectionLayout.setVisibility(View.VISIBLE);
+ //mRVBlog.setVisibility(View.GONE);
+ }
+
+ pullToRefresh = findViewById(R.id.refreshLayout);
+ pullToRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
+ @Override
+ public void onRefresh() {
+ if (checkConnection(activity)) {
+ new StoreActivity.AsyncLogin().execute();
+ } else {
+ noConnectionLayout.setVisibility(View.VISIBLE);
+ pullToRefresh.setRefreshing(false);
+ }
+ }
+ });
+ }
+ public boolean checkConnection(Context context) {
+ final ConnectivityManager connMgr = (ConnectivityManager) context
+ .getSystemService(Context.CONNECTIVITY_SERVICE);
+
+ if (connMgr != null) {
+ NetworkInfo activeNetworkInfo = connMgr.getActiveNetworkInfo();
+
+ if (activeNetworkInfo != null) { // connected to the internet
+ // connected to the mobile provider's data plan
+ if (activeNetworkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
+ // connected to wifi
+ return true;
+ } else
+ return activeNetworkInfo.getType() == ConnectivityManager.TYPE_MOBILE;
+ }
+ }
+ return false;
+ }
+
+ private class AsyncLogin extends AsyncTask {
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+
+ //this method will be running on UI thread
+
+ }
+
+ @Override
+ protected String doInBackground(String... params) {
+ HttpsURLConnection con = null;
+ try {
+ URL u = new URL(Config.storeJson);
+ con = (HttpsURLConnection) u.openConnection();
+
+ con.connect();
+
+ BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream()));
+ StringBuilder sb = new StringBuilder();
+ String line;
+ while ((line = br.readLine()) != null) {
+ sb.append(line + "\n");
+ }
+ br.close();
+ Data = sb.toString();
+
+ return (Data);
+
+ } catch (MalformedURLException ex) {
+ ex.printStackTrace();
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ } finally {
+ if (con != null) {
+ try {
+ con.disconnect();
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+ }
+ return ("unsuccessful!");
+ }
+
+ }
+
+ @Override
+ protected void onPostExecute(String result) {
+
+ //this method will be running on UI thread
+ pullToRefresh.setRefreshing(false);
+
+ noConnectionLayout.setVisibility(View.GONE);
+
+ List data = new ArrayList<>();
+
+ try {
+
+ JSONArray jArray = new JSONArray(Data);
+
+ // Extract data from json and store into ArrayList as class objects
+ for (int i = 0; i < jArray.length(); i++) {
+ JSONObject json_data = jArray.getJSONObject(i);
+ DataStore storeData = new DataStore();
+ storeData.itemName = json_data.getString("item_name");
+ storeData.itemIcon = json_data.getString("item_icon");
+ storeData.itemData = json_data.getString("item_data");
+ storeData.itemSize = json_data.getString("item_size");
+ storeData.itemLink = json_data.getString("item_link");
+ storeData.itemPreviewMain = json_data.getString("item_preview_main");
+ storeData.itemPreview1 = json_data.getString("item_preview_1");
+ storeData.itemPreview2 = json_data.getString("item_preview_2");
+ data.add(storeData);
+ }
+
+ // Setup and Handover data to recyclerview
+
+ } catch (JSONException e) {
+ Toast.makeText(activity, e.toString(), Toast.LENGTH_LONG).show();
+ }
+ mRVStore = (RecyclerView) findViewById(R.id.storeRv);
+ mAdapter = new AdapterStore(activity, data);
+ mRVStore.setAdapter(mAdapter);
+ mRVStore.setLayoutManager(new LinearLayoutManager(activity));
+
+ }
+
+ }
+ @Override
+ public boolean onOptionsItemSelected(@NonNull MenuItem item) {
+ if(item.getItemId()== android.R.id.home){
+ finish();
+ }
+ return super.onOptionsItemSelected(item);
+ }
+}
diff --git a/app/src/main/java/com/epicstudios/vectras/StoreItemActivity.java b/app/src/main/java/com/epicstudios/vectras/StoreItemActivity.java
new file mode 100644
index 0000000..e8fb7b0
--- /dev/null
+++ b/app/src/main/java/com/epicstudios/vectras/StoreItemActivity.java
@@ -0,0 +1,248 @@
+package com.epicstudios.vectras;
+
+import android.app.Dialog;
+import android.app.IntentService;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.*;
+import android.text.Html;
+import android.util.Log;
+import android.view.MenuItem;
+import android.view.View;
+import android.webkit.MimeTypeMap;
+import android.webkit.URLUtil;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.os.PowerManager;
+import androidx.appcompat.app.AlertDialog;
+import com.bumptech.glide.Glide;
+import com.epicstudios.vectras.utils.FileUtils;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.FileOutputStream;
+import java.net.HttpURLConnection;
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+import com.epicstudios.vectras.R;
+import com.epicstudios.vectras.utils.UIUtils;
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import android.os.PowerManager;
+
+public class StoreItemActivity extends AppCompatActivity {
+ public StoreItemActivity activity;
+ public String TAG = "StoreItemActivity";
+ public static String icon, name, size, desc, descStr, prvMain, prv1, prv2, link;
+ public TextView itemName, itemSize, itemDesc;
+ public Button dBtn;
+ public ImageView itemIcon, itemPrvMain, itemPrv1, itemPrv2;
+
+ boolean isImageFitToScreen;
+
+ @Override
+ protected void onCreate(Bundle bundle) {
+ activity = this;
+ super.onCreate(bundle);
+ setContentView(R.layout.activity_store_item);
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setDisplayShowHomeEnabled(true);
+ toolbar.setTitle(getString(R.string.app_name));
+ itemIcon = findViewById(R.id.ivIcon);
+ itemName = findViewById(R.id.textName);
+ itemSize = findViewById(R.id.textSize);
+ dBtn = findViewById(R.id.btn_download);
+ itemDesc = findViewById(R.id.descTxt);
+ itemPrvMain = findViewById(R.id.ivPrvMain);
+ itemPrv1 = findViewById(R.id.ivPrv1);
+ itemPrv2 = findViewById(R.id.ivPrv2);
+
+ dBtn.setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View view) {
+ startDownload();
+ }
+ });
+ itemName.setText(name);
+ itemSize.setText(size);
+
+ Glide.with(this).load(icon).into(itemIcon);
+ Glide.with(this).load(prvMain).into(itemPrvMain);
+ Glide.with(this).load(prv1).into(itemPrv1);
+ Glide.with(this).load(prv2).into(itemPrv2);
+ new Thread(new Runnable() {
+
+ public void run() {
+
+ BufferedReader reader = null;
+ final StringBuilder builder = new StringBuilder();
+
+ try {
+ // Create a URL for the desired page
+ URL url = new URL(desc); //My text file location
+ //First open the connection
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ conn.setConnectTimeout(60000); // timing out in a minute
+
+ BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
+
+ //t=(TextView)findViewById(R.id.TextView1); // ideally do this in onCreate()
+ String str;
+ while ((str = in.readLine()) != null) {
+ builder.append(str);
+ }
+ in.close();
+ } catch (Exception e) {
+ itemDesc.setText("no internet connection");
+ UIUtils.toastLong(StoreItemActivity.this, "check your internet connection");
+ Log.d("VECTRAS", e.toString());
+ }
+
+ //since we are in background thread, to post results we have to go back to ui thread. do the following for that
+
+ StoreItemActivity.this.runOnUiThread(new Runnable() {
+ public void run() {
+ descStr = builder.toString(); // My TextFile has 3 lines
+ itemDesc.setText(Html.fromHtml(descStr));
+ }
+ });
+
+ }
+ }).start();
+ itemPrvMain.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ ImagePrvActivity.linkIv = prvMain;
+ startActivity(new Intent(activity, ImagePrvActivity.class));
+ }
+ });
+ itemPrv1.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ ImagePrvActivity.linkIv = prv1;
+ startActivity(new Intent(activity, ImagePrvActivity.class));
+ }
+ });
+ itemPrv2.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ ImagePrvActivity.linkIv = prv2;
+ startActivity(new Intent(activity, ImagePrvActivity.class));
+ }
+ });
+ }
+
+ public static final int DIALOG_DOWNLOAD_PROGRESS = 0;
+ private ProgressDialog mProgressDialog;
+
+ private void startDownload() {
+ String url = link;
+ new DownloadFileAsync().execute(url);
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ switch (id) {
+ case DIALOG_DOWNLOAD_PROGRESS:
+ mProgressDialog = new ProgressDialog(this, R.style.MainDialogTheme);
+ mProgressDialog.setMessage("Downloading file..");
+ mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
+ mProgressDialog.setCancelable(false);
+ mProgressDialog.show();
+ return mProgressDialog;
+ default:
+ return null;
+ }
+ }
+
+ class DownloadFileAsync extends AsyncTask {
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ showDialog(DIALOG_DOWNLOAD_PROGRESS);
+ }
+
+ @Override
+ protected String doInBackground(String... aurl) {
+ int count;
+
+ try {
+ URL url = new URL(aurl[0]);
+ URLConnection conexion = url.openConnection();
+ conexion.connect();
+
+ int lenghtOfFile = conexion.getContentLength();
+ Log.d(TAG, "Lenght of file: " + lenghtOfFile);
+ String fileName = URLUtil.guessFileName(link,null,null);
+ InputStream input = new BufferedInputStream(url.openStream());
+ OutputStream output = new FileOutputStream(Config.sharedFolder+fileName);
+
+ byte data[] = new byte[1024];
+
+ long total = 0;
+
+ while ((count = input.read(data)) != -1) {
+ total += count;
+ publishProgress("" + (int) ((total * 100) / lenghtOfFile));
+ output.write(data, 0, count);
+ }
+
+ output.flush();
+ output.close();
+ input.close();
+ } catch (Exception e) {
+ }
+ return null;
+
+ }
+
+ protected void onProgressUpdate(String... progress) {
+ Log.d(TAG, progress[0]);
+ mProgressDialog.setProgress(Integer.parseInt(progress[0]));
+ }
+
+ @Override
+ protected void onPostExecute(String unused) {
+ dismissDialog(DIALOG_DOWNLOAD_PROGRESS);
+ AlertDialog ad;
+ ad = new AlertDialog.Builder(activity, R.style.MainDialogTheme).create();
+ ad.setTitle("Downloaded Successfully!");
+ String fileName = URLUtil.guessFileName(link,null,null);
+ ad.setMessage("Downloaded to path: "+Config.sharedFolder+fileName+" boot vectras to check your downloads in QEMU VFAT partition.");
+ ad.setButton(Dialog.BUTTON_POSITIVE, "OK", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ return;
+ }
+ });
+ ad.show();
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(@NonNull MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ finish();
+ }
+ return super.onOptionsItemSelected(item);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/epicstudios/vectras/VectrasApp.java b/app/src/main/java/com/epicstudios/vectras/VectrasApp.java
new file mode 100644
index 0000000..8b4ecd3
--- /dev/null
+++ b/app/src/main/java/com/epicstudios/vectras/VectrasApp.java
@@ -0,0 +1,353 @@
+package com.epicstudios.vectras;
+
+import android.app.Activity;
+import android.app.Application;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.res.Resources;
+import android.graphics.Typeface;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.ViewGroup;
+import android.widget.HorizontalScrollView;
+import android.widget.ScrollView;
+import android.widget.TextView;
+import android.widget.Toast;
+import com.epicstudios.vectras.logger.VectrasStatus;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.Thread.UncaughtExceptionHandler;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class VectrasApp extends Application {
+
+ private static Handler MAIN_HANDLER = new Handler(Looper.getMainLooper());
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ CrashHandler.getInstance().registerGlobal(this);
+ CrashHandler.getInstance().registerPart(this);
+ try {
+ Class.forName("android.os.AsyncTask");
+ } catch (Throwable ignore) {
+ // ignored
+ }
+ }
+
+ public static void write(InputStream input, OutputStream output) throws IOException {
+ byte[] buf = new byte[1024 * 8];
+ int len;
+ while ((len = input.read(buf)) != -1) {
+ output.write(buf, 0, len);
+ }
+ }
+
+ public static void write(File file, byte[] data) throws IOException {
+ File parent = file.getParentFile();
+ if (parent != null && !parent.exists())
+ parent.mkdirs();
+
+ ByteArrayInputStream input = new ByteArrayInputStream(data);
+ FileOutputStream output = new FileOutputStream(file);
+ try {
+ write(input, output);
+ } finally {
+ closeIO(input, output);
+ }
+ }
+
+ public static String toString(InputStream input) throws IOException {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ write(input, output);
+ try {
+ return output.toString("UTF-8");
+ } finally {
+ closeIO(input, output);
+ }
+ }
+
+ public static void closeIO(Closeable... closeables) {
+ for (Closeable closeable : closeables) {
+ try {
+ if (closeable != null)
+ closeable.close();
+ } catch (IOException ignored) {
+ }
+ }
+ }
+
+ public static class CrashHandler {
+
+ public static final UncaughtExceptionHandler DEFAULT_UNCAUGHT_EXCEPTION_HANDLER = Thread
+ .getDefaultUncaughtExceptionHandler();
+
+ private static CrashHandler sInstance;
+
+ private PartCrashHandler mPartCrashHandler;
+
+ public static CrashHandler getInstance() {
+ if (sInstance == null) {
+ sInstance = new CrashHandler();
+ }
+ return sInstance;
+ }
+
+ public void registerGlobal(Context context) {
+ registerGlobal(context, null);
+ }
+
+ public void registerGlobal(Context context, String crashDir) {
+ Thread.setDefaultUncaughtExceptionHandler(
+ new UncaughtExceptionHandlerImpl(context.getApplicationContext(), crashDir));
+ }
+
+ public void unregister() {
+ Thread.setDefaultUncaughtExceptionHandler(DEFAULT_UNCAUGHT_EXCEPTION_HANDLER);
+ }
+
+ public void registerPart(Context context) {
+ unregisterPart(context);
+ mPartCrashHandler = new PartCrashHandler(context.getApplicationContext());
+ MAIN_HANDLER.postAtFrontOfQueue(mPartCrashHandler);
+ }
+
+ public void unregisterPart(Context context) {
+ if (mPartCrashHandler != null) {
+ mPartCrashHandler.isRunning.set(false);
+ mPartCrashHandler = null;
+ }
+ }
+
+ private static class PartCrashHandler implements Runnable {
+
+ private final Context mContext;
+
+ public AtomicBoolean isRunning = new AtomicBoolean(true);
+
+ public PartCrashHandler(Context context) {
+ this.mContext = context;
+ }
+
+ @Override
+ public void run() {
+ while (isRunning.get()) {
+ try {
+ Looper.loop();
+ } catch (final Throwable e) {
+ e.printStackTrace();
+ if (isRunning.get()) {
+ MAIN_HANDLER.post(new Runnable() {
+
+ @Override
+ public void run() {
+ VectrasStatus.logError("[E] "+e.getMessage()+"");
+ }
+ });
+ } else {
+ if (e instanceof RuntimeException) {
+ throw (RuntimeException) e;
+ } else {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private static class UncaughtExceptionHandlerImpl implements UncaughtExceptionHandler {
+
+ private static DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy_MM_dd-HH_mm_ss");
+
+ private final Context mContext;
+
+ private final File mCrashDir;
+
+ public UncaughtExceptionHandlerImpl(Context context, String crashDir) {
+ this.mContext = context;
+ this.mCrashDir = TextUtils.isEmpty(crashDir) ? new File(mContext.getExternalCacheDir(), "crash")
+ : new File(crashDir);
+ }
+
+ @Override
+ public void uncaughtException(Thread thread, Throwable throwable) {
+ try {
+
+ String log = buildLog(throwable);
+ writeLog(log);
+
+ try {
+ Intent intent = new Intent(mContext, CrashActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(Intent.EXTRA_TEXT, log);
+ mContext.startActivity(intent);
+ } catch (Throwable e) {
+ e.printStackTrace();
+ writeLog(e.toString());
+ }
+
+ throwable.printStackTrace();
+ android.os.Process.killProcess(android.os.Process.myPid());
+ System.exit(0);
+
+ } catch (Throwable e) {
+ if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null)
+ DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(thread, throwable);
+ }
+ }
+
+ private String buildLog(Throwable throwable) {
+ String time = DATE_FORMAT.format(new Date());
+
+ String versionName = "unknown";
+ long versionCode = 0;
+ try {
+ PackageInfo packageInfo = mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0);
+ versionName = packageInfo.versionName;
+ versionCode = Build.VERSION.SDK_INT >= 28 ? packageInfo.getLongVersionCode()
+ : packageInfo.versionCode;
+ } catch (Throwable ignored) {
+ }
+
+ LinkedHashMap head = new LinkedHashMap();
+ head.put("Time Of Crash", time);
+ head.put("Device", String.format("%s, %s", Build.MANUFACTURER, Build.MODEL));
+ head.put("Android Version", String.format("%s (%d)", Build.VERSION.RELEASE, Build.VERSION.SDK_INT));
+ head.put("App Version", String.format("%s (%d)", versionName, versionCode));
+ head.put("Kernel", getKernel());
+ head.put("Support Abis",
+ Build.VERSION.SDK_INT >= 21 && Build.SUPPORTED_ABIS != null
+ ? Arrays.toString(Build.SUPPORTED_ABIS)
+ : "unknown");
+ head.put("Fingerprint", Build.FINGERPRINT);
+
+ StringBuilder builder = new StringBuilder();
+
+ for (String key : head.keySet()) {
+ if (builder.length() != 0)
+ builder.append("\n");
+ builder.append(key);
+ builder.append(" : ");
+ builder.append(head.get(key));
+ }
+
+ builder.append("\n\n");
+ builder.append(Log.getStackTraceString(throwable));
+
+ return builder.toString();
+ }
+
+ private void writeLog(String log) {
+ String time = DATE_FORMAT.format(new Date());
+ File file = new File(mCrashDir, "crash_" + time + ".txt");
+ try {
+ write(file, log.getBytes("UTF-8"));
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ }
+
+ private static String getKernel() {
+ try {
+ return VectrasApp.toString(new FileInputStream("/proc/version")).trim();
+ } catch (Throwable e) {
+ return e.getMessage();
+ }
+ }
+ }
+ }
+
+ public static final class CrashActivity extends Activity {
+
+ private String mLog;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setTheme(android.R.style.Theme_DeviceDefault);
+ setTitle("App Crash");
+
+ mLog = getIntent().getStringExtra(Intent.EXTRA_TEXT);
+
+ ScrollView contentView = new ScrollView(this);
+ contentView.setFillViewport(true);
+
+ HorizontalScrollView horizontalScrollView = new HorizontalScrollView(this);
+
+ TextView textView = new TextView(this);
+ int padding = dp2px(16);
+ textView.setPadding(padding, padding, padding, padding);
+ textView.setText(mLog);
+ textView.setTextIsSelectable(true);
+ textView.setTypeface(Typeface.DEFAULT);
+ textView.setLinksClickable(true);
+
+ horizontalScrollView.addView(textView);
+ contentView.addView(horizontalScrollView, ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT);
+
+ setContentView(contentView);
+ }
+
+ private void restart() {
+ Intent intent = getPackageManager().getLaunchIntentForPackage(getPackageName());
+ if (intent != null) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+ }
+ finish();
+ android.os.Process.killProcess(android.os.Process.myPid());
+ System.exit(0);
+ }
+
+ private static int dp2px(float dpValue) {
+ final float scale = Resources.getSystem().getDisplayMetrics().density;
+ return (int) (dpValue * scale + 0.5f);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ menu.add(0, android.R.id.copy, 0, android.R.string.copy).setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.copy:
+ ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
+ cm.setPrimaryClip(ClipData.newPlainText(getPackageName(), mLog));
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public void onBackPressed() {
+ restart();
+ }
+ }
+}
diff --git a/app/src/main/java/com/epicstudios/vectras/VectrasSDLActivity.java b/app/src/main/java/com/epicstudios/vectras/VectrasSDLActivity.java
new file mode 100644
index 0000000..af7cad4
--- /dev/null
+++ b/app/src/main/java/com/epicstudios/vectras/VectrasSDLActivity.java
@@ -0,0 +1,1258 @@
+package com.epicstudios.vectras;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.Configuration;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.SurfaceHolder;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+import android.widget.ScrollView;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+import android.widget.TextView;
+import android.widget.Toast;
+import androidx.appcompat.app.ActionBar;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentTransaction;
+import com.epicstudios.vectras.R;
+import com.epicstudios.vectras.Fragment.ControlsFragment;
+import com.epicstudios.vectras.logger.VectrasStatus;
+import com.epicstudios.vectras.utils.FileUtils;
+import com.epicstudios.vectras.utils.QmpClient;
+import com.epicstudios.vectras.utils.UIUtils;
+import java.io.File;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.egl.EGLSurface;
+import org.json.JSONObject;
+import org.libsdl.app.ClearRenderer;
+import org.libsdl.app.SDLActivity;
+import org.libsdl.app.SDLSurface;
+
+/**
+ * SDL Activity
+ */
+public class VectrasSDLActivity extends SDLActivity {
+
+ public static final int KEYBOARD = 10000;
+ public static final int QUIT = 10001;
+ public static final int HELP = 10002;
+ public static boolean monitorMode = false;
+ private boolean mouseOn = false;
+ private Object lockTime = new Object();
+ private boolean timeQuit = false;
+ private Thread timeListenerThread;
+ private ProgressDialog progDialog;
+ public static Activity activity ;
+
+ public String cd_iso_path = null;
+
+ // HDD
+ public String hda_img_path = null;
+ public String hdb_img_path = null;
+ public String hdc_img_path = null;
+ public String hdd_img_path = null;
+
+ public String fda_img_path = null;
+ public String fdb_img_path = null;
+ public String cpu = null;
+ public String TAG = "VMExecutor";
+
+ public int aiomaxthreads = 1;
+ // Default Settings
+ public int memory = 128;
+ public String bootdevice = null;
+ // net
+ public String net_cfg = "None";
+ public int nic_num = 1;
+ public String vga_type = "std";
+ public String hd_cache = "default";
+ public String nic_driver = null;
+ public String soundcard = null;
+ public String lib = "libvectras.so";
+ public String lib_path = null;
+ public int restart = 0;
+ public String snapshot_name = "Vectras";
+ public int disableacpi = 0;
+ public int disablehpet = 0;
+ public int disabletsc = 0;
+ public static int enablebluetoothmouse = 0;
+ public int enableqmp = 0;
+ public int enablevnc = 0;
+ public String vnc_passwd = null;
+ public int vnc_allow_external = 0;
+ public String qemu_dev = null;
+ public String qemu_dev_value = null;
+ public String base_dir = Config.basefiledir;
+ public String dns_addr = null;
+ private boolean once = true;
+ public static boolean zoomable = false;
+ private String status = null;
+
+ public static Handler handler;
+
+ // This is what SDL runs in. It invokes SDL_main(), eventually
+ private static Thread mSDLThread;
+
+ // EGL private objects
+ private static EGLContext mEGLContext;
+ private static EGLSurface mEGLSurface;
+ private static EGLDisplay mEGLDisplay;
+ private static EGLConfig mEGLConfig;
+ private static int mGLMajor, mGLMinor;
+
+ public static int width;
+ public static int height;
+ public static int screen_width;
+ public static int screen_height;
+
+ private static Activity activity1;
+
+ // public static void showTextInput(int x, int y, int w, int h) {
+ // // Transfer the task to the main thread as a Runnable
+ // // mSingleton.commandHandler.post(new ShowTextInputHandler(x, y, w, h));
+ // }
+
+ public static void singleClick(final MotionEvent event, final int pointer_id) {
+
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ // Log.d("SDL", "Mouse Single Click");
+ SDLActivity.onNativeTouch(event.getDeviceId(), Config.SDL_MOUSE_LEFT, MotionEvent.ACTION_DOWN, 0, 0, 0);
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException ex) {
+ // Log.v("singletap", "Could not sleep");
+ }
+ SDLActivity.onNativeTouch(event.getDeviceId(), Config.SDL_MOUSE_LEFT, MotionEvent.ACTION_UP, 0, 0, 0);
+ }
+ });
+ t.start();
+ }
+
+ private void promptBluetoothMouse(final Activity activity) {
+
+
+ final AlertDialog alertDialog;
+ alertDialog = new AlertDialog.Builder(activity, R.style.MainDialogTheme).create();
+ alertDialog.setTitle("Enable Bluetooth Mouse");
+
+ LinearLayout mLayout = new LinearLayout(this);
+ mLayout.setPadding(20,20,20,20);
+ mLayout.setOrientation(LinearLayout.VERTICAL);
+
+ TextView textView = new TextView(activity);
+ textView.setVisibility(View.VISIBLE);
+ textView.setText(
+ "Step 1: Disable Mouse Acceleration inside the Guest OS.\n\tFor DSL use command: dsl@box:/>xset m 1\n"
+ + "Step 2: Pair your Bluetooth Mouse and press OK!\n"
+ + "Step 3: Move your mouse pointer to all desktop corners to calibrate.\n");
+
+ LinearLayout.LayoutParams textViewParams = new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+ mLayout.addView(textView, textViewParams);
+ alertDialog.setView(mLayout);
+
+ final Handler handler = this.handler;
+
+ // alertDialog.setMessage(body);
+ alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "OK", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ MotionEvent a = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
+ VectrasSDLActivity.singleClick(a, 0);
+ // SDLActivityCommon.onNativeMouseReset(0, 0,
+ // MotionEvent.ACTION_MOVE, vm_width / 2, vm_height / 2, 0);
+ VectrasSDLActivity.enablebluetoothmouse = 1;
+
+ }
+ });
+ alertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancel", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ VectrasSDLActivity.enablebluetoothmouse = 0;
+ return;
+ }
+ });
+ alertDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ VectrasSDLActivity.enablebluetoothmouse = 0;
+ return;
+
+ }
+ });
+ alertDialog.show();
+
+ }
+
+ public static void sendCtrlAtlKey(int code) {
+
+ SDLActivity.onNativeKeyDown(KeyEvent.KEYCODE_CTRL_LEFT);
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ SDLActivity.onNativeKeyDown(KeyEvent.KEYCODE_ALT_LEFT);
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ SDLActivity.onNativeKeyDown(code);
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ SDLActivity.onNativeKeyUp(KeyEvent.KEYCODE_CTRL_LEFT);
+ SDLActivity.onNativeKeyUp(KeyEvent.KEYCODE_ALT_LEFT);
+ SDLActivity.onNativeKeyUp(code);
+ }
+
+ public void stopTimeListener() {
+ Log.v("SaveVM", "Stopping Listener");
+ synchronized (this.lockTime) {
+ this.timeQuit = true;
+ this.lockTime.notifyAll();
+ }
+ }
+
+ public void onDestroy() {
+
+ // Now wait for the SDL thread to quit
+ Log.v("VectrasSDL", "Waiting for SDL thread to quit");
+ if (mSDLThread != null) {
+ try {
+ mSDLThread.join();
+ } catch (Exception e) {
+ Log.v("SDL", "Problem stopping thread: " + e);
+ }
+ mSDLThread = null;
+
+ Log.v("SDL", "Finished waiting for SDL thread");
+ }
+ this.stopTimeListener();
+ super.onDestroy();
+ }
+
+ public void timeListener() {
+ while (timeQuit != true) {
+ status = checkCompletion();
+ // Log.v("timeListener", "Status: " + status);
+ if (status == null
+ || status.equals("")
+ || status.equals("DONE")
+ || status.equals("ERROR")
+ ) {
+ Log.v("Inside", "Saving state is done: " + status);
+ stopTimeListener();
+ return;
+ }
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException ex) {
+ Log.v("SaveVM", "Could not sleep");
+ }
+ }
+ Log.v("SaveVM", "Save state complete");
+
+ }
+
+ public void startTimeListener() {
+ this.stopTimeListener();
+ timeQuit = false;
+ try {
+ Log.v("Listener", "Time Listener Started...");
+ timeListener();
+ synchronized (lockTime) {
+ while (timeQuit == false) {
+ lockTime.wait();
+ }
+ lockTime.notifyAll();
+ }
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ Log.v("SaveVM", "Time listener thread error: " + ex.getMessage());
+ }
+ Log.v("Listener", "Time listener thread exited...");
+
+ }
+
+ public void onHideToolbar(){
+ ActionBar bar = this.getSupportActionBar();
+ if (bar != null) {
+ bar.hide();
+ }
+ }
+
+
+ private void onMouse() {
+
+ MotionEvent a = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
+ VectrasSDLActivity.singleClick(a, 0);
+ // SDLActivityCommon.onNativeMouseReset(0, 0, MotionEvent.ACTION_MOVE,
+ // vm_width / 2, vm_height / 2, 0);
+ //Toast.makeText(this.getApplicationContext(), "Mouse Trackpad Mode enabled", Toast.LENGTH_SHORT).show();
+ }
+
+ private void onCtrlAltDel() {
+
+ SDLActivity.onNativeKeyDown(KeyEvent.KEYCODE_CTRL_RIGHT);
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ SDLActivity.onNativeKeyDown(KeyEvent.KEYCODE_ALT_RIGHT);
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ SDLActivity.onNativeKeyDown(KeyEvent.KEYCODE_FORWARD_DEL);
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ SDLActivity.onNativeKeyUp(KeyEvent.KEYCODE_FORWARD_DEL);
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ SDLActivity.onNativeKeyUp(KeyEvent.KEYCODE_ALT_RIGHT);
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ SDLActivity.onNativeKeyUp(KeyEvent.KEYCODE_CTRL_RIGHT);
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ }
+
+ private void onCtrlC() {
+
+ SDLActivity.onNativeKeyDown(KeyEvent.KEYCODE_CTRL_RIGHT);
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ SDLActivity.onNativeKeyDown(KeyEvent.KEYCODE_C);
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ SDLActivity.onNativeKeyUp(KeyEvent.KEYCODE_C);
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ SDLActivity.onNativeKeyUp(KeyEvent.KEYCODE_CTRL_RIGHT);
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ }
+
+ public void resetVM() {
+
+ new AlertDialog.Builder(VectrasSDLActivity.activity, R.style.MainDialogTheme).setTitle("Reset VM")
+ .setMessage("To avoid any corrupt data make sure you "
+ + "have already shutdown the Operating system from within the VM. Continue?")
+ .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ new Thread(new Runnable() {
+ public void run() {
+ Log.v("SDL", "VM is reset");
+ onRestartVM();
+ }
+ }).start();
+
+ }
+ }).setNegativeButton("No", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ }
+ }).show();
+ }
+
+ public static void stopVM(Context activity, boolean exit) {
+
+ new AlertDialog.Builder(activity, R.style.MainDialogTheme).setTitle("Shutdown VM")
+ .setMessage("To avoid any corrupt data make sure you "
+ + "have already shutdown the Operating system from within the VM. Continue?")
+ .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ new Thread(new Runnable() {
+ public void run() {
+ Log.v("SDL", "VM is stopped");
+ nativeQuit();
+ }
+ }).start();
+
+ }
+ }).setNegativeButton("No", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ }
+ }).show();
+ }
+
+ private static void setStretchToScreen() {
+
+
+ new Thread(new Runnable() {
+ public void run() {
+ VectrasSDLActivity.stretchToScreen = true;
+ VectrasSDLActivity.fitToScreen = false;
+ sendCtrlAtlKey(KeyEvent.KEYCODE_6);
+ }
+ }).start();
+
+ }
+
+ private static void setFitToScreen() {
+
+
+ new Thread(new Runnable() {
+ public void run() {
+ VectrasSDLActivity.stretchToScreen = false;
+ VectrasSDLActivity.fitToScreen = true;
+ sendCtrlAtlKey(KeyEvent.KEYCODE_5);
+
+ }
+ }).start();
+
+ }
+
+ private void setOneToOne() {
+
+ new Thread(new Runnable() {
+ public void run() {
+ VectrasSDLActivity.stretchToScreen = false;
+ VectrasSDLActivity.fitToScreen = false;
+ sendCtrlAtlKey(KeyEvent.KEYCODE_U);
+ }
+ }).start();
+
+ }
+
+ public static void setFullScreen() {
+
+
+ new Thread(new Runnable() {
+ public void run() {
+ sendCtrlAtlKey(KeyEvent.KEYCODE_F);
+ }
+ }).start();
+
+ }
+
+ public static void setZoomIn() {
+
+ new Thread(new Runnable() {
+ public void run() {
+ VectrasSDLActivity.stretchToScreen = false;
+ VectrasSDLActivity.fitToScreen = false;
+ sendCtrlAtlKey(KeyEvent.KEYCODE_4);
+ }
+ }).start();
+
+ }
+
+ public static void setZoomOut() {
+
+
+ new Thread(new Runnable() {
+ public void run() {
+ VectrasSDLActivity.stretchToScreen = false;
+ VectrasSDLActivity.fitToScreen = false;
+ sendCtrlAtlKey(KeyEvent.KEYCODE_3);
+
+ }
+ }).start();
+
+ }
+
+ public static void setZoomable() {
+
+ zoomable = true;
+
+ }
+
+ public static void onMonitor() {
+ new Thread(new Runnable() {
+ public void run() {
+ monitorMode = true;
+ // final KeyEvent altDown = new KeyEvent(downTime, eventTime,
+ // KeyEvent.ACTION_DOWN,
+ // KeyEvent.KEYCODE_2, 1, KeyEvent.META_ALT_LEFT_ON);
+ sendCtrlAtlKey(KeyEvent.KEYCODE_2);
+ // sendCtrlAtlKey(altDown);
+ Log.v("Vectras", "Monitor On");
+ }
+ }).start();
+
+ }
+
+ public static void onVMConsole() {
+ monitorMode = false;
+ sendCtrlAtlKey(KeyEvent.KEYCODE_1);
+ }
+
+ private void onSaveState(final String stateName) {
+ // onMonitor();
+ // try {
+ // Thread.sleep(1000);
+ // } catch (InterruptedException ex) {
+ // Logger.getLogger(VectrasVNCActivity.class.getName()).log(
+ // Level.SEVERE, null, ex);
+ // }
+ // vncCanvas.sendText("savevm " + stateName + "\n");
+ // Toast.makeText(this.getApplicationContext(),
+ // "Please wait while saving VM State", Toast.LENGTH_LONG).show();
+ new Thread(new Runnable() {
+ public void run() {
+ Log.v("SDL", "Saving VM1");
+ nativePause();
+ // VectrasActivity.vmexecutor.saveVM1(stateName);
+
+ nativeResume();
+
+ }
+ }).start();
+
+ // try {
+ // Thread.sleep(1000);
+ // } catch (InterruptedException ex) {
+ // Logger.getLogger(VectrasVNCActivity.class.getName()).log(
+ // Level.SEVERE, null, ex);
+ // }
+ // onSDL();
+ ((MainActivity) MainActivity.activity).saveSnapshotDB(stateName);
+
+ progDialog = ProgressDialog.show(activity, "Please Wait", "Saving VM State...", true);
+ SaveVM a = new SaveVM();
+ a.execute();
+
+ }
+
+ public void saveStateDB(String snapshot_name) {
+
+ }
+
+ private void onSaveState1(String stateName) {
+ // Log.v("onSaveState1", stateName);
+ monitorMode = true;
+ sendCtrlAtlKey(KeyEvent.KEYCODE_2);
+
+ sendText("savevm " + stateName + "\n");
+ saveStateDB(stateName);
+
+ sendCommand(COMMAND_SAVEVM, "vm");
+
+ }
+
+ // FIXME: We need this to able to catch complex characters strings like
+ // grave and send it as text
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (event.getAction() == KeyEvent.ACTION_MULTIPLE && event.getKeyCode() == KeyEvent.KEYCODE_UNKNOWN) {
+ sendText(event.getCharacters().toString());
+ return true;
+ } else
+ return super.dispatchKeyEvent(event);
+
+ }
+
+ private static void sendText(String string) {
+
+ // Log.v("sendText", string);
+ KeyCharacterMap keyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
+ KeyEvent[] keyEvents = keyCharacterMap.getEvents(string.toCharArray());
+ if (keyEvents != null)
+ for (int i = 0; i < keyEvents.length; i++) {
+
+ if (keyEvents[i].getAction() == KeyEvent.ACTION_DOWN) {
+ // Log.v("sendText", "Up: " + keyEvents[i].getKeyCode());
+ SDLActivity.onNativeKeyDown(keyEvents[i].getKeyCode());
+ } else if (keyEvents[i].getAction() == KeyEvent.ACTION_UP) {
+ // Log.v("sendText", "Down: " + keyEvents[i].getKeyCode());
+ SDLActivity.onNativeKeyUp(keyEvents[i].getKeyCode());
+ }
+ }
+ }
+
+ private class SaveVM extends AsyncTask {
+
+ @Override
+ protected Void doInBackground(Void... arg0) {
+ // Log.v("handler", "Save VM");
+ startTimeListener();
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void test) {
+ try {
+ if (progDialog.isShowing()) {
+ progDialog.dismiss();
+ }
+ monitorMode = false;
+ sendCtrlAtlKey(KeyEvent.KEYCODE_1);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ }
+ }
+
+ private void fullScreen() {
+ // AbstractScaling.getById(R.id.itemFitToScreen).setScaleTypeForActivity(
+ // this);
+ // showPanningState();
+ }
+
+ public void promptStateName(final Activity activity) {
+ // Log.v("promptStateName", "ask");
+
+ final AlertDialog alertDialog;
+ alertDialog = new AlertDialog.Builder(activity, R.style.MainDialogTheme).create();
+ alertDialog.setTitle("Snapshot/State Name");
+ final EditText stateView = new EditText(activity);
+
+ stateView.setEnabled(true);
+ stateView.setVisibility(View.VISIBLE);
+ stateView.setSingleLine();
+ alertDialog.setView(stateView);
+
+ // alertDialog.setMessage(body);
+ alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "Create", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+
+ progDialog = ProgressDialog.show(activity, "Please Wait", "Saving VM State...", true);
+ new Thread(new Runnable() {
+ public void run() {
+ // Log.v("promptStateName", a.getText().toString());
+ onSaveState1(stateView.getText().toString());
+ }
+ }).start();
+
+ return;
+ }
+ });
+ alertDialog.show();
+
+ }
+
+ public void pausedVM() {
+
+ MainActivity.vmexecutor.paused = 1;
+ ((MainActivity) MainActivity.activity).saveStateVMDB();
+
+ new AlertDialog.Builder(this, R.style.MainDialogTheme).setTitle("Paused").setMessage("VM is now Paused tap OK to exit")
+ .setPositiveButton("OK", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ if (MainActivity.vmexecutor != null) {
+ MainActivity.vmexecutor.stopvm(0);
+ } else if (activity.getParent() != null) {
+ activity.getParent().finish();
+ } else {
+ activity.finish();
+ }
+ }
+ }).show();
+ }
+
+
+ public void pausedErrorVM(String errStr) {
+
+
+ new AlertDialog.Builder(this, R.style.MainDialogTheme).setTitle("Error").setMessage(errStr)
+ .setPositiveButton("OK", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ String command = QmpClient.cont();
+ String msg = QmpClient.sendCommand(command);
+ }
+ });
+ t.start();
+ }
+ }).show();
+ }
+
+ private String checkCompletion() {
+ String save_state = "";
+ String pause_state = "";
+ if (MainActivity.vmexecutor != null) {
+ // Get the state of saving full disk snapshot
+ //save_state = MainActivity.vmexecutor.get_save_state();
+
+ // Get the state of saving the VM memory only
+ pause_state = MainActivity.vmexecutor.get_pause_state();
+// Log.d(TAG, "save_state = " + save_state);
+// Log.d(TAG, "pause_state = " + pause_state);
+ }
+ if (pause_state.equals("SAVING")) {
+ return pause_state;
+ } else if (pause_state.equals("DONE")) {
+ // FIXME: We wait for 5 secs to complete the state save not ideal
+ // for large OSes
+ // we should find a way to detect when QMP is really done so we
+ // don't get corrupt file states
+ new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ pausedVM();
+ }
+ }, 100);
+ return pause_state;
+
+ } else if (pause_state.equals("ERROR")) {
+ new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ pausedErrorVM("Could not pause VM. View log file for details");
+ }
+ }, 100);
+ return pause_state;
+ }
+ return save_state;
+ }
+
+ private static boolean fitToScreen = Config.enable_qemu_fullScreen;
+ private static boolean stretchToScreen = false; // Start with fitToScreen
+
+ // Setup
+ protected void onCreate(Bundle savedInstanceState) {
+ // Log.v("SDL", "onCreate()");
+ activity = this;
+
+ VectrasSDLActivity.activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ VectrasSDLActivity.activity.getWindow().getDecorView().setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+ |View.SYSTEM_UI_FLAG_FULLSCREEN
+ |View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+
+ );
+
+ super.onCreate(savedInstanceState);
+
+ Log.v("SDL", "Max Mem = " + Runtime.getRuntime().maxMemory());
+ this.handler = commandHandler;
+ this.activity1 = this;
+
+ // So we can call stuff from static callbacks
+ mSingleton = this;
+
+ createUI(0, 0);
+
+ FrameLayout fragmentLayout = findViewById(R.id.fragmentLayout);
+
+ setFragment(new ControlsFragment());
+
+ // new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
+ // @Override
+ // public void run() {
+ // UIUtils.setOrientation(activity);
+ // }
+ // }, 2000);
+
+ UIUtils.showHints(this);
+
+ this.resumeVM();
+
+ }
+
+ protected void setFragment(Fragment fragment) {
+ FragmentTransaction t = getSupportFragmentManager().beginTransaction();
+ t.replace(R.id.fragmentLayout, fragment);
+ t.commit();
+ }
+
+ public SDLSurface getSDLSurface() {
+
+ if (mSurface == null)
+ mSurface = new SDLSurface(activity);
+ return mSurface;
+ }
+
+ private void setScreenSize() {
+
+ // WindowManager wm = (WindowManager) this
+ // .getSystemService(Context.WINDOW_SERVICE);
+ // Display display = wm.getDefaultDisplay();
+ // this.screen_width = display.getWidth();
+ // this.screen_height = display.getHeight();
+
+ }
+
+ private void createUI(int w, int h) {
+
+ // Set up the surface
+ mSurface = getSDLSurface();
+ mSurface.setRenderer(new ClearRenderer());
+
+ // mSurface.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+ // setContentView(mSurface);
+
+ int width = w;
+ int height = h;
+ if (width == 0) {
+ width = RelativeLayout.LayoutParams.WRAP_CONTENT;
+ }
+ if (height == 0) {
+ height = RelativeLayout.LayoutParams.WRAP_CONTENT;
+ }
+
+ setContentView(R.layout.main_sdl);
+
+ RelativeLayout mLayout = (RelativeLayout) findViewById(R.id.sdl);
+ RelativeLayout.LayoutParams surfaceParams = new RelativeLayout.LayoutParams(width, height);
+ surfaceParams.addRule(RelativeLayout.CENTER_HORIZONTAL);
+ surfaceParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
+
+ mLayout.addView(mSurface, surfaceParams);
+
+ SurfaceHolder holder = mSurface.getHolder();
+ setScreenSize();
+ }
+
+ protected void onPause() {
+ Log.v("SDL", "onPause()");
+ super.onPause();
+
+ }
+
+ private void onKeyboard() {
+ InputMethodManager inputMgr = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+ // inputMgr.toggleSoftInput(0, 0);
+ inputMgr.showSoftInput(this.mSurface, InputMethodManager.SHOW_FORCED);
+ }
+
+ public void onSelectMenuVol() {
+
+ final AlertDialog alertDialog;
+ alertDialog = new AlertDialog.Builder(activity).create();
+ alertDialog.setTitle("Volume");
+
+ LinearLayout.LayoutParams volParams = new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+
+ LinearLayout t = createVolumePanel();
+ t.setLayoutParams(volParams);
+
+ ScrollView s = new ScrollView(activity);
+ s.addView(t);
+ alertDialog.setView(s);
+ alertDialog.setButton(Dialog.BUTTON_POSITIVE, "OK", new DialogInterface.OnClickListener() {
+
+ public void onClick(DialogInterface dialog, int which) {
+ alertDialog.cancel();
+ }
+ });
+ alertDialog.show();
+
+ }
+
+ public LinearLayout createVolumePanel() {
+ LinearLayout layout = new LinearLayout (this);
+ layout.setPadding(20, 20, 20, 20);
+
+ LinearLayout.LayoutParams volparams = new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
+
+ SeekBar vol = new SeekBar(this);
+ vol.setMax(maxVolume);
+ int volume = getCurrentVolume();
+ vol.setProgress(volume);
+ vol.setLayoutParams(volparams);
+
+ ((SeekBar) vol).setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
+
+ public void onProgressChanged(SeekBar s, int progress, boolean touch) {
+ setVolume(progress);
+ }
+
+ public void onStartTrackingTouch(SeekBar arg0) {
+
+ }
+
+ public void onStopTrackingTouch(SeekBar arg0) {
+
+ }
+ });
+
+ layout.addView(vol);
+
+ return layout;
+
+ }
+
+ protected void onResume() {
+ Log.v("SDL", "onResume()");
+
+ // if (status == null || status.equals("") || status.equals("DONE"))
+ // SDLActivity.nativeResume();
+
+ // mSurface.reSize();
+ // new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
+ // @Override
+ // public void run() {
+ // UIUtils.setOrientation(activity);
+ // }
+ // }, 1000);
+
+ super.onResume();
+ onMouse();
+ }
+
+ // static void resume() {
+ // Log.v("Resume", "Resuming -> Full Screeen");
+ // if (SDLActivityCommon.fitToScreen)
+ // SDLActivityCommon.setFitToScreen();
+ // if (SDLActivityCommon.stretchToScreen)
+ // SDLActivityCommon.setStretchToScreen();
+ // else
+ // VectrasActivity.vmexecutor.toggleFullScreen();
+ // }
+
+ // Messages from the SDLMain thread
+ static int COMMAND_CHANGE_TITLE = 1;
+ static int COMMAND_SAVEVM = 2;
+
+ public void loadLibraries() {
+ // No loading of .so we do it outside
+ }
+
+ public void onRestartVM() {
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ if (MainActivity.vmexecutor != null) {
+ Log.v(TAG, "Restarting the VM...");
+ MainActivity.vmexecutor.stopvm(1);
+
+ } else {
+ Log.v(TAG, "Not running VM...");
+ }
+ }
+ });
+ t.start();
+ }
+
+ public void promptPause(final Activity activity) {
+
+ final AlertDialog alertDialog;
+ alertDialog = new AlertDialog.Builder(activity).create();
+ alertDialog.setTitle("Pause VM");
+ TextView stateView = new TextView(activity);
+ stateView.setText("This make take a while depending on the RAM size used");
+ stateView.setPadding(10, 10, 10, 10);
+ alertDialog.setView(stateView);
+
+ alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "Pause", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ onPauseVM();
+ return;
+ }
+ });
+ alertDialog.show();
+ }
+
+ public void startSaveVMListener() {
+ stopTimeListener();
+ timeQuit = false;
+ try {
+ Log.v("Listener", "Time Listener Started...");
+ timeListener();
+ synchronized (lockTime) {
+ while (timeQuit == false) {
+ lockTime.wait();
+ }
+ lockTime.notifyAll();
+ }
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ Log.v("SaveVM", "Time listener thread error: " + ex.getMessage());
+ }
+// new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
+// @Override
+// public void run() {
+// Toast.makeText(getApplicationContext(), "VM State saved", Toast.LENGTH_LONG).show();
+// }
+// }, 1000);
+
+ Log.v("Listener", "Time listener thread exited...");
+
+ }
+
+ // Currently not working due to SDL can only support 1 window for Android
+ private void onHMP() {
+ monitorMode = true;
+ sendCtrlAtlKey(KeyEvent.KEYCODE_2);
+
+ }
+ // private void onPauseVM() {
+ // Thread t = new Thread(new Runnable() {
+ // public void run() {
+ // // Delete any previous state file
+ // if (VectrasActivity.vmexecutor.save_state_name != null) {
+ // File file = new File(VectrasActivity.vmexecutor.save_state_name);
+ // if (file.exists()) {
+ // file.delete();
+ // }
+ // }
+ //
+ // VectrasActivity.vmexecutor.paused = 1;
+ // ((VectrasActivity) VectrasActivity.activity).saveStateVMDB();
+ //
+ // onHMP();
+ // new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
+ // @Override
+ // public void run() {
+ // Toast.makeText(getApplicationContext(), "Please wait while saving VM
+ // State", Toast.LENGTH_LONG)
+ // .show();
+ // }
+ // }, 500);
+ // try {
+ // Thread.sleep(500);
+ // } catch (InterruptedException ex) {
+ // Logger.getLogger(VectrasVNCActivity.class.getName()).log(Level.SEVERE,
+ // null, ex);
+ // }
+ //
+ // String commandStop = "stop\n";
+ // for (int i = 0; i < commandStop.length(); i++)
+ // sendText(commandStop.charAt(i) + "");
+ //
+ // String commandMigrate = "migrate fd:"
+ // +
+ // VectrasActivity.vmexecutor.get_fd(VectrasActivity.vmexecutor.save_state_name)
+ // + "\n";
+ // for (int i = 0; i < commandMigrate.length(); i++)
+ // sendText(commandMigrate.charAt(i) + "");
+ //
+ // new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
+ // @Override
+ // public void run() {
+ // VMListener a = new VMListener();
+ // a.execute();
+ // }
+ // }, 0);
+ // }
+ // });
+ // t.start();
+ //
+ // }
+
+ private void onPauseVM() {
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ // Delete any previous state file
+ if ("VECTRAS" != null) {
+ File file = new File("VECTRAS");
+ if (file.exists()) {
+ file.delete();
+ }
+ }
+
+ new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(getApplicationContext(), "Please wait while saving VM State", Toast.LENGTH_SHORT)
+ .show();
+ }
+ }, 0);
+
+ String uri = "fd:" + MainActivity.vmexecutor.get_fd("VECTRAS");
+ String command = QmpClient.stop();
+ String msg = QmpClient.sendCommand(command);
+ if (msg != null)
+ Log.i(TAG, msg);
+ command = QmpClient.migrate(false, false, uri);
+ msg = QmpClient.sendCommand(command);
+ if (msg != null) {
+ Log.i(TAG, msg);
+ processMigrationResponse(msg);
+ }
+
+ // XXX: We cant be sure that the machine state is completed
+ // saving
+ // new Handler(Looper.getMainLooper()).postDelayed(new
+ // Runnable() {
+ // @Override
+ // public void run() {
+ // pausedVM();
+ // }
+ // }, 1000);
+
+ // XXX: Instead we poll to see if migration is complete
+ new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ VMListener a = new VMListener();
+ a.execute();
+ }
+ }, 0);
+ VectrasStatus.logInfo(String.format("VMPaused"));
+ }
+ });
+ t.start();
+
+ }
+
+ private void processMigrationResponse(String response) {
+ String errorStr = null;
+ try {
+ JSONObject object = new JSONObject(response);
+ errorStr = object.getString("error");
+ }catch (Exception ex) {
+
+ }
+ if (errorStr != null) {
+ String descStr = null;
+
+ try {
+ JSONObject descObj = new JSONObject(errorStr);
+ descStr = descObj.getString("desc");
+ }catch (Exception ex) {
+
+ }
+ final String descStr1 = descStr;
+
+ new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ pausedErrorVM(descStr1!=null?descStr1:"Could not pause VM. View log for details");
+ }
+ }, 100);
+
+ }
+
+ }
+
+ private class VMListener extends AsyncTask {
+
+ @Override
+ protected Void doInBackground(Void... arg0) {
+ startSaveVMListener();
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void test) {
+ // if (progDialog.isShowing()) {
+ // progDialog.dismiss();
+ // }
+
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ boolean res = this.mSurface.onTouchProcess(this.mSurface, event);
+ res = this.mSurface.onTouchEventProcess(event);
+ return false;
+ }
+
+ private void resumeVM() {
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ if (MainActivity.vmexecutor.paused == 1) {
+
+ MainActivity.vmexecutor.paused = 0;
+ // new Handler(Looper.getMainLooper()).postDelayed(new
+ // Runnable() {
+ // @Override
+ // public void run() {
+ // Toast.makeText(getApplicationContext(), "Please wait
+ // while resuming VM State",
+ // Toast.LENGTH_SHORT).show();
+ // }
+ // }, 500);
+
+ String command = QmpClient.cont();
+ String msg = QmpClient.sendCommand(command);
+ if (msg != null)
+ Log.i(TAG, msg);
+ new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ onMouse();
+ }
+ }, 500);
+ }
+ VectrasStatus.logInfo(String.format("VMResumed"));
+ }
+ });
+ t.start();
+
+ }
+
+ public static void stop() {
+ // Log.d(TAG, "Pressed Back");
+
+ // super.onBackPressed();
+ stopVM(activity, false);
+ }
+
+ public void onBackPressed() {
+ // Log.d(TAG, "Pressed Back");
+
+ // super.onBackPressed();
+ stopVM(activity, false);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ this.supportInvalidateOptionsMenu();
+ }
+}
diff --git a/app/src/main/java/com/epicstudios/vectras/VectrasSDLSurfaceCompat.java b/app/src/main/java/com/epicstudios/vectras/VectrasSDLSurfaceCompat.java
new file mode 100644
index 0000000..7a4f24f
--- /dev/null
+++ b/app/src/main/java/com/epicstudios/vectras/VectrasSDLSurfaceCompat.java
@@ -0,0 +1,61 @@
+package com.epicstudios.vectras;
+
+public class VectrasSDLSurfaceCompat //extends SDLSurface
+//implements View.OnGenericMotionListener
+{
+// public VectrasSDLSurfaceCompat(Context context) {
+// super(context);
+//// this.setOnGenericMotionListener(this);
+// // TODO Auto-generated constructor stub
+// }
+
+// @Override
+// public boolean onGenericMotion(View v, MotionEvent event) {
+//
+//
+// if (VectrasSDLActivity.enablebluetoothmouse == 0) {
+// return false;
+// }
+// float x = event.getX();
+// float y = event.getY();
+// float p = event.getPressure();
+// int action = event.getAction();
+//
+// if (x < (VectrasSDLActivity.width - VectrasSDLActivity.vm_width) / 2) {
+// return true;
+// } else if (x > VectrasSDLActivity.width
+// - (VectrasSDLActivity.width - VectrasSDLActivity.vm_width) / 2) {
+// return true;
+// }
+//
+// if (y < (VectrasSDLActivity.height - VectrasSDLActivity.vm_height) / 2) {
+// return true;
+// } else if (y > VectrasSDLActivity.height
+// - (VectrasSDLActivity.height - VectrasSDLActivity.vm_height) / 2) {
+// return true;
+// }
+//
+// if (action == MotionEvent.ACTION_HOVER_MOVE) {
+//// Log.v("onGenericMotion", "Moving to (X,Y)=(" + x
+//// * VectrasSDLActivity.width_mult + "," + y
+//// * VectrasSDLActivity.height_mult + ")");
+//
+// VectrasSDLActivity.onNativeTouch(0, 1, MotionEvent.ACTION_MOVE, x
+// * VectrasSDLActivity.width_mult, y * VectrasSDLActivity.height_mult, p);
+// }
+//
+// if (event.getButtonState() == MotionEvent.BUTTON_SECONDARY) {
+//// Log.v("onGenericMotion", "Right Click (X,Y)=" + x
+//// * VectrasSDLActivity.width_mult + "," + y
+//// * VectrasSDLActivity.height_mult + ")");
+// rightClick(event);
+// }
+//
+// // save current
+// old_x = x * VectrasSDLActivity.width_mult;
+// old_y = y * VectrasSDLActivity.height_mult;
+// return true;
+// }
+//
+
+}
diff --git a/app/src/main/java/com/epicstudios/vectras/VectrasService.java b/app/src/main/java/com/epicstudios/vectras/VectrasService.java
new file mode 100644
index 0000000..b3225b1
--- /dev/null
+++ b/app/src/main/java/com/epicstudios/vectras/VectrasService.java
@@ -0,0 +1,223 @@
+package com.epicstudios.vectras;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.net.wifi.WifiManager;
+import android.os.Build;
+import android.net.wifi.WifiManager.WifiLock;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import androidx.core.app.TaskStackBuilder;
+import com.epicstudios.vectras.Config;
+import android.util.Log;
+import androidx.core.app.NotificationCompat;
+import com.epicstudios.vectras.R;
+import com.epicstudios.vectras.jni.VMExecutor;
+import com.epicstudios.vectras.logger.VectrasStatus;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+public class VectrasService extends Service {
+
+ private static final String TAG = "VectrasService";
+ private static WifiLock mWifiLock;
+ public static VectrasService service;
+ private static WakeLock mWakeLock;
+ public static final int notifID = 1000;
+ private NotificationCompat.Builder builder;
+ private Notification mNotification;
+
+ @Override
+ public IBinder onBind(Intent arg0) {
+
+ return null;
+ }
+
+ public static VMExecutor executor;
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ final String action = intent.getAction();
+ final Bundle b = intent.getExtras();
+ final int ui = b.getInt("ui", 0);
+
+ if (action.equals(Config.ACTION_START)) {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ //fetching notifications from server
+ //if there is notifications then call this method
+ setUpAsForeground("VM Running in Background");
+ }
+ }).start();
+ setUpAsForeground("VM Running in Background");
+ VectrasStatus.logInfo(String.format("VM Running in Background"));
+
+ startLogging();
+
+ Log.v(TAG, "Starting the VM");
+ VectrasStatus.logInfo(String.format("Starting the VM"));
+ executor.loadNativeLibs();
+
+ setupLocks();
+
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+
+ String res = executor.startvm();
+ Log.d(TAG, res);
+ if (VectrasSDLActivity.activity != null)
+ VectrasSDLActivity.activity.finish();
+
+ releaseLocks();
+ stopService();
+ MainActivity.activity.cleanup();
+ // NotificationManager notificationManager = (NotificationManager) service.getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
+ // notificationManager.cancelAll();
+ //service.stopSelf();
+ // VectrasActivity.sendHandlerMessage(VectrasActivity.OShandler,
+ // Const.VM_STOPPED);
+
+ }
+ });
+ t.start();
+
+ }
+
+ // Don't restart if killed
+ return START_NOT_STICKY;
+ }
+
+ private void setUpAsForeground(String text) {
+
+ Intent notificationIntent = new Intent(this, VectrasSDLActivity.class);
+ PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE);
+
+ Intent stopnotificationIntent = new Intent(this, VectrasService.class);
+ stopnotificationIntent.setAction(Config.ACTION_STOP);
+ PendingIntent Intent = PendingIntent.getService(this, 0, stopnotificationIntent, PendingIntent.FLAG_IMMUTABLE);
+
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(this, Config.notificationChannelID)
+ .setSmallIcon(R.mipmap.ic_launcher)
+ .setContentTitle(getString(R.string.app_name))
+ .setContentText(text)
+ .setPriority(NotificationCompat.PRIORITY_HIGH)
+ .setColor(Color.BLUE)
+ .setDefaults(Notification.DEFAULT_ALL)
+ .setFullScreenIntent(pendingIntent, true)
+ .setAutoCancel(true)
+ .setContentIntent(pendingIntent)
+ .addAction(android.R.drawable.ic_media_pause, "Stop VM", Intent);
+
+
+ Notification notification = builder.build();
+
+ if (Build.VERSION.SDK_INT >= 26) {
+ NotificationChannel channel = new NotificationChannel(Config.notificationChannelID, Config.notificationChannelID, NotificationManager.IMPORTANCE_DEFAULT);
+ channel.setDescription(getString(R.string.app_name));
+ NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.createNotificationChannel(channel);
+ }
+ startForeground(1000, notification);
+ }
+
+ private void stopForegroundService() {
+ MainActivity.vmexecutor.stopvm(0);
+ stopForeground(true);
+ stopSelf();
+ }
+
+ public static StringBuilder log = null;
+
+ private void startLogging() {
+
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+
+ FileOutputStream os = null;
+ File logFile = new File(Config.logFilePath);
+ if (logFile.exists()) {
+ logFile.delete();
+ }
+ try {
+ Runtime.getRuntime().exec("logcat -c");
+ Process process = Runtime.getRuntime().exec("logcat v main");
+ os = new FileOutputStream(logFile);
+ BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+
+ log = new StringBuilder();
+ String line = "";
+ while ((line = bufferedReader.readLine()) != null) {
+ log.append(line + "\n");
+ os.write((line + "\n").getBytes("UTF-8"));
+ os.flush();
+ }
+ } catch (IOException e) {
+
+ } finally {
+ try {
+ os.flush();
+ os.close();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ }
+
+ }
+ });
+ t.start();
+ }
+
+ @Override
+ public void onCreate() {
+ Log.d(TAG, "debug: Creating " + TAG);
+ VectrasStatus.logInfo(String.format("debug: Creating " + TAG));
+ service = this;
+
+ }
+
+ private void setupLocks() {
+
+ mWifiLock = ((WifiManager) service.getApplicationContext().getSystemService(Context.WIFI_SERVICE))
+ .createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "WIFI_VECTRAS");
+ mWifiLock.setReferenceCounted(false);
+
+ PowerManager pm = (PowerManager) service.getApplicationContext().getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WAKELOCK_VECTRAS");
+ mWakeLock.setReferenceCounted(false);
+ }
+
+ private static void releaseLocks() {
+
+ if (mWifiLock != null && mWifiLock.isHeld()) {
+ Log.d(TAG, "Release Wifi lock...");
+ mWifiLock.release();
+ }
+
+ if (mWakeLock != null && mWakeLock.isHeld()) {
+ Log.d(TAG, "Release Wake lock...");
+ mWakeLock.release();
+ }
+
+ }
+
+ public static void stopService() {
+ service.stopForeground(true);
+ service.stopSelf();
+ }
+
+}
diff --git a/app/src/main/java/com/epicstudios/vectras/adapter/LogsAdapter.java b/app/src/main/java/com/epicstudios/vectras/adapter/LogsAdapter.java
new file mode 100644
index 0000000..ff91c9b
--- /dev/null
+++ b/app/src/main/java/com/epicstudios/vectras/adapter/LogsAdapter.java
@@ -0,0 +1,385 @@
+package com.epicstudios.vectras.adapter;
+
+import androidx.lifecycle.Observer;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver;
+import com.epicstudios.vectras.logger.LogItem;
+import java.util.Collections;
+import android.widget.TextView;
+import android.content.Context;
+import android.view.ViewGroup;
+import android.view.LayoutInflater;
+import java.util.Vector;
+import android.database.DataSetObserver;
+import java.util.Date;
+import android.text.format.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Locale;
+import android.os.Message;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.View;
+import com.epicstudios.vectras.R;
+import android.text.Html;
+import android.view.MotionEvent;
+import com.epicstudios.vectras.logger.VectrasStatus;
+
+public class LogsAdapter extends RecyclerView.Adapter
+ implements VectrasStatus.LogListener ,Handler.Callback,
+ View.OnTouchListener
+{
+ private static final int MESSAGE_NEWLOG = 0;
+
+ private static final int MESSAGE_CLEARLOG = 1;
+
+ private static final int MESSAGE_NEWTS = 2;
+ private static final int MESSAGE_NEWLOGLEVEL = 3;
+
+ public static final int TIME_FORMAT_NONE = 0;
+ public static final int TIME_FORMAT_SHORT = 1;
+ public static final int TIME_FORMAT_ISO = 2;
+ private static final int MAX_STORED_LOG_ENTRIES = 1000;
+
+ private Vector allEntries = new Vector<>();
+
+ private Vector currentLevelEntries = new Vector();
+
+ private Handler mHandler;
+ private Context mContext;
+ private OnItemClickListener itemClickListener;
+ private LinearLayoutManager mLinearLayoutManager;
+
+ private Vector observers = new Vector<>();
+
+ private int mTimeFormat = -100;
+ private int mLogLevel = 3;
+ private boolean mLockAutoScroll = false;
+
+
+ /**
+ * Interfaces
+ */
+
+ public interface OnItemClickListener
+ {
+ void onItemClick(View view, int position, String logText);
+ void onItemLongClick(View view, int position, String logText);
+ }
+
+ public class logViewHolder extends RecyclerView.ViewHolder
+ {
+ TextView textLog;
+
+ logViewHolder(View itemView)
+ {
+ super(itemView);
+
+ this.textLog = itemView.findViewById(R.id.textLog);
+ }
+ }
+
+
+ public LogsAdapter(LinearLayoutManager layoutManager,
+ Context context)
+ {
+ this.mContext = context;
+ this.mLinearLayoutManager = layoutManager;
+
+ setLogLevel(VectrasStatus.LogLevel.DEBUG.getInt());
+
+ initLogBuffer();
+ if (mHandler == null)
+ {
+ mHandler = new Handler(this);
+ }
+
+ VectrasStatus.addLogListener(this);
+ }
+
+ public void setOnItemClickListener(OnItemClickListener listener) {
+ this.itemClickListener = listener;
+ }
+
+ private void initLogBuffer()
+ {
+ allEntries.clear();
+ Collections.addAll(allEntries, VectrasStatus.getlogbuffer());
+ initCurrentMessages();
+ }
+
+ private void initCurrentMessages()
+ {
+ currentLevelEntries.clear();
+ for (LogItem li : allEntries)
+ {
+ if (li.getLogLevel().getInt() <= mLogLevel)
+ currentLevelEntries.add(li);
+ }
+ }
+
+ @Override
+ public logViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
+ {
+ Context context = parent.getContext();
+ LayoutInflater inflater = LayoutInflater.from(context);
+
+ View logView = inflater.inflate(R.layout.list_item_log,
+ parent, false);
+ logView.setOnTouchListener(this);
+
+ return new logViewHolder(logView);
+ }
+
+ @Override
+ public void onBindViewHolder(final logViewHolder viewHolder,
+ final int position)
+ {
+ final String text;
+
+ try
+ {
+ LogItem logItem = currentLevelEntries.get(position);
+ String msg = logItem.getString(mContext);
+ String time = getTime(logItem, mTimeFormat);
+ text = (!time.isEmpty() ? String.format("[%s] ", time) : "") + msg;
+ viewHolder.textLog.setText(Html.fromHtml(text));
+ }
+ catch (Exception e)
+ {
+ VectrasStatus.logException(e);
+ return;
+ }
+
+ viewHolder.textLog.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v)
+ {
+ if (itemClickListener != null)
+ itemClickListener.onItemClick(v, position, text);
+ }
+ });
+
+ viewHolder.textLog.setOnLongClickListener(new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v)
+ {
+ if (itemClickListener != null)
+ itemClickListener.onItemLongClick(v, position, text);
+ return true;
+ }
+ });
+ }
+
+ @Override
+ public void registerAdapterDataObserver(RecyclerView.AdapterDataObserver observer)
+ {
+ super.registerAdapterDataObserver(observer);
+ observers.add(observer);
+ }
+
+ @Override
+ public void unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver observer)
+ {
+ super.unregisterAdapterDataObserver(observer);
+ observers.remove(observer);
+ }
+
+ @Override
+ public int getItemCount()
+ {
+ return currentLevelEntries.size();
+ }
+
+ @Override
+ public long getItemId(int position)
+ {
+ return ((Object) currentLevelEntries.get(position)).hashCode();
+ }
+
+ public boolean isEmpty()
+ {
+ return currentLevelEntries.isEmpty();
+ }
+
+ @Override
+ public void onAttachedToRecyclerView(
+ RecyclerView recyclerView)
+ {
+ super.onAttachedToRecyclerView(recyclerView);
+ }
+
+ @Override
+ public boolean onTouch(View p1, MotionEvent event)
+ {
+ // aqui deveria pausar autoscroll
+ /*int action = event.getAction();
+
+ if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_MOVE){
+ mLockAutoScroll = true;
+
+ return true;
+ }
+
+ mLockAutoScroll = false;*/
+
+ return false;
+ }
+
+ private String getTime(LogItem le, int time)
+ {
+ if (time != TIME_FORMAT_NONE)
+ {
+ Date d = new Date(le.getLogtime());
+ java.text.DateFormat timeformat;
+ if (time == TIME_FORMAT_SHORT)
+ timeformat = new SimpleDateFormat("HH:mm a");
+ else
+ timeformat = DateFormat.getTimeFormat(mContext);
+
+ return timeformat.format(d);
+
+ }
+ else
+ {
+ return "";
+ }
+ }
+
+
+ /**
+ * Handler implementação
+ */
+
+ @Override
+ public boolean handleMessage(Message msg)
+ {
+ // We have been called
+ if (msg.what == MESSAGE_NEWLOG)
+ {
+ LogItem logMessage = msg.getData().getParcelable("logmessage");
+ if (addLogMessage(logMessage))
+ {
+
+ for (AdapterDataObserver observer : observers)
+ {
+ observer.onChanged();
+ }
+
+ if (!mLockAutoScroll)
+ scrollToLastPosition();
+ }
+ }
+ else if (msg.what == MESSAGE_CLEARLOG)
+ {
+ for (AdapterDataObserver observer : observers)
+ {
+ observer.onChanged();
+ }
+ initLogBuffer();
+ }
+ else if (msg.what == MESSAGE_NEWTS)
+ {
+ for (AdapterDataObserver observer : observers)
+ {
+ observer.onChanged();
+ }
+ }
+ else if (msg.what == MESSAGE_NEWLOGLEVEL)
+ {
+ initCurrentMessages();
+
+ for (AdapterDataObserver observer : observers)
+ {
+ observer.onChanged();
+ }
+
+ }
+
+ return true;
+ }
+
+
+ /**
+ * @param logmessage
+ * @return True if the current entries have changed
+ */
+ private boolean addLogMessage(LogItem logmessage)
+ {
+ allEntries.add(logmessage);
+
+ if (allEntries.size() > MAX_STORED_LOG_ENTRIES)
+ {
+ Vector oldAllEntries = allEntries;
+ allEntries = new Vector(allEntries.size());
+ for (int i = 50; i < oldAllEntries.size(); i++)
+ {
+ allEntries.add(oldAllEntries.elementAt(i));
+ }
+ initCurrentMessages();
+ return true;
+ }
+ else
+ {
+ if (logmessage.getLogLevel().getInt() <= mLogLevel)
+ {
+ currentLevelEntries.add(logmessage);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ }
+
+ public LogItem getItem(int position)
+ {
+ return currentLevelEntries.get(position);
+ }
+
+ public void clearLog()
+ {
+ // Actually is probably called from GUI Thread as result of the user
+ // pressing a button. But better safe than sorry
+ VectrasStatus.clearLog();
+ }
+
+ public void scrollToLastPosition()
+ {
+ // scroll para ultima mensagem
+ mLinearLayoutManager.scrollToPosition(
+ mLinearLayoutManager.getItemCount() - 1);
+ }
+
+ public void setLogLevel(int level) {
+ mLogLevel = level;
+ }
+
+
+ /**
+ * LogListener
+ */
+
+ @Override
+ public void newLog(LogItem logMessage)
+ {
+ Message msg = Message.obtain();
+
+ assert (msg != null);
+ msg.what = MESSAGE_NEWLOG;
+
+ Bundle bundle = new Bundle();
+ bundle.putParcelable("logmessage", logMessage);
+
+ msg.setData(bundle);
+ mHandler.sendMessage(msg);
+ }
+
+ @Override
+ public void onClear()
+ {
+ mHandler.sendEmptyMessage(MESSAGE_CLEARLOG);
+ }
+
+}
diff --git a/app/src/main/java/com/epicstudios/vectras/jni/VMExecutor.java b/app/src/main/java/com/epicstudios/vectras/jni/VMExecutor.java
new file mode 100644
index 0000000..b737a22
--- /dev/null
+++ b/app/src/main/java/com/epicstudios/vectras/jni/VMExecutor.java
@@ -0,0 +1,185 @@
+package com.epicstudios.vectras.jni;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import com.epicstudios.vectras.Config;
+import com.epicstudios.vectras.MainActivity;
+import com.epicstudios.vectras.VectrasService;
+import com.epicstudios.vectras.logger.VectrasStatus;
+import com.epicstudios.vectras.utils.FileUtils;
+import com.epicstudios.vectras.utils.RamInfo;
+import com.epicstudios.vectras.utils.UIUtils;
+import java.io.File;
+import android.os.Environment;
+
+public class VMExecutor {
+
+ private static Context context;
+ private final String TAG = "VMExecutor";
+ public int paused;
+ public String base_dir = Config.basefiledir;
+ public boolean busy = false;
+ public boolean libLoaded = false;
+
+ // Default Settings
+ private String libqemu = Config.libqemupath;
+ private int restart = 0;
+ private int width;
+ private int height;
+
+ public static final File fileExtra = new File(Config.basefiledir + "config_extra.txt");
+ public static final String extraParams = FileUtils.readFromFile(MainActivity.activity, fileExtra);
+
+ /**
+ * @throws Exception
+ */
+ public VMExecutor(Context context) throws Exception {
+
+ }
+
+ public void loadNativeLibs() {
+ libLoaded = true;
+ }
+
+ // Load the shared lib
+ private void loadNativeLib(String lib, String destDir) {
+ if (true) {
+ String libLocation = destDir + "/" + lib;
+ try {
+ System.load(libLocation);
+ } catch (Exception ex) {
+ VectrasStatus.logInfo(String.format("failed to load native library: "+ex));
+ Log.e("JNIExample", "failed to load native library: " + ex);
+ }
+ }
+
+ }
+
+ public String startvm() {
+
+ String res = null;
+
+ Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
+ @Override
+ public void uncaughtException(Thread thread, Throwable e) {
+ e.printStackTrace();
+ VectrasStatus.logInfo(String.format("Vectras Uncaught Exception: "+e.toString()));
+ Log.e(TAG, "Vectras Uncaught Exception: " + e.toString());
+ }
+ });
+
+ try {
+ String Extras = null;
+ if (extraParams == "null") {
+ Extras = null;
+ } else {
+ Extras = extraParams;
+ }
+ if (extraParams == "error") {
+ VectrasStatus.logInfo(String.format(" QEMU Params Error: "+extraParams));
+ return "error";
+ }
+ VectrasStatus.logInfo(String.format("QEMU PARAMS: "+extraParams+" -m "+ RamInfo.vectrasMemory()));
+ res = start(this.libqemu, MainActivity.params, extraParams+" -m "+ RamInfo.vectrasMemory(), paused, "VECTRAS");
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ VectrasStatus.logInfo(String.format("Vectras Exception: " + ex.toString()));
+ Log.e(TAG, "Vectras Exception: " + ex.toString());
+ }
+ return res;
+ }
+
+ //JNI Methods
+ public native String start(String lib_path, Object[] params, String params_extra, int paused, String save_state);
+
+ protected native String stop(int restart);
+
+ protected native void scale();
+
+ protected native String getpausestate();
+
+ public native String pausevm(String uri);
+
+ protected native void resize();
+
+ protected native void togglefullscreen();
+
+ protected native String getstate();
+
+ public String startvm(Context context) {
+ VectrasService.executor = this;
+ Intent i = new Intent(Config.ACTION_START, null, context, VectrasService.class);
+ Bundle b = new Bundle();
+ // b.putString("machine_type", this.machine_type);
+ b.putInt("ui", 1);
+ i.putExtras(b);
+ context.startService(i);
+
+ VectrasStatus.logInfo(String.format("VMStarted"));
+ Log.v(TAG, "startVMService");
+ return "startVMService";
+
+ }
+
+ public String stopvm(int restart) {
+ Log.v(TAG, "Stopping the VM");
+ VectrasStatus.logInfo(String.format("Stopping the VM"));
+ this.restart = restart;
+ return this.stop(this.restart);
+ }
+
+ public String resumevm() {
+ // Set to delete previous snapshots after vm resumed
+ Log.v(TAG, "Resume the VM");
+ VectrasStatus.logInfo(String.format("Resume the VM"));
+ String res = startvm();
+ Log.d(TAG, res);
+ return res;
+ }
+
+ public String get_pause_state() {
+ if (this.libLoaded)
+ return this.getpausestate();
+ return "";
+ }
+
+ public String get_state() {
+ return this.getstate();
+ }
+
+ public void resizeScreen() {
+
+ this.resize();
+
+ }
+
+ public void toggleFullScreen() {
+
+ this.togglefullscreen();
+
+ }
+
+ public void screenScale(int width, int height) {
+
+ this.width = width;
+ this.height = height;
+
+ this.scale();
+
+ }
+
+ public int get_fd(String path) {
+ int fd = FileUtils.get_fd(context, path);
+ return fd;
+
+ }
+
+ public int close_fd(int fd) {
+ int res = FileUtils.close_fd(fd);
+ return res;
+
+ }
+
+}
diff --git a/app/src/main/java/com/epicstudios/vectras/logger/LogItem.java b/app/src/main/java/com/epicstudios/vectras/logger/LogItem.java
new file mode 100644
index 0000000..6e11df7
--- /dev/null
+++ b/app/src/main/java/com/epicstudios/vectras/logger/LogItem.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+
+package com.epicstudios.vectras.logger;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.epicstudios.vectras.R;
+import android.content.Context;
+import java.util.Locale;
+import java.util.UnknownFormatConversionException;
+import java.util.FormatFlagsConversionMismatchException;
+import android.annotation.SuppressLint;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import android.content.pm.PackageManager;
+import java.io.ByteArrayInputStream;
+import android.content.pm.Signature;
+import java.security.MessageDigest;
+import java.util.Arrays;
+import android.content.pm.PackageInfo;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+
+/**
+ * Created by arne on 24.04.16.
+ */
+public class LogItem implements Parcelable {
+
+ private Object[] mArgs = null;
+ private String mMessage = null;
+ private int mResourceId;
+ // Default log priority
+ VectrasStatus.LogLevel mLevel = VectrasStatus.LogLevel.INFO;
+ private long logtime = System.currentTimeMillis();
+ private int mVerbosityLevel = -1;
+
+ public LogItem(int resId, Object... args) {
+ mResourceId = resId;
+ mArgs = args;
+ }
+
+ public LogItem(VectrasStatus.LogLevel loglevel, int verblevel, String msg) {
+ mLevel = loglevel;
+ mMessage = msg;
+ mVerbosityLevel = verblevel;
+ }
+
+ public LogItem(VectrasStatus.LogLevel level, int resId, Object... args) {
+ mLevel = level;
+ mResourceId = resId;
+ mArgs = args;
+ }
+
+ public LogItem(VectrasStatus.LogLevel loglevel, String msg) {
+ mLevel = loglevel;
+ mMessage = msg;
+ }
+
+
+ public LogItem(VectrasStatus.LogLevel loglevel, int ressourceId) {
+ mResourceId = ressourceId;
+ mLevel = loglevel;
+ }
+
+ @Override
+ public String toString() {
+ return getString(null);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeArray(mArgs);
+ dest.writeString(mMessage);
+ dest.writeInt(mResourceId);
+ dest.writeInt(mLevel.getInt());
+
+ dest.writeLong(logtime);
+ }
+
+ public LogItem(Parcel in) {
+ mArgs = in.readArray(Object.class.getClassLoader());
+ mMessage = in.readString();
+ mResourceId = in.readInt();
+ mLevel = VectrasStatus.LogLevel.getEnumByValue(in.readInt());
+ logtime = in.readLong();
+ }
+
+ public static final Creator CREATOR
+ = new Creator() {
+ public LogItem createFromParcel(Parcel in) {
+ return new LogItem(in);
+ }
+
+ public LogItem[] newArray(int size) {
+ return new LogItem[size];
+ }
+ };
+
+ public VectrasStatus.LogLevel getLogLevel() {
+ return mLevel;
+ }
+
+ public long getLogtime() {
+ return logtime;
+ }
+
+ public String getMessage() {
+ return mMessage;
+ }
+
+ public String getString(Context c) {
+ try {
+ if (mMessage != null) {
+ return mMessage;
+ } else {
+ if (c != null) {
+ if (mResourceId == R.string.app_name)
+ return getAppInfoString(c);
+ else if (mArgs == null)
+ return c.getString(mResourceId);
+ else
+ return c.getString(mResourceId, mArgs);
+ } else {
+ String str = String.format(Locale.ENGLISH, "Log (no context) resid %d", mResourceId);
+ if (mArgs != null)
+ str += join("|", mArgs);
+
+ return str;
+ }
+ }
+ } catch (UnknownFormatConversionException e) {
+ if (c != null)
+ throw new UnknownFormatConversionException(e.getLocalizedMessage() + getString(null));
+ else
+ throw e;
+ } catch (java.util.FormatFlagsConversionMismatchException e) {
+ if (c != null)
+ throw new FormatFlagsConversionMismatchException(e.getLocalizedMessage() + getString(null), e.getConversion());
+ else
+ throw e;
+ }
+ }
+
+ //private String listb = "";
+
+ // The lint is wrong here
+ @SuppressLint("StringFormatMatches")
+ private String getAppInfoString(Context c) {
+ c.getPackageManager();
+ String apksign = "error getting package signature";
+
+ String version = "error getting version";
+ try {
+ @SuppressLint("PackageManagerGetSignatures")
+ Signature raw = c.getPackageManager().getPackageInfo(c.getPackageName(), PackageManager.GET_SIGNATURES).signatures[0];
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ X509Certificate cert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(raw.toByteArray()));
+ MessageDigest md = MessageDigest.getInstance("SHA-1");
+ byte[] der = cert.getEncoded();
+ md.update(der);
+ byte[] digest = md.digest();
+ if (!Arrays.equals(digest, VectrasStatus.oficialkey) && !Arrays.equals(digest, VectrasStatus.oficialdebugkey))
+ apksign = "";
+ PackageInfo packageinfo = c.getPackageManager().getPackageInfo(c.getPackageName(), 0);
+ version = String.format("%s Projeto %d", packageinfo.versionName, packageinfo.versionCode);
+
+ } catch (PackageManager.NameNotFoundException | CertificateException |
+ NoSuchAlgorithmException ignored) {
+ }
+
+ /* Object[] argsext = Arrays.copyOf(mArgs, mArgs.length);
+ argsext[argsext.length - 1] = apksign;
+ argsext[argsext.length - 2] = version;*/
+
+ return c.getString(R.string.app_name, version, apksign);
+
+ }
+
+ // TextUtils.join will cause not macked exeception in tests ....
+ public static String join(CharSequence delimiter, Object[] tokens) {
+ StringBuilder sb = new StringBuilder();
+ boolean firstTime = true;
+ for (Object token : tokens) {
+ if (firstTime) {
+ firstTime = false;
+ } else {
+ sb.append(delimiter);
+ }
+ sb.append(token);
+ }
+ return sb.toString();
+ }
+
+ public int getVerbosityLevel() {
+ if (mVerbosityLevel == -1) {
+ // Hack:
+ // For message not from OpenVPN, report the status level as log level
+ return mLevel.getInt();
+ }
+ return mVerbosityLevel;
+ }
+}
diff --git a/app/src/main/java/com/epicstudios/vectras/logger/VMStatus.java b/app/src/main/java/com/epicstudios/vectras/logger/VMStatus.java
new file mode 100644
index 0000000..d6e986b
--- /dev/null
+++ b/app/src/main/java/com/epicstudios/vectras/logger/VMStatus.java
@@ -0,0 +1,33 @@
+package com.epicstudios.vectras.logger;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public enum VMStatus implements Parcelable {
+ V_STARTVM,
+ V_STOPVM,
+ UNKNOWN_LEVEL;
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(ordinal());
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public VMStatus createFromParcel(Parcel in) {
+ return VMStatus.values()[in.readInt()];
+ }
+
+ @Override
+ public VMStatus[] newArray(int size) {
+ return new VMStatus[size];
+ }
+ };
+}
+
diff --git a/app/src/main/java/com/epicstudios/vectras/logger/VectrasStatus.java b/app/src/main/java/com/epicstudios/vectras/logger/VectrasStatus.java
new file mode 100644
index 0000000..a77e7da
--- /dev/null
+++ b/app/src/main/java/com/epicstudios/vectras/logger/VectrasStatus.java
@@ -0,0 +1,355 @@
+package com.epicstudios.vectras.logger;
+
+import static android.provider.Settings.System.getString;
+
+import java.io.StringWriter;
+import java.io.PrintWriter;
+import java.util.LinkedList;
+import java.util.Vector;
+import android.os.Build;
+import com.epicstudios.vectras.R;
+import android.content.Intent;
+import android.content.Context;
+import android.os.Message;
+import java.io.File;
+import android.os.HandlerThread;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import java.util.Iterator;
+import java.util.Locale;
+
+public class VectrasStatus
+{
+ private static final LinkedList logbuffer;
+
+ private static Vector logListener;
+ private static Vector stateListener;
+
+ private static VMStatus mLastLevel = VMStatus.V_STOPVM;
+
+ private static String mLaststatemsg = "";
+ private static String mLaststate = "NOPROCESS";
+ private static int mLastStateresid = R.string.noproccesses;
+ private static Intent mLastIntent = null;
+
+
+ static final int MAXLOGENTRIES = 1000;
+
+ public static boolean isVMActive() {
+ return mLastLevel != VMStatus.V_STOPVM && !(mLastLevel == VMStatus.V_STOPVM);
+ }
+
+ public static String getLastState() {
+ return mLaststate;
+ }
+
+ public static String getLastCleanLogMessage(Context c) {
+ String message = mLaststatemsg;
+ switch (mLastLevel) {
+ case V_STARTVM:
+ String[] parts = mLaststatemsg.split(",");
+ if (parts.length >= 7)
+ message = String.format(Locale.US, "%s %s", parts[1], parts[6]);
+ break;
+ }
+
+ while (message.endsWith(","))
+ message = message.substring(0, message.length() - 1);
+
+ String status = mLaststate;
+ if (status.equals("NOPROCESS"))
+ return message;
+
+ String prefix = c.getString(mLastStateresid);
+ if (mLastStateresid == R.string.unknownstate)
+ message = status + message;
+ if (message.length() > 0)
+ prefix += ": ";
+
+ return prefix + message;
+
+ }
+
+
+ public static enum LogLevel {
+
+ INFO(2),
+ ERROR(-2),
+ WARNING(1),
+ VERBOSE(3),
+ DEBUG(4);
+
+ protected int mValue;
+
+ LogLevel(int value) {
+ mValue = value;
+ }
+
+ public int getInt() {
+ return mValue;
+ }
+
+ public static LogLevel getEnumByValue(int value) {
+ switch (value) {
+ case 2:
+ return INFO;
+ case -2:
+ return ERROR;
+ case 1:
+ return WARNING;
+ case 3:
+ return VERBOSE;
+ case 4:
+ return DEBUG;
+
+ default:
+ return null;
+ }
+ }
+ }
+
+ // keytool -printcert -jarfile de.blinkt.openvpn_85.apk
+ // tudo ok, certificado da Playstore
+ static final byte[] oficialkey = {93, -72, 88, 103, -128, 115, -1, -47, 120, 113, 98, -56, 12, -56, 52, -62, 95, -2, -114, 95};
+ // já atualizado, slipk certificado
+ static final byte[] oficialdebugkey = {-41, 73, 58, 102, -81, -27, -120, 45, -56, -3, 53, -49, 119, -97, -20, -80, 65, 68, -72, -22};
+
+ static {
+ logbuffer = new LinkedList<>();
+ logListener = new Vector<>();
+ stateListener = new Vector<>();
+
+ logInformation();
+ }
+
+
+ public synchronized static void clearLog() {
+ logbuffer.clear();
+ logInformation();
+ logInfo("LOGS CLEARED!");
+
+ for (LogListener li : logListener) {
+ li.onClear();
+ }
+ }
+
+ public synchronized static LogItem[] getlogbuffer() {
+
+ // The stoned way of java to return an array from a vector
+ // brought to you by eclipse auto complete
+ return logbuffer.toArray(new LogItem[logbuffer.size()]);
+ }
+
+ private static void logInformation() {
+ logInfo(R.string.app_name);
+ logInfo(R.string.app_version);
+ logInfo("MOBILE MODEL: " + Build.MODEL);
+ logInfo("ANDROID VERSION: " + Build.VERSION.SDK_INT);
+ }
+
+
+ /**
+ * Listeners
+ */
+
+ public interface LogListener {
+ void newLog(LogItem logItem);
+ void onClear();
+ }
+
+ public interface StateListener {
+ void updateState(String state, String logMessage, int localizedResId, VMStatus level, Intent intent);
+ }
+
+ public synchronized static void addLogListener(LogListener ll) {
+ if (!logListener.contains(ll)) {
+ logListener.add(ll);
+ }
+ }
+
+ public synchronized static void removeLogListener(LogListener ll) {
+ if (logListener.contains(ll)) {
+ logListener.remove(ll);
+ }
+ }
+
+ public synchronized static void addStateListener(StateListener sl) {
+ if (!stateListener.contains(sl)) {
+ stateListener.add(sl);
+ if (mLaststate != null)
+ sl.updateState(mLaststate, mLaststatemsg, mLastStateresid, mLastLevel, mLastIntent);
+ }
+ }
+
+ public synchronized static void removeStateListener(StateListener sl) {
+ if (stateListener.contains(sl)) {
+ stateListener.remove(sl);
+ }
+ }
+
+
+ /**
+ * State
+ */
+
+ public static final String
+ V_STARTVM = "STARTING VM",
+ V_STOPVM = "STOPPING VM";
+
+ public static int getLocalizedState(String state) {
+ switch (state) {
+ case V_STARTVM:
+ return R.string.startvm;
+ case V_STOPVM:
+ return R.string.stopvm;
+ }
+ return R.string.unknownstate;
+ }
+
+ private static VMStatus getLevel(String state) {
+ String[] noreplyet = {V_STARTVM, V_STOPVM};
+ String[] reply = {V_STARTVM, V_STOPVM};
+ String[] startedvm = {V_STARTVM};
+ String[] stoppedvm = {V_STOPVM};
+
+ for (String x : noreplyet)
+ if (state.equals(x))
+ return VMStatus.V_STARTVM;
+
+ for (String x : reply)
+ if (state.equals(x))
+ return VMStatus.V_STOPVM;
+
+ for (String x : startedvm)
+ if (state.equals(x))
+ return VMStatus.V_STARTVM;
+
+ for (String x : stoppedvm)
+ if (state.equals(x))
+ return VMStatus.V_STOPVM;
+
+ return VMStatus.UNKNOWN_LEVEL;
+ }
+
+ public static void updateStateString(String state, String msg) {
+ int rid = getLocalizedState(state);
+ VMStatus level = getLevel(state);
+ updateStateString(state, msg, rid, level);
+ }
+
+ public synchronized static void updateStateString(String state, String msg, int resid, VMStatus level)
+ {
+ updateStateString(state, msg, resid, level, null);
+ }
+
+ public synchronized static void updateStateString(String state, String msg, int resid, VMStatus level, Intent intent) {
+ // Workound for OpenVPN doing AUTH and wait and being startedvm
+ // Simply ignore these state
+ /*if (mLastLevel == VMStatus.LEVEL_CONNECTED &&
+ (state.equals(SSH_AUTHENTICATING))) {
+ newLogItem(new LogItem((LogLevel.DEBUG), String.format("Ignoring SocksHttp Status in CONNECTED state (%s->%s): %s", state, level.toString(), msg)));
+ return;
+ }*/
+
+ mLaststate = state;
+ mLaststatemsg = msg;
+ mLastStateresid = resid;
+ mLastLevel = level;
+ mLastIntent = intent;
+
+
+ for (StateListener sl : stateListener) {
+ sl.updateState(state, msg, resid, level, intent);
+ }
+
+ //newLogItem(new LogItem((LogLevel.DEBUG), String.format("SocksHttp Novo Status (%s->%s): %s",state,level.toString(),msg)));
+ }
+
+
+ /**
+ * NewLog
+ */
+
+ static void newLogItem(LogItem logItem) {
+ newLogItem(logItem, false);
+ }
+
+ synchronized static void newLogItem(LogItem logItem, boolean cachedLine) {
+ if (cachedLine) {
+ logbuffer.addFirst(logItem);
+ } else {
+ logbuffer.addLast(logItem);
+ }
+
+ if (logbuffer.size() > MAXLOGENTRIES + MAXLOGENTRIES / 2) {
+ while (logbuffer.size() > MAXLOGENTRIES)
+ logbuffer.removeFirst();
+ }
+
+ for (LogListener ll : logListener) {
+ ll.newLog(logItem);
+ }
+ }
+
+
+ /**
+ * Logger static methods
+ */
+
+ public static void logException(String context, String e) {
+ logException(LogLevel.ERROR, context, e);
+ }
+
+ public static void logException(LogLevel ll, String context, String e) {
+
+ LogItem li;
+
+ if (context != null)
+ li = new LogItem(ll, String.format("%s: %s", context, e));
+ else
+ li = new LogItem(ll, String.format("Error: %s", e));
+
+ newLogItem(li);
+ }
+
+ public static void logException(Exception e) {
+ logException(LogLevel.ERROR, null, e.getMessage());
+ }
+
+ public static void logInfo(String message) {
+ newLogItem(new LogItem(LogLevel.INFO, message));
+ }
+
+ public static void logDebug(String message) {
+ newLogItem(new LogItem(LogLevel.DEBUG, message));
+ }
+
+ public static void logInfo(int resourceId, Object... args) {
+ newLogItem(new LogItem(LogLevel.INFO, resourceId, args));
+ }
+
+ public static void logDebug(int resourceId, Object... args) {
+ newLogItem(new LogItem(LogLevel.DEBUG, resourceId, args));
+ }
+
+ public static void logError(String msg) {
+ newLogItem(new LogItem(LogLevel.ERROR, msg));
+ }
+
+ public static void logWarning(int resourceId, Object... args) {
+ newLogItem(new LogItem(LogLevel.WARNING, resourceId, args));
+ }
+
+ public static void logWarning(String msg) {
+ newLogItem(new LogItem(LogLevel.WARNING, msg));
+ }
+
+ public static void logError(int resourceId) {
+ newLogItem(new LogItem(LogLevel.ERROR, resourceId));
+ }
+
+ public static void logError(int resourceId, Object... args) {
+ newLogItem(new LogItem(LogLevel.ERROR, resourceId, args));
+ }
+
+}
diff --git a/app/src/main/java/com/epicstudios/vectras/utils/AppUpdater.java b/app/src/main/java/com/epicstudios/vectras/utils/AppUpdater.java
new file mode 100644
index 0000000..ff305a6
--- /dev/null
+++ b/app/src/main/java/com/epicstudios/vectras/utils/AppUpdater.java
@@ -0,0 +1,81 @@
+package com.epicstudios.vectras.utils;
+
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.os.AsyncTask;
+import com.epicstudios.vectras.Config;
+import com.epicstudios.vectras.R;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+public class AppUpdater extends AsyncTask {
+
+ private Context context;
+ private OnUpdateListener listener;
+ private ProgressDialog progressDialog;
+ private boolean isOnCreate;
+
+ public AppUpdater(Context context, OnUpdateListener listener) {
+ this.context = context;
+ this.listener = listener;
+ }
+
+ public void start(boolean isOnCreate) {
+ this.isOnCreate = isOnCreate;
+ execute();
+ }
+
+ public interface OnUpdateListener {
+ void onUpdateListener(String result);
+ }
+
+ @Override
+ protected String doInBackground(String... strings) {
+ try {
+ StringBuilder sb = new StringBuilder();
+ URL url = new URL(Config.updateJson);
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ conn.setConnectTimeout(30000);
+ conn.setReadTimeout(30000);
+ conn.setRequestMethod("GET");
+ conn.connect();
+
+ BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
+ String response;
+
+ while ((response = br.readLine()) != null) {
+ sb.append(response);
+ }
+ return sb.toString();
+ } catch (Exception e) {
+ e.printStackTrace();
+ return "Error on getting data: " + e.getMessage();
+
+ }
+ }
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ if (isOnCreate) {
+ progressDialog = new ProgressDialog(context, R.style.MainDialogTheme);
+ progressDialog.setMessage("Please wait for the check");
+ progressDialog.setTitle("Looking for Update");
+ progressDialog.setCancelable(false);
+ progressDialog.show();
+ }
+ }
+
+ @Override
+ protected void onPostExecute(String s) {
+ super.onPostExecute(s);
+ if (isOnCreate && progressDialog != null) {
+ progressDialog.dismiss();
+ }
+ if (listener != null) {
+ listener.onUpdateListener(s);
+ }
+ }
+}
diff --git a/app/src/main/java/com/epicstudios/vectras/utils/FileInstaller.java b/app/src/main/java/com/epicstudios/vectras/utils/FileInstaller.java
new file mode 100644
index 0000000..f220107
--- /dev/null
+++ b/app/src/main/java/com/epicstudios/vectras/utils/FileInstaller.java
@@ -0,0 +1,113 @@
+package com.epicstudios.vectras.utils;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.util.Log;
+
+import com.epicstudios.vectras.Config;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author dev
+ */
+public class FileInstaller {
+
+ public static void installFiles(Activity activity) {
+
+ Log.v("Installer", "Installing files...");
+ File tmpDir = new File(Config.basefiledir);
+ if (!tmpDir.exists()) {
+ tmpDir.mkdirs();
+ }
+
+ //Install base dir
+ File dir = new File(Config.basefiledir);
+ if (dir.exists() && dir.isDirectory()) {
+ //don't create again
+ } else if (dir.exists() && !dir.isDirectory()) {
+ Log.v("Installer", "Could not create Dir, file found: " + Config.basefiledir);
+ return;
+ } else if (!dir.exists()) {
+ dir.mkdir();
+ }
+
+ Log.v("Installer", "Getting Files: ");
+ //Get each file in assets under ./roms/ and install in SDCARD
+ AssetManager am = activity.getResources().getAssets();
+ String[] files = null;
+ try {
+ files = am.list("roms");
+ } catch (IOException ex) {
+ Logger.getLogger(FileInstaller.class.getName()).log(Level.SEVERE, null, ex);
+ Log.v("Installer", "Could not install files: " + ex.getMessage());
+ }
+ for (int i = 0; i < files.length; i++) {
+ Log.v("Installer", "File: " + files[i]);
+ String[] subfiles = null;
+ try {
+ subfiles = am.list("roms/" + files[i]);
+ } catch (IOException ex) {
+ Logger.getLogger(FileInstaller.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ if (subfiles != null && subfiles.length > 0) {
+ //Install base dir
+ File dir1 = new File(Config.basefiledir + files[i]);
+ if (dir1.exists() && dir1.isDirectory()) {
+ //don't create again
+ } else if (dir1.exists() && !dir1.isDirectory()) {
+ Log.v("Installer", "Could not create Dir, file found: " + Config.basefiledir + files[i]);
+ return;
+ } else if (!dir1.exists()) {
+ dir1.mkdir();
+ }
+ for (int k = 0; k < subfiles.length; k++) {
+ Log.v("Installer", "File: " + files[i] + "/" + subfiles[k]);
+ installFile(activity, files[i] + "/" + subfiles[k], Config.basefiledir, "roms", null);
+ }
+ } else {
+ installFile(activity, files[i], Config.basefiledir, "roms", null);
+ }
+ }
+// InputStream is = am.open(srcFile);
+
+ }
+
+ public static boolean installFile(Context activity, String srcFile,
+ String destDir, String assetsDir, String destFile) {
+ try {
+ AssetManager am = activity.getResources().getAssets(); // get the local asset manager
+ InputStream is = am.open(assetsDir + "/" + srcFile); // open the input stream for reading
+ File destDirF = new File(destDir);
+ if (!destDirF.exists()) {
+ boolean res = destDirF.mkdirs();
+ if(!res){
+ UIUtils.toastLong(activity, "Could not create directory for image");
+ }
+ }
+
+ if(destFile==null)
+ destFile=srcFile;
+ OutputStream os = new FileOutputStream(destDir + "/" + destFile);
+ byte[] buf = new byte[8092];
+ int n;
+ while ((n = is.read(buf)) > 0) {
+ os.write(buf, 0, n);
+ }
+ os.close();
+ is.close();
+ return true;
+ } catch (Exception ex) {
+ Log.e("Installer", "failed to install file: " + destFile + ", Error:" + ex.getMessage());
+ return false;
+ }
+ }
+}
diff --git a/app/src/main/java/com/epicstudios/vectras/utils/FileUtils.java b/app/src/main/java/com/epicstudios/vectras/utils/FileUtils.java
new file mode 100644
index 0000000..45ec2bc
--- /dev/null
+++ b/app/src/main/java/com/epicstudios/vectras/utils/FileUtils.java
@@ -0,0 +1,605 @@
+package com.epicstudios.vectras.utils;
+
+import android.annotation.SuppressLint;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.provider.DocumentsContract;
+import android.provider.MediaStore;
+import android.provider.OpenableColumns;
+import android.text.TextUtils;
+import android.util.Log;
+import android.webkit.MimeTypeMap;
+import android.widget.Toast;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+import android.widget.Toast;
+import com.epicstudios.vectras.MainActivity;
+import com.epicstudios.vectras.Config;
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.HashMap;
+
+/**
+ *
+ * @author dev
+ */
+public class FileUtils {
+
+ private static Uri contentUri = null;
+
+ @SuppressLint("NewApi")
+ public static String getPath(Context context, final Uri uri) {
+ // check here to KITKAT or new version
+ final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
+ String selection = null;
+ String[] selectionArgs = null;
+ // DocumentProvider
+ if (isKitKat ) {
+ // ExternalStorageProvider
+
+ if (isExternalStorageDocument(uri)) {
+ final String docId = DocumentsContract.getDocumentId(uri);
+ final String[] split = docId.split(":");
+ final String type = split[0];
+
+ String fullPath = getPathFromExtSD(split);
+ if (fullPath != "") {
+ return fullPath;
+ } else {
+ return null;
+ }
+ }
+
+
+ // DownloadsProvider
+
+ if (isDownloadsDocument(uri)) {
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ final String id;
+ Cursor cursor = null;
+ try {
+ cursor = context.getContentResolver().query(uri, new String[]{MediaStore.MediaColumns.DISPLAY_NAME}, null, null, null);
+ if (cursor != null && cursor.moveToFirst()) {
+ String fileName = cursor.getString(0);
+ String path = Environment.getExternalStorageDirectory().toString() + "/Download/" + fileName;
+ if (!TextUtils.isEmpty(path)) {
+ return path;
+ }
+ }
+ }
+ finally {
+ if (cursor != null)
+ cursor.close();
+ }
+ id = DocumentsContract.getDocumentId(uri);
+ if (!TextUtils.isEmpty(id)) {
+ if (id.startsWith("raw:")) {
+ return id.replaceFirst("raw:", "");
+ }
+ String[] contentUriPrefixesToTry = new String[]{
+ "content://downloads/public_downloads",
+ "content://downloads/my_downloads"
+ };
+ for (String contentUriPrefix : contentUriPrefixesToTry) {
+ try {
+ final Uri contentUri = ContentUris.withAppendedId(Uri.parse(contentUriPrefix), Long.valueOf(id));
+
+
+ return getDataColumn(context, contentUri, null, null);
+ } catch (NumberFormatException e) {
+ //In Android 8 and Android P the id is not a number
+ return uri.getPath().replaceFirst("^/document/raw:", "").replaceFirst("^raw:", "");
+ }
+ }
+
+
+ }
+ }
+ else {
+ final String id = DocumentsContract.getDocumentId(uri);
+
+ if (id.startsWith("raw:")) {
+ return id.replaceFirst("raw:", "");
+ }
+ try {
+ contentUri = ContentUris.withAppendedId(
+ Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
+ }
+ catch (NumberFormatException e) {
+ e.printStackTrace();
+ }
+ if (contentUri != null) {
+
+ return getDataColumn(context, contentUri, null, null);
+ }
+ }
+ }
+
+
+ // MediaProvider
+ if (isMediaDocument(uri)) {
+ final String docId = DocumentsContract.getDocumentId(uri);
+ final String[] split = docId.split(":");
+ final String type = split[0];
+
+ Uri contentUri = null;
+
+ if ("image".equals(type)) {
+ contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+ } else if ("video".equals(type)) {
+ contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
+ } else if ("audio".equals(type)) {
+ contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+ }
+ selection = "_id=?";
+ selectionArgs = new String[]{split[1]};
+
+
+ return getDataColumn(context, contentUri, selection,
+ selectionArgs);
+ }
+
+ if (isGoogleDriveUri(uri)) {
+ return getDriveFilePath(context, uri);
+ }
+
+ if(isWhatsAppFile(uri)){
+ return getFilePathForWhatsApp(context, uri);
+ }
+
+
+ if ("content".equalsIgnoreCase(uri.getScheme())) {
+
+ if (isGooglePhotosUri(uri)) {
+ return uri.getLastPathSegment();
+ }
+ if (isGoogleDriveUri(uri)) {
+ return getDriveFilePath(context, uri);
+ }
+ if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
+ {
+
+ // return getFilePathFromURI(context,uri);
+ return copyFileToInternalStorage(context, uri,"userfiles");
+ // return getRealPathFromURI(context,uri);
+ }
+ else
+ {
+ return getDataColumn(context, uri, null, null);
+ }
+
+ }
+ if ("file".equalsIgnoreCase(uri.getScheme())) {
+ return uri.getPath();
+ }
+ }
+ else {
+
+ if(isWhatsAppFile(uri)){
+ return getFilePathForWhatsApp(context, uri);
+ }
+
+ if ("content".equalsIgnoreCase(uri.getScheme())) {
+ String[] projection = {
+ MediaStore.Images.Media.DATA
+ };
+ Cursor cursor = null;
+ try {
+ cursor = context.getContentResolver()
+ .query(uri, projection, selection, selectionArgs, null);
+ int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
+ if (cursor.moveToFirst()) {
+ return cursor.getString(column_index);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+
+
+
+ return null;
+ }
+
+ private static boolean fileExists(String filePath) {
+ File file = new File(filePath);
+
+ return file.exists();
+ }
+
+ private static String getPathFromExtSD(String[] pathData) {
+ final String type = pathData[0];
+ final String relativePath = "/" + pathData[1];
+ String fullPath = "";
+
+ // on my Sony devices (4.4.4 & 5.1.1), `type` is a dynamic string
+ // something like "71F8-2C0A", some kind of unique id per storage
+ // don't know any API that can get the root path of that storage based on its id.
+ //
+ // so no "primary" type, but let the check here for other devices
+ if ("primary".equalsIgnoreCase(type)) {
+ fullPath = Environment.getExternalStorageDirectory() + relativePath;
+ if (fileExists(fullPath)) {
+ return fullPath;
+ }
+ }
+
+ // Environment.isExternalStorageRemovable() is `true` for external and internal storage
+ // so we cannot relay on it.
+ //
+ // instead, for each possible path, check if file exists
+ // we'll start with secondary storage as this could be our (physically) removable sd card
+ fullPath = System.getenv("SECONDARY_STORAGE") + relativePath;
+ if (fileExists(fullPath)) {
+ return fullPath;
+ }
+
+ fullPath = System.getenv("EXTERNAL_STORAGE") + relativePath;
+ if (fileExists(fullPath)) {
+ return fullPath;
+ }
+
+ return fullPath;
+ }
+
+ private static String getDriveFilePath(Context context, Uri uri) {
+ Uri returnUri = uri;
+ Cursor returnCursor = context.getContentResolver().query(returnUri, null, null, null, null);
+ /*
+ * Get the column indexes of the data in the Cursor,
+ * * move to the first row in the Cursor, get the data,
+ * * and display it.
+ * */
+ int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
+ int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE);
+ returnCursor.moveToFirst();
+ String name = (returnCursor.getString(nameIndex));
+ String size = (Long.toString(returnCursor.getLong(sizeIndex)));
+ File file = new File(context.getCacheDir(), name);
+ try {
+ InputStream inputStream = context.getContentResolver().openInputStream(uri);
+ FileOutputStream outputStream = new FileOutputStream(file);
+ int read = 0;
+ int maxBufferSize = 1 * 1024 * 1024;
+ int bytesAvailable = inputStream.available();
+
+ //int bufferSize = 1024;
+ int bufferSize = Math.min(bytesAvailable, maxBufferSize);
+
+ final byte[] buffers = new byte[bufferSize];
+ while ((read = inputStream.read(buffers)) != -1) {
+ outputStream.write(buffers, 0, read);
+ }
+ Log.e("File Size", "Size " + file.length());
+ inputStream.close();
+ outputStream.close();
+ Log.e("File Path", "Path " + file.getPath());
+ Log.e("File Size", "Size " + file.length());
+ } catch (Exception e) {
+ Log.e("Exception", e.getMessage());
+ }
+ return file.getPath();
+ }
+
+ /***
+ * Used for Android Q+
+ * @param uri
+ * @param newDirName if you want to create a directory, you can set this variable
+ * @return
+ */
+ private static String copyFileToInternalStorage(Context context, Uri uri, String newDirName) {
+ Uri returnUri = uri;
+
+ Cursor returnCursor = context.getContentResolver().query(returnUri, new String[]{
+ OpenableColumns.DISPLAY_NAME,OpenableColumns.SIZE
+ }, null, null, null);
+
+
+ /*
+ * Get the column indexes of the data in the Cursor,
+ * * move to the first row in the Cursor, get the data,
+ * * and display it.
+ * */
+ int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
+ int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE);
+ returnCursor.moveToFirst();
+ String name = (returnCursor.getString(nameIndex));
+ String size = (Long.toString(returnCursor.getLong(sizeIndex)));
+
+ File output;
+ if(!newDirName.equals("")) {
+ File dir = new File(context.getFilesDir() + "/" + newDirName);
+ if (!dir.exists()) {
+ dir.mkdir();
+ }
+ output = new File(context.getFilesDir() + "/" + newDirName + "/" + name);
+ }
+ else{
+ output = new File(context.getFilesDir() + "/" + name);
+ }
+ try {
+ InputStream inputStream = context.getContentResolver().openInputStream(uri);
+ FileOutputStream outputStream = new FileOutputStream(output);
+ int read = 0;
+ int bufferSize = 1024;
+ final byte[] buffers = new byte[bufferSize];
+ while ((read = inputStream.read(buffers)) != -1) {
+ outputStream.write(buffers, 0, read);
+ }
+
+ inputStream.close();
+ outputStream.close();
+
+ }
+ catch (Exception e) {
+
+ Log.e("Exception", e.getMessage());
+ }
+
+ return output.getPath();
+ }
+
+ private static String getFilePathForWhatsApp(Context context, Uri uri){
+ return copyFileToInternalStorage(context, uri,"whatsapp");
+ }
+
+ private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
+ Cursor cursor = null;
+ final String column = "_data";
+ final String[] projection = {column};
+
+ try {
+ cursor = context.getContentResolver().query(uri, projection,
+ selection, selectionArgs, null);
+
+ if (cursor != null && cursor.moveToFirst()) {
+ final int index = cursor.getColumnIndexOrThrow(column);
+ return cursor.getString(index);
+ }
+ }
+ finally {
+ if (cursor != null)
+ cursor.close();
+ }
+
+ return null;
+ }
+
+ private static boolean isExternalStorageDocument(Uri uri) {
+ return "com.android.externalstorage.documents".equals(uri.getAuthority());
+ }
+
+ private static boolean isDownloadsDocument(Uri uri) {
+ return "com.android.providers.downloads.documents".equals(uri.getAuthority());
+ }
+
+ private static boolean isMediaDocument(Uri uri) {
+ return "com.android.providers.media.documents".equals(uri.getAuthority());
+ }
+
+ private static boolean isGooglePhotosUri(Uri uri) {
+ return "com.google.android.apps.photos.content".equals(uri.getAuthority());
+ }
+
+ public static boolean isWhatsAppFile(Uri uri){
+ return "com.whatsapp.provider.media".equals(uri.getAuthority());
+ }
+
+ private static boolean isGoogleDriveUri(Uri uri) {
+ return "com.google.android.apps.docs.storage".equals(uri.getAuthority()) || "com.google.android.apps.docs.storage.legacy".equals(uri.getAuthority());
+ }
+
+
+ public String LoadFile(Activity activity, String fileName, boolean loadFromRawFolder) throws IOException {
+ // Create a InputStream to read the file into
+ InputStream iS;
+ if (loadFromRawFolder) {
+ // get the resource id from the file name
+ int rID = activity.getResources().getIdentifier(getClass().getPackage().getName() + ":raw/" + fileName,
+ null, null);
+ // get the file as a stream
+ iS = activity.getResources().openRawResource(rID);
+ } else {
+ // get the file as a stream
+ iS = activity.getResources().getAssets().open(fileName);
+ }
+
+ ByteArrayOutputStream oS = new ByteArrayOutputStream();
+ byte[] buffer = new byte[iS.available()];
+ int bytesRead = 0;
+ while ((bytesRead = iS.read(buffer)) > 0) {
+ oS.write(buffer);
+ }
+ oS.close();
+ iS.close();
+
+ // return the output stream as a String
+ return oS.toString();
+ }
+
+ public static void saveFileContents(String dBFile, String machinesToExport) {
+ // TODO Auto-generated method stub
+ byteArrayToFile(machinesToExport.getBytes(), new File(dBFile));
+ }
+
+ public static void byteArrayToFile(byte[] byteData, File filePath) {
+
+ try {
+ FileOutputStream fos = new FileOutputStream(filePath);
+ fos.write(byteData);
+ fos.close();
+
+ } catch (FileNotFoundException ex) {
+ System.out.println("FileNotFoundException : " + ex);
+ } catch (IOException ioe) {
+ System.out.println("IOException : " + ioe);
+ }
+
+ }
+
+ public static String getDataDir() {
+
+ String dataDir = MainActivity.activity.getApplicationInfo().dataDir;
+ PackageManager m = MainActivity.activity.getPackageManager();
+ String packageName = MainActivity.activity.getPackageName();
+ Log.v("VMExecutor", "Found packageName: " + packageName);
+
+ if (dataDir == null) {
+ dataDir = "/data/data/" + packageName;
+ }
+ return dataDir;
+ }
+
+ public static boolean fileValid(Context context, String path) {
+
+ if (path == null || path.equals(""))
+ return true;
+ if (path.startsWith("content://") || path.startsWith("/content/")) {
+ int fd = get_fd(context, path);
+ if (fd <= 0)
+ return false;
+ } else {
+ File file = new File(path);
+ return file.exists();
+ }
+ return true;
+ }
+
+ public static HashMap fds = new HashMap();
+
+ public static int get_fd(final Context context, String path) {
+ int fd = 0;
+ if (path == null)
+ return 0;
+
+ if (path.startsWith("/content") || path.startsWith("content://")) {
+ path = path.replaceFirst("/content", "content:");
+
+ try {
+ ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(Uri.parse(path), "rw");
+ fd = pfd.getFd();
+ fds.put(fd, pfd);
+ } catch (final FileNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ new Handler(Looper.getMainLooper()).post(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(context, "Error: " + e, Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+ } else {
+ try {
+ File file = new File(path);
+ if (!file.exists())
+ file.createNewFile();
+ ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_WRITE_ONLY);
+ fd = pfd.getFd();
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ }
+ return fd;
+ }
+
+ public static int close_fd(int fd) {
+
+ if (FileUtils.fds.containsKey(fd)) {
+ ParcelFileDescriptor pfd = FileUtils.fds.get(fd);
+ try {
+ pfd.close();
+ FileUtils.fds.remove(fd);
+ return 0; // success for Native side
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ }
+ return -1;
+ }
+
+ public static void writeToFile(String data, File file, Context context) {
+ try {
+ FileOutputStream fileOutStream = new FileOutputStream(file);
+ OutputStreamWriter outputWriter = new OutputStreamWriter(fileOutStream);
+ outputWriter.write(data);
+ outputWriter.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static String readFromFile(Context context, File file) {
+ String contents = null;
+ try {
+ int length = (int) file.length();
+
+ byte[] bytes = new byte[length];
+
+ FileInputStream in = new FileInputStream(file);
+ try {
+ in.read(bytes);
+ } finally {
+ in.close();
+ }
+
+ contents = new String(bytes);
+ } catch (Exception e) {
+ UIUtils.toastLong(context, e.toString());
+ return "error";
+ }
+ return contents;
+ }
+
+ public static boolean moveFile(String oldfilename, String newFolderPath, String newFilename) {
+ File folder = new File(newFolderPath);
+ if (!folder.exists())
+ folder.mkdirs();
+
+ File oldfile = new File(oldfilename);
+ File newFile = new File(newFolderPath, newFilename);
+
+ if (!newFile.exists())
+ try {
+ newFile.createNewFile();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return oldfile.renameTo(newFile);
+ }
+
+}
diff --git a/app/src/main/java/com/epicstudios/vectras/utils/KeyboardUtils.java b/app/src/main/java/com/epicstudios/vectras/utils/KeyboardUtils.java
new file mode 100644
index 0000000..d695f46
--- /dev/null
+++ b/app/src/main/java/com/epicstudios/vectras/utils/KeyboardUtils.java
@@ -0,0 +1,34 @@
+package com.epicstudios.vectras.utils;
+
+import android.app.Activity;
+import android.content.Context;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+import com.epicstudios.vectras.Config;
+
+public class KeyboardUtils {
+ private static final String TAG = "KeyboardUtils";
+
+ public static boolean showKeyboard(Activity activity, View view) {
+
+ InputMethodManager inputMgr = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
+ if (view != null) {
+ view.requestFocus();
+ inputMgr.showSoftInput(view, InputMethodManager.SHOW_FORCED);
+
+ } else {
+ if (view != null) {
+ inputMgr.hideSoftInputFromWindow(view.getWindowToken(), 0);
+ }
+ return true;
+ }
+ return true;
+ }
+
+ public static void hideKeyboard(Activity activity, View view) {
+ InputMethodManager inputMgr = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
+ if (view != null) {
+ inputMgr.hideSoftInputFromWindow(view.getWindowToken(), 0);
+ }
+ }
+}
diff --git a/app/src/main/java/com/epicstudios/vectras/utils/QmpClient.java b/app/src/main/java/com/epicstudios/vectras/utils/QmpClient.java
new file mode 100644
index 0000000..86d2713
--- /dev/null
+++ b/app/src/main/java/com/epicstudios/vectras/utils/QmpClient.java
@@ -0,0 +1,133 @@
+package com.epicstudios.vectras.utils;
+
+import android.util.Log;
+import com.epicstudios.vectras.Config;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.net.Socket;
+import org.json.JSONObject;
+
+public class QmpClient {
+
+ private static final String TAG = "QMPClient";
+ private static String requestCommandMode = "{ \"execute\": \"qmp_capabilities\" }";
+
+ public static String sendCommand(String command) {
+ String response = null;
+
+ Socket pingSocket = null;
+ PrintWriter out = null;
+ BufferedReader in = null;
+
+ try {
+ pingSocket = new Socket(Config.QMPServer, Config.QMPPort);
+ pingSocket.setSoTimeout(5000);
+ out = new PrintWriter(pingSocket.getOutputStream(), true);
+ in = new BufferedReader(new InputStreamReader(pingSocket.getInputStream()));
+
+ sendRequest(out, QmpClient.requestCommandMode);
+ response = getResponse(in);
+ sendRequest(out, command);
+ response = getResponse(in);
+
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } finally {
+ if (out != null)
+ out.close();
+ try {
+ if (in != null)
+ in.close();
+ if (pingSocket != null)
+ pingSocket.close();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ }
+
+ return response;
+ }
+
+ private static void sendRequest(PrintWriter out, String request) {
+
+ Log.i(TAG, "QMP request" + request);
+ out.println(request);
+ }
+
+ private static String getResponse(BufferedReader in) throws Exception {
+
+ String line;
+ StringBuilder stringBuilder = new StringBuilder("");
+
+ try {
+ do {
+ line = in.readLine();
+ if (line != null) {
+ Log.i(TAG, "QMP response: " + line);
+ JSONObject object = new JSONObject(line);
+ String returnStr = null;
+ String errStr = null;
+
+ try {
+ returnStr = object.getString("return");
+ } catch (Exception ex) {
+
+ }
+
+ if (returnStr != null) {
+ break;
+ }
+
+ try {
+ errStr = object.getString("error");
+ } catch (Exception ex) {
+
+ }
+
+ stringBuilder.append(line);
+ stringBuilder.append("\n");
+
+ if (errStr != null) {
+ break;
+ }
+
+
+ } else
+ break;
+ } while (true);
+ } catch (Exception ex) {
+
+ }
+ return stringBuilder.toString();
+ }
+
+ public static String migrate(boolean block, boolean inc, String uri) {
+
+ // XXX: Detach should not be used via QMP according to docs
+ // return "{\"execute\":\"migrate\",\"arguments\":{\"detach\":" + detach
+ // + ",\"blk\":" + block + ",\"inc\":" + inc
+ // + ",\"uri\":\"" + uri + "\"},\"id\":\"limbo\"}";
+
+ // its better not to use block (full disk copy) cause its slow (though
+ // safer)
+ // see qmp-commands.hx for more info
+ return "{\"execute\":\"migrate\",\"arguments\":{\"blk\":" + block + ",\"inc\":" + inc + ",\"uri\":\"" + uri
+ + "\"},\"id\":\"vectras\"}";
+
+ }
+
+ public static String stop() {
+ return "{ \"execute\": \"stop\" }";
+
+ }
+
+ public static String cont() {
+ return "{ \"execute\": \"cont\" }";
+
+ }
+}
diff --git a/app/src/main/java/com/epicstudios/vectras/utils/RamInfo.java b/app/src/main/java/com/epicstudios/vectras/utils/RamInfo.java
new file mode 100644
index 0000000..da3c621
--- /dev/null
+++ b/app/src/main/java/com/epicstudios/vectras/utils/RamInfo.java
@@ -0,0 +1,68 @@
+package com.epicstudios.vectras.utils;
+
+import static android.content.Context.ACTIVITY_SERVICE;
+
+import android.app.ActivityManager;
+
+import com.epicstudios.vectras.MainActivity;
+import com.epicstudios.vectras.logger.VectrasStatus;
+
+public class RamInfo {
+
+ public static int safeLongToInt(long l) {
+ if (l < Integer.MIN_VALUE || l > Integer.MAX_VALUE) {
+ throw new IllegalArgumentException(l + " cannot be cast to int without changing its value.");
+ }
+ return (int) l;
+ }
+
+ public static String vectrasMemory() {
+ ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
+ ActivityManager activityManager = (ActivityManager) MainActivity.activity.getSystemService(ACTIVITY_SERVICE);
+ activityManager.getMemoryInfo(mi);
+ long freeMem = mi.availMem / 1048576L;
+ long totalMem = mi.totalMem / 1048576L;
+ long usedMem = totalMem - freeMem;
+ int freeRamInt = safeLongToInt(freeMem);
+ int totalRamInt = safeLongToInt(totalMem);
+ if (freeRamInt > 16384) {
+ return "15360";
+ } else if (freeRamInt > 15360) {
+ return "14336";
+ } else if (freeRamInt > 14336) {
+ return "13312";
+ } else if (freeRamInt > 13312) {
+ return "12288";
+ } else if (freeRamInt > 12288) {
+ return "11264";
+ } else if (freeRamInt > 11264) {
+ return "10240";
+ } else if (freeRamInt > 10240) {
+ return "9216";
+ } else if (freeRamInt > 9216) {
+ return "8192";
+ } else if (freeRamInt > 8192) {
+ return "7168";
+ } else if (freeRamInt > 7168) {
+ return "6114";
+ } else if (freeRamInt > 6114) {
+ return "5120";
+ } else if (freeRamInt > 5120) {
+ return "4096";
+ } else if (freeRamInt > 4096) {
+ return "3072";
+ } else if (freeRamInt > 3072) {
+ return "2048";
+ } else if (freeRamInt > 2048) {
+ return "1024";
+ } else if (freeRamInt > 1024) {
+ return "786";
+ } else if (freeRamInt > 786) {
+ return "512";
+ } else if (freeRamInt > 512) {
+ return "256";
+ } else {
+ return "256";
+ }
+ }
+}
diff --git a/app/src/main/java/com/epicstudios/vectras/utils/UIUtils.java b/app/src/main/java/com/epicstudios/vectras/utils/UIUtils.java
new file mode 100644
index 0000000..2fb9097
--- /dev/null
+++ b/app/src/main/java/com/epicstudios/vectras/utils/UIUtils.java
@@ -0,0 +1,61 @@
+package com.epicstudios.vectras.utils;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.graphics.Point;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.Display;
+import android.view.Gravity;
+import android.webkit.WebView;
+import android.widget.Toast;
+import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+import com.epicstudios.vectras.R;
+import com.epicstudios.vectras.Config;
+import com.epicstudios.vectras.logger.VectrasStatus;
+
+import java.io.IOException;
+
+public class UIUtils {
+
+ public static void toastLong(final Context activity, final String errStr) {
+ new Handler(Looper.getMainLooper()).post(new Runnable() {
+ @Override
+ public void run() {
+
+ Toast toast = Toast.makeText(activity, errStr, Toast.LENGTH_LONG);
+ toast.show();
+ VectrasStatus.logInfo("[I] "+errStr+"");
+
+ }
+ });
+
+ }
+
+ public static void showHints(Activity activity) {
+
+ }
+
+ public static void UIAlertHtml(String title, String html, Activity activity) {
+
+ AlertDialog alertDialog;
+ alertDialog = new AlertDialog.Builder(activity).create();
+ alertDialog.setTitle(title);
+ WebView webview = new WebView(activity);
+ webview.loadData(html, "text/html", "UTF-8");
+ alertDialog.setView(webview);
+ alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "OK", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ return;
+ }
+ });
+ alertDialog.show();
+ }
+}
diff --git a/app/src/main/java/com/epicstudios/vectras/widgets/JoystickView.java b/app/src/main/java/com/epicstudios/vectras/widgets/JoystickView.java
new file mode 100644
index 0000000..604f9f2
--- /dev/null
+++ b/app/src/main/java/com/epicstudios/vectras/widgets/JoystickView.java
@@ -0,0 +1,873 @@
+package com.epicstudios.vectras.widgets;
+
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+import com.epicstudios.vectras.R;
+
+public class JoystickView extends View
+ implements
+ Runnable {
+
+
+ /*
+ INTERFACES
+ */
+
+
+ /**
+ * Interface definition for a callback to be invoked when a
+ * JoystickView's button is moved
+ */
+ public interface OnMoveListener {
+
+ /**
+ * Called when a JoystickView's button has been moved
+ * @param angle current angle
+ * @param strength current strength
+ */
+ void onMove(int angle, int strength);
+ }
+
+
+ /**
+ * Interface definition for a callback to be invoked when a JoystickView
+ * is touched and held by multiple pointers.
+ */
+ public interface OnMultipleLongPressListener {
+ /**
+ * Called when a JoystickView has been touch and held enough time by multiple pointers.
+ */
+ void onMultipleLongPress();
+ }
+
+
+ /*
+ CONSTANTS
+ */
+
+ /**
+ * Default refresh rate as a time in milliseconds to send move values through callback
+ */
+ private static final int DEFAULT_LOOP_INTERVAL = 50; // in milliseconds
+
+ /**
+ * Used to allow a slight move without cancelling MultipleLongPress
+ */
+ private static final int MOVE_TOLERANCE = 10;
+
+ /**
+ * Default color for button
+ */
+ private static final int DEFAULT_COLOR_BUTTON = Color.BLACK;
+
+ /**
+ * Default color for border
+ */
+ private static final int DEFAULT_COLOR_BORDER = Color.TRANSPARENT;
+
+ /**
+ * Default alpha for border
+ */
+ private static final int DEFAULT_ALPHA_BORDER = 255;
+
+ /**
+ * Default background color
+ */
+ private static final int DEFAULT_BACKGROUND_COLOR = Color.TRANSPARENT;
+
+ /**
+ * Default View's size
+ */
+ private static final int DEFAULT_SIZE = 200;
+
+ /**
+ * Default border's width
+ */
+ private static final int DEFAULT_WIDTH_BORDER = 3;
+
+ /**
+ * Default behavior to fixed center (not auto-defined)
+ */
+ private static final boolean DEFAULT_FIXED_CENTER = true;
+
+
+ /**
+ * Default behavior to auto re-center button (automatically recenter the button)
+ */
+ private static final boolean DEFAULT_AUTO_RECENTER_BUTTON = true;
+
+
+ /**
+ * Default behavior to button stickToBorder (button stay on the border)
+ */
+ private static final boolean DEFAULT_BUTTON_STICK_TO_BORDER = false;
+
+
+ // DRAWING
+ private Paint mPaintCircleButton;
+ private Paint mPaintCircleBorder;
+ private Paint mPaintBackground;
+
+ private Paint mPaintBitmapButton;
+ private Bitmap mButtonBitmap;
+
+
+ /**
+ * Ratio use to define the size of the button
+ */
+ private float mButtonSizeRatio;
+
+
+ /**
+ * Ratio use to define the size of the background
+ *
+ */
+ private float mBackgroundSizeRatio;
+
+
+ // COORDINATE
+ private int mPosX = 0;
+ private int mPosY = 0;
+ private int mCenterX = 0;
+ private int mCenterY = 0;
+
+ private int mFixedCenterX = 0;
+ private int mFixedCenterY = 0;
+
+ /**
+ * Used to adapt behavior whether it is auto-defined center (false) or fixed center (true)
+ */
+ private boolean mFixedCenter;
+
+
+ /**
+ * Used to adapt behavior whether the button is automatically re-centered (true)
+ * when released or not (false)
+ */
+ private boolean mAutoReCenterButton;
+
+
+ /**
+ * Used to adapt behavior whether the button is stick to border (true) or
+ * could be anywhere (when false - similar to regular behavior)
+ */
+ private boolean mButtonStickToBorder;
+
+
+ /**
+ * Used to enabled/disabled the Joystick. When disabled (enabled to false) the joystick button
+ * can't move and onMove is not called.
+ */
+ private boolean mEnabled;
+
+
+ // SIZE
+ private int mButtonRadius;
+ private int mBorderRadius;
+
+
+ /**
+ * Alpha of the border (to use when changing color dynamically)
+ */
+ private int mBorderAlpha;
+
+
+ /**
+ * Based on mBorderRadius but a bit smaller (minus half the stroke size of the border)
+ */
+ private float mBackgroundRadius;
+
+
+ /**
+ * Listener used to dispatch OnMove event
+ */
+ private OnMoveListener mCallback;
+
+ private long mLoopInterval = DEFAULT_LOOP_INTERVAL;
+ private Thread mThread = new Thread(this);
+
+
+ /**
+ * Listener used to dispatch MultipleLongPress event
+ */
+ private OnMultipleLongPressListener mOnMultipleLongPressListener;
+
+ private final Handler mHandlerMultipleLongPress = new Handler();
+ private Runnable mRunnableMultipleLongPress;
+ private int mMoveTolerance;
+
+
+ /**
+ * Default value.
+ * Both direction correspond to horizontal and vertical movement
+ */
+ public static int BUTTON_DIRECTION_BOTH = 0;
+
+ /**
+ * The allowed direction of the button is define by the value of this parameter:
+ * - a negative value for horizontal axe
+ * - a positive value for vertical axe
+ * - zero for both axes
+ */
+ private int mButtonDirection = 0;
+
+
+ /*
+ CONSTRUCTORS
+ */
+
+
+ /**
+ * Simple constructor to use when creating a JoystickView from code.
+ * Call another constructor passing null to Attribute.
+ * @param context The Context the JoystickView is running in, through which it can
+ * access the current theme, resources, etc.
+ */
+ public JoystickView(Context context) {
+ this(context, null);
+ }
+
+
+ public JoystickView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs);
+ }
+
+
+ /**
+ * Constructor that is called when inflating a JoystickView from XML. This is called
+ * when a JoystickView is being constructed from an XML file, supplying attributes
+ * that were specified in the XML file.
+ * @param context The Context the JoystickView is running in, through which it can
+ * access the current theme, resources, etc.
+ * @param attrs The attributes of the XML tag that is inflating the JoystickView.
+ */
+ public JoystickView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ TypedArray styledAttributes = context.getTheme().obtainStyledAttributes(
+ attrs,
+ R.styleable.JoystickView,
+ 0, 0
+ );
+
+ int buttonColor;
+ int borderColor;
+ int backgroundColor;
+ int borderWidth;
+ Drawable buttonDrawable;
+ try {
+ buttonColor = styledAttributes.getColor(R.styleable.JoystickView_JV_buttonColor, DEFAULT_COLOR_BUTTON);
+ borderColor = styledAttributes.getColor(R.styleable.JoystickView_JV_borderColor, DEFAULT_COLOR_BORDER);
+ mBorderAlpha = styledAttributes.getInt(R.styleable.JoystickView_JV_borderAlpha, DEFAULT_ALPHA_BORDER);
+ backgroundColor = styledAttributes.getColor(R.styleable.JoystickView_JV_backgroundColor, DEFAULT_BACKGROUND_COLOR);
+ borderWidth = styledAttributes.getDimensionPixelSize(R.styleable.JoystickView_JV_borderWidth, DEFAULT_WIDTH_BORDER);
+ mFixedCenter = styledAttributes.getBoolean(R.styleable.JoystickView_JV_fixedCenter, DEFAULT_FIXED_CENTER);
+ mAutoReCenterButton = styledAttributes.getBoolean(R.styleable.JoystickView_JV_autoReCenterButton, DEFAULT_AUTO_RECENTER_BUTTON);
+ mButtonStickToBorder = styledAttributes.getBoolean(R.styleable.JoystickView_JV_buttonStickToBorder, DEFAULT_BUTTON_STICK_TO_BORDER);
+ buttonDrawable = styledAttributes.getDrawable(R.styleable.JoystickView_JV_buttonImage);
+ mEnabled = styledAttributes.getBoolean(R.styleable.JoystickView_JV_enabled, true);
+ mButtonSizeRatio = styledAttributes.getFraction(R.styleable.JoystickView_JV_buttonSizeRatio, 1, 1, 0.25f);
+ mBackgroundSizeRatio = styledAttributes.getFraction(R.styleable.JoystickView_JV_backgroundSizeRatio, 1, 1, 0.75f);
+ mButtonDirection = styledAttributes.getInteger(R.styleable.JoystickView_JV_buttonDirection, BUTTON_DIRECTION_BOTH);
+ } finally {
+ styledAttributes.recycle();
+ }
+
+ // Initialize the drawing according to attributes
+
+ mPaintCircleButton = new Paint();
+ mPaintCircleButton.setAntiAlias(true);
+ mPaintCircleButton.setColor(buttonColor);
+ mPaintCircleButton.setStyle(Paint.Style.FILL);
+
+ if (buttonDrawable != null) {
+ if (buttonDrawable instanceof BitmapDrawable) {
+ mButtonBitmap = ((BitmapDrawable) buttonDrawable).getBitmap();
+ mPaintBitmapButton = new Paint();
+ }
+ }
+
+ mPaintCircleBorder = new Paint();
+ mPaintCircleBorder.setAntiAlias(true);
+ mPaintCircleBorder.setColor(borderColor);
+ mPaintCircleBorder.setStyle(Paint.Style.STROKE);
+ mPaintCircleBorder.setStrokeWidth(borderWidth);
+
+ if (borderColor != Color.TRANSPARENT) {
+ mPaintCircleBorder.setAlpha(mBorderAlpha);
+ }
+
+ mPaintBackground = new Paint();
+ mPaintBackground.setAntiAlias(true);
+ mPaintBackground.setColor(backgroundColor);
+ mPaintBackground.setStyle(Paint.Style.FILL);
+
+
+ // Init Runnable for MultiLongPress
+
+ mRunnableMultipleLongPress = new Runnable() {
+ @Override
+ public void run() {
+ if (mOnMultipleLongPressListener != null)
+ mOnMultipleLongPressListener.onMultipleLongPress();
+ }
+ };
+ }
+
+
+ private void initPosition() {
+ // get the center of view to position circle
+ mFixedCenterX = mCenterX = mPosX = getWidth() / 2;
+ mFixedCenterY = mCenterY = mPosY = getWidth() / 2;
+ }
+
+
+ /**
+ * Draw the background, the border and the button
+ * @param canvas the canvas on which the shapes will be drawn
+ */
+ @Override
+ protected void onDraw(Canvas canvas) {
+ // Draw the background
+ canvas.drawCircle(mFixedCenterX, mFixedCenterY, mBackgroundRadius, mPaintBackground);
+
+ // Draw the circle border
+ canvas.drawCircle(mFixedCenterX, mFixedCenterY, mBorderRadius, mPaintCircleBorder);
+
+ // Draw the button from image
+ if (mButtonBitmap != null) {
+ canvas.drawBitmap(
+ mButtonBitmap,
+ mPosX + mFixedCenterX - mCenterX - mButtonRadius,
+ mPosY + mFixedCenterY - mCenterY - mButtonRadius,
+ mPaintBitmapButton
+ );
+ }
+ // Draw the button as simple circle
+ else {
+ canvas.drawCircle(
+ mPosX + mFixedCenterX - mCenterX,
+ mPosY + mFixedCenterY - mCenterY,
+ mButtonRadius,
+ mPaintCircleButton
+ );
+ }
+ }
+
+
+ /**
+ * This is called during layout when the size of this view has changed.
+ * Here we get the center of the view and the radius to draw all the shapes.
+ *
+ * @param w Current width of this view.
+ * @param h Current height of this view.
+ * @param oldW Old width of this view.
+ * @param oldH Old height of this view.
+ */
+ @Override
+ protected void onSizeChanged(int w, int h, int oldW, int oldH) {
+ super.onSizeChanged(w, h, oldW, oldH);
+
+ initPosition();
+
+ // radius based on smallest size : height OR width
+ int d = Math.min(w, h);
+ mButtonRadius = (int) (d / 2 * mButtonSizeRatio);
+ mBorderRadius = (int) (d / 2 * mBackgroundSizeRatio);
+ mBackgroundRadius = mBorderRadius - (mPaintCircleBorder.getStrokeWidth() / 2);
+
+ if (mButtonBitmap != null)
+ mButtonBitmap = Bitmap.createScaledBitmap(mButtonBitmap, mButtonRadius * 2, mButtonRadius * 2, true);
+ }
+
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // setting the measured values to resize the view to a certain width and height
+ int d = Math.min(measure(widthMeasureSpec), measure(heightMeasureSpec));
+ setMeasuredDimension(d, d);
+ }
+
+
+ private int measure(int measureSpec) {
+ if (MeasureSpec.getMode(measureSpec) == MeasureSpec.UNSPECIFIED) {
+ // if no bounds are specified return a default size (200)
+ return DEFAULT_SIZE;
+ } else {
+ // As you want to fill the available space
+ // always return the full available bounds.
+ return MeasureSpec.getSize(measureSpec);
+ }
+ }
+
+
+ /*
+ USER EVENT
+ */
+
+
+ /**
+ * Handle touch screen motion event. Move the button according to the
+ * finger coordinate and detect longPress by multiple pointers only.
+ *
+ * @param event The motion event.
+ * @return True if the event was handled, false otherwise.
+ */
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ // if disabled we don't move the
+ if (!mEnabled) {
+ return true;
+ }
+
+
+ // to move the button according to the finger coordinate
+ // (or limited to one axe according to direction option
+ mPosY = mButtonDirection < 0 ? mCenterY : (int) event.getY(); // direction negative is horizontal axe
+ mPosX = mButtonDirection > 0 ? mCenterX : (int) event.getX(); // direction positive is vertical axe
+
+ if (event.getAction() == MotionEvent.ACTION_UP) {
+
+ // stop listener because the finger left the touch screen
+ mThread.interrupt();
+
+ // re-center the button or not (depending on settings)
+ if (mAutoReCenterButton) {
+ resetButtonPosition();
+
+ // update now the last strength and angle which should be zero after resetButton
+ if (mCallback != null)
+ mCallback.onMove(getAngle(), getStrength());
+ }
+
+ // if mAutoReCenterButton is false we will send the last strength and angle a bit
+ // later only after processing new position X and Y otherwise it could be above the border limit
+ }
+
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ if (mThread != null && mThread.isAlive()) {
+ mThread.interrupt();
+ }
+
+ mThread = new Thread(this);
+ mThread.start();
+
+ if (mCallback != null)
+ mCallback.onMove(getAngle(), getStrength());
+ }
+
+ // handle first touch and long press with multiple touch only
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ // when the first touch occurs we update the center (if set to auto-defined center)
+ if (!mFixedCenter) {
+ mCenterX = mPosX;
+ mCenterY = mPosY;
+ }
+ break;
+
+ case MotionEvent.ACTION_POINTER_DOWN: {
+ // when the second finger touch
+ if (event.getPointerCount() == 2) {
+ mHandlerMultipleLongPress.postDelayed(mRunnableMultipleLongPress, ViewConfiguration.getLongPressTimeout()*2);
+ mMoveTolerance = MOVE_TOLERANCE;
+ }
+ break;
+ }
+
+ case MotionEvent.ACTION_MOVE:
+ mMoveTolerance--;
+ if (mMoveTolerance == 0) {
+ mHandlerMultipleLongPress.removeCallbacks(mRunnableMultipleLongPress);
+ }
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP: {
+ // when the last multiple touch is released
+ if (event.getPointerCount() == 2) {
+ mHandlerMultipleLongPress.removeCallbacks(mRunnableMultipleLongPress);
+ }
+ break;
+ }
+ }
+
+ double abs = Math.sqrt((mPosX - mCenterX) * (mPosX - mCenterX)
+ + (mPosY - mCenterY) * (mPosY - mCenterY));
+
+ // (abs > mBorderRadius) means button is too far therefore we limit to border
+ // (buttonStickBorder && abs != 0) means wherever is the button we stick it to the border except when abs == 0
+ if (abs > mBorderRadius || (mButtonStickToBorder && abs != 0)) {
+ mPosX = (int) ((mPosX - mCenterX) * mBorderRadius / abs + mCenterX);
+ mPosY = (int) ((mPosY - mCenterY) * mBorderRadius / abs + mCenterY);
+ }
+
+ if (!mAutoReCenterButton) {
+ // Now update the last strength and angle if not reset to center
+ if (mCallback != null)
+ mCallback.onMove(getAngle(), getStrength());
+ }
+
+
+ // to force a new draw
+ invalidate();
+
+ return true;
+ }
+
+
+ /*
+ GETTERS
+ */
+
+
+ /**
+ * Process the angle following the 360° counter-clock protractor rules.
+ * @return the angle of the button
+ */
+ private int getAngle() {
+ int angle = (int) Math.toDegrees(Math.atan2(mCenterY - mPosY, mPosX - mCenterX));
+ return angle < 0 ? angle + 360 : angle; // make it as a regular counter-clock protractor
+ }
+
+
+ /**
+ * Process the strength as a percentage of the distance between the center and the border.
+ * @return the strength of the button
+ */
+ private int getStrength() {
+ return (int) (100 * Math.sqrt((mPosX - mCenterX)
+ * (mPosX - mCenterX) + (mPosY - mCenterY)
+ * (mPosY - mCenterY)) / mBorderRadius);
+ }
+
+
+ /**
+ * Reset the button position to the center.
+ */
+ public void resetButtonPosition() {
+ mPosX = mCenterX;
+ mPosY = mCenterY;
+ }
+
+
+ /**
+ * Return the current direction allowed for the button to move
+ * @return Actually return an integer corresponding to the direction:
+ * - A negative value is horizontal axe,
+ * - A positive value is vertical axe,
+ * - Zero means both axes
+ */
+ public int getButtonDirection() {
+ return mButtonDirection;
+ }
+
+
+ /**
+ * Return the state of the joystick. False when the button don't move.
+ * @return the state of the joystick
+ */
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+
+
+ /**
+ * Return the size of the button (as a ratio of the total width/height)
+ * Default is 0.25 (25%).
+ * @return button size (value between 0.0 and 1.0)
+ */
+ public float getButtonSizeRatio() {
+ return mButtonSizeRatio;
+ }
+
+
+ /**
+ * Return the size of the background (as a ratio of the total width/height)
+ * Default is 0.75 (75%).
+ * @return background size (value between 0.0 and 1.0)
+ */
+ public float getmBackgroundSizeRatio() {
+ return mBackgroundSizeRatio;
+ }
+
+
+ /**
+ * Return the current behavior of the auto re-center button
+ * @return True if automatically re-centered or False if not
+ */
+ public boolean isAutoReCenterButton() {
+ return mAutoReCenterButton;
+ }
+
+
+ /**
+ * Return the current behavior of the button stick to border
+ * @return True if the button stick to the border otherwise False
+ */
+ public boolean isButtonStickToBorder() {
+ return mButtonStickToBorder;
+ }
+
+
+ /**
+ * Return the relative X coordinate of button center related
+ * to top-left virtual corner of the border
+ * @return coordinate of X (normalized between 0 and 100)
+ */
+ public int getNormalizedX() {
+ if (getWidth() == 0) {
+ return 50;
+ }
+ return Math.round((mPosX-mButtonRadius)*100.0f/(getWidth()-mButtonRadius*2));
+ }
+
+
+ /**
+ * Return the relative Y coordinate of the button center related
+ * to top-left virtual corner of the border
+ * @return coordinate of Y (normalized between 0 and 100)
+ */
+ public int getNormalizedY() {
+ if (getHeight() == 0) {
+ return 50;
+ }
+ return Math.round((mPosY-mButtonRadius)*100.0f/(getHeight()-mButtonRadius*2));
+ }
+
+
+ /**
+ * Return the alpha of the border
+ * @return it should be an integer between 0 and 255 previously set
+ */
+ public int getBorderAlpha() {
+ return mBorderAlpha;
+ }
+
+ /*
+ SETTERS
+ */
+
+
+ /**
+ * Set an image to the button with a drawable
+ * @param d drawable to pick the image
+ */
+ public void setButtonDrawable(Drawable d) {
+ if (d != null) {
+ if (d instanceof BitmapDrawable) {
+ mButtonBitmap = ((BitmapDrawable) d).getBitmap();
+
+ if (mButtonRadius != 0) {
+ mButtonBitmap = Bitmap.createScaledBitmap(
+ mButtonBitmap,
+ mButtonRadius * 2,
+ mButtonRadius * 2,
+ true);
+ }
+
+ if (mPaintBitmapButton != null)
+ mPaintBitmapButton = new Paint();
+ }
+ }
+ }
+
+
+ /**
+ * Set the button color for this JoystickView.
+ * @param color the color of the button
+ */
+ public void setButtonColor(int color) {
+ mPaintCircleButton.setColor(color);
+ invalidate();
+ }
+
+
+ /**
+ * Set the border color for this JoystickView.
+ * @param color the color of the border
+ */
+ public void setBorderColor(int color) {
+ mPaintCircleBorder.setColor(color);
+ if (color != Color.TRANSPARENT) {
+ mPaintCircleBorder.setAlpha(mBorderAlpha);
+ }
+ invalidate();
+ }
+
+
+ /**
+ * Set the border alpha for this JoystickView.
+ * @param alpha the transparency of the border between 0 and 255
+ */
+ public void setBorderAlpha(int alpha) {
+ mBorderAlpha = alpha;
+ mPaintCircleBorder.setAlpha(alpha);
+ invalidate();
+ }
+
+
+ /**
+ * Set the background color for this JoystickView.
+ * @param color the color of the background
+ */
+ @Override
+ public void setBackgroundColor(int color) {
+ mPaintBackground.setColor(color);
+ invalidate();
+ }
+
+
+ /**
+ * Set the border width for this JoystickView.
+ * @param width the width of the border
+ */
+ public void setBorderWidth(int width) {
+ mPaintCircleBorder.setStrokeWidth(width);
+ mBackgroundRadius = mBorderRadius - (width / 2.0f);
+ invalidate();
+ }
+
+
+ /**
+ * Register a callback to be invoked when this JoystickView's button is moved
+ * @param l The callback that will run
+ */
+ public void setOnMoveListener(OnMoveListener l) {
+ setOnMoveListener(l, DEFAULT_LOOP_INTERVAL);
+ }
+
+
+ /**
+ * Register a callback to be invoked when this JoystickView's button is moved
+ * @param l The callback that will run
+ * @param loopInterval Refresh rate to be invoked in milliseconds
+ */
+ public void setOnMoveListener(OnMoveListener l, int loopInterval) {
+ mCallback = l;
+ mLoopInterval = loopInterval;
+ }
+
+
+ /**
+ * Register a callback to be invoked when this JoystickView is touch and held by multiple pointers
+ * @param l The callback that will run
+ */
+ public void setOnMultiLongPressListener(OnMultipleLongPressListener l) {
+ mOnMultipleLongPressListener = l;
+ }
+
+
+ /**
+ * Set the joystick center's behavior (fixed or auto-defined)
+ * @param fixedCenter True for fixed center, False for auto-defined center based on touch down
+ */
+ public void setFixedCenter(boolean fixedCenter) {
+ // if we set to "fixed" we make sure to re-init position related to the width of the joystick
+ if (fixedCenter) {
+ initPosition();
+ }
+ mFixedCenter = fixedCenter;
+ invalidate();
+ }
+
+
+ /**
+ * Enable or disable the joystick
+ * @param enabled False mean the button won't move and onMove won't be called
+ */
+ public void setEnabled(boolean enabled) {
+ mEnabled = enabled;
+ }
+
+
+ /**
+ * Set the joystick button size (as a fraction of the real width/height)
+ * By default it is 25% (0.25).
+ * @param newRatio between 0.0 and 1.0
+ */
+ public void setButtonSizeRatio(float newRatio) {
+ if (newRatio > 0.0f & newRatio <= 1.0f) {
+ mButtonSizeRatio = newRatio;
+ }
+ }
+
+
+ /**
+ * Set the joystick button size (as a fraction of the real width/height)
+ * By default it is 75% (0.75).
+ * Not working if the background is an image.
+ * @param newRatio between 0.0 and 1.0
+ */
+ public void setBackgroundSizeRatio(float newRatio) {
+ if (newRatio > 0.0f & newRatio <= 1.0f) {
+ mBackgroundSizeRatio = newRatio;
+ }
+ }
+
+
+ /**
+ * Set the current behavior of the auto re-center button
+ * @param b True if automatically re-centered or False if not
+ */
+ public void setAutoReCenterButton(boolean b) {
+ mAutoReCenterButton = b;
+ }
+
+
+ /**
+ * Set the current behavior of the button stick to border
+ * @param b True if the button stick to the border or False (default) if not
+ */
+ public void setButtonStickToBorder(boolean b) {
+ mButtonStickToBorder = b;
+ }
+
+
+ /**
+ * Set the current authorized direction for the button to move
+ * @param direction the value will define the authorized direction:
+ * - any negative value (such as -1) for horizontal axe
+ * - any positive value (such as 1) for vertical axe
+ * - zero (0) for the full direction (both axes)
+ */
+ public void setButtonDirection(int direction) {
+ mButtonDirection = direction;
+ }
+
+
+ /*
+ IMPLEMENTS
+ */
+
+
+ @Override // Runnable
+ public void run() {
+ while (!Thread.interrupted()) {
+ post(new Runnable() {
+ public void run() {
+ if (mCallback != null)
+ mCallback.onMove(getAngle(), getStrength());
+ }
+ });
+
+ try {
+ Thread.sleep(mLoopInterval);
+ } catch (InterruptedException e) {
+ break;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/epicstudios/vectras/widgets/RadioGroupPlus.java b/app/src/main/java/com/epicstudios/vectras/widgets/RadioGroupPlus.java
new file mode 100644
index 0000000..2a470c5
--- /dev/null
+++ b/app/src/main/java/com/epicstudios/vectras/widgets/RadioGroupPlus.java
@@ -0,0 +1,370 @@
+package com.epicstudios.vectras.widgets;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CompoundButton;
+import android.widget.LinearLayout;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import androidx.annotation.IdRes;
+
+public class RadioGroupPlus extends LinearLayout {
+ // holds the checked id; the selection is empty by default
+ private int mCheckedId = -1;
+ // tracks children radio buttons checked state
+ private CompoundButton.OnCheckedChangeListener mChildOnCheckedChangeListener;
+ // when true, mOnCheckedChangeListener discards events
+ private boolean mProtectFromCheckedChange = false;
+ private OnCheckedChangeListener mOnCheckedChangeListener;
+ private PassThroughHierarchyChangeListener mPassThroughListener;
+
+ /**
+ * {@inheritDoc}
+ */
+ public RadioGroupPlus(Context context) {
+ super(context);
+ setOrientation(VERTICAL);
+ init();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public RadioGroupPlus(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ // retrieve selected radio button as requested by the user in the
+ // XML layout file
+ //TODO: fix ignored attributes
+// TypedArray attributes = context.obtainStyledAttributes(
+// attrs, com.android.internal.R.styleable.RadioGroup, com.android.internal.R.attr.radioButtonStyle, 0);
+
+// int value = attributes.getResourceId(com.android.internal.R.styleable.RadioGroup_checkedButton, View.NO_ID);
+// if (value != View.NO_ID) {
+// mCheckedId = value;
+// }
+
+// final int index = attributes.getInt(com.android.internal.R.styleable.RadioGroup_orientation, VERTICAL);
+// setOrientation(index);
+
+// attributes.recycle();
+ init();
+ }
+
+ private void init() {
+ mChildOnCheckedChangeListener = new CheckedStateTracker();
+ mPassThroughListener = new PassThroughHierarchyChangeListener();
+ super.setOnHierarchyChangeListener(mPassThroughListener);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
+ // the user listener is delegated to our pass-through listener
+ mPassThroughListener.mOnHierarchyChangeListener = listener;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ // checks the appropriate radio button as requested in the XML file
+ if (mCheckedId != -1) {
+ mProtectFromCheckedChange = true;
+ setCheckedStateForView(mCheckedId, true);
+ mProtectFromCheckedChange = false;
+ setCheckedId(mCheckedId);
+ }
+ }
+
+ @Override
+ public void addView(View child, int index, ViewGroup.LayoutParams params) {
+ if (child instanceof RadioButton) {
+ final RadioButton button = (RadioButton) child;
+ if (button.isChecked()) {
+ mProtectFromCheckedChange = true;
+ if (mCheckedId != -1) {
+ setCheckedStateForView(mCheckedId, false);
+ }
+ mProtectFromCheckedChange = false;
+ setCheckedId(button.getId());
+ }
+ }
+
+ super.addView(child, index, params);
+ }
+
+ /**
+ *
Sets the selection to the radio button whose identifier is passed in
+ * parameter. Using -1 as the selection identifier clears the selection;
+ * such an operation is equivalent to invoking {@link #clearCheck()}.
+ *
+ * @param id the unique id of the radio button to select in this group
+ * @see #getCheckedRadioButtonId()
+ * @see #clearCheck()
+ */
+ public void check(@IdRes int id) {
+ // don't even bother
+ if (id != -1 && (id == mCheckedId)) {
+ return;
+ }
+
+ if (mCheckedId != -1) {
+ setCheckedStateForView(mCheckedId, false);
+ }
+
+ if (id != -1) {
+ setCheckedStateForView(id, true);
+ }
+
+ setCheckedId(id);
+ }
+
+ private void setCheckedId(@IdRes int id) {
+ mCheckedId = id;
+ if (mOnCheckedChangeListener != null) {
+ mOnCheckedChangeListener.onCheckedChanged(this, mCheckedId);
+ }
+ }
+
+ private void setCheckedStateForView(int viewId, boolean checked) {
+ View checkedView = findViewById(viewId);
+ if (checkedView != null && checkedView instanceof RadioButton) {
+ ((RadioButton) checkedView).setChecked(checked);
+ }
+ }
+
+ /**
+ *
Returns the identifier of the selected radio button in this group.
+ * Upon empty selection, the returned value is -1.
+ *
+ * @return the unique id of the selected radio button in this group
+ * @attr ref android.R.styleable#RadioGroup_checkedButton
+ * @see #check(int)
+ * @see #clearCheck()
+ */
+ @IdRes
+ public int getCheckedRadioButtonId() {
+ return mCheckedId;
+ }
+
+ /**
+ *
Clears the selection. When the selection is cleared, no radio button
+ * in this group is selected and {@link #getCheckedRadioButtonId()} returns
+ * null.
Fixes the child's width to
+ * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and the child's
+ * height to {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}
+ * when not specified in the XML file.
+ *
+ * @param a the styled attributes set
+ * @param widthAttr the width attribute to fetch
+ * @param heightAttr the height attribute to fetch
+ */
+ @Override
+ protected void setBaseAttributes(TypedArray a,
+ int widthAttr, int heightAttr) {
+
+ if (a.hasValue(widthAttr)) {
+ width = a.getLayoutDimension(widthAttr, "layout_width");
+ } else {
+ width = WRAP_CONTENT;
+ }
+
+ if (a.hasValue(heightAttr)) {
+ height = a.getLayoutDimension(heightAttr, "layout_height");
+ } else {
+ height = WRAP_CONTENT;
+ }
+ }
+ }
+
+ /**
+ *
Interface definition for a callback to be invoked when the checked
+ * radio button changed in this group.
Called when the checked radio button has changed. When the
+ * selection is cleared, checkedId is -1.
+ *
+ * @param group the group in which the checked radio button has changed
+ * @param checkedId the unique identifier of the newly checked radio button
+ */
+ public void onCheckedChanged(RadioGroupPlus group, @IdRes int checkedId);
+ }
+
+ private class CheckedStateTracker implements CompoundButton.OnCheckedChangeListener {
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ // prevents from infinite recursion
+ if (mProtectFromCheckedChange) {
+ return;
+ }
+
+ mProtectFromCheckedChange = true;
+ if (mCheckedId != -1) {
+ setCheckedStateForView(mCheckedId, false);
+ }
+ mProtectFromCheckedChange = false;
+
+ int id = buttonView.getId();
+ setCheckedId(id);
+ }
+ }
+
+ /**
+ *
A pass-through listener acts upon the events and dispatches them
+ * to another listener. This allows the table layout to set its own internal
+ * hierarchy change listener without preventing the user to setup his.
+ */
+ private class PassThroughHierarchyChangeListener implements
+ ViewGroup.OnHierarchyChangeListener {
+ private ViewGroup.OnHierarchyChangeListener mOnHierarchyChangeListener;
+
+ public void traverseTree(View view) {
+ if (view instanceof RadioButton) {
+ int id = view.getId();
+ // generates an id if it's missing
+ if (id == View.NO_ID) {
+ id = View.generateViewId();
+ view.setId(id);
+ }
+ ((RadioButton) view).setOnCheckedChangeListener(
+ mChildOnCheckedChangeListener);
+ }
+ if (!(view instanceof ViewGroup)) {
+ return;
+ }
+ ViewGroup viewGroup = (ViewGroup) view;
+ if (viewGroup.getChildCount() == 0) {
+ return;
+ }
+ for (int i = 0; i < viewGroup.getChildCount(); i++) {
+ traverseTree(viewGroup.getChildAt(i));
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void onChildViewAdded(View parent, View child) {
+ traverseTree(child);
+ if (parent == RadioGroupPlus.this && child instanceof RadioButton) {
+ int id = child.getId();
+ // generates an id if it's missing
+ if (id == View.NO_ID) {
+ id = View.generateViewId();
+ child.setId(id);
+ }
+ ((RadioButton) child).setOnCheckedChangeListener(
+ mChildOnCheckedChangeListener);
+ }
+
+ if (mOnHierarchyChangeListener != null) {
+ mOnHierarchyChangeListener.onChildViewAdded(parent, child);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void onChildViewRemoved(View parent, View child) {
+ if (parent == RadioGroupPlus.this && child instanceof RadioButton) {
+ ((RadioButton) child).setOnCheckedChangeListener(null);
+ }
+
+ if (mOnHierarchyChangeListener != null) {
+ mOnHierarchyChangeListener.onChildViewRemoved(parent, child);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/libsdl/app/ClearRenderer.java b/app/src/main/java/org/libsdl/app/ClearRenderer.java
new file mode 100644
index 0000000..3d277ff
--- /dev/null
+++ b/app/src/main/java/org/libsdl/app/ClearRenderer.java
@@ -0,0 +1,25 @@
+package org.libsdl.app;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+import android.opengl.GLSurfaceView;
+import android.util.Log;
+
+public class ClearRenderer implements GLSurfaceView.Renderer {
+ public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+ // Do nothing special.
+ Log.v("onSurfaceCreated", "...");
+ }
+
+ public void onSurfaceChanged(GL10 gl, int w, int h) {
+ Log.v("onSurfaceChanged", "...");
+ gl.glViewport(0, 0, w, h);
+ }
+
+ public void onDrawFrame(GL10 gl) {
+ Log.v("onDrawFrame", "...");
+ gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
+
+ }
+}
diff --git a/app/src/main/java/org/libsdl/app/SDLActivity.java b/app/src/main/java/org/libsdl/app/SDLActivity.java
new file mode 100644
index 0000000..c04fb84
--- /dev/null
+++ b/app/src/main/java/org/libsdl/app/SDLActivity.java
@@ -0,0 +1,1474 @@
+package org.libsdl.app;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioTrack;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.text.InputType;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Gravity;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.inputmethod.BaseInputConnection;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import androidx.appcompat.app.AppCompatActivity;
+import com.epicstudios.vectras.MainActivity;
+import com.epicstudios.vectras.R;
+import com.epicstudios.vectras.VectrasSDLActivity;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * SDL Activity
+ */
+public class SDLActivity extends AppCompatActivity {
+ private static final String TAG = "SDL";
+
+ // Keep track of the paused state
+ public static boolean mIsPaused, mIsSurfaceReady, mHasFocus;
+ public static boolean mExitCalledFromJava;
+
+ public static native void createEngine();
+
+ public static native void createBufferQueueAudioPlayer(int sampleRate, int samplesPerBuf);
+
+ public static native void shutdown();
+
+ /**
+ * If shared libraries (e.g. SDL or the native application) could not be
+ * loaded.
+ */
+ public static boolean mBrokenLibraries;
+
+ // If we want to separate mouse and touch events.
+ // This is only toggled in native code when a hint is set!
+ public static boolean mSeparateMouseAndTouch;
+
+ // Main components
+ protected static SDLActivity mSingleton;
+ protected static SDLSurface mSurface;
+ protected static View mTextEdit;
+ protected static ViewGroup mLayout;
+ protected static SDLJoystickHandler mJoystickHandler;
+
+ // This is what SDL runs in. It invokes SDL_main(), eventually
+ protected static Thread mSDLThread;
+
+ // Audio
+ protected static AudioTrack mAudioTrack;
+
+ /**
+ * This method is called by SDL before loading the native shared libraries.
+ * It can be overridden to provide names of shared libraries to be loaded.
+ * The default implementation returns the defaults. It never returns null.
+ * An array returned by a new implementation must at least contain "SDL2".
+ * Also keep in mind that the order the libraries are loaded may matter.
+ *
+ * @return names of shared libraries to be loaded (e.g. "SDL2", "main").
+ */
+ // protected String[] getLibraries() {
+ // return new String[] {
+ // "SDL2",
+ // // "SDL2_image",
+ // // "SDL2_mixer",
+ // // "SDL2_net",
+ // // "SDL2_ttf",
+ // "main"
+ // };
+ // }
+
+ // Load the .so
+ public void loadLibraries() {
+ // for (String lib : getLibraries()) {
+ // System.loadLibrary(lib);
+ // }
+ }
+
+ // XXX: LIMBO
+ public static int vm_width;
+ public static int vm_height;
+ // public static float width_mult = (float) 1.0;
+ // public static float height_mult = (float) 1.0;
+
+ private static int maxBufferSize;
+
+ private static int bufferSize;
+
+ private static Thread audioThread;
+
+ private static int buffersSize;
+
+ public static void setSDLResolution(int width, int height) {
+
+ vm_width = width;
+ vm_height = height;
+
+ // new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
+ // @Override
+ // public void run() {
+ // int newWidth = mSurface.getWidth();
+ // int newHeight = mSurface.getHeight();
+ // if(mSingleton.getResources().getConfiguration().orientation ==
+ // Configuration.ORIENTATION_PORTRAIT){
+ // //Adjust the Height
+ // newHeight = (int) ((float) newWidth * vm_height / (float) vm_width);
+ // } else if(mSingleton.getResources().getConfiguration().orientation ==
+ // Configuration.ORIENTATION_LANDSCAPE){
+ // //Adjust the Width
+ // newWidth = (int) ((float) newHeight * vm_width / (float) vm_height);
+ //
+ // //TODO: If it's not fullscreen we might want to adjust the height
+ // instead so the surface align on top
+ //
+ // }
+ //
+ // Log.v("setSDLResolution", "Resizing Surface to " + newWidth + "x" +
+ // newHeight);
+ // mSurface.getHolder().setFixedSize(newWidth, newHeight);
+ // }
+ // }, 0);
+
+ }
+
+ /**
+ * This method is called by SDL before starting the native application
+ * thread. It can be overridden to provide the arguments after the
+ * application name. The default implementation returns an empty array. It
+ * never returns null.
+ *
+ * @return arguments for the native application.
+ */
+ protected String[] getArguments() {
+ return new String[0];
+ }
+
+ public static void initialize() {
+ // The static nature of the singleton and Android quirkyness force us to
+ // initialize everything here
+ // Otherwise, when exiting the app and returning to it, these variables
+ // *keep* their pre exit values
+ mSingleton = null;
+ mSurface = null;
+ mTextEdit = null;
+ mLayout = null;
+ mJoystickHandler = null;
+ mSDLThread = null;
+ mAudioTrack = null;
+ mExitCalledFromJava = false;
+ mBrokenLibraries = false;
+ mIsPaused = false;
+ mIsSurfaceReady = false;
+ mHasFocus = true;
+ }
+
+ // Setup
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ Log.v(TAG, "Device: " + android.os.Build.DEVICE);
+ Log.v(TAG, "Model: " + android.os.Build.MODEL);
+ Log.v(TAG, "onCreate(): " + mSingleton);
+ super.onCreate(savedInstanceState);
+
+ SDLActivity.initialize();
+ // So we can call stuff from static callbacks
+ mSingleton = this;
+
+ setupVolume();
+
+ // Load shared libraries
+ String errorMsgBrokenLib = "";
+ try {
+ loadLibraries();
+ } catch (UnsatisfiedLinkError e) {
+ System.err.println(e.getMessage());
+ mBrokenLibraries = true;
+ errorMsgBrokenLib = e.getMessage();
+ } catch (Exception e) {
+ System.err.println(e.getMessage());
+ mBrokenLibraries = true;
+ errorMsgBrokenLib = e.getMessage();
+ }
+
+ if (mBrokenLibraries) {
+ AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this);
+ dlgAlert.setMessage(
+ "An error occurred while trying to start the application. Please try again and/or reinstall."
+ + System.getProperty("line.separator") + System.getProperty("line.separator") + "Error: "
+ + errorMsgBrokenLib);
+ dlgAlert.setTitle("SDL Error");
+ dlgAlert.setPositiveButton("Exit", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ // if this button is clicked, close current activity
+ SDLActivity.mSingleton.finish();
+ }
+ });
+ dlgAlert.setCancelable(false);
+ dlgAlert.create().show();
+
+ return;
+ }
+
+ // XXX: Limbo no need
+ // Set up the surface
+ // mSurface = new SDLSurface(getApplication());
+
+ if (Build.VERSION.SDK_INT >= 12) {
+ mJoystickHandler = new SDLJoystickHandler_API12();
+ } else {
+ mJoystickHandler = new SDLJoystickHandler();
+ }
+
+ // XXX: Limbo no need
+ // mLayout = new AbsoluteLayout(this);
+ // mLayout.addView(mSurface);
+
+ // setContentView(mLayout);
+
+ // Get filename from "Open with" of another application
+ Intent intent = getIntent();
+
+ if (intent != null && intent.getData() != null) {
+ String filename = intent.getData().getPath();
+ if (filename != null) {
+ Log.v(TAG, "Got filename: " + filename);
+ SDLActivity.onNativeDropFile(filename);
+ }
+ }
+ }
+
+ // Events
+ @Override
+ protected void onPause() {
+ Log.v(TAG, "onPause()");
+ super.onPause();
+
+ if (SDLActivity.mBrokenLibraries) {
+ return;
+ }
+ SDLActivity.handlePause();
+ }
+
+ @Override
+ protected void onResume() {
+ Log.v(TAG, "onResume()");
+ super.onResume();
+
+ if (SDLActivity.mBrokenLibraries) {
+ return;
+ }
+
+ SDLActivity.handleResume();
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+ Log.v(TAG, "onWindowFocusChanged(): " + hasFocus);
+
+ if (SDLActivity.mBrokenLibraries) {
+ return;
+ }
+
+ SDLActivity.mHasFocus = hasFocus;
+ if (hasFocus) {
+ SDLActivity.handleResume();
+ }
+ }
+
+ @Override
+ public void onLowMemory() {
+ Log.v(TAG, "onLowMemory()");
+ super.onLowMemory();
+
+ if (SDLActivity.mBrokenLibraries) {
+ return;
+ }
+
+ SDLActivity.nativeLowMemory();
+ }
+
+ @Override
+ protected void onDestroy() {
+ Log.v(TAG, "onDestroy()");
+
+ if (SDLActivity.mBrokenLibraries) {
+ super.onDestroy();
+ // Reset everything in case the user re opens the app
+ SDLActivity.initialize();
+ return;
+ }
+
+ // Send a quit message to the application
+ SDLActivity.mExitCalledFromJava = true;
+ SDLActivity.nativeQuit();
+
+ // Now wait for the SDL thread to quit
+ if (SDLActivity.mSDLThread != null) {
+ try {
+ SDLActivity.mSDLThread.join();
+ } catch (Exception e) {
+ Log.v(TAG, "Problem stopping thread: " + e);
+ }
+ SDLActivity.mSDLThread = null;
+
+ // Log.v(TAG, "Finished waiting for SDL thread");
+ }
+
+ super.onDestroy();
+ // Reset everything in case the user re opens the app
+ SDLActivity.initialize();
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+
+ if (SDLActivity.mBrokenLibraries) {
+ return false;
+ }
+
+ int keyCode = event.getKeyCode();
+ // Ignore certain special keys so they're handled by Android
+ if (
+ // keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode ==
+ // KeyEvent.KEYCODE_VOLUME_UP||
+ keyCode == KeyEvent.KEYCODE_CAMERA || keyCode == 168
+ || /* API 11: KeyEvent.KEYCODE_ZOOM_IN */
+ keyCode == 169 /* API 11: KeyEvent.KEYCODE_ZOOM_OUT */
+ ) {
+ return false;
+ } else if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) {
+ this.onBackPressed();
+ }
+ return super.dispatchKeyEvent(event);
+ }
+
+ /**
+ * Called by onPause or surfaceDestroyed. Even if surfaceDestroyed is the
+ * first to be called, mIsSurfaceReady should still be set to 'true' during
+ * the call to onPause (in a usual scenario).
+ */
+ public static void handlePause() {
+ if (!SDLActivity.mIsPaused && SDLActivity.mIsSurfaceReady) {
+ SDLActivity.mIsPaused = true;
+ SDLActivity.nativePause();
+ mSurface.handlePause();
+ }
+ }
+
+ /**
+ * Called by onResume or surfaceCreated. An actual resume should be done
+ * only when the surface is ready. Note: Some Android variants may send
+ * multiple surfaceChanged events, so we don't need to resume every time we
+ * get one of those events, only if it comes after surfaceDestroyed
+ */
+ public static void handleResume() {
+ Log.v(TAG, "HandleResume");
+ if (SDLActivity.mIsPaused && SDLActivity.mIsSurfaceReady && SDLActivity.mHasFocus) {
+ SDLActivity.mIsPaused = false;
+ SDLActivity.nativeResume();
+ mSurface.handleResume();
+ }
+ }
+
+ /* The native thread has finished */
+ public static void handleNativeExit() {
+ SDLActivity.mSDLThread = null;
+ mSingleton.finish();
+ }
+
+ // Messages from the SDLMain thread
+ static final int COMMAND_CHANGE_TITLE = 1;
+ static final int COMMAND_UNUSED = 2;
+ static final int COMMAND_TEXTEDIT_HIDE = 3;
+ static final int COMMAND_SET_KEEP_SCREEN_ON = 5;
+
+ protected static final int COMMAND_USER = 0x8000;
+
+ /**
+ * This method is called by SDL if SDL did not handle a message itself. This
+ * happens if a received message contains an unsupported command. Method can
+ * be overwritten to handle Messages in a different class.
+ *
+ * @param command
+ * the command of the message.
+ * @param param
+ * the parameter of the message. May be null.
+ * @return if the message was handled in overridden method.
+ */
+ protected boolean onUnhandledMessage(int command, Object param) {
+ return false;
+ }
+
+ /**
+ * A Handler class for Messages from native SDL applications. It uses
+ * current Activities as target (e.g. for the title). static to prevent
+ * implicit references to enclosing object.
+ */
+ protected static class SDLCommandHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ Context context = getContext();
+ if (context == null) {
+ Log.e(TAG, "error handling message, getContext() returned null");
+ return;
+ }
+ switch (msg.arg1) {
+ case COMMAND_CHANGE_TITLE:
+ if (context instanceof Activity) {
+ ((Activity) context).setTitle((String) msg.obj);
+ } else {
+ Log.e(TAG, "error handling message, getContext() returned no Activity");
+ }
+ break;
+ case COMMAND_TEXTEDIT_HIDE:
+ if (mTextEdit != null) {
+ mTextEdit.setVisibility(View.GONE);
+
+ InputMethodManager imm = (InputMethodManager) context
+ .getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0);
+ }
+ break;
+ case COMMAND_SET_KEEP_SCREEN_ON: {
+ Window window = ((Activity) context).getWindow();
+ if (window != null) {
+ if ((msg.obj instanceof Integer) && (((Integer) msg.obj).intValue() != 0)) {
+ window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ } else {
+ window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+ }
+ break;
+ }
+ default:
+ if ((context instanceof SDLActivity)
+ && !((SDLActivity) context).onUnhandledMessage(msg.arg1, msg.obj)) {
+ Log.e(TAG, "error handling message, command is " + msg.arg1);
+ }
+ }
+ }
+ }
+
+ // Handler for the messages
+ protected Handler commandHandler = new SDLCommandHandler();
+
+ protected int maxVolume;
+
+ // Send a message from the SDLMain thread
+ protected boolean sendCommand(int command, Object data) {
+ Message msg = commandHandler.obtainMessage();
+ msg.arg1 = command;
+ msg.obj = data;
+ return commandHandler.sendMessage(msg);
+ }
+
+ // C functions we call
+ public static native int nativeInit(Object arguments);
+
+ public static native void nativeLowMemory();
+
+ public static native void nativeQuit();
+
+ public static native void nativePause();
+
+ public static native void nativeResume();
+
+ public static native void onNativeDropFile(String filename);
+
+ public static native void onNativeResize(int x, int y, int format, float rate);
+
+ public static native int onNativePadDown(int device_id, int keycode);
+
+ public static native int onNativePadUp(int device_id, int keycode);
+
+ public static native void onNativeJoy(int device_id, int axis, float value);
+
+ public static native void onNativeHat(int device_id, int hat_id, int x, int y);
+
+ public static native void onNativeKeyDown(int keycode);
+
+ public static native void onNativeKeyUp(int keycode);
+
+ public static native void onNativeKeyboardFocusLost();
+
+ public static native void onNativeMouse(int button, int action, float x, float y);
+
+ public static native void onNativeTouch(int touchDevId, int pointerFingerId, int action, float x, float y, float p);
+
+ public static native void onNativeAccel(float x, float y, float z);
+
+ public static native void onNativeSurfaceChanged();
+
+ public static native void onNativeSurfaceDestroyed();
+
+ public static native int nativeAddJoystick(int device_id, String name, int is_accelerometer, int nbuttons,
+ int naxes, int nhats, int nballs);
+
+ public static native int nativeRemoveJoystick(int device_id);
+
+ public static native String nativeGetHint(String name);
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static boolean setActivityTitle(String title) {
+ // Called from SDLMain() thread and can't directly affect the view
+ return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title);
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static boolean sendMessage(int command, int param) {
+ return mSingleton.sendCommand(command, Integer.valueOf(param));
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static Context getContext() {
+ return mSingleton;
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ *
+ * @return result of getSystemService(name) but executed on UI thread.
+ */
+ public Object getSystemServiceFromUiThread(final String name) {
+ final Object lock = new Object();
+ final Object[] results = new Object[2]; // array for writable variables
+ synchronized (lock) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (lock) {
+ results[0] = getSystemService(name);
+ results[1] = Boolean.TRUE;
+ lock.notify();
+ }
+ }
+ });
+ if (results[1] == null) {
+ try {
+ lock.wait();
+ } catch (InterruptedException ex) {
+ ex.printStackTrace();
+ }
+ }
+ }
+ return results[0];
+ }
+
+ static class ShowTextInputTask implements Runnable {
+ /*
+ * This is used to regulate the pan&scan method to have some offset from
+ * the bottom edge of the input region and the top edge of an input
+ * method (soft keyboard)
+ */
+ static final int HEIGHT_PADDING = 15;
+
+ public int x, y, w, h;
+
+ public ShowTextInputTask(int x, int y, int w, int h) {
+ this.x = x;
+ this.y = y;
+ this.w = w;
+ this.h = h;
+ }
+
+ @Override
+ public void run() {
+ LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT);
+
+ if (mTextEdit == null) {
+ mTextEdit = new DummyEdit(getContext());
+
+ mLayout.addView(mTextEdit, params);
+ } else {
+ mTextEdit.setLayoutParams(params);
+ }
+
+ mTextEdit.setVisibility(View.VISIBLE);
+ mTextEdit.requestFocus();
+
+ InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.showSoftInput(mTextEdit, 0);
+ }
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static boolean showTextInput(int x, int y, int w, int h) {
+ // Transfer the task to the main thread as a Runnable
+ return mSingleton.commandHandler.post(new ShowTextInputTask(x, y, w, h));
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static Surface getNativeSurface() {
+ return SDLActivity.mSurface.getNativeSurface();
+ }
+
+ // Audio
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static int audioInit(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) {
+ int channelConfig = isStereo ? AudioFormat.CHANNEL_OUT_STEREO : AudioFormat.CHANNEL_OUT_MONO;
+ int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT;
+ int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1);
+
+ Log.v(TAG, "SDL audio: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " "
+ + (sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer");
+
+ // Let the user pick a larger buffer if they really want -- but ye
+ // gods they probably shouldn't, the minimums are horrifyingly high
+ // latency already
+ desiredFrames = Math.max(desiredFrames,
+ (AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize);
+ // desiredFrames =2048;
+ // desiredFrames *= 10;
+
+ if (mAudioTrack == null) {
+ mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig, audioFormat,
+ desiredFrames * frameSize, AudioTrack.MODE_STREAM);
+
+ // Instantiating AudioTrack can "succeed" without an exception and
+ // the track may still be invalid
+ // Ref:
+ // https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java
+ // Ref:
+ // http://developer.android.com/reference/android/media/AudioTrack.html#getState()
+
+ if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
+ Log.e(TAG, "Failed during initialization of Audio Track");
+ mAudioTrack = null;
+ return -1;
+ }
+
+ mAudioTrack.play();
+ }
+
+ Log.v(TAG,
+ "SDL audio: got " + ((mAudioTrack.getChannelCount() >= 2) ? "stereo" : "mono") + " "
+ + ((mAudioTrack.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " "
+ + (mAudioTrack.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer");
+
+ return 0;
+ }
+
+ public AudioManager am;
+
+ protected void setupVolume() {
+ if (am == null) {
+ am = (AudioManager) mSingleton.getSystemService(Context.AUDIO_SERVICE);
+ maxVolume = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ }
+ }
+
+ public void setVolume(int volume) {
+ if(am!=null)
+ am.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
+ }
+
+ protected int getCurrentVolume() {
+ int volumeTmp = 0;
+ if(am!=null)
+ volumeTmp = am.getStreamVolume(AudioManager.STREAM_MUSIC);
+ return volumeTmp;
+ }
+
+ static ArrayList audioShortBuffer = new ArrayList();
+ static Object audioLock = new Object();
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static void audioWriteShortBuffer(short[] buffer) {
+ // Log.d(TAG, "audioWriteShortBuffer start: " + buffer.length);
+ for (int i = 0; i < buffer.length;) {
+ int result = mAudioTrack.write(buffer, i, buffer.length - i);
+ // Log.d(TAG, "Wrote to audioWriteShortBuffer: " + result);
+ if (result > 0) {
+ i += result;
+ } else if (result == 0) {
+ try {
+ Thread.sleep(1);
+ } catch (InterruptedException e) {
+ // Nom nom
+ // Log.e(TAG, "SDL: Audio Short interrupted: " + e);
+ }
+ } else {
+ // Log.w(TAG, "SDL audio: error return from write(short)");
+ return;
+ }
+ }
+ // Log.d(TAG, "audioWriteShortBuffer end");
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static void audioWriteByteBuffer(byte[] buffer) {
+ // Log.d(TAG, "audioWriteByteBuffer start: " + buffer.length);
+ for (int i = 0; i < buffer.length;) {
+ int result = mAudioTrack.write(buffer, i, buffer.length - i);
+ if (result > 0) {
+ i += result;
+ } else if (result == 0) {
+ try {
+ Thread.sleep(1);
+ } catch (InterruptedException e) {
+ // Nom nom
+ // Log.e(TAG, "SDL: Audio Byte interrupted: " + e);
+ }
+ } else {
+ // Log.w(TAG, "SDL audio: error return from write(byte)");
+ return;
+ }
+ }
+ // Log.d(TAG, "audioWriteByteBuffer end");
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static void audioQuit() {
+ Log.d(TAG, "audioQuit start");
+ if (mAudioTrack != null) {
+ mAudioTrack.stop();
+ mAudioTrack = null;
+ }
+ Log.d(TAG, "audioQuit end");
+ }
+
+ // Input
+
+ /**
+ * This method is called by SDL using JNI.
+ *
+ * @return an array which may be empty but is never null.
+ */
+ public static int[] inputGetInputDeviceIds(int sources) {
+ int[] ids = InputDevice.getDeviceIds();
+ int[] filtered = new int[ids.length];
+ int used = 0;
+ for (int i = 0; i < ids.length; ++i) {
+ InputDevice device = InputDevice.getDevice(ids[i]);
+ if ((device != null) && ((device.getSources() & sources) != 0)) {
+ filtered[used++] = device.getId();
+ }
+ }
+ return Arrays.copyOf(filtered, used);
+ }
+
+ // Joystick glue code, just a series of stubs that redirect to the
+ // SDLJoystickHandler instance
+ public static boolean handleJoystickMotionEvent(MotionEvent event) {
+ return mJoystickHandler.handleMotionEvent(event);
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static void pollInputDevices() {
+ if (SDLActivity.mSDLThread != null) {
+ mJoystickHandler.pollInputDevices();
+ }
+ }
+
+ // APK expansion files support
+
+ /** com.android.vending.expansion.zipfile.ZipResourceFile object or null. */
+ private Object expansionFile;
+
+ /**
+ * com.android.vending.expansion.zipfile.ZipResourceFile's getInputStream()
+ * or null.
+ */
+ private Method expansionFileMethod;
+
+ /**
+ * This method was called by SDL using JNI.
+ *
+ * @deprecated because of an incorrect name
+ */
+ @Deprecated
+ public InputStream openAPKExtensionInputStream(String fileName) throws IOException {
+ return openAPKExpansionInputStream(fileName);
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ *
+ * @return an InputStream on success or null if no expansion file was used.
+ * @throws IOException
+ * on errors. Message is set for the SDL error message.
+ */
+ public InputStream openAPKExpansionInputStream(String fileName) throws IOException {
+ // Get a ZipResourceFile representing a merger of both the main and
+ // patch files
+ if (expansionFile == null) {
+ String mainHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_MAIN_FILE_VERSION");
+ if (mainHint == null) {
+ return null; // no expansion use if no main version was set
+ }
+ String patchHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_PATCH_FILE_VERSION");
+ if (patchHint == null) {
+ return null; // no expansion use if no patch version was set
+ }
+
+ Integer mainVersion;
+ Integer patchVersion;
+ try {
+ mainVersion = Integer.valueOf(mainHint);
+ patchVersion = Integer.valueOf(patchHint);
+ } catch (NumberFormatException ex) {
+ ex.printStackTrace();
+ throw new IOException("No valid file versions set for APK expansion files", ex);
+ }
+
+ try {
+ // To avoid direct dependency on Google APK expansion library
+ // that is
+ // not a part of Android SDK we access it using reflection
+ expansionFile = Class.forName("com.android.vending.expansion.zipfile.APKExpansionSupport")
+ .getMethod("getAPKExpansionZipFile", Context.class, int.class, int.class)
+ .invoke(null, this, mainVersion, patchVersion);
+
+ expansionFileMethod = expansionFile.getClass().getMethod("getInputStream", String.class);
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ expansionFile = null;
+ expansionFileMethod = null;
+ throw new IOException("Could not access APK expansion support library", ex);
+ }
+ }
+
+ // Get an input stream for a known file inside the expansion file ZIPs
+ InputStream fileStream;
+ try {
+ fileStream = (InputStream) expansionFileMethod.invoke(expansionFile, fileName);
+ } catch (Exception ex) {
+ // calling "getInputStream" failed
+ ex.printStackTrace();
+ throw new IOException("Could not open stream from APK expansion file", ex);
+ }
+
+ if (fileStream == null) {
+ // calling "getInputStream" was successful but null was returned
+ throw new IOException("Could not find path in APK expansion file");
+ }
+
+ return fileStream;
+ }
+
+ // Messagebox
+
+ /**
+ * Result of current messagebox. Also used for blocking the calling thread.
+ */
+ protected final int[] messageboxSelection = new int[1];
+
+ /** Id of current dialog. */
+ protected int dialogs = 0;
+
+ /**
+ * This method is called by SDL using JNI. Shows the messagebox from UI
+ * thread and block calling thread. buttonFlags, buttonIds and buttonTexts
+ * must have same length.
+ *
+ * @param buttonFlags
+ * array containing flags for every button.
+ * @param buttonIds
+ * array containing id for every button.
+ * @param buttonTexts
+ * array containing text for every button.
+ * @param colors
+ * null for default or array of length 5 containing colors.
+ * @return button id or -1.
+ */
+ public int messageboxShowMessageBox(final int flags, final String title, final String message,
+ final int[] buttonFlags, final int[] buttonIds, final String[] buttonTexts, final int[] colors) {
+
+ messageboxSelection[0] = -1;
+
+ // sanity checks
+
+ if ((buttonFlags.length != buttonIds.length) && (buttonIds.length != buttonTexts.length)) {
+ return -1; // implementation broken
+ }
+
+ // collect arguments for Dialog
+
+ final Bundle args = new Bundle();
+ args.putInt("flags", flags);
+ args.putString("title", title);
+ args.putString("message", message);
+ args.putIntArray("buttonFlags", buttonFlags);
+ args.putIntArray("buttonIds", buttonIds);
+ args.putStringArray("buttonTexts", buttonTexts);
+ args.putIntArray("colors", colors);
+
+ // trigger Dialog creation on UI thread
+
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ showDialog(dialogs++, args);
+ }
+ });
+
+ // block the calling thread
+
+ synchronized (messageboxSelection) {
+ try {
+ messageboxSelection.wait();
+ } catch (InterruptedException ex) {
+ ex.printStackTrace();
+ return -1;
+ }
+ }
+
+ // return selected value
+
+ return messageboxSelection[0];
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int ignore, Bundle args) {
+
+ // TODO set values from "flags" to messagebox dialog
+
+ // get colors
+
+ int[] colors = args.getIntArray("colors");
+ int backgroundColor;
+ int textColor;
+ int buttonBorderColor;
+ int buttonBackgroundColor;
+ int buttonSelectedColor;
+ if (colors != null) {
+ int i = -1;
+ backgroundColor = colors[++i];
+ textColor = colors[++i];
+ buttonBorderColor = colors[++i];
+ buttonBackgroundColor = colors[++i];
+ buttonSelectedColor = colors[++i];
+ } else {
+ backgroundColor = Color.TRANSPARENT;
+ textColor = Color.TRANSPARENT;
+ buttonBorderColor = Color.TRANSPARENT;
+ buttonBackgroundColor = Color.TRANSPARENT;
+ buttonSelectedColor = Color.TRANSPARENT;
+ }
+
+ // create dialog with title and a listener to wake up calling thread
+
+ final Dialog dialog = new Dialog(this);
+ dialog.setTitle(args.getString("title"));
+ dialog.setCancelable(false);
+ dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
+ @Override
+ public void onDismiss(DialogInterface unused) {
+ synchronized (messageboxSelection) {
+ messageboxSelection.notify();
+ }
+ }
+ });
+
+ // create text
+
+ TextView message = new TextView(this);
+ message.setGravity(Gravity.CENTER);
+ message.setText(args.getString("message"));
+ if (textColor != Color.TRANSPARENT) {
+ message.setTextColor(textColor);
+ }
+
+ // create buttons
+
+ int[] buttonFlags = args.getIntArray("buttonFlags");
+ int[] buttonIds = args.getIntArray("buttonIds");
+ String[] buttonTexts = args.getStringArray("buttonTexts");
+
+ final SparseArray