debug version build 37 v2.0
This commit is contained in:
Epic Studios 2023-12-28 19:29:36 +02:00
parent d35988d2a1
commit a47a6ea153
461 changed files with 26391 additions and 8275 deletions

View file

@ -0,0 +1,184 @@
package com.vectras.vm;
import android.annotation.SuppressLint;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.AdView;
import com.vectras.vm.utils.UIUtils;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import com.vectras.vm.R;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class AboutActivity extends AppCompatActivity implements View.OnClickListener{
Button btn_osl, btn_clog, btn_youtube, btn_github,btn_telegram;
String appInfo;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_about);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
toolbar.setTitle(getString(R.string.app_name));
//btn
btn_telegram = (Button) findViewById(R.id.btn_telegram);
btn_youtube = (Button) findViewById(R.id.btn_youtube);
btn_github = (Button) findViewById(R.id.btn_github);
btn_osl = (Button) findViewById(R.id.btn_osl);
btn_clog = (Button) findViewById(R.id.btn_changelog);
//onclicklistener
btn_telegram.setOnClickListener(this);
btn_github.setOnClickListener(this);
btn_youtube.setOnClickListener(this);
btn_osl.setOnClickListener(this);
btn_clog.setOnClickListener(this);
AdView mAdView = findViewById(R.id.adView);
AdRequest adRequest = new AdRequest.Builder().build();
mAdView.loadAd(adRequest);
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(AppConfig.vectrasInfo); //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) {
UIUtils.toastLong(AboutActivity.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
runOnUiThread(new Runnable(){
public void run(){
appInfo = builder.toString(); // My TextFile has 3 lines
}
});
}
}).start();
FloatingActionButton fab = findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent i = new Intent(Intent.ACTION_SEND);
i.setType("message/rfc822");
i.putExtra(Intent.EXTRA_EMAIL , new String[]{"noureldeenelsayed856@gmail.com"});
i.putExtra(Intent.EXTRA_SUBJECT, "Vectras User: " + Build.BRAND);
i.putExtra(Intent.EXTRA_TEXT , "Device Model: \n" + Build.MODEL + "\n");
try {
startActivity(Intent.createChooser(i, "Send mail..."));
} catch (android.content.ActivityNotFoundException ex) {
Snackbar.make(view, "There are no email clients installed.", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
}
});
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if(item.getItemId()== android.R.id.home){
finish();
}
return super.onOptionsItemSelected(item);
}
public static final int TG = R.id.btn_telegram;
public static final int YT = R.id.btn_youtube;
public static final int GT = R.id.btn_github;
public static final int CL = R.id.btn_changelog;
public static final int OSL = R.id.btn_osl;
@Override
public void onClick(View v) {
int id = v.getId();
if (id == TG) {
String tg = "https://t.me/vectras_os";
Intent f = new Intent(Intent.ACTION_VIEW);
f.setData(Uri.parse(tg));
startActivity(f);
} else if (id == YT) {
String tw = "https://www.youtube.com/@XOURELDEEN";
Intent w = new Intent(Intent.ACTION_VIEW);
w.setData(Uri.parse(tw));
startActivity(w);
} else if (id == GT) {
String gt = AppConfig.vectrasRepo;
Intent g = new Intent(Intent.ACTION_VIEW);
g.setData(Uri.parse(gt));
startActivity(g);
} else if (id == CL) {
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this, R.style.MainDialogTheme);
alertDialogBuilder.setTitle("Changelog");
alertDialogBuilder
.setMessage(getString(R.string.app_version))
.setCancelable(true)
.setIcon(R.mipmap.ic_launcher)
.setNegativeButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
AlertDialog alertDialog = alertDialogBuilder.create();
alertDialog.show();
} else if (id == OSL) {
AlertDialog.Builder alertDialogOSL = new AlertDialog.Builder(this, R.style.MainDialogTheme);
alertDialogOSL.setTitle("APP INFO");
alertDialogOSL
.setMessage(appInfo)
.setCancelable(true)
.setIcon(R.drawable.round_info_24)
.setNegativeButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
AlertDialog alertDialogosl = alertDialogOSL.create();
alertDialogosl.show();
}
}
}

View file

@ -0,0 +1,34 @@
package com.vectras.vm;
import android.os.Environment;
import android.widget.ImageView.ScaleType;
import java.io.File;
import java.util.Hashtable;
/**
*
* @author dev
*/
public class AppConfig {
// App Config
public static final String vectrasWebsite = "https://vectras.netlify.com/";
public static final String vectrasRaw = "https://raw.githubusercontent.com/epicstudios856/Vectras-windows-emulator/main/";
public static final String vectrasLicense = vectrasRaw + "LICENSE.md";
public static final String vectrasPrivacy = vectrasRaw + "PRIVACYANDPOLICY.md";
public static final String vectrasTerms = vectrasRaw + "TERMSOFSERVICE.md";
public static final String vectrasInfo = vectrasRaw + "info.md";
public static final String vectrasRepo = "https://github.com/epicstudios856/Vectras-windows-emulator/tree/main/";
public static final String updateJson = vectrasRaw + "UpdateConfig.json";
public static final String blogJson = vectrasRaw + "news_list.json";
public static final String storeJson = vectrasRaw + "store_list.json";
public static final String romsJson = vectrasRaw + "roms.json";
// App config
public static final String datadirpath = SplashActivity.activity.getExternalFilesDir("data")+"/";
public static final String sharedFolder = datadirpath + "Vectras/ProgramFiles/";
public static final String basefiledir = datadirpath + "Vectras/.qemu/";
public static final String maindirpath = datadirpath + "/Vectras/";
}

View file

@ -0,0 +1,98 @@
package com.vectras.vm.Blog;
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.vectras.vm.R;
import com.vectras.vm.Fragment.HomeFragment;
import com.vectras.vm.PostActivity;
import java.util.Collections;
import java.util.List;
import com.vectras.vm.MainActivity;
public class AdapterBlog extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Context context;
private LayoutInflater inflater;
List<DataBlog> data = Collections.emptyList();
DataBlog current;
int currentPos = 0;
// create constructor to innitilize context and data sent from MainActivity
public AdapterBlog(Context context, List<DataBlog> 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_post, 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 DataBlog current = data.get(position);
myHolder.textTitle.setText(current.postTitle);
myHolder.textDate.setText("Date: " + current.postDate);
Glide.with(MainActivity.activity).load(current.postThumb).into(myHolder.ivThumb);
Animation animation;
animation = AnimationUtils.loadAnimation(MainActivity.activity, android.R.anim.slide_in_left);
animation.setDuration(300);
myHolder.cdPost.startAnimation(animation);
animation = null;
myHolder.cdPost.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
PostActivity.title = current.postTitle;
PostActivity.content = current.postContent;
PostActivity.date = current.postDate;
PostActivity.thumb = current.postThumb;
MainActivity.activity.startActivity(new Intent(MainActivity.activity, PostActivity.class));
}
});
}
// return total item from List
@Override
public int getItemCount() {
return data.size();
}
class MyHolder extends RecyclerView.ViewHolder {
CardView cdPost;
TextView textTitle;
ImageView ivThumb;
TextView textDate;
// create constructor to get widget reference
public MyHolder(View itemView) {
super(itemView);
cdPost = (CardView) itemView.findViewById(R.id.cdPost);
textTitle = (TextView) itemView.findViewById(R.id.textTitle);
ivThumb = (ImageView) itemView.findViewById(R.id.ivThumb);
textDate = (TextView) itemView.findViewById(R.id.textDate);
}
}
}

View file

@ -0,0 +1,9 @@
package com.vectras.vm.Blog;
public class DataBlog {
public String postThumb;
public String postTitle;
public String postContent;
public String postDate;
}

View file

@ -0,0 +1,250 @@
package com.vectras.vm.Fragment;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.net.NetworkRequest;
import android.os.Bundle;
import android.telephony.NetworkScanRequest;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Toast;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.vectras.vm.RomsManagerActivity;
import com.vectras.vm.MainRoms.AdapterMainRoms;
import com.vectras.vm.MainRoms.DataMainRoms;
import com.vectras.vm.R;
import com.vectras.vm.Blog.AdapterBlog;
import com.vectras.vm.Blog.DataBlog;
import com.vectras.vm.AppConfig;
import com.vectras.vm.MainActivity;
import com.vectras.vm.utils.FileUtils;
import com.vectras.vm.utils.UIUtils;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.UnknownHostException;
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;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class HomeFragment extends Fragment {
View view;
public static RecyclerView mRVBlog;
public static RecyclerView mRVMainRoms;
public static LinearLayout romsLayout;
private AdapterBlog mAdapter;
public static AdapterMainRoms mMainAdapter;
public MainActivity activity;
public String Data;
public static LinearLayout noConnectionLayout;
public SwipeRefreshLayout pullToRefresh;
public ImageView ivRomsManager;
public static JSONArray jArray;
public static List<DataMainRoms> data;
/*private ImageButton mStop;
private ImageButton mRestart;*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// TODO Auto-generated method stub
activity = MainActivity.activity;
view = inflater.inflate(R.layout.home_fragment, container, false);
romsLayout = view.findViewById(R.id.romsLayout);
ivRomsManager = view.findViewById(R.id.ivRomsManager);
noConnectionLayout = view.findViewById(R.id.noConnectionLayout);
mRVBlog = view.findViewById(R.id.blogRv);
if (checkConnection(activity)) {
new AsyncLogin().execute();
noConnectionLayout.setVisibility(View.GONE);
//mRVBlog.setVisibility(View.VISIBLE);
} else {
noConnectionLayout.setVisibility(View.VISIBLE);
//mRVBlog.setVisibility(View.GONE);
}
pullToRefresh = view.findViewById(R.id.refreshLayout);
pullToRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
if (checkConnection(activity)) {
new AsyncLogin().execute();
} else {
noConnectionLayout.setVisibility(View.VISIBLE);
pullToRefresh.setRefreshing(false);
}
}
});
data=new ArrayList<>();
try {
jArray = new JSONArray(FileUtils.readFromFile(MainActivity.activity, new File(AppConfig.maindirpath
+ "roms-data.json")));
// 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);
DataMainRoms romsMainData = new DataMainRoms();
romsMainData.itemName= json_data.getString("imgName");
romsMainData.itemIcon= json_data.getString("imgIcon");
romsMainData.itemPath= json_data.getString("imgPath");
romsMainData.itemExtra= json_data.getString("imgExtra");
data.add(romsMainData);
}
// Setup and Handover data to recyclerview
mRVMainRoms = (RecyclerView)view.findViewById(R.id.mRVMainRoms);
mMainAdapter = new AdapterMainRoms(MainActivity.activity, data);
mRVMainRoms.setAdapter(mMainAdapter);
mRVMainRoms.setLayoutManager(new LinearLayoutManager(MainActivity.activity));
} catch (JSONException e) {
Toast.makeText(MainActivity.activity, e.toString(), Toast.LENGTH_LONG).show();
}
ivRomsManager.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.activity, RomsManagerActivity.class));
}
});
return view;
}
/**
* CHECK WHETHER INTERNET CONNECTION IS AVAILABLE OR NOT
*/
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<String, String, String> {
@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(AppConfig.blogJson);
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<DataBlog> 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);
DataBlog postData = new DataBlog();
postData.postThumb = json_data.getString("post_thumb");
postData.postTitle = json_data.getString("post_title");
postData.postContent = json_data.getString("post_content");
postData.postDate = json_data.getString("post_date");
data.add(postData);
}
// Setup and Handover data to recyclerview
} catch (JSONException e) {
Toast.makeText(activity, e.toString(), Toast.LENGTH_LONG).show();
}
mRVBlog = (RecyclerView) view.findViewById(R.id.blogRv);
mAdapter = new AdapterBlog(activity, data);
mRVBlog.setAdapter(mAdapter);
mRVBlog.setLayoutManager(new LinearLayoutManager(activity));
}
}
}

View file

@ -0,0 +1,57 @@
package com.vectras.vm.Fragment;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.vectras.vm.R;
import com.vectras.vm.RomsManagerActivity;
import com.vectras.vm.MainActivity;
import com.vectras.vm.adapter.LogsAdapter;
import com.vectras.vm.utils.UIUtils;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textfield.TextInputLayout;
public class LoggerFragment extends Fragment {
View view;
MainActivity activity = MainActivity.activity;
private final String CREDENTIAL_SHARED_PREF = "settings_prefs";
private LogsAdapter mLogAdapter;
private RecyclerView logList;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// TODO Auto-generated method stub
view = inflater.inflate(R.layout.fragment_logs, container, false);
LinearLayoutManager layoutManager = new LinearLayoutManager(MainActivity.activity);
mLogAdapter = new LogsAdapter(layoutManager, MainActivity.activity);
logList = (RecyclerView) view.findViewById(R.id.recyclerLog);
logList.setAdapter(mLogAdapter);
logList.setLayoutManager(layoutManager);
mLogAdapter.scrollToLastPosition();
return view;
}
}

View file

@ -0,0 +1,46 @@
package com.vectras.vm.Fragment;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;
import androidx.fragment.app.Fragment;
import com.vectras.vm.R;
import com.vectras.vm.RomsManagerActivity;
import com.vectras.vm.MainActivity;
import com.vectras.vm.utils.UIUtils;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textfield.TextInputLayout;
public class SettingsFragment extends Fragment {
View view;
MainActivity activity = MainActivity.activity;
private final String CREDENTIAL_SHARED_PREF = "settings_prefs";
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// TODO Auto-generated method stub
view = inflater.inflate(R.layout.settings_fragment, container, false);
return view;
}
}

View file

@ -0,0 +1,22 @@
package com.vectras.vm;
import android.os.Bundle;
import android.widget.ImageView;
import androidx.appcompat.app.AppCompatActivity;
import com.bumptech.glide.Glide;
import com.vectras.vm.R;
public class ImagePrvActivity extends AppCompatActivity {
public static String linkIv;
public ImageView ivPrv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.iv_prv);
ivPrv = findViewById(R.id.ivPrv);
Glide.with(this).load(linkIv).into(ivPrv);
}
}

View file

@ -0,0 +1,503 @@
package com.vectras.vm;
import static android.os.Build.VERSION.SDK_INT;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.preference.PreferenceManager;
import android.text.Html;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.viewpager.widget.ViewPager;
import com.google.firebase.auth.FirebaseAuth;
import com.vectras.qemu.Config;
import com.vectras.qemu.MainActivityCommon;
import com.vectras.qemu.MainSettingsManager;
import com.vectras.qemu.jni.StartVM;
import com.vectras.vm.Fragment.HomeFragment;
import com.vectras.vm.Fragment.LoggerFragment;
import com.vectras.vm.logger.VectrasStatus;
import com.vectras.vm.utils.AppUpdater;
import com.vectras.qemu.utils.FileInstaller;
import com.vectras.qemu.utils.RamInfo;
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.AdView;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.navigation.NavigationView;
import org.json.JSONException;
import org.json.JSONObject;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import java.util.TimerTask;
import java.util.Timer;
public class MainActivity extends AppCompatActivity {
private static Handler MAIN_HANDLER = new Handler(Looper.getMainLooper());
public static final String TAG = "Main Activity";
// Static
public static final String CREDENTIAL_SHARED_PREF = "settings_prefs";
public static MainActivity activity = null;
private FirebaseAuth mAuth;
public DrawerLayout mainDrawer;
public Toolbar mainToolbar;
public static TextView totalRam;
public static TextView usedRam;
public static TextView freeRam;
public static TextView ipTxt;
public FloatingActionButton mStart;
private Timer _timer = new Timer();
private TimerTask t;
public ViewPager viewPager;
MenuItem prevMenuItem;
int pager_number = 2;
public static void UIAlert(String title, String body, Activity activity) {
AlertDialog ad;
ad = new AlertDialog.Builder(activity, R.style.MainDialogTheme).create();
ad.setTitle(title);
ad.setMessage(body);
ad.setButton(Dialog.BUTTON_POSITIVE, "OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
return;
}
});
ad.show();
}
public void FAB_Click(View v) {
/*Thread thread = new Thread(new Runnable() {
public void run() {
MainActivity.onStartButton();
}
});
thread.setPriority(Thread.MIN_PRIORITY);
thread.start();*/
if (HomeFragment.romsLayout.getVisibility() == View.GONE) {
Animation animation;
animation = AnimationUtils.loadAnimation(MainActivity.activity, R.anim.slide_from_left);
animation.setDuration(300);
HomeFragment.romsLayout.startAnimation(animation);
Animation animation2;
animation2 = AnimationUtils.loadAnimation(MainActivity.activity, R.anim.slide_from_top);
animation2.setDuration(300);
HomeFragment.mRVBlog.startAnimation(animation2);
HomeFragment.romsLayout.setVisibility(View.VISIBLE);
} else if (HomeFragment.romsLayout.getVisibility() == View.VISIBLE) {
Animation animation;
animation = AnimationUtils.loadAnimation(MainActivity.activity, R.anim.slide_to_left);
animation.setDuration(300);
HomeFragment.romsLayout.startAnimation(animation);
Animation animation2;
animation2 = AnimationUtils.loadAnimation(MainActivity.activity, R.anim.slide_from_top);
animation2.setDuration(300);
HomeFragment.mRVBlog.startAnimation(animation2);
HomeFragment.romsLayout.setVisibility(View.GONE);
}
}
// This is easier: traverse the interfaces and get the local IPs
public static String getLocalIpAddress() {
try {
for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {
NetworkInterface intf = en.nextElement();
for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
InetAddress inetAddress = enumIpAddr.nextElement();
if (!inetAddress.isLoopbackAddress() && inetAddress.getHostAddress().toString().contains(".")) {
return inetAddress.getHostAddress().toString();
}
}
}
} catch (SocketException ex) {
ex.printStackTrace();
}
return null;
}
/**
* Called when the activity is first created.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MainActivityCommon.activity = this;
MainActivityCommon.clearNotifications();
MainActivityCommon.setupFolders();
MainActivityCommon.setupStrictMode();
MainActivityCommon.execTimer();
MainActivityCommon.checkAndLoadLibs();
Config.logFilePath = Config.cacheDir + "/vectras/vectras-log.txt";
activity = this;
this.setContentView(R.layout.main);
this.setupWidgets();
initNavigationMenu();
FileInstaller.installFiles(activity, false);
//updateApp(true);
//mAuth = FirebaseAuth.getInstance();
}
public static PackageInfo getAppInfo(Context context) {
try {
return context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException(e);
}
}
public void updateApp(final boolean showDialog) {
new AppUpdater(this, new AppUpdater.OnUpdateListener() {
@Override
public void onUpdateListener(String result) {
try {
if (!result.contains("Error on getting data")) {
final JSONObject obj = new JSONObject(result);
PackageInfo pinfo = getAppInfo(getApplicationContext());
int versionCode = pinfo.versionCode;
if (versionCode < obj.getInt("versionCode")) {
AlertDialog.Builder alert = new AlertDialog.Builder(activity, R.style.MainDialogTheme);
alert.setTitle("Install the latest version")
.setMessage(Html.fromHtml(obj.getString("Message") + "<br><br>update size:<br>" + 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;
}
}
// 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) {
}
});
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);
View headerView = navigationView.getHeaderView(0);
TextView navUsername = (TextView) headerView.findViewById(R.id.usernameTxt);
navUsername.setText(FirebaseAuth.getInstance().getCurrentUser().getDisplayName());
TextView navEmail = (TextView) headerView.findViewById(R.id.emailTxt);
navEmail.setText(FirebaseAuth.getInstance().getCurrentUser().getEmail());
//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 = AppConfig.vectrasWebsite;
Intent w = new Intent(Intent.ACTION_VIEW);
w.setData(Uri.parse(tw));
startActivity(w);
} else if (id == R.id.navigation_item_settings) {
startActivity(new Intent(activity, MainSettingsManager.class));
} 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);
ipTxt.setVisibility(View.GONE);
t = new TimerTask() {
@Override
public void run() {
runOnUiThread(new Runnable() {
@Override
public void run() {
ActivityManager.MemoryInfo miI = new ActivityManager.MemoryInfo();
ActivityManager activityManagerr = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
activityManagerr.getMemoryInfo(miI);
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 (" + RamInfo.vectrasMemory() + ")");
ProgressBar progressBar = findViewById(R.id.progressBar);
progressBar.setMax((int) totalMemory);
if (SDK_INT >= Build.VERSION_CODES.N) {
progressBar.setProgress((int) usedMemory, true);
} else {
progressBar.setProgress((int) usedMemory);
}
}
});
}
};
_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);
if (MainSettingsManager.getPromptUpdateVersion(activity))
updateApp(true);
/*FirebaseUser user = mAuth.getCurrentUser();
TextView usernameTxt = findViewById(R.id.usernameTxt);
TextView emailTxt = findViewById(R.id.emailTxt);
ImageView profilePic = findViewById(R.id.profilePic);
if (user != null) {
// Name, email address, and profile photo Url
String name = user.getDisplayName();
String email = user.getEmail();
Uri photoUrl = user.getPhotoUrl();
// Check if user's email is verified
boolean emailVerified = user.isEmailVerified();
// The user's ID, unique to the Firebase project. Do NOT use this value to
// authenticate with your backend server, if you have one. Use
// FirebaseUser.getIdToken() instead.
String uid = user.getUid();
usernameTxt.setText(name);
emailTxt.setText(email);
if (photoUrl != null)
Glide.with(activity).load(photoUrl.toString()).into(profilePic);
}*/
}
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();
}
}
private void goToURL(String url) {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(url));
activity.startActivity(i);
}
@Override
public void onStop() {
super.onStop();
}
@Override
public void onDestroy() {
super.onDestroy();
MainActivityCommon.stopTimeListener();
}
public void onPause() {
super.onPause();
MainActivityCommon.stopTimeListener();
}
public void onResume() {
super.onResume();
MainActivityCommon.execTimer();
}
}

View file

@ -0,0 +1,195 @@
package com.vectras.vm.MainRoms;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.vectras.qemu.Config;
import com.vectras.qemu.MainActivityCommon;
import com.vectras.vm.AppConfig;
import com.vectras.vm.Fragment.HomeFragment;
import com.vectras.vm.MainActivity;
import com.vectras.vm.R;
import com.vectras.vm.utils.UIUtils;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.Writer;
import java.util.Collections;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
public class AdapterMainRoms extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Context context;
private LayoutInflater inflater;
List<DataMainRoms> data = Collections.emptyList();
DataMainRoms current;
int currentPos = 0;
private int mSelectedItem = -1;
// create constructor to innitilize context and data sent from MainActivity
public AdapterMainRoms(Context context, List<DataMainRoms> 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_main_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 DataMainRoms current = data.get(position);
myHolder.textName.setText(current.itemName);
Bitmap bmImg = BitmapFactory.decodeFile(current.itemIcon);
myHolder.ivIcon.setImageBitmap(bmImg);
myHolder.cdRoms.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
if (!Config.loadNativeLibsEarly && Config.loadNativeLibsMainThread) {
MainActivityCommon.setupNativeLibs();
}
ProgressDialog mProgressDialog = new ProgressDialog(MainActivity.activity, R.style.MainDialogTheme);
mProgressDialog.setTitle("Starting VM");
mProgressDialog.setMessage("Please Wait...");
mProgressDialog.setCancelable(false);
mProgressDialog.show();
Config.hda_path = current.itemPath;
Config.extra_params = current.itemExtra;
new Timer().schedule(new TimerTask() {
@Override
public void run() {
Thread thread = new Thread(new Runnable() {
public void run() {
if (!Config.loadNativeLibsEarly && !Config.loadNativeLibsMainThread) {
MainActivityCommon.setupNativeLibs();
}
MainActivityCommon.onStartButton();
}
});
thread.setPriority(Thread.MIN_PRIORITY);
thread.start();
mProgressDialog.dismiss();
}
}, 3000);
}
});
myHolder.cdRoms.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
showDialog();
return false;
}
});
}
private void showDialog() {
final Dialog dialog = new Dialog(MainActivity.activity, R.style.MainDialogTheme);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.setContentView(R.layout.rom_options_layout);
LinearLayout removeLayout = dialog.findViewById(R.id.layoutRemove);
removeLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
AlertDialog ad;
ad = new AlertDialog.Builder(MainActivity.activity, R.style.MainDialogTheme).create();
ad.setTitle("Remove " + current.itemName);
ad.setMessage("Are you sure?");
ad.setButton(Dialog.BUTTON_POSITIVE, "REMOVE " + current.itemName, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
MainActivity.activity.deleteFile(current.itemPath);
HomeFragment.mMainAdapter = new AdapterMainRoms(MainActivity.activity, HomeFragment.data);
HomeFragment.data.remove(currentPos);
HomeFragment.mRVMainRoms.setAdapter(HomeFragment.mMainAdapter);
HomeFragment.mRVMainRoms.setLayoutManager(new LinearLayoutManager(MainActivity.activity));
HomeFragment.jArray.remove(currentPos);
try {
Writer output = null;
File jsonFile = new File(AppConfig.maindirpath + "roms-data" + ".json");
output = new BufferedWriter(new FileWriter(jsonFile));
output.write(HomeFragment.jArray.toString().replace("\\", "").replace("//", "/"));
output.close();
} catch (Exception e) {
UIUtils.toastLong(MainActivity.activity, e.toString());
}
UIUtils.toastLong(MainActivity.activity, current.itemName + " are removed successfully!");
return;
}
});
ad.setButton(Dialog.BUTTON_POSITIVE, "CANCEL", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
return;
}
});
ad.show();
}
});
dialog.show();
dialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
dialog.getWindow().getAttributes().windowAnimations = R.style.DialogAnimation;
dialog.getWindow().setGravity(Gravity.BOTTOM);
}
// return total item from List
@Override
public int getItemCount() {
return data.size();
}
class MyHolder extends RecyclerView.ViewHolder {
CardView cdRoms;
TextView textName;
ImageView ivIcon;
// create constructor to get widget reference
public MyHolder(View itemView) {
super(itemView);
cdRoms = (CardView) itemView.findViewById(R.id.cdItem);
textName = (TextView) itemView.findViewById(R.id.textName);
ivIcon = (ImageView) itemView.findViewById(R.id.ivIcon);
}
}
}

View file

@ -0,0 +1,7 @@
package com.vectras.vm.MainRoms;
public class DataMainRoms {
public String itemIcon;
public String itemName;
public String itemPath;
public String itemExtra;
}

View file

@ -0,0 +1,110 @@
package com.vectras.vm;
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.vectras.vm.R;
import com.vectras.vm.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();
}
}

View file

@ -0,0 +1,119 @@
package com.vectras.vm.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.view.animation.Animation;
import android.view.animation.AnimationUtils;
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.vectras.vm.AppConfig;
import com.bumptech.glide.Glide;
import com.vectras.vm.RomsManagerActivity;
import com.vectras.vm.MainActivity;
import com.vectras.vm.R;
import com.vectras.vm.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<RecyclerView.ViewHolder> {
private Context context;
private LayoutInflater inflater;
List<DataRoms> 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<DataRoms> 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(RomsManagerActivity.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);
}
if (current.itemAvail)
myHolder.checkBox.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mSelectedItem = position;
notifyItemRangeChanged(0, data.size());
RomsManagerActivity.selected = true;
RomsManagerActivity.selectedPath = current.itemPath;
RomsManagerActivity.selectedExtra = current.itemExtra;
RomsManagerActivity.selectedName = current.itemName+" "+current.itemArch;
RomsManagerActivity.selectedLink = current.itemUrl;
RomsManagerActivity.selectedIcon = current.itemIcon;
}
});
}
// return total item from List
@Override
public int getItemCount() {
return data.size();
}
class MyHolder extends RecyclerView.ViewHolder {
TextView textName, textAvail, textSize;
ImageView ivIcon;
RadioButton checkBox;
// create constructor to get widget reference
public MyHolder(View itemView) {
super(itemView);
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);
}
}
}

View file

@ -0,0 +1,13 @@
package com.vectras.vm.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;
}

View file

@ -0,0 +1,574 @@
package com.vectras.vm;
import static android.content.Intent.ACTION_OPEN_DOCUMENT;
import android.app.Activity;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.text.Html;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.webkit.URLUtil;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.vectras.vm.AppConfig;
import com.vectras.vm.MainRoms.AdapterMainRoms;
import com.vectras.vm.MainRoms.DataMainRoms;
import com.vectras.vm.Roms.AdapterRoms;
import com.vectras.vm.Roms.DataRoms;
import com.vectras.vm.logger.VectrasStatus;
import com.vectras.qemu.utils.FileInstaller;
import com.vectras.vm.utils.FileUtils;
import java.io.BufferedInputStream;
import com.vectras.vm.utils.UIUtils;
import com.google.android.material.button.MaterialButton;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.InputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Writer;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import javax.net.ssl.HttpsURLConnection;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class RomsManagerActivity extends AppCompatActivity {
public static RomsManagerActivity activity;
public static MaterialButton goBtn;
public static CheckBox acceptLiceneseChkBox;
public static AlertDialog ad;
public static String license;
private RecyclerView mRVRoms;
private AdapterRoms mAdapter;
public String Data;
public static Boolean selected = false;
public static String selectedPath = null;
public static String selectedExtra = null;
public static String selectedLink = null;
public static String selectedName = null;
public static String selectedIcon = null;
/**
* Called when the activity is first created.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
activity = this;
this.setContentView(R.layout.activity_roms_manager);
mRVRoms = findViewById(R.id.romsRv);
new RomsManagerActivity.AsyncLogin().execute();
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(AppConfig.vectrasTerms); //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) {
acceptLiceneseChkBox.setEnabled(false);
MainActivity.UIAlert("no internet connection", e.toString(), activity);
}
//since we are in background thread, to post results we have to go back to ui thread. do the following for that
activity.runOnUiThread(new Runnable() {
public void run() {
license = builder.toString(); // My TextFile has 3 lines
acceptLiceneseChkBox.setEnabled(true);
}
});
}
}).start();
acceptLiceneseChkBox = findViewById(R.id.acceptLiceneseChkBox);
acceptLiceneseChkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
UIAlertLicense("Terms&Conditions", license, activity);
} else {
goBtn.setEnabled(false);
}
}
});
goBtn = (MaterialButton) findViewById(R.id.goBtn);
goBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
onFirstStartup();
}
});
}
public static void UIAlertLicense(String title, String html, final Activity activity) {
AlertDialog alertDialog;
alertDialog = new AlertDialog.Builder(activity, R.style.MainDialogTheme).create();
alertDialog.setTitle(title);
alertDialog.setCancelable(true);
alertDialog.setMessage(Html.fromHtml(html));
alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "I Acknowledge", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
acceptLiceneseChkBox.setChecked(true);
goBtn.setEnabled(true);
return;
}
});
alertDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
acceptLiceneseChkBox.setChecked(false);
goBtn.setEnabled(false);
}
});
alertDialog.show();
}
private class AsyncLogin extends AsyncTask<String, String, String> {
@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(AppConfig.romsJson);
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
List<DataRoms> 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);
DataRoms romsData = new DataRoms();
romsData.itemName = json_data.getString("rom_name");
romsData.itemIcon = json_data.getString("rom_icon");
romsData.itemUrl = json_data.getString("rom_url");
romsData.itemPath = json_data.getString("rom_path");
romsData.itemAvail = json_data.getBoolean("rom_avail");
romsData.itemSize = json_data.getString("rom_size");
romsData.itemArch = json_data.getString("rom_arch");
romsData.itemExtra = json_data.getString("rom_extra");
data.add(romsData);
}
// Setup and Handover data to recyclerview
} catch (JSONException e) {
UIUtils.toastLong(activity,e.toString());
}
mRVRoms = (RecyclerView) findViewById(R.id.romsRv);
mAdapter = new AdapterRoms(activity, data);
mRVRoms.setAdapter(mAdapter);
mRVRoms.setLayoutManager(new LinearLayoutManager(activity));
}
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == android.R.id.home) {
finish();
}
return super.onOptionsItemSelected(item);
}
public class RomsJso extends JSONObject {
public JSONObject makeJSONObject (String imgName, String imgIcon, String imgPath, String imgExtra) {
JSONObject obj = new JSONObject() ;
try {
obj.put("imgName", imgName);
obj.put("imgIcon", imgIcon);
obj.put("imgPath", imgPath);
obj.put("imgExtra", imgExtra);
} catch (JSONException e) {
e.printStackTrace();
}
return obj;
}
}
public static final String CREDENTIAL_SHARED_PREF = "settings_prefs";
private void startIconDownload() {
new DownloadsImage().execute(selectedIcon);
}
public void onFirstStartup() {
if (selected) {
if (FileUtils.fileValid(activity, AppConfig.maindirpath+selectedPath)) {
SharedPreferences credentials = activity.getSharedPreferences(CREDENTIAL_SHARED_PREF, Context.MODE_PRIVATE);
ProgressDialog mProgressDialog = new ProgressDialog(this, R.style.MainDialogTheme);
mProgressDialog.setTitle("Data Setup");
mProgressDialog.setMessage("Please Wait...");
mProgressDialog.setCancelable(false);
mProgressDialog.show();
//FileInstaller.installFiles(activity, false);
SharedPreferences.Editor editor = credentials.edit();
editor.putBoolean("isFirstLaunch", Boolean.TRUE);
editor.apply();
RomsJso obj = new RomsJso();
startIconDownload();
final File jsonFile = new File(AppConfig.maindirpath + "roms-data" + ".json");
if (jsonFile.exists()) {
try {
List<DataMainRoms> data=new ArrayList<>();
JSONArray jArray = new JSONArray(FileUtils.readFromFile(MainActivity.activity, jsonFile));
try {
// 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);
DataMainRoms romsMainData = new DataMainRoms();
romsMainData.itemName= json_data.getString("imgName");
romsMainData.itemIcon= json_data.getString("imgIcon");
romsMainData.itemPath= json_data.getString("imgPath");
romsMainData.itemExtra= json_data.getString("imgExtra");
data.add(romsMainData);
}
} catch (JSONException e) {
Toast.makeText(MainActivity.activity, e.toString(), Toast.LENGTH_LONG).show();
}
JSONObject jsonObject = obj.makeJSONObject(selectedName, AppConfig.maindirpath+"icons/"+selectedPath.replace(".IMG", ".jpg"), AppConfig.maindirpath + selectedPath, selectedExtra);
jArray.put(jsonObject);
try {
Writer output = null;
output = new BufferedWriter(new FileWriter(jsonFile));
output.write(jArray.toString().replace("\\","").replace("//","/"));
output.close();
} catch (Exception e) {
UIUtils.toastLong(activity, e.toString());
}
} catch (JSONException e) {
UIUtils.toastLong(activity, e.toString());
}
} else {
JSONObject jsonObject = obj.makeJSONObject(selectedName, AppConfig.maindirpath+"icons/"+selectedPath.replace(".IMG", ".jpg"), AppConfig.maindirpath + selectedPath, selectedExtra);
JSONArray jsonArray = new JSONArray();
jsonArray.put(jsonObject);
try {
Writer output = null;
output = new BufferedWriter(new FileWriter(jsonFile));
output.write(jsonArray.toString().replace("\\","").replace("//","/"));
output.close();
} catch (Exception e) {
UIUtils.toastLong(activity, e.toString());
}
VectrasStatus.logInfo("Welcome to Vectras ♡");
}
activity.startActivity(new Intent(activity, MainActivity.class));
/*new Timer().schedule(new TimerTask() {
@Override
public void run() {
mProgressDialog.dismiss(); }
}, 3000);*/
} else {
AlertDialog ad;
ad = new AlertDialog.Builder(activity, R.style.MainDialogTheme).create();
ad.setTitle(selectedPath.replace(".IMG", ".vbi")+" Needs to import");
ad.setMessage("press import button and select "+selectedPath.replace(".IMG", ".vbi")+" file.");
ad.setButton(Dialog.BUTTON_POSITIVE, "IMPORT", (dialog, which) -> {
Intent intent = new Intent(ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
// Optionally, specify a URI for the file that should appear in the
// system file picker when it loads.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, Environment.DIRECTORY_DOWNLOADS);
}
startActivityForResult(intent, 0);
});
ad.setButton(Dialog.BUTTON_NEGATIVE, "DOWNLAOD "+selectedPath.replace(".IMG", ".vbi"), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
String gt = selectedLink;
Intent g = new Intent(Intent.ACTION_VIEW);
g.setData(Uri.parse(gt));
RomsManagerActivity.activity.startActivity(g);
RomsManagerActivity.activity.finish();
}
});
ad.show();
}
} else {
AlertDialog ad;
ad = new AlertDialog.Builder(activity, R.style.MainDialogTheme).create();
ad.setTitle("Please Select");
ad.setMessage("Select the os (operating system) you need.");
ad.setButton(Dialog.BUTTON_POSITIVE, "OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
return;
}
});
ad.show();
}
}
public String getPath(Uri uri) {
return FileUtils.getPath(activity, uri);
}
public ProgressDialog progressDialog =null;
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 0 && resultCode == RESULT_OK){
Uri content_describer = data.getData();
File selectedFilePath = new File(getPath(content_describer));
if (selectedFilePath.getName().equals(selectedPath.replace(".IMG", ".vbi"))) {
try {
progressDialog = new ProgressDialog(activity,
R.style.MainDialogTheme);
progressDialog.setTitle("Extracting");
progressDialog.setMessage("Please wait...");
progressDialog.setCancelable(false);
progressDialog.show(); // Showing Progress Dialog
Thread t = new Thread() {
public void run() {
FileInputStream zipFile = null;
try {
zipFile = (FileInputStream) getContentResolver().openInputStream(content_describer);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
File targetDirectory = new File(AppConfig.maindirpath);
ZipInputStream zis = null;
zis = new ZipInputStream(
new BufferedInputStream(zipFile));
try {
ZipEntry ze;
int count;
byte[] buffer = new byte[8192];
while ((ze = zis.getNextEntry()) != null) {
File file = new File(targetDirectory, ze.getName());
File dir = ze.isDirectory() ? file : file.getParentFile();
if (!dir.isDirectory() && !dir.mkdirs())
throw new FileNotFoundException("Failed to ensure directory: " +
dir.getAbsolutePath());
if (ze.isDirectory())
continue;
try (FileOutputStream fout = new FileOutputStream(file)) {
while ((count = zis.read(buffer)) != -1)
fout.write(buffer, 0, count);
}
/* if time should be restored as well
long time = ze.getTime();
if (time > 0)
file.setLastModified(time);
*/
}
} catch (IOException e) {
UIUtils.toastLong(activity, e.toString());
throw new RuntimeException(e);
} finally {
progressDialog.cancel(); // cancelling Dialog.
try {
zis.close();
} catch (IOException e) {
UIUtils.toastLong(activity, e.toString());
throw new RuntimeException(e);
}
}
}
};
t.start();
} catch (Exception e) {
progressDialog.dismiss(); // Close Progress Dialog
UIUtils.toastLong(activity, e.toString());
throw new RuntimeException(e);
}
} else {
MainActivity.UIAlert("File not supported", "please select "+selectedPath.replace(".IMG", ".vbi")+" file to continue.", activity);
}
}
}
public static final int DIALOG_DOWNLOAD_PROGRESS = 0;
static class DownloadsImage extends AsyncTask<String, Void,Void>{
@Override
protected Void doInBackground(String... strings) {
URL url = null;
try {
url = new URL(strings[0]);
} catch (MalformedURLException e) {
e.printStackTrace();
}
Bitmap bm = null;
try {
bm = BitmapFactory.decodeStream(url.openConnection().getInputStream());
} catch (IOException e) {
e.printStackTrace();
}
//Create Path to save Image
File path = new File(AppConfig.maindirpath + "icons"); //Creates app specific folder
if(!path.exists()) {
path.mkdirs();
}
File imageFile = new File(path, selectedPath.replace(".IMG", ".jpg")); // Imagename.png
FileOutputStream out = null;
try {
out = new FileOutputStream(imageFile);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
try{
bm.compress(Bitmap.CompressFormat.PNG, 100, out); // Compress Image
out.flush();
out.close();
// Tell the media scanner about the new file so that it is
// immediately available to the user.
MediaScannerConnection.scanFile(activity,new String[] { imageFile.getAbsolutePath() }, null,new MediaScannerConnection.OnScanCompletedListener() {
public void onScanCompleted(String path, Uri uri) {
// Log.i("ExternalStorage", "Scanned " + path + ":");
// Log.i("ExternalStorage", "-> uri=" + uri);
}
});
} catch(Exception ignored) {
}
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
}
}
@Override
public void onBackPressed() {
super.onBackPressed();
if (getParentActivityIntent()==MainActivity.activity.getIntent()
)
finish();
}
}

View file

@ -0,0 +1,79 @@
package com.vectras.vm;
import static android.os.Build.VERSION.SDK_INT;
import android.Manifest;
import android.animation.ValueAnimator;
import android.app.ActivityManager;
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.airbnb.lottie.LottieAnimationView;
import com.google.firebase.auth.FirebaseAuth;
import com.vectras.qemu.MainActivityCommon;
import com.vectras.qemu.utils.RamInfo;
import com.vectras.vm.R;
import com.vectras.vm.MainActivity;
import com.vectras.vm.ui.login.LoginActivity;
import com.vectras.vm.utils.UIUtils;
import java.io.File;
public class SplashActivity extends AppCompatActivity implements Runnable {
public AlertDialog ad;
public static SplashActivity activity;
private FirebaseAuth mAuth;
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
activity = this;
File baseDir = new File(AppConfig.basefiledir);
if (!baseDir.exists()) {
baseDir.mkdirs();
}
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_splash);
new Handler().postDelayed(this, 3000);
File sharedDir = new File(AppConfig.sharedFolder);
if (!sharedDir.exists()) {
sharedDir.mkdirs();
}
File mainDir = new File(AppConfig.maindirpath);
if (!mainDir.exists()) {
mainDir.mkdirs();
}
// Initialize Firebase Auth
mAuth = FirebaseAuth.getInstance();
RamInfo.activity = activity;
}
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, LoginActivity.class));
} else {
startActivity(new Intent(this, MainActivity.class));
}
finish();
}
}

View file

@ -0,0 +1,103 @@
package com.vectras.vm.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.vectras.vm.R;
import com.vectras.vm.Fragment.HomeFragment;
import com.vectras.vm.PostActivity;
import java.util.Collections;
import java.util.List;
import com.vectras.vm.MainActivity;
import com.vectras.vm.StoreActivity;
import com.vectras.vm.StoreItemActivity;
public class AdapterStore extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Context context;
private LayoutInflater inflater;
List<DataStore> data = Collections.emptyList();
DataStore current;
int currentPos = 0;
// create constructor to innitilize context and data sent from MainActivity
public AdapterStore(Context context, List<DataStore> 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);
}
}
}

View file

@ -0,0 +1,13 @@
package com.vectras.vm.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;
}

View file

@ -0,0 +1,199 @@
package com.vectras.vm;
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.vectras.vm.R;
import com.vectras.vm.Blog.AdapterBlog;
import com.vectras.vm.Blog.DataBlog;
import com.vectras.vm.Fragment.HomeFragment;
import com.vectras.vm.Store.AdapterStore;
import com.vectras.vm.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<String, String, String> {
@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(AppConfig.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<DataStore> 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);
}
}

View file

@ -0,0 +1,248 @@
package com.vectras.vm;
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.vectras.vm.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.vectras.vm.R;
import com.vectras.vm.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<String, String, String> {
@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(AppConfig.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: "+AppConfig.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);
}
}

View file

@ -0,0 +1,353 @@
package com.vectras.vm;
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.vectras.vm.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("<font color='red'>[E] >"+ mContext.getApplicationContext().toString() +e.getMessage()+"</font>");
}
});
} 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<String, String> head = new LinkedHashMap<String, String>();
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();
}
}
}

View file

@ -0,0 +1,385 @@
package com.vectras.vm.adapter;
import androidx.lifecycle.Observer;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver;
import com.vectras.vm.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.vectras.vm.R;
import android.text.Html;
import android.view.MotionEvent;
import com.vectras.vm.logger.VectrasStatus;
public class LogsAdapter extends RecyclerView.Adapter<LogsAdapter.logViewHolder>
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<LogItem> allEntries = new Vector<>();
private Vector<LogItem> currentLevelEntries = new Vector<LogItem>();
private Handler mHandler;
private Context mContext;
private OnItemClickListener itemClickListener;
private LinearLayoutManager mLinearLayoutManager;
private Vector<AdapterDataObserver> 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<LogItem> oldAllEntries = allEntries;
allEntries = new Vector<LogItem>(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);
}
}

View file

@ -0,0 +1,29 @@
package com.vectras.vm.data;
import com.vectras.vm.data.model.LoggedInUser;
import java.io.IOException;
/**
* Class that handles authentication w/ login credentials and retrieves user information.
*/
public class LoginDataSource {
public Result<LoggedInUser> login(String username, String password) {
try {
// TODO: handle loggedInUser authentication
LoggedInUser fakeUser =
new LoggedInUser(
java.util.UUID.randomUUID().toString(),
"Jane Doe");
return new Result.Success<>(fakeUser);
} catch (Exception e) {
return new Result.Error(new IOException("Error logging in", e));
}
}
public void logout() {
// TODO: revoke authentication
}
}

View file

@ -0,0 +1,54 @@
package com.vectras.vm.data;
import com.vectras.vm.data.model.LoggedInUser;
/**
* Class that requests authentication and user information from the remote data source and
* maintains an in-memory cache of login status and user credentials information.
*/
public class LoginRepository {
private static volatile LoginRepository instance;
private LoginDataSource dataSource;
// If user credentials will be cached in local storage, it is recommended it be encrypted
// @see https://developer.android.com/training/articles/keystore
private LoggedInUser user = null;
// private constructor : singleton access
private LoginRepository(LoginDataSource dataSource) {
this.dataSource = dataSource;
}
public static LoginRepository getInstance(LoginDataSource dataSource) {
if (instance == null) {
instance = new LoginRepository(dataSource);
}
return instance;
}
public boolean isLoggedIn() {
return user != null;
}
public void logout() {
user = null;
dataSource.logout();
}
private void setLoggedInUser(LoggedInUser user) {
this.user = user;
// If user credentials will be cached in local storage, it is recommended it be encrypted
// @see https://developer.android.com/training/articles/keystore
}
public Result<LoggedInUser> login(String username, String password) {
// handle login
Result<LoggedInUser> result = dataSource.login(username, password);
if (result instanceof Result.Success) {
setLoggedInUser(((Result.Success<LoggedInUser>) result).getData());
}
return result;
}
}

View file

@ -0,0 +1,48 @@
package com.vectras.vm.data;
/**
* A generic class that holds a result success w/ data or an error exception.
*/
public class Result<T> {
// hide the private constructor to limit subclass types (Success, Error)
private Result() {
}
@Override
public String toString() {
if (this instanceof Result.Success) {
Result.Success success = (Result.Success) this;
return "Success[data=" + success.getData().toString() + "]";
} else if (this instanceof Result.Error) {
Result.Error error = (Result.Error) this;
return "Error[exception=" + error.getError().toString() + "]";
}
return "";
}
// Success sub-class
public final static class Success<T> extends Result {
private T data;
public Success(T data) {
this.data = data;
}
public T getData() {
return this.data;
}
}
// Error sub-class
public final static class Error extends Result {
private Exception error;
public Error(Exception error) {
this.error = error;
}
public Exception getError() {
return this.error;
}
}
}

View file

@ -0,0 +1,23 @@
package com.vectras.vm.data.model;
/**
* Data class that captures user information for logged in users retrieved from LoginRepository
*/
public class LoggedInUser {
private String userId;
private String displayName;
public LoggedInUser(String userId, String displayName) {
this.userId = userId;
this.displayName = displayName;
}
public String getUserId() {
return userId;
}
public String getDisplayName() {
return displayName;
}
}

View file

@ -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.vectras.vm.logger;
import android.os.Parcel;
import android.os.Parcelable;
import com.vectras.vm.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<LogItem> CREATOR
= new Creator<LogItem>() {
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;
}
}

View file

@ -0,0 +1,33 @@
package com.vectras.vm.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<VMStatus> CREATOR = new Creator<VMStatus>() {
@Override
public VMStatus createFromParcel(Parcel in) {
return VMStatus.values()[in.readInt()];
}
@Override
public VMStatus[] newArray(int size) {
return new VMStatus[size];
}
};
}

View file

@ -0,0 +1,355 @@
package com.vectras.vm.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.vectras.vm.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<LogItem> logbuffer;
private static Vector<LogListener> logListener;
private static Vector<StateListener> 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};
// 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));
}
}

View file

@ -0,0 +1,205 @@
package com.vectras.vm.ui.login;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.gms.auth.api.Auth;
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
import com.google.android.gms.auth.api.signin.GoogleSignInResult;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.SignInButton;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.OptionalPendingResult;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
import com.vectras.vm.R;
import com.vectras.vm.RomsManagerActivity;
/**
* Activity to demonstrate basic retrieval of the Google user's ID, email address, and basic
* profile.
*/
public class GoogleSignInActivity extends AppCompatActivity implements
GoogleApiClient.OnConnectionFailedListener,
View.OnClickListener {
private static final String TAG = "GoogleSignInActivity";
private static final int RC_SIGN_IN = 9001;
private GoogleApiClient mGoogleApiClient;
private TextView mStatusTextView;
private ProgressDialog mProgressDialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_google_sign_in);
// Views
mStatusTextView = (TextView) findViewById(R.id.status);
// Button listeners
findViewById(R.id.sign_in_button).setOnClickListener(this);
findViewById(R.id.sign_out_button).setOnClickListener(this);
findViewById(R.id.disconnect_button).setOnClickListener(this);
// [START configure_signin]
// Configure sign-in to request the user's ID, email address, and basic
// profile. ID and basic profile are included in DEFAULT_SIGN_IN.
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestEmail()
.build();
// [END configure_signin]
// [START build_client]
// Build a GoogleApiClient with access to the Google Sign-In API and the
// options specified by gso.
mGoogleApiClient = new GoogleApiClient.Builder(this)
.enableAutoManage(this /* FragmentActivity */, this /* OnConnectionFailedListener */)
.addApi(Auth.GOOGLE_SIGN_IN_API, gso)
.build();
// [END build_client]
// [START customize_button]
// Set the dimensions of the sign-in button.
SignInButton signInButton = (SignInButton) findViewById(R.id.sign_in_button);
signInButton.setSize(SignInButton.SIZE_STANDARD);
// [END customize_button]
}
@Override
public void onStart() {
super.onStart();
OptionalPendingResult<GoogleSignInResult> opr = Auth.GoogleSignInApi.silentSignIn(mGoogleApiClient);
if (opr.isDone()) {
// If the user's cached credentials are valid, the OptionalPendingResult will be "done"
// and the GoogleSignInResult will be available instantly.
Log.d(TAG, "Got cached sign-in");
GoogleSignInResult result = opr.get();
handleSignInResult(result);
} else {
// If the user has not previously signed in on this device or the sign-in has expired,
// this asynchronous branch will attempt to sign in the user silently. Cross-device
// single sign-on will occur in this branch.
showProgressDialog();
opr.setResultCallback(new ResultCallback<GoogleSignInResult>() {
@Override
public void onResult(GoogleSignInResult googleSignInResult) {
hideProgressDialog();
handleSignInResult(googleSignInResult);
}
});
}
}
// [START onActivityResult]
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// Result returned from launching the Intent from GoogleSignInApi.getSignInIntent(...);
if (requestCode == RC_SIGN_IN) {
GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
handleSignInResult(result);
}
}
// [END onActivityResult]
// [START handleSignInResult]
private void handleSignInResult(GoogleSignInResult result) {
Log.d(TAG, "handleSignInResult:" + result.isSuccess());
if (result.isSuccess()) {
// Signed in successfully, show authenticated UI.
GoogleSignInAccount acct = result.getSignInAccount();
mStatusTextView.setText(getString(Integer.parseInt(acct.getDisplayName())));
updateUI(true);
} else {
// Signed out, show unauthenticated UI.
updateUI(false);
}
}
// [END handleSignInResult]
// [START signIn]
private void signIn() {
Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(mGoogleApiClient);
startActivityForResult(signInIntent, RC_SIGN_IN);
}
// [END signIn]
// [START signOut]
private void signOut() {
Auth.GoogleSignInApi.signOut(mGoogleApiClient).setResultCallback(
new ResultCallback<Status>() {
@Override
public void onResult(Status status) {
// [START_EXCLUDE]
updateUI(false);
// [END_EXCLUDE]
}
});
}
// [END signOut]
// [START revokeAccess]
private void revokeAccess() {
Auth.GoogleSignInApi.revokeAccess(mGoogleApiClient).setResultCallback(
new ResultCallback<Status>() {
@Override
public void onResult(Status status) {
// [START_EXCLUDE]
updateUI(false);
// [END_EXCLUDE]
}
});
}
// [END revokeAccess]
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
// An unresolvable error has occurred and Google APIs (including Sign-In) will not
// be available.
Log.d(TAG, "onConnectionFailed:" + connectionResult);
}
private void showProgressDialog() {
if (mProgressDialog == null) {
mProgressDialog = new ProgressDialog(this);
mProgressDialog.setMessage("Loading...");
mProgressDialog.setIndeterminate(true);
}
mProgressDialog.show();
}
private void hideProgressDialog() {
if (mProgressDialog != null && mProgressDialog.isShowing()) {
mProgressDialog.hide();
}
}
private void updateUI(boolean signedIn) {
if (signedIn)
startActivity(new Intent(GoogleSignInActivity.this, RomsManagerActivity.class));
}
@Override
public void onClick(View v) {
int id = v.getId();
if (id == R.id.sign_in_button) {
signIn();
} else if (id == R.id.sign_out_button) {
signOut();
} else if (id == R.id.disconnect_button) {
revokeAccess();
}
}
}

View file

@ -0,0 +1,150 @@
package com.vectras.vm.ui.login;
import android.app.ProgressDialog;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.KeyEvent;
import android.view.inputmethod.EditorInfo;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.gms.auth.api.Auth;
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
import com.google.android.gms.auth.api.signin.GoogleSignInResult;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.textfield.TextInputEditText;
import com.google.firebase.auth.AuthResult;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.auth.UserProfileChangeRequest;
import com.vectras.vm.R;
import com.vectras.vm.RomsManagerActivity;
public class LoginActivity extends AppCompatActivity {
private FirebaseAuth mAuth;
private TextView mStatusTextView;
// ...
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
MaterialButton signinButton = findViewById(R.id.signinBtn);
MaterialButton signupButton = findViewById(R.id.signupBtn);
MaterialButton signwithGoogleButton = findViewById(R.id.signwithGoogleBtn);
TextInputEditText usernameEditText = findViewById(R.id.username);
TextInputEditText passwordEditText = findViewById(R.id.password);
mStatusTextView = findViewById(R.id.errorTxt);
// Initialize Firebase Auth
mAuth = FirebaseAuth.getInstance();
TextWatcher afterTextChangedListener = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// ignore
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// ignore
}
@Override
public void afterTextChanged(Editable s) {
if (usernameEditText.getText().toString().trim().length() > 0) {
signinButton.setEnabled(true);
} else {
signinButton.setEnabled(false);
}
if (passwordEditText.getText().toString().trim().length() > 0) {
signinButton.setEnabled(true);
} else {
signinButton.setEnabled(false);
}
}
};
usernameEditText.addTextChangedListener(afterTextChangedListener);
passwordEditText.addTextChangedListener(afterTextChangedListener);
passwordEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_DONE) {
}
return false;
}
});
signinButton.setOnClickListener(v -> mAuth.signInWithEmailAndPassword(usernameEditText.getText().toString(), passwordEditText.getText().toString())
.addOnCompleteListener(LoginActivity.this, (OnCompleteListener<AuthResult>) task -> {
if (task.isSuccessful()) {
// Sign in success, update UI with the signed-in user's information
FirebaseUser user = mAuth.getCurrentUser();
updateUI(user);
} else {
mStatusTextView.setText(task.getException().toString());
// If sign in fails, display a message to the user.
Toast.makeText(LoginActivity.this, "Authentication failed.",
Toast.LENGTH_SHORT).show();
updateUI(null);
}
}));
signupButton.setOnClickListener(v -> startActivity(new Intent(this, SignupActivity.class)));
signwithGoogleButton.setOnClickListener(v -> startActivity(new Intent(this, GoogleSignInActivity.class)));
}
public void updateUI(Object USER) {
if (USER != null) {
FirebaseAuth auth = FirebaseAuth.getInstance();
FirebaseUser user = auth.getCurrentUser();
if (user != null && !user.isEmailVerified()) {
user.sendEmailVerification()
.addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
startActivity(new Intent(LoginActivity.this, RomsManagerActivity.class));
}
});
}
if (user != null && user.isEmailVerified())
startActivity(new Intent(LoginActivity.this, RomsManagerActivity.class));
}
}
@Override
public void onStart() {
super.onStart();
// Check if user is signed in (non-null) and update UI accordingly.
FirebaseUser currentUser = mAuth.getCurrentUser();
if (currentUser != null) {
currentUser.reload();
}
updateUI(currentUser);
}
}

View file

@ -0,0 +1,139 @@
package com.vectras.vm.ui.login;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.KeyEvent;
import android.view.inputmethod.EditorInfo;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.textfield.TextInputEditText;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.auth.UserProfileChangeRequest;
import com.vectras.vm.R;
import com.vectras.vm.RomsManagerActivity;
public class SignupActivity extends AppCompatActivity {
private FirebaseAuth mAuth;
private TextView mStatusTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_signup);
MaterialButton signupButton = findViewById(R.id.signupBtn);
TextInputEditText fullNameEditText = findViewById(R.id.fullName);
TextInputEditText usernameEditText = findViewById(R.id.username);
TextInputEditText passwordEditText = findViewById(R.id.password);
mStatusTextView = findViewById(R.id.errorTxt);
// Initialize Firebase Auth
mAuth = FirebaseAuth.getInstance();
TextWatcher afterTextChangedListener = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// ignore
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// ignore
}
@Override
public void afterTextChanged(Editable s) {
if (usernameEditText.getText().toString().trim().length() > 0) {
signupButton.setEnabled(true);
} else {
signupButton.setEnabled(false);
}
if (passwordEditText.getText().toString().trim().length() > 0) {
signupButton.setEnabled(true);
} else {
signupButton.setEnabled(false);
}
if (fullNameEditText.getText().toString().trim().length() > 0) {
signupButton.setEnabled(true);
} else {
signupButton.setEnabled(false);
}
}
};
usernameEditText.addTextChangedListener(afterTextChangedListener);
passwordEditText.addTextChangedListener(afterTextChangedListener);
passwordEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_DONE) {
}
return false;
}
});
signupButton.setOnClickListener(v -> mAuth.createUserWithEmailAndPassword(usernameEditText.getText().toString(), passwordEditText.getText().toString())
.addOnCompleteListener(SignupActivity.this, task -> {
if (task.isSuccessful()) {
// Sign in success, update UI with the signed-in user's information
Log.d("SIGNUP", "createUserWithEmail:success");
FirebaseUser user = mAuth.getCurrentUser();
updateUI(user);
} else {
// If sign in fails, display a message to the user.
Log.w("SIGNUP", "createUserWithEmail:failure", task.getException());
mStatusTextView.setText(task.getException().toString());
Toast.makeText(SignupActivity.this, "Authentication failed.",
Toast.LENGTH_SHORT).show();
updateUI(null);
}
}));
}
public void updateUI(Object USER) {
if (USER != null) {
FirebaseAuth auth = FirebaseAuth.getInstance();
FirebaseUser user = auth.getCurrentUser();
if (user != null && !user.isEmailVerified()) {
TextInputEditText fullNameEditText = findViewById(R.id.fullName);
UserProfileChangeRequest profileUpdates = new UserProfileChangeRequest.Builder()
.setDisplayName(String.valueOf(fullNameEditText.getText()))
.build();
user.updateProfile(profileUpdates)
.addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
if (task.isSuccessful()) {
user.sendEmailVerification()
.addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
startActivity(new Intent(SignupActivity.this, RomsManagerActivity.class));
}
});
}
}
});
}
}
}
}

View file

@ -0,0 +1,81 @@
package com.vectras.vm.utils;
import android.app.ProgressDialog;
import android.content.Context;
import android.os.AsyncTask;
import com.vectras.vm.AppConfig;
import com.vectras.vm.R;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class AppUpdater extends AsyncTask<String, String, String> {
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(AppConfig.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);
}
}
}

View file

@ -0,0 +1,605 @@
package com.vectras.vm.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.vectras.vm.MainActivity;
import com.vectras.vm.AppConfig;
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<Integer, ParcelFileDescriptor> fds = new HashMap<Integer, ParcelFileDescriptor>();
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);
}
}

View file

@ -0,0 +1,61 @@
package com.vectras.vm.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.vectras.vm.R;
import com.vectras.vm.AppConfig;
import com.vectras.vm.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("<font color='yellow'>[I] "+errStr+"</font>");
}
});
}
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();
}
}

View file

@ -0,0 +1,873 @@
package com.vectras.vm.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.vectras.vm.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;
}
}
}
}

View file

@ -0,0 +1,370 @@
package com.vectras.vm.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);
}
/**
* <p>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()}.</p>
*
* @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);
}
}
/**
* <p>Returns the identifier of the selected radio button in this group.
* Upon empty selection, the returned value is -1.</p>
*
* @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;
}
/**
* <p>Clears the selection. When the selection is cleared, no radio button
* in this group is selected and {@link #getCheckedRadioButtonId()} returns
* null.</p>
*
* @see #check(int)
* @see #getCheckedRadioButtonId()
*/
public void clearCheck() {
check(-1);
}
/**
* <p>Register a callback to be invoked when the checked radio button
* changes in this group.</p>
*
* @param listener the callback to call on checked state change
*/
public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
mOnCheckedChangeListener = listener;
}
/**
* {@inheritDoc}
*/
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new RadioGroupPlus.LayoutParams(getContext(), attrs);
}
/**
* {@inheritDoc}
*/
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof RadioGroup.LayoutParams;
}
@Override
protected LinearLayout.LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
@Override
public CharSequence getAccessibilityClassName() {
return RadioGroup.class.getName();
}
public static class LayoutParams extends LinearLayout.LayoutParams {
/**
* {@inheritDoc}
*/
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
}
/**
* {@inheritDoc}
*/
public LayoutParams(int w, int h) {
super(w, h);
}
/**
* {@inheritDoc}
*/
public LayoutParams(int w, int h, float initWeight) {
super(w, h, initWeight);
}
/**
* {@inheritDoc}
*/
public LayoutParams(ViewGroup.LayoutParams p) {
super(p);
}
/**
* {@inheritDoc}
*/
public LayoutParams(MarginLayoutParams source) {
super(source);
}
/**
* <p>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.</p>
*
* @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;
}
}
}
/**
* <p>Interface definition for a callback to be invoked when the checked
* radio button changed in this group.</p>
*/
public interface OnCheckedChangeListener {
/**
* <p>Called when the checked radio button has changed. When the
* selection is cleared, checkedId is -1.</p>
*
* @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);
}
}
/**
* <p>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.</p>
*/
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);
}
}
}
}