+ * This method is called in the UI thread-- it updates internal status, but
+ * does not change the bitmap data or send a network request until
+ * syncScroll is called
+ *
+ * @param newx
+ * Position of left edge of visible part in full-frame
+ * coordinates
+ * @param newy
+ * Position of top edge of visible part in full-frame coordinates
+ */
+ abstract void scrollChanged(int newx, int newy);
+
+ /**
+ * Sync scroll -- called from network thread; copies scroll changes from UI
+ * to network state
+ */
+ abstract void syncScroll();
+
+ /**
+ * Release resources
+ */
+ void dispose() {
+ if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+ if (mbitmap != null)
+ mbitmap.recycle();
+ memGraphics = null;
+ bitmapPixels = null;
+ }
+}
diff --git a/app/src/main/java/android/androidVNC/AbstractBitmapDrawable.java b/app/src/main/java/android/androidVNC/AbstractBitmapDrawable.java
new file mode 100644
index 0000000..a311cce
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/AbstractBitmapDrawable.java
@@ -0,0 +1,100 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.DrawableContainer;
+
+/**
+ * @author Michael A. MacDonald
+ *
+ */
+public class AbstractBitmapDrawable extends DrawableContainer {
+ Rect cursorRect;
+ Rect clipRect;
+
+ AbstractBitmapData data;
+
+ static final Paint _defaultPaint;
+ static final Paint _whitePaint;
+ static final Paint _blackPaint;
+
+ static {
+ _defaultPaint = new Paint();
+ _whitePaint = new Paint();
+ _whitePaint.setColor(0xffffffff);
+ _blackPaint = new Paint();
+ _blackPaint.setColor(0xff000000);
+ }
+
+ AbstractBitmapDrawable(AbstractBitmapData data)
+ {
+ this.data = data;
+ cursorRect = new Rect();
+ clipRect = new Rect();
+ }
+
+ void draw(Canvas canvas, int xoff, int yoff)
+ {
+ canvas.drawBitmap(data.mbitmap, xoff, yoff, _defaultPaint);
+ if(data.vncCanvas.connection.getUseLocalCursor())
+ {
+ setCursorRect(data.vncCanvas.mouseX, data.vncCanvas.mouseY);
+ clipRect.set(cursorRect);
+ if (canvas.clipRect(cursorRect))
+ {
+ drawCursor(canvas);
+ }
+ }
+ }
+
+ void drawCursor(Canvas canvas)
+ {
+ canvas.drawRect(cursorRect,_whitePaint);
+ canvas.drawRect((float)cursorRect.left + 1, (float)cursorRect.top + 1, (float)cursorRect.right - 1, (float)cursorRect.bottom - 1, _blackPaint);
+ }
+
+ void setCursorRect(int mouseX, int mouseY)
+ {
+ cursorRect.left = mouseX - 2;
+ cursorRect.right = cursorRect.left + 4;
+ cursorRect.top = mouseY - 2;
+ cursorRect.bottom = cursorRect.top + 4;
+ }
+
+ /* (non-Javadoc)
+ * @see android.graphics.drawable.DrawableContainer#getIntrinsicHeight()
+ */
+ @Override
+ public int getIntrinsicHeight() {
+ return data.framebufferheight;
+ }
+
+ /* (non-Javadoc)
+ * @see android.graphics.drawable.DrawableContainer#getIntrinsicWidth()
+ */
+ @Override
+ public int getIntrinsicWidth() {
+ return data.framebufferwidth;
+ }
+
+ /* (non-Javadoc)
+ * @see android.graphics.drawable.DrawableContainer#getOpacity()
+ */
+ @Override
+ public int getOpacity() {
+ return PixelFormat.OPAQUE;
+ }
+
+ /* (non-Javadoc)
+ * @see android.graphics.drawable.DrawableContainer#isStateful()
+ */
+ @Override
+ public boolean isStateful() {
+ return false;
+ }
+}
diff --git a/app/src/main/java/android/androidVNC/AbstractGestureInputHandler.java b/app/src/main/java/android/androidVNC/AbstractGestureInputHandler.java
new file mode 100644
index 0000000..8ed2e70
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/AbstractGestureInputHandler.java
@@ -0,0 +1,88 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+
+/**
+ * An AbstractInputHandler that uses GestureDetector to detect standard gestures in touch events
+ *
+ * @author Michael A. MacDonald
+ */
+abstract class AbstractGestureInputHandler extends GestureDetector.SimpleOnGestureListener implements AbstractInputHandler {
+ protected GestureDetector gestures = new GestureDetector(this);
+ private VncCanvasActivity activity;
+
+ float xInitialFocus;
+ float yInitialFocus;
+ boolean inScaling;
+
+ private static final String TAG = "AbstractGestureInputHandler";
+
+ AbstractGestureInputHandler(VncCanvasActivity c)
+ {
+ activity = c;
+ gestures.setOnDoubleTapListener(this);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent evt) {
+ //MK
+ if(evt.getAction()==MotionEvent.ACTION_CANCEL)
+ return true;
+
+ return gestures.onTouchEvent(evt);
+ }
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.OnScaleGestureListener#onScale(com.antlersoft.android.bc.IBCScaleGestureDetector)
+ */
+// @Override
+// public boolean onScale(IBCScaleGestureDetector detector) {
+// boolean consumed = true;
+// //if (detector.)
+// //Log.i(TAG,"Focus("+detector.getFocusX()+","+detector.getFocusY()+") scaleFactor = "+detector.getScaleFactor());
+// // Calculate focus shift
+// float fx = detector.getFocusX();
+// float fy = detector.getFocusY();
+// double xfs = fx - xInitialFocus;
+// double yfs = fy - yInitialFocus;
+// double fs = Math.sqrt(xfs * xfs + yfs * yfs);
+// if (Math.abs(1.0 - detector.getScaleFactor())<0.02)
+// consumed = false;
+// if (fs * 2< Math.abs(detector.getCurrentSpan() - detector.getPreviousSpan()))
+// {
+// inScaling = true;
+// if (consumed)
+// {
+// //Log.i(TAG,"Adjust scaling "+detector.getScaleFactor());
+// if (activity.vncCanvas != null && activity.vncCanvas.scaling != null)
+// activity.vncCanvas.scaling.adjust(activity, detector.getScaleFactor(), fx, fy);
+// }
+// }
+// return consumed;
+// }
+//
+// /* (non-Javadoc)
+// * @see com.antlersoft.android.bc.OnScaleGestureListener#onScaleBegin(com.antlersoft.android.bc.IBCScaleGestureDetector)
+// */
+// @Override
+// public boolean onScaleBegin(IBCScaleGestureDetector detector) {
+// xInitialFocus = detector.getFocusX();
+// yInitialFocus = detector.getFocusY();
+// inScaling = false;
+// //Log.i(TAG,"scale begin ("+xInitialFocus+","+yInitialFocus+")");
+// return true;
+// }
+//
+// /* (non-Javadoc)
+// * @see com.antlersoft.android.bc.OnScaleGestureListener#onScaleEnd(com.antlersoft.android.bc.IBCScaleGestureDetector)
+// */
+// @Override
+// public void onScaleEnd(IBCScaleGestureDetector detector) {
+// //Log.i(TAG,"scale end");
+// inScaling = false;
+// }
+}
diff --git a/app/src/main/java/android/androidVNC/AbstractInputHandler.java b/app/src/main/java/android/androidVNC/AbstractInputHandler.java
new file mode 100644
index 0000000..47e49dc
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/AbstractInputHandler.java
@@ -0,0 +1,54 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+/**
+ * The VncCanvasActivity has several different ways of handling input from the touchscreen,
+ * keyboard, buttons and trackball. These will be represented by different implementations
+ * of this interface. Putting the different modes in different classes
+ * will keep the logic clean. The relevant Activity callbacks in VncCanvasActivity
+ * are forwarded to methods in AbstractInputHandler.
+ *
+ * It is expected that the implementations will be contained within
+ * VncCanvasActivity, so they can do things like super.VncCanvasActivity.onXXX to invoke
+ * default behavior.
+ * @author Michael A. MacDonald
+ *
+ */
+public interface AbstractInputHandler {
+ /**
+ * Note: Menu key code is handled before this is called
+ * @see android.app.Activity#onKeyDown(int keyCode, KeyEvent evt)
+ */
+ boolean onKeyDown(int keyCode, KeyEvent evt);
+ /**
+ * Note: Menu key code is handled before this is called
+ * @see android.app.Activity#onKeyUp(int keyCode, KeyEvent evt)
+ */
+ boolean onKeyUp(int keyCode, KeyEvent evt);
+ /* (non-Javadoc)
+ * @see android.app.Activity#onTrackballEvent(android.view.MotionEvent)
+ */
+ boolean onTrackballEvent( MotionEvent evt);
+ /* (non-Javadoc)
+ * @see android.app.Activity#onTrackballEvent(android.view.MotionEvent)
+ */
+ boolean onTouchEvent( MotionEvent evt);
+
+ /**
+ * Return a user-friendly description for this mode; it will be displayed in a toaster
+ * when changing modes.
+ * @return
+ */
+ public CharSequence getHandlerDescription();
+
+ /**
+ * Return an internal name for this handler; this name will be stable across language
+ * and version changes
+ */
+ String getName();
+}
diff --git a/app/src/main/java/android/androidVNC/AbstractScaling.java b/app/src/main/java/android/androidVNC/AbstractScaling.java
new file mode 100644
index 0000000..f33209d
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/AbstractScaling.java
@@ -0,0 +1,122 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import android.widget.ImageView;
+
+import com.vectras.vm.R;
+
+/**
+ * @author Michael A. MacDonald
+ *
+ * A scaling mode for the VncCanvas; based on ImageView.ScaleType
+ */
+public abstract class AbstractScaling {
+ private static final int scaleModeIds[] = { R.id.itemFitToScreen, R.id.itemOneToOne, R.id.itemZoomable };
+
+ private static AbstractScaling[] scalings;
+
+ public static AbstractScaling getById(int id)
+ {
+ if ( scalings==null)
+ {
+ scalings=new AbstractScaling[scaleModeIds.length];
+ }
+ for ( int i=0; i(64, (float)0.25);
+ orderedList = new Vector(32, 8);
+ }
+
+ public void add(CapabilityInfo capinfo) {
+ Integer key = new Integer(capinfo.getCode());
+ infoMap.put(key, capinfo);
+ }
+
+ public void add(int code, String vendor, String name, String desc) {
+ Integer key = new Integer(code);
+ infoMap.put(key, new CapabilityInfo(code, vendor, name, desc));
+ }
+
+ public boolean isKnown(int code) {
+ return infoMap.containsKey(new Integer(code));
+ }
+
+ public CapabilityInfo getInfo(int code) {
+ return (CapabilityInfo)infoMap.get(new Integer(code));
+ }
+
+ public String getDescription(int code) {
+ CapabilityInfo capinfo = (CapabilityInfo)infoMap.get(new Integer(code));
+ if (capinfo == null)
+ return null;
+
+ return capinfo.getDescription();
+ }
+
+ public boolean enable(CapabilityInfo other) {
+ Integer key = new Integer(other.getCode());
+ CapabilityInfo capinfo = (CapabilityInfo)infoMap.get(key);
+ if (capinfo == null)
+ return false;
+
+ boolean enabled = capinfo.enableIfEquals(other);
+ if (enabled)
+ orderedList.addElement(key);
+
+ return enabled;
+ }
+
+ public boolean isEnabled(int code) {
+ CapabilityInfo capinfo = (CapabilityInfo)infoMap.get(new Integer(code));
+ if (capinfo == null)
+ return false;
+
+ return capinfo.isEnabled();
+ }
+
+ public int numEnabled() {
+ return orderedList.size();
+ }
+
+ public int getByOrder(int idx) {
+ int code;
+ try {
+ code = ((Integer)orderedList.elementAt(idx)).intValue();
+ } catch (ArrayIndexOutOfBoundsException e) {
+ code = 0;
+ }
+ return code;
+ }
+
+ // Protected data
+
+ protected Hashtable infoMap;
+ protected Vector orderedList;
+}
+
diff --git a/app/src/main/java/android/androidVNC/ColorModel256.java b/app/src/main/java/android/androidVNC/ColorModel256.java
new file mode 100644
index 0000000..f759fe3
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/ColorModel256.java
@@ -0,0 +1,266 @@
+package android.androidVNC;
+
+public class ColorModel256 {
+
+ public final static int [] colors;
+
+ static {
+ colors = new int[256];
+ colors[0]=0xff000000;
+ colors[1]=0xff240000;
+ colors[2]=0xff490000;
+ colors[3]=0xff6d0000;
+ colors[4]=0xff920000;
+ colors[5]=0xffb60000;
+ colors[6]=0xffdb0000;
+ colors[7]=0xffff0000;
+ colors[8]=0xff002400;
+ colors[9]=0xff242400;
+ colors[10]=0xff492400;
+ colors[11]=0xff6d2400;
+ colors[12]=0xff922400;
+ colors[13]=0xffb62400;
+ colors[14]=0xffdb2400;
+ colors[15]=0xffff2400;
+ colors[16]=0xff004900;
+ colors[17]=0xff244900;
+ colors[18]=0xff494900;
+ colors[19]=0xff6d4900;
+ colors[20]=0xff924900;
+ colors[21]=0xffb64900;
+ colors[22]=0xffdb4900;
+ colors[23]=0xffff4900;
+ colors[24]=0xff006d00;
+ colors[25]=0xff246d00;
+ colors[26]=0xff496d00;
+ colors[27]=0xff6d6d00;
+ colors[28]=0xff926d00;
+ colors[29]=0xffb66d00;
+ colors[30]=0xffdb6d00;
+ colors[31]=0xffff6d00;
+ colors[32]=0xff009200;
+ colors[33]=0xff249200;
+ colors[34]=0xff499200;
+ colors[35]=0xff6d9200;
+ colors[36]=0xff929200;
+ colors[37]=0xffb69200;
+ colors[38]=0xffdb9200;
+ colors[39]=0xffff9200;
+ colors[40]=0xff00b600;
+ colors[41]=0xff24b600;
+ colors[42]=0xff49b600;
+ colors[43]=0xff6db600;
+ colors[44]=0xff92b600;
+ colors[45]=0xffb6b600;
+ colors[46]=0xffdbb600;
+ colors[47]=0xffffb600;
+ colors[48]=0xff00db00;
+ colors[49]=0xff24db00;
+ colors[50]=0xff49db00;
+ colors[51]=0xff6ddb00;
+ colors[52]=0xff92db00;
+ colors[53]=0xffb6db00;
+ colors[54]=0xffdbdb00;
+ colors[55]=0xffffdb00;
+ colors[56]=0xff00ff00;
+ colors[57]=0xff24ff00;
+ colors[58]=0xff49ff00;
+ colors[59]=0xff6dff00;
+ colors[60]=0xff92ff00;
+ colors[61]=0xffb6ff00;
+ colors[62]=0xffdbff00;
+ colors[63]=0xffffff00;
+ colors[64]=0xff000055;
+ colors[65]=0xff240055;
+ colors[66]=0xff490055;
+ colors[67]=0xff6d0055;
+ colors[68]=0xff920055;
+ colors[69]=0xffb60055;
+ colors[70]=0xffdb0055;
+ colors[71]=0xffff0055;
+ colors[72]=0xff002455;
+ colors[73]=0xff242455;
+ colors[74]=0xff492455;
+ colors[75]=0xff6d2455;
+ colors[76]=0xff922455;
+ colors[77]=0xffb62455;
+ colors[78]=0xffdb2455;
+ colors[79]=0xffff2455;
+ colors[80]=0xff004955;
+ colors[81]=0xff244955;
+ colors[82]=0xff494955;
+ colors[83]=0xff6d4955;
+ colors[84]=0xff924955;
+ colors[85]=0xffb64955;
+ colors[86]=0xffdb4955;
+ colors[87]=0xffff4955;
+ colors[88]=0xff006d55;
+ colors[89]=0xff246d55;
+ colors[90]=0xff496d55;
+ colors[91]=0xff6d6d55;
+ colors[92]=0xff926d55;
+ colors[93]=0xffb66d55;
+ colors[94]=0xffdb6d55;
+ colors[95]=0xffff6d55;
+ colors[96]=0xff009255;
+ colors[97]=0xff249255;
+ colors[98]=0xff499255;
+ colors[99]=0xff6d9255;
+ colors[100]=0xff929255;
+ colors[101]=0xffb69255;
+ colors[102]=0xffdb9255;
+ colors[103]=0xffff9255;
+ colors[104]=0xff00b655;
+ colors[105]=0xff24b655;
+ colors[106]=0xff49b655;
+ colors[107]=0xff6db655;
+ colors[108]=0xff92b655;
+ colors[109]=0xffb6b655;
+ colors[110]=0xffdbb655;
+ colors[111]=0xffffb655;
+ colors[112]=0xff00db55;
+ colors[113]=0xff24db55;
+ colors[114]=0xff49db55;
+ colors[115]=0xff6ddb55;
+ colors[116]=0xff92db55;
+ colors[117]=0xffb6db55;
+ colors[118]=0xffdbdb55;
+ colors[119]=0xffffdb55;
+ colors[120]=0xff00ff55;
+ colors[121]=0xff24ff55;
+ colors[122]=0xff49ff55;
+ colors[123]=0xff6dff55;
+ colors[124]=0xff92ff55;
+ colors[125]=0xffb6ff55;
+ colors[126]=0xffdbff55;
+ colors[127]=0xffffff55;
+ colors[128]=0xff0000aa;
+ colors[129]=0xff2400aa;
+ colors[130]=0xff4900aa;
+ colors[131]=0xff6d00aa;
+ colors[132]=0xff9200aa;
+ colors[133]=0xffb600aa;
+ colors[134]=0xffdb00aa;
+ colors[135]=0xffff00aa;
+ colors[136]=0xff0024aa;
+ colors[137]=0xff2424aa;
+ colors[138]=0xff4924aa;
+ colors[139]=0xff6d24aa;
+ colors[140]=0xff9224aa;
+ colors[141]=0xffb624aa;
+ colors[142]=0xffdb24aa;
+ colors[143]=0xffff24aa;
+ colors[144]=0xff0049aa;
+ colors[145]=0xff2449aa;
+ colors[146]=0xff4949aa;
+ colors[147]=0xff6d49aa;
+ colors[148]=0xff9249aa;
+ colors[149]=0xffb649aa;
+ colors[150]=0xffdb49aa;
+ colors[151]=0xffff49aa;
+ colors[152]=0xff006daa;
+ colors[153]=0xff246daa;
+ colors[154]=0xff496daa;
+ colors[155]=0xff6d6daa;
+ colors[156]=0xff926daa;
+ colors[157]=0xffb66daa;
+ colors[158]=0xffdb6daa;
+ colors[159]=0xffff6daa;
+ colors[160]=0xff0092aa;
+ colors[161]=0xff2492aa;
+ colors[162]=0xff4992aa;
+ colors[163]=0xff6d92aa;
+ colors[164]=0xff9292aa;
+ colors[165]=0xffb692aa;
+ colors[166]=0xffdb92aa;
+ colors[167]=0xffff92aa;
+ colors[168]=0xff00b6aa;
+ colors[169]=0xff24b6aa;
+ colors[170]=0xff49b6aa;
+ colors[171]=0xff6db6aa;
+ colors[172]=0xff92b6aa;
+ colors[173]=0xffb6b6aa;
+ colors[174]=0xffdbb6aa;
+ colors[175]=0xffffb6aa;
+ colors[176]=0xff00dbaa;
+ colors[177]=0xff24dbaa;
+ colors[178]=0xff49dbaa;
+ colors[179]=0xff6ddbaa;
+ colors[180]=0xff92dbaa;
+ colors[181]=0xffb6dbaa;
+ colors[182]=0xffdbdbaa;
+ colors[183]=0xffffdbaa;
+ colors[184]=0xff00ffaa;
+ colors[185]=0xff24ffaa;
+ colors[186]=0xff49ffaa;
+ colors[187]=0xff6dffaa;
+ colors[188]=0xff92ffaa;
+ colors[189]=0xffb6ffaa;
+ colors[190]=0xffdbffaa;
+ colors[191]=0xffffffaa;
+ colors[192]=0xff0000ff;
+ colors[193]=0xff2400ff;
+ colors[194]=0xff4900ff;
+ colors[195]=0xff6d00ff;
+ colors[196]=0xff9200ff;
+ colors[197]=0xffb600ff;
+ colors[198]=0xffdb00ff;
+ colors[199]=0xffff00ff;
+ colors[200]=0xff0024ff;
+ colors[201]=0xff2424ff;
+ colors[202]=0xff4924ff;
+ colors[203]=0xff6d24ff;
+ colors[204]=0xff9224ff;
+ colors[205]=0xffb624ff;
+ colors[206]=0xffdb24ff;
+ colors[207]=0xffff24ff;
+ colors[208]=0xff0049ff;
+ colors[209]=0xff2449ff;
+ colors[210]=0xff4949ff;
+ colors[211]=0xff6d49ff;
+ colors[212]=0xff9249ff;
+ colors[213]=0xffb649ff;
+ colors[214]=0xffdb49ff;
+ colors[215]=0xffff49ff;
+ colors[216]=0xff006dff;
+ colors[217]=0xff246dff;
+ colors[218]=0xff496dff;
+ colors[219]=0xff6d6dff;
+ colors[220]=0xff926dff;
+ colors[221]=0xffb66dff;
+ colors[222]=0xffdb6dff;
+ colors[223]=0xffff6dff;
+ colors[224]=0xff0092ff;
+ colors[225]=0xff2492ff;
+ colors[226]=0xff4992ff;
+ colors[227]=0xff6d92ff;
+ colors[228]=0xff9292ff;
+ colors[229]=0xffb692ff;
+ colors[230]=0xffdb92ff;
+ colors[231]=0xffff92ff;
+ colors[232]=0xff00b6ff;
+ colors[233]=0xff24b6ff;
+ colors[234]=0xff49b6ff;
+ colors[235]=0xff6db6ff;
+ colors[236]=0xff92b6ff;
+ colors[237]=0xffb6b6ff;
+ colors[238]=0xffdbb6ff;
+ colors[239]=0xffffb6ff;
+ colors[240]=0xff00dbff;
+ colors[241]=0xff24dbff;
+ colors[242]=0xff49dbff;
+ colors[243]=0xff6ddbff;
+ colors[244]=0xff92dbff;
+ colors[245]=0xffb6dbff;
+ colors[246]=0xffdbdbff;
+ colors[247]=0xffffdbff;
+ colors[248]=0xff00ffff;
+ colors[249]=0xff24ffff;
+ colors[250]=0xff49ffff;
+ colors[251]=0xff6dffff;
+ colors[252]=0xff92ffff;
+ colors[253]=0xffb6ffff;
+ colors[254]=0xffdbffff;
+ colors[255]=0xffffffff;
+ }
+}
diff --git a/app/src/main/java/android/androidVNC/ColorModel64.java b/app/src/main/java/android/androidVNC/ColorModel64.java
new file mode 100644
index 0000000..7236da8
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/ColorModel64.java
@@ -0,0 +1,267 @@
+package android.androidVNC;
+
+public class ColorModel64 {
+
+ public final static int [] colors;
+
+ static {
+ colors = new int[256];
+ colors[0]=0xff000000;
+ colors[1]=0xff000055;
+ colors[2]=0xff0000aa;
+ colors[3]=0xff0000ff;
+ colors[4]=0xff005500;
+ colors[5]=0xff005555;
+ colors[6]=0xff0055aa;
+ colors[7]=0xff0055ff;
+ colors[8]=0xff00aa00;
+ colors[9]=0xff00aa55;
+ colors[10]=0xff00aaaa;
+ colors[11]=0xff00aaff;
+ colors[12]=0xff00ff00;
+ colors[13]=0xff00ff55;
+ colors[14]=0xff00ffaa;
+ colors[15]=0xff00ffff;
+ colors[16]=0xff550000;
+ colors[17]=0xff550055;
+ colors[18]=0xff5500aa;
+ colors[19]=0xff5500ff;
+ colors[20]=0xff555500;
+ colors[21]=0xff555555;
+ colors[22]=0xff5555aa;
+ colors[23]=0xff5555ff;
+ colors[24]=0xff55aa00;
+ colors[25]=0xff55aa55;
+ colors[26]=0xff55aaaa;
+ colors[27]=0xff55aaff;
+ colors[28]=0xff55ff00;
+ colors[29]=0xff55ff55;
+ colors[30]=0xff55ffaa;
+ colors[31]=0xff55ffff;
+ colors[32]=0xffaa0000;
+ colors[33]=0xffaa0055;
+ colors[34]=0xffaa00aa;
+ colors[35]=0xffaa00ff;
+ colors[36]=0xffaa5500;
+ colors[37]=0xffaa5555;
+ colors[38]=0xffaa55aa;
+ colors[39]=0xffaa55ff;
+ colors[40]=0xffaaaa00;
+ colors[41]=0xffaaaa55;
+ colors[42]=0xffaaaaaa;
+ colors[43]=0xffaaaaff;
+ colors[44]=0xffaaff00;
+ colors[45]=0xffaaff55;
+ colors[46]=0xffaaffaa;
+ colors[47]=0xffaaffff;
+ colors[48]=0xffff0000;
+ colors[49]=0xffff0055;
+ colors[50]=0xffff00aa;
+ colors[51]=0xffff00ff;
+ colors[52]=0xffff5500;
+ colors[53]=0xffff5555;
+ colors[54]=0xffff55aa;
+ colors[55]=0xffff55ff;
+ colors[56]=0xffffaa00;
+ colors[57]=0xffffaa55;
+ colors[58]=0xffffaaaa;
+ colors[59]=0xffffaaff;
+ colors[60]=0xffffff00;
+ colors[61]=0xffffff55;
+ colors[62]=0xffffffaa;
+ colors[63]=0xffffffff;
+ colors[64]=0xff000000;
+ colors[65]=0xff000055;
+ colors[66]=0xff0000aa;
+ colors[67]=0xff0000ff;
+ colors[68]=0xff005500;
+ colors[69]=0xff005555;
+ colors[70]=0xff0055aa;
+ colors[71]=0xff0055ff;
+ colors[72]=0xff00aa00;
+ colors[73]=0xff00aa55;
+ colors[74]=0xff00aaaa;
+ colors[75]=0xff00aaff;
+ colors[76]=0xff00ff00;
+ colors[77]=0xff00ff55;
+ colors[78]=0xff00ffaa;
+ colors[79]=0xff00ffff;
+ colors[80]=0xff550000;
+ colors[81]=0xff550055;
+ colors[82]=0xff5500aa;
+ colors[83]=0xff5500ff;
+ colors[84]=0xff555500;
+ colors[85]=0xff555555;
+ colors[86]=0xff5555aa;
+ colors[87]=0xff5555ff;
+ colors[88]=0xff55aa00;
+ colors[89]=0xff55aa55;
+ colors[90]=0xff55aaaa;
+ colors[91]=0xff55aaff;
+ colors[92]=0xff55ff00;
+ colors[93]=0xff55ff55;
+ colors[94]=0xff55ffaa;
+ colors[95]=0xff55ffff;
+ colors[96]=0xffaa0000;
+ colors[97]=0xffaa0055;
+ colors[98]=0xffaa00aa;
+ colors[99]=0xffaa00ff;
+ colors[100]=0xffaa5500;
+ colors[101]=0xffaa5555;
+ colors[102]=0xffaa55aa;
+ colors[103]=0xffaa55ff;
+ colors[104]=0xffaaaa00;
+ colors[105]=0xffaaaa55;
+ colors[106]=0xffaaaaaa;
+ colors[107]=0xffaaaaff;
+ colors[108]=0xffaaff00;
+ colors[109]=0xffaaff55;
+ colors[110]=0xffaaffaa;
+ colors[111]=0xffaaffff;
+ colors[112]=0xffff0000;
+ colors[113]=0xffff0055;
+ colors[114]=0xffff00aa;
+ colors[115]=0xffff00ff;
+ colors[116]=0xffff5500;
+ colors[117]=0xffff5555;
+ colors[118]=0xffff55aa;
+ colors[119]=0xffff55ff;
+ colors[120]=0xffffaa00;
+ colors[121]=0xffffaa55;
+ colors[122]=0xffffaaaa;
+ colors[123]=0xffffaaff;
+ colors[124]=0xffffff00;
+ colors[125]=0xffffff55;
+ colors[126]=0xffffffaa;
+ colors[127]=0xffffffff;
+ colors[128]=0xff000000;
+ colors[129]=0xff000055;
+ colors[130]=0xff0000aa;
+ colors[131]=0xff0000ff;
+ colors[132]=0xff005500;
+ colors[133]=0xff005555;
+ colors[134]=0xff0055aa;
+ colors[135]=0xff0055ff;
+ colors[136]=0xff00aa00;
+ colors[137]=0xff00aa55;
+ colors[138]=0xff00aaaa;
+ colors[139]=0xff00aaff;
+ colors[140]=0xff00ff00;
+ colors[141]=0xff00ff55;
+ colors[142]=0xff00ffaa;
+ colors[143]=0xff00ffff;
+ colors[144]=0xff550000;
+ colors[145]=0xff550055;
+ colors[146]=0xff5500aa;
+ colors[147]=0xff5500ff;
+ colors[148]=0xff555500;
+ colors[149]=0xff555555;
+ colors[150]=0xff5555aa;
+ colors[151]=0xff5555ff;
+ colors[152]=0xff55aa00;
+ colors[153]=0xff55aa55;
+ colors[154]=0xff55aaaa;
+ colors[155]=0xff55aaff;
+ colors[156]=0xff55ff00;
+ colors[157]=0xff55ff55;
+ colors[158]=0xff55ffaa;
+ colors[159]=0xff55ffff;
+ colors[160]=0xffaa0000;
+ colors[161]=0xffaa0055;
+ colors[162]=0xffaa00aa;
+ colors[163]=0xffaa00ff;
+ colors[164]=0xffaa5500;
+ colors[165]=0xffaa5555;
+ colors[166]=0xffaa55aa;
+ colors[167]=0xffaa55ff;
+ colors[168]=0xffaaaa00;
+ colors[169]=0xffaaaa55;
+ colors[170]=0xffaaaaaa;
+ colors[171]=0xffaaaaff;
+ colors[172]=0xffaaff00;
+ colors[173]=0xffaaff55;
+ colors[174]=0xffaaffaa;
+ colors[175]=0xffaaffff;
+ colors[176]=0xffff0000;
+ colors[177]=0xffff0055;
+ colors[178]=0xffff00aa;
+ colors[179]=0xffff00ff;
+ colors[180]=0xffff5500;
+ colors[181]=0xffff5555;
+ colors[182]=0xffff55aa;
+ colors[183]=0xffff55ff;
+ colors[184]=0xffffaa00;
+ colors[185]=0xffffaa55;
+ colors[186]=0xffffaaaa;
+ colors[187]=0xffffaaff;
+ colors[188]=0xffffff00;
+ colors[189]=0xffffff55;
+ colors[190]=0xffffffaa;
+ colors[191]=0xffffffff;
+ colors[192]=0xff000000;
+ colors[193]=0xff000055;
+ colors[194]=0xff0000aa;
+ colors[195]=0xff0000ff;
+ colors[196]=0xff005500;
+ colors[197]=0xff005555;
+ colors[198]=0xff0055aa;
+ colors[199]=0xff0055ff;
+ colors[200]=0xff00aa00;
+ colors[201]=0xff00aa55;
+ colors[202]=0xff00aaaa;
+ colors[203]=0xff00aaff;
+ colors[204]=0xff00ff00;
+ colors[205]=0xff00ff55;
+ colors[206]=0xff00ffaa;
+ colors[207]=0xff00ffff;
+ colors[208]=0xff550000;
+ colors[209]=0xff550055;
+ colors[210]=0xff5500aa;
+ colors[211]=0xff5500ff;
+ colors[212]=0xff555500;
+ colors[213]=0xff555555;
+ colors[214]=0xff5555aa;
+ colors[215]=0xff5555ff;
+ colors[216]=0xff55aa00;
+ colors[217]=0xff55aa55;
+ colors[218]=0xff55aaaa;
+ colors[219]=0xff55aaff;
+ colors[220]=0xff55ff00;
+ colors[221]=0xff55ff55;
+ colors[222]=0xff55ffaa;
+ colors[223]=0xff55ffff;
+ colors[224]=0xffaa0000;
+ colors[225]=0xffaa0055;
+ colors[226]=0xffaa00aa;
+ colors[227]=0xffaa00ff;
+ colors[228]=0xffaa5500;
+ colors[229]=0xffaa5555;
+ colors[230]=0xffaa55aa;
+ colors[231]=0xffaa55ff;
+ colors[232]=0xffaaaa00;
+ colors[233]=0xffaaaa55;
+ colors[234]=0xffaaaaaa;
+ colors[235]=0xffaaaaff;
+ colors[236]=0xffaaff00;
+ colors[237]=0xffaaff55;
+ colors[238]=0xffaaffaa;
+ colors[239]=0xffaaffff;
+ colors[240]=0xffff0000;
+ colors[241]=0xffff0055;
+ colors[242]=0xffff00aa;
+ colors[243]=0xffff00ff;
+ colors[244]=0xffff5500;
+ colors[245]=0xffff5555;
+ colors[246]=0xffff55aa;
+ colors[247]=0xffff55ff;
+ colors[248]=0xffffaa00;
+ colors[249]=0xffffaa55;
+ colors[250]=0xffffaaaa;
+ colors[251]=0xffffaaff;
+ colors[252]=0xffffff00;
+ colors[253]=0xffffff55;
+ colors[254]=0xffffffaa;
+ colors[255]=0xffffffff;
+
+ }
+}
diff --git a/app/src/main/java/android/androidVNC/ColorModel8.java b/app/src/main/java/android/androidVNC/ColorModel8.java
new file mode 100644
index 0000000..0c1b2de
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/ColorModel8.java
@@ -0,0 +1,266 @@
+package android.androidVNC;
+
+public class ColorModel8 {
+
+ public final static int [] colors;
+
+ static {
+ colors = new int[256];
+ colors[0]=0xff000000;
+ colors[1]=0xff0000ff;
+ colors[2]=0xff00ff00;
+ colors[3]=0xff00ffff;
+ colors[4]=0xffff0000;
+ colors[5]=0xffff00ff;
+ colors[6]=0xffffff00;
+ colors[7]=0xffffffff;
+ colors[8]=0xff000000;
+ colors[9]=0xff0000ff;
+ colors[10]=0xff00ff00;
+ colors[11]=0xff00ffff;
+ colors[12]=0xffff0000;
+ colors[13]=0xffff00ff;
+ colors[14]=0xffffff00;
+ colors[15]=0xffffffff;
+ colors[16]=0xff000000;
+ colors[17]=0xff0000ff;
+ colors[18]=0xff00ff00;
+ colors[19]=0xff00ffff;
+ colors[20]=0xffff0000;
+ colors[21]=0xffff00ff;
+ colors[22]=0xffffff00;
+ colors[23]=0xffffffff;
+ colors[24]=0xff000000;
+ colors[25]=0xff0000ff;
+ colors[26]=0xff00ff00;
+ colors[27]=0xff00ffff;
+ colors[28]=0xffff0000;
+ colors[29]=0xffff00ff;
+ colors[30]=0xffffff00;
+ colors[31]=0xffffffff;
+ colors[32]=0xff000000;
+ colors[33]=0xff0000ff;
+ colors[34]=0xff00ff00;
+ colors[35]=0xff00ffff;
+ colors[36]=0xffff0000;
+ colors[37]=0xffff00ff;
+ colors[38]=0xffffff00;
+ colors[39]=0xffffffff;
+ colors[40]=0xff000000;
+ colors[41]=0xff0000ff;
+ colors[42]=0xff00ff00;
+ colors[43]=0xff00ffff;
+ colors[44]=0xffff0000;
+ colors[45]=0xffff00ff;
+ colors[46]=0xffffff00;
+ colors[47]=0xffffffff;
+ colors[48]=0xff000000;
+ colors[49]=0xff0000ff;
+ colors[50]=0xff00ff00;
+ colors[51]=0xff00ffff;
+ colors[52]=0xffff0000;
+ colors[53]=0xffff00ff;
+ colors[54]=0xffffff00;
+ colors[55]=0xffffffff;
+ colors[56]=0xff000000;
+ colors[57]=0xff0000ff;
+ colors[58]=0xff00ff00;
+ colors[59]=0xff00ffff;
+ colors[60]=0xffff0000;
+ colors[61]=0xffff00ff;
+ colors[62]=0xffffff00;
+ colors[63]=0xffffffff;
+ colors[64]=0xff000000;
+ colors[65]=0xff0000ff;
+ colors[66]=0xff00ff00;
+ colors[67]=0xff00ffff;
+ colors[68]=0xffff0000;
+ colors[69]=0xffff00ff;
+ colors[70]=0xffffff00;
+ colors[71]=0xffffffff;
+ colors[72]=0xff000000;
+ colors[73]=0xff0000ff;
+ colors[74]=0xff00ff00;
+ colors[75]=0xff00ffff;
+ colors[76]=0xffff0000;
+ colors[77]=0xffff00ff;
+ colors[78]=0xffffff00;
+ colors[79]=0xffffffff;
+ colors[80]=0xff000000;
+ colors[81]=0xff0000ff;
+ colors[82]=0xff00ff00;
+ colors[83]=0xff00ffff;
+ colors[84]=0xffff0000;
+ colors[85]=0xffff00ff;
+ colors[86]=0xffffff00;
+ colors[87]=0xffffffff;
+ colors[88]=0xff000000;
+ colors[89]=0xff0000ff;
+ colors[90]=0xff00ff00;
+ colors[91]=0xff00ffff;
+ colors[92]=0xffff0000;
+ colors[93]=0xffff00ff;
+ colors[94]=0xffffff00;
+ colors[95]=0xffffffff;
+ colors[96]=0xff000000;
+ colors[97]=0xff0000ff;
+ colors[98]=0xff00ff00;
+ colors[99]=0xff00ffff;
+ colors[100]=0xffff0000;
+ colors[101]=0xffff00ff;
+ colors[102]=0xffffff00;
+ colors[103]=0xffffffff;
+ colors[104]=0xff000000;
+ colors[105]=0xff0000ff;
+ colors[106]=0xff00ff00;
+ colors[107]=0xff00ffff;
+ colors[108]=0xffff0000;
+ colors[109]=0xffff00ff;
+ colors[110]=0xffffff00;
+ colors[111]=0xffffffff;
+ colors[112]=0xff000000;
+ colors[113]=0xff0000ff;
+ colors[114]=0xff00ff00;
+ colors[115]=0xff00ffff;
+ colors[116]=0xffff0000;
+ colors[117]=0xffff00ff;
+ colors[118]=0xffffff00;
+ colors[119]=0xffffffff;
+ colors[120]=0xff000000;
+ colors[121]=0xff0000ff;
+ colors[122]=0xff00ff00;
+ colors[123]=0xff00ffff;
+ colors[124]=0xffff0000;
+ colors[125]=0xffff00ff;
+ colors[126]=0xffffff00;
+ colors[127]=0xffffffff;
+ colors[128]=0xff000000;
+ colors[129]=0xff0000ff;
+ colors[130]=0xff00ff00;
+ colors[131]=0xff00ffff;
+ colors[132]=0xffff0000;
+ colors[133]=0xffff00ff;
+ colors[134]=0xffffff00;
+ colors[135]=0xffffffff;
+ colors[136]=0xff000000;
+ colors[137]=0xff0000ff;
+ colors[138]=0xff00ff00;
+ colors[139]=0xff00ffff;
+ colors[140]=0xffff0000;
+ colors[141]=0xffff00ff;
+ colors[142]=0xffffff00;
+ colors[143]=0xffffffff;
+ colors[144]=0xff000000;
+ colors[145]=0xff0000ff;
+ colors[146]=0xff00ff00;
+ colors[147]=0xff00ffff;
+ colors[148]=0xffff0000;
+ colors[149]=0xffff00ff;
+ colors[150]=0xffffff00;
+ colors[151]=0xffffffff;
+ colors[152]=0xff000000;
+ colors[153]=0xff0000ff;
+ colors[154]=0xff00ff00;
+ colors[155]=0xff00ffff;
+ colors[156]=0xffff0000;
+ colors[157]=0xffff00ff;
+ colors[158]=0xffffff00;
+ colors[159]=0xffffffff;
+ colors[160]=0xff000000;
+ colors[161]=0xff0000ff;
+ colors[162]=0xff00ff00;
+ colors[163]=0xff00ffff;
+ colors[164]=0xffff0000;
+ colors[165]=0xffff00ff;
+ colors[166]=0xffffff00;
+ colors[167]=0xffffffff;
+ colors[168]=0xff000000;
+ colors[169]=0xff0000ff;
+ colors[170]=0xff00ff00;
+ colors[171]=0xff00ffff;
+ colors[172]=0xffff0000;
+ colors[173]=0xffff00ff;
+ colors[174]=0xffffff00;
+ colors[175]=0xffffffff;
+ colors[176]=0xff000000;
+ colors[177]=0xff0000ff;
+ colors[178]=0xff00ff00;
+ colors[179]=0xff00ffff;
+ colors[180]=0xffff0000;
+ colors[181]=0xffff00ff;
+ colors[182]=0xffffff00;
+ colors[183]=0xffffffff;
+ colors[184]=0xff000000;
+ colors[185]=0xff0000ff;
+ colors[186]=0xff00ff00;
+ colors[187]=0xff00ffff;
+ colors[188]=0xffff0000;
+ colors[189]=0xffff00ff;
+ colors[190]=0xffffff00;
+ colors[191]=0xffffffff;
+ colors[192]=0xff000000;
+ colors[193]=0xff0000ff;
+ colors[194]=0xff00ff00;
+ colors[195]=0xff00ffff;
+ colors[196]=0xffff0000;
+ colors[197]=0xffff00ff;
+ colors[198]=0xffffff00;
+ colors[199]=0xffffffff;
+ colors[200]=0xff000000;
+ colors[201]=0xff0000ff;
+ colors[202]=0xff00ff00;
+ colors[203]=0xff00ffff;
+ colors[204]=0xffff0000;
+ colors[205]=0xffff00ff;
+ colors[206]=0xffffff00;
+ colors[207]=0xffffffff;
+ colors[208]=0xff000000;
+ colors[209]=0xff0000ff;
+ colors[210]=0xff00ff00;
+ colors[211]=0xff00ffff;
+ colors[212]=0xffff0000;
+ colors[213]=0xffff00ff;
+ colors[214]=0xffffff00;
+ colors[215]=0xffffffff;
+ colors[216]=0xff000000;
+ colors[217]=0xff0000ff;
+ colors[218]=0xff00ff00;
+ colors[219]=0xff00ffff;
+ colors[220]=0xffff0000;
+ colors[221]=0xffff00ff;
+ colors[222]=0xffffff00;
+ colors[223]=0xffffffff;
+ colors[224]=0xff000000;
+ colors[225]=0xff0000ff;
+ colors[226]=0xff00ff00;
+ colors[227]=0xff00ffff;
+ colors[228]=0xffff0000;
+ colors[229]=0xffff00ff;
+ colors[230]=0xffffff00;
+ colors[231]=0xffffffff;
+ colors[232]=0xff000000;
+ colors[233]=0xff0000ff;
+ colors[234]=0xff00ff00;
+ colors[235]=0xff00ffff;
+ colors[236]=0xffff0000;
+ colors[237]=0xffff00ff;
+ colors[238]=0xffffff00;
+ colors[239]=0xffffffff;
+ colors[240]=0xff000000;
+ colors[241]=0xff0000ff;
+ colors[242]=0xff00ff00;
+ colors[243]=0xff00ffff;
+ colors[244]=0xffff0000;
+ colors[245]=0xffff00ff;
+ colors[246]=0xffffff00;
+ colors[247]=0xffffffff;
+ colors[248]=0xff000000;
+ colors[249]=0xff0000ff;
+ colors[250]=0xff00ff00;
+ colors[251]=0xff00ffff;
+ colors[252]=0xffff0000;
+ colors[253]=0xffff00ff;
+ colors[254]=0xffffff00;
+ colors[255]=0xffffffff;
+ }
+}
diff --git a/app/src/main/java/android/androidVNC/CompactBitmapData.java b/app/src/main/java/android/androidVNC/CompactBitmapData.java
new file mode 100644
index 0000000..e256516
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/CompactBitmapData.java
@@ -0,0 +1,110 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import java.io.IOException;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+
+import com.vectras.qemu.Config;
+
+/**
+ * @author Michael A. MacDonald
+ *
+ */
+class CompactBitmapData extends AbstractBitmapData {
+
+ class CompactBitmapDrawable extends AbstractBitmapDrawable
+ {
+ CompactBitmapDrawable()
+ {
+ super(CompactBitmapData.this);
+ }
+ /* (non-Javadoc)
+ * @see android.graphics.drawable.DrawableContainer#draw(android.graphics.Canvas)
+ */
+ @Override
+ public void draw(Canvas canvas) {
+ draw(canvas, 0, 0);
+ }
+ }
+
+ CompactBitmapData(RfbProto rfb, VncCanvas c)
+ {
+ super(rfb,c);
+ bitmapwidth=framebufferwidth;
+ bitmapheight=framebufferheight;
+
+ mbitmap = Bitmap.createBitmap(rfb.framebufferWidth, rfb.framebufferHeight, Config.bitmapConfig);
+ memGraphics = new Canvas(mbitmap);
+ bitmapPixels = new int[rfb.framebufferWidth * rfb.framebufferHeight];
+ }
+
+ @Override
+ void writeFullUpdateRequest(boolean incremental) throws IOException {
+ rfb.writeFramebufferUpdateRequest(0, 0, framebufferwidth, framebufferheight, incremental);
+ }
+
+ @Override
+ boolean validDraw(int x, int y, int w, int h) {
+ return true;
+ }
+
+ @Override
+ int offset(int x, int y) {
+ return y * bitmapwidth + x;
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#createDrawable()
+ */
+ @Override
+ AbstractBitmapDrawable createDrawable() {
+ return new CompactBitmapDrawable();
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#updateBitmap(int, int, int, int)
+ */
+ @Override
+ void updateBitmap(int x, int y, int w, int h) {
+ mbitmap.setPixels(bitmapPixels, offset(x,y), bitmapwidth, x, y, w, h);
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#copyRect(android.graphics.Rect, android.graphics.Rect, android.graphics.Paint)
+ */
+ @Override
+ void copyRect(Rect src, Rect dest, Paint paint) {
+ memGraphics.drawBitmap(mbitmap, src, dest, paint);
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#drawRect(int, int, int, int, android.graphics.Paint)
+ */
+ @Override
+ void drawRect(int x, int y, int w, int h, Paint paint) {
+ memGraphics.drawRect(x, y, x + w, y + h, paint);
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#scrollChanged(int, int)
+ */
+ @Override
+ void scrollChanged(int newx, int newy) {
+ // Don't need to do anything here
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#syncScroll()
+ */
+ @Override
+ void syncScroll() {
+ // Don't need anything here either
+
+ }
+}
diff --git a/app/src/main/java/android/androidVNC/ConnectionBean.java b/app/src/main/java/android/androidVNC/ConnectionBean.java
new file mode 100644
index 0000000..5a27c30
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/ConnectionBean.java
@@ -0,0 +1,181 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import android.widget.ImageView.ScaleType;
+
+import com.vectras.qemu.Config;
+
+/**
+ * @author Michael A. MacDonald
+ *
+ */
+public class ConnectionBean {
+
+ private String address = "localhost";
+ private String password = "";
+ private int port = 5901;
+ private String colorModel = COLORMODEL.C64.nameString();
+ private String InputMode = VncCanvasActivity.TOUCH_ZOOM_MODE;
+ private String scaleMode = "";
+ private String nickname = "limbo";
+ private long forceFull = 0;
+ private boolean useLocalCursor = false;
+ private boolean followMouse = true;
+ private String userName;
+ private long id = 0;
+
+ public ConnectionBean() {
+ setAddress(Config.defaultVNCHost);
+ setUserName(Config.defaultVNCUsername);
+ setPassword(Config.defaultVNCPasswd);
+ setPort(Config.defaultVNCPort + 5900);
+ setColorModel(Config.defaultVNCColorMode);
+ if (Config.enable_qemu_fullScreen)
+ setScaleMode(Config.defaultFullscreenScaleMode);
+ else
+ setScaleMode(Config.defaultScaleModeCenter);
+ setInputMode(Config.defaultInputMode);
+ }
+
+ private void setUserName(String string) {
+
+ this.userName = string;
+
+ }
+
+ public void setInputMode(String touchZoomMode) {
+
+ this.InputMode = touchZoomMode;
+
+ }
+
+ void setPort(int i) {
+
+ this.port = i;
+ }
+
+ void setColorModel(String nameString) {
+
+ this.colorModel = nameString;
+
+ }
+
+ void setAddress(String string) {
+
+ this.address = string;
+ }
+
+ void setPassword(String string) {
+
+ this.password = string;
+ }
+
+ public long get_Id() {
+
+ return 0;
+ }
+
+ ScaleType getScaleMode() {
+ return ScaleType.valueOf(getScaleModeAsString());
+ }
+
+ private String getScaleModeAsString() {
+
+ return scaleMode;
+ }
+
+ void setScaleMode(ScaleType value) {
+ setScaleModeAsString(value.toString());
+ }
+
+ private void setScaleModeAsString(String string) {
+
+ this.scaleMode = string;
+
+ }
+
+ public String getAddress() {
+
+ return this.address;
+ }
+
+ public void setNickname(String address2) {
+
+ this.nickname = address2;
+ }
+
+ public int getPort() {
+
+ return port;
+ }
+
+ public String getInputMode() {
+
+ return this.InputMode;
+ }
+
+ public String getPassword() {
+
+ return this.password;
+ }
+
+ public long getForceFull() {
+
+ return this.forceFull;
+ }
+
+ public boolean getUseLocalCursor() {
+
+ return this.useLocalCursor;
+ }
+
+ public String getNickname() {
+
+ return nickname;
+ }
+
+ public String getColorModel() {
+
+ return this.colorModel;
+ }
+
+ public void setForceFull(long l) {
+
+ this.forceFull = l;
+ }
+
+ public void setUseLocalCursor(boolean checked) {
+
+ this.setUseLocalCursor(checked);
+ }
+
+ public void setFollowMouse(boolean b) {
+
+ this.followMouse = b;
+
+ }
+
+ public boolean getFollowMouse() {
+
+ return this.followMouse;
+ }
+
+ public String getUserName() {
+
+ return userName;
+ }
+
+ public void setConnectionId(long get_Id) {
+
+ this.id = get_Id;
+
+ }
+
+ public boolean getFollowPan() {
+
+ return false;
+ }
+
+}
diff --git a/app/src/main/java/android/androidVNC/ConnectionSettable.java b/app/src/main/java/android/androidVNC/ConnectionSettable.java
new file mode 100644
index 0000000..dfc64cc
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/ConnectionSettable.java
@@ -0,0 +1,12 @@
+/**
+ *
+ */
+package android.androidVNC;
+
+/**
+ * @author mike
+ *
+ */
+interface ConnectionSettable {
+ void setConnection(ConnectionBean connection);
+}
diff --git a/app/src/main/java/android/androidVNC/DH.java b/app/src/main/java/android/androidVNC/DH.java
new file mode 100644
index 0000000..aaf6b10
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/DH.java
@@ -0,0 +1,183 @@
+package android.androidVNC;
+// CRYPTO LIBRARY FOR EXCHANGING KEYS
+// USING THE DIFFIE-HELLMAN KEY EXCHANGE PROTOCOL
+
+// The diffie-hellman can be used to securely exchange keys
+// between parties, where a third party eavesdropper given
+// the values being transmitted cannot determine the key.
+
+// Implemented by Lee Griffiths, Jan 2004.
+// This software is freeware, you may use it to your discretion,
+// however by doing so you take full responsibility for any damage
+// it may cause.
+
+// Hope you find it useful, even if you just use some of the functions
+// out of it like the prime number generator and the XtoYmodN function.
+
+// It would be great if you could send me emails to: lee.griffiths@first4internet.co.uk
+// with any suggestions, comments, or questions!
+
+// Enjoy.
+
+// Adopted to ms-logon for ultravnc and ported to Java by marscha, 2006.
+
+//import java.lang.Math;
+
+public class DH {
+
+ public DH() {
+ maxNum = (((long) 1) << DH_MAX_BITS) - 1;
+ }
+
+ public DH(long generator, long modulus) throws Exception {
+ maxNum = (((long) 1) << DH_MAX_BITS) - 1;
+ if (generator >= maxNum || modulus >= maxNum)
+ throw new Exception("Modulus or generator too large.");
+ gen = generator;
+ mod = modulus;
+ }
+
+ private long rng(long limit) {
+ return (long) (java.lang.Math.random() * limit);
+ }
+
+ //Performs the miller-rabin primality test on a guessed prime n.
+ //trials is the number of attempts to verify this, because the function
+ //is not 100% accurate it may be a composite. However setting the trial
+ //value to around 5 should guarantee success even with very large primes
+ private boolean millerRabin (long n, int trials) {
+ long a = 0;
+
+ for (int i = 0; i < trials; i++) {
+ a = rng(n - 3) + 2;// gets random value in [2..n-1]
+ if (XpowYmodN(a, n - 1, n) != 1) return false; //n composite, return false
+ }
+ return true; // n probably prime
+ }
+
+ //Generates a large prime number by
+ //choosing a randomly large integer, and ensuring the value is odd
+ //then uses the miller-rabin primality test on it to see if it is prime
+ //if not the value gets increased until it is prime
+ private long generatePrime() {
+ long prime = 0;
+
+ do {
+ long start = rng(maxNum);
+ prime = tryToGeneratePrime(start);
+ } while (prime == 0);
+ return prime;
+ }
+
+ private long tryToGeneratePrime(long prime) {
+ //ensure it is an odd number
+ if ((prime & 1) == 0)
+ prime += 1;
+
+ long cnt = 0;
+ while (!millerRabin(prime, 25) && (cnt++ < DH_RANGE) && prime < maxNum) {
+ prime += 2;
+ if ((prime % 3) == 0) prime += 2;
+ }
+ return (cnt >= DH_RANGE || prime >= maxNum) ? 0 : prime;
+ }
+
+ //Raises X to the power Y in modulus N
+ //the values of X, Y, and N can be massive, and this can be
+ //achieved by first calculating X to the power of 2 then
+ //using power chaining over modulus N
+ private long XpowYmodN(long x, long y, long N) {
+ long result = 1;
+ final long oneShift63 = ((long) 1) << 63;
+
+ for (int i = 0; i < 64; y <<= 1, i++){
+ result = result * result % N;
+ if ((y & oneShift63) != 0)
+ result = result * x % N;
+ }
+ return result;
+ }
+
+ public void createKeys() {
+ gen = generatePrime();
+ mod = generatePrime();
+
+ if (gen > mod) {
+ long swap = gen;
+ gen = mod;
+ mod = swap;
+ }
+ }
+
+ public long createInterKey() {
+ priv = rng(maxNum);
+ return pub = XpowYmodN(gen,priv,mod);
+ }
+
+ public long createEncryptionKey(long interKey) throws Exception {
+ if (interKey >= maxNum){
+ throw new Exception("interKey too large");
+ }
+ return key = XpowYmodN(interKey,priv,mod);
+ }
+
+
+ public long getValue(int flags) {
+ switch (flags) {
+ case DH_MOD:
+ return mod;
+ case DH_GEN:
+ return gen;
+ case DH_PRIV:
+ return priv;
+ case DH_PUB:
+ return pub;
+ case DH_KEY:
+ return key;
+ default:
+ return (long) 0;
+ }
+ }
+
+ public int bits(long number){
+ for (int i = 0; i < 64; i++){
+ number /= 2;
+ if (number < 2) return i;
+ }
+ return 0;
+ }
+
+ public static byte[] longToBytes(long number) {
+ byte[] bytes = new byte[8];
+ for (int i = 0; i < 8; i++) {
+ bytes[i] = (byte) (0xff & (number >> (8 * (7 - i))));
+ }
+ return bytes;
+ }
+
+ public static long bytesToLong(byte[] bytes) {
+ long result = 0;
+ for (int i = 0; i < 8; i++) {
+ result <<= 8;
+ result += (byte) bytes[i];
+ }
+ return result;
+ }
+
+ private long gen;
+ private long mod;
+ private long priv;
+ private long pub;
+ private long key;
+ private long maxNum;
+
+ private static final int DH_MAX_BITS = 31;
+ private static final int DH_RANGE = 100;
+
+ private static final int DH_MOD = 1;
+ private static final int DH_GEN = 2;
+ private static final int DH_PRIV = 3;
+ private static final int DH_PUB = 4;
+ private static final int DH_KEY = 5;
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/android/androidVNC/DPadMouseKeyHandler.java b/app/src/main/java/android/androidVNC/DPadMouseKeyHandler.java
new file mode 100644
index 0000000..5f75df5
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/DPadMouseKeyHandler.java
@@ -0,0 +1,117 @@
+/**
+ * Copyright (c) 2010 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import android.graphics.PointF;
+import android.os.Handler;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+/**
+ * Input handlers delegate to this class to handle keystrokes; this detects keystrokes
+ * from the DPad and uses them to perform mouse actions; other keystrokes are passed to
+ * VncCanvasActivity.defaultKeyXXXHandler
+ *
+ * @author Michael A. MacDonald
+ *
+ */
+class DPadMouseKeyHandler {
+ private MouseMover mouseMover;
+ private boolean mouseDown;
+ private VncCanvasActivity activity;
+ private VncCanvas canvas;
+ private boolean isMoving;
+
+ DPadMouseKeyHandler(VncCanvasActivity activity, Handler handler)
+ {
+ this.activity = activity;
+ canvas = activity.vncCanvas;
+ mouseMover = new MouseMover(activity, handler);
+ }
+
+ public boolean onKeyDown(int keyCode, KeyEvent evt) {
+ int xv = 0;
+ int yv = 0;
+ boolean result = true;
+ switch (keyCode) {
+// case KeyEvent.KEYCODE_DPAD_LEFT:
+// xv = -1;
+// break;
+// case KeyEvent.KEYCODE_DPAD_RIGHT:
+// xv = 1;
+// break;
+// case KeyEvent.KEYCODE_DPAD_UP:
+// yv = -1;
+// break;
+// case KeyEvent.KEYCODE_DPAD_DOWN:
+// yv = 1;
+// break;
+// case KeyEvent.KEYCODE_DPAD_CENTER:
+// if (!mouseDown) {
+// mouseDown = true;
+// result = canvas.processPointerEvent(canvas.mouseX, canvas.mouseY, MotionEvent.ACTION_DOWN, evt.getMetaState(), mouseDown, canvas.cameraButtonDown);
+// }
+// break;
+ default:
+ result = activity.defaultKeyDownHandler(keyCode, evt);
+ break;
+ }
+ if ((xv != 0 || yv != 0) && !isMoving) {
+ final int x = xv;
+ final int y = yv;
+ isMoving = true;
+ mouseMover.start(x, y, new Panner.VelocityUpdater() {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.Panner.VelocityUpdater#updateVelocity(android.graphics.Point,
+ * long)
+ */
+ @Override
+ public boolean updateVelocity(PointF p, long interval) {
+ double scale = (1.2 * (double) interval / 50.0);
+ if (Math.abs(p.x) < 500)
+ p.x += (int) (scale * x);
+ if (Math.abs(p.y) < 500)
+ p.y += (int) (scale * y);
+ return true;
+ }
+
+ });
+ canvas.processPointerEvent(canvas.mouseX + x, canvas.mouseY + y, MotionEvent.ACTION_MOVE, evt.getMetaState(),
+ mouseDown, canvas.cameraButtonDown, false, false);
+
+ }
+ return result;
+ }
+
+ public boolean onKeyUp(int keyCode, KeyEvent evt) {
+ boolean result = false;
+
+ switch (keyCode) {
+// case KeyEvent.KEYCODE_DPAD_LEFT:
+// case KeyEvent.KEYCODE_DPAD_RIGHT:
+// case KeyEvent.KEYCODE_DPAD_UP:
+// case KeyEvent.KEYCODE_DPAD_DOWN:
+// mouseMover.stop();
+// isMoving = false;
+// result = true;
+// break;
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ if (mouseDown) {
+ mouseDown = false;
+ result = canvas.processPointerEvent(canvas.mouseX, canvas.mouseY, MotionEvent.ACTION_UP, evt.getMetaState(),
+ mouseDown, canvas.cameraButtonDown, false, false);
+ } else {
+ result = true;
+ }
+ break;
+ default:
+ result = activity.defaultKeyUpHandler(keyCode, evt);
+ break;
+ }
+ return result;
+ }
+}
diff --git a/app/src/main/java/android/androidVNC/DesCipher.java b/app/src/main/java/android/androidVNC/DesCipher.java
new file mode 100644
index 0000000..2d7bed3
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/DesCipher.java
@@ -0,0 +1,539 @@
+//
+// This DES class has been extracted from package Acme.Crypto for use in VNC.
+// The bytebit[] array has been reversed so that the most significant bit
+// in each byte of the key is ignored, not the least significant. Also the
+// unnecessary odd parity code has been removed.
+//
+// These changes are:
+// Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
+//
+// This software is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+//
+
+// DesCipher - the DES encryption method
+//
+// The meat of this code is by Dave Zimmerman , and is:
+//
+// Copyright (c) 1996 Widget Workshop, Inc. All Rights Reserved.
+//
+// Permission to use, copy, modify, and distribute this software
+// and its documentation for NON-COMMERCIAL or COMMERCIAL purposes and
+// without fee is hereby granted, provided that this copyright notice is kept
+// intact.
+//
+// WIDGET WORKSHOP MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY
+// OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+// TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+// PARTICULAR PURPOSE, OR NON-INFRINGEMENT. WIDGET WORKSHOP SHALL NOT BE LIABLE
+// FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
+// DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
+//
+// THIS SOFTWARE IS NOT DESIGNED OR INTENDED FOR USE OR RESALE AS ON-LINE
+// CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING FAIL-SAFE
+// PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT
+// NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE
+// SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+// SOFTWARE COULD LEAD DIRECTLY TO DEATH, PERSONAL INJURY, OR SEVERE
+// PHYSICAL OR ENVIRONMENTAL DAMAGE ("HIGH RISK ACTIVITIES"). WIDGET WORKSHOP
+// SPECIFICALLY DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTY OF FITNESS FOR
+// HIGH RISK ACTIVITIES.
+//
+//
+// The rest is:
+//
+// Copyright (C) 1996 by Jef Poskanzer . All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+// SUCH DAMAGE.
+//
+// Visit the ACME Labs Java page for up-to-date versions of this and other
+// fine Java utilities: http://www.acme.com/java/
+
+/// The DES encryption method.
+//
+// This is surprisingly fast, for pure Java. On a SPARC 20, wrapped
+// in Acme.Crypto.EncryptedOutputStream or Acme.Crypto.EncryptedInputStream,
+// it does around 7000 bytes/second.
+//
+// Most of this code is by Dave Zimmerman , and is
+// Copyright (c) 1996 Widget Workshop, Inc. See the source file for details.
+//
+// @see Des3Cipher
+// @see EncryptedOutputStream
+// @see EncryptedInputStream
+
+package android.androidVNC;
+
+//- import java.io.*;
+
+
+public class DesCipher
+ {
+
+ // Constructor, byte-array key.
+ public DesCipher( byte[] key )
+ {
+ setKey( key );
+ }
+
+ // Key routines.
+
+ private int[] encryptKeys = new int[32];
+ private int[] decryptKeys = new int[32];
+
+ /// Set the key.
+ public void setKey( byte[] key )
+ {
+ deskey( key, true, encryptKeys );
+ deskey( key, false, decryptKeys );
+ }
+
+ // Turn an 8-byte key into internal keys.
+ private void deskey( byte[] keyBlock, boolean encrypting, int[] KnL )
+ {
+ int i, j, l, m, n;
+ int[] pc1m = new int[56];
+ int[] pcr = new int[56];
+ int[] kn = new int[32];
+
+ for ( j = 0; j < 56; ++j )
+ {
+ l = pc1[j];
+ m = l & 07;
+ pc1m[j] = ( (keyBlock[l >>> 3] & bytebit[m]) != 0 )? 1: 0;
+ }
+
+ for ( i = 0; i < 16; ++i )
+ {
+ if ( encrypting )
+ m = i << 1;
+ else
+ m = (15-i) << 1;
+ n = m+1;
+ kn[m] = kn[n] = 0;
+ for ( j = 0; j < 28; ++j )
+ {
+ l = j+totrot[i];
+ if ( l < 28 )
+ pcr[j] = pc1m[l];
+ else
+ pcr[j] = pc1m[l-28];
+ }
+ for ( j=28; j < 56; ++j )
+ {
+ l = j+totrot[i];
+ if ( l < 56 )
+ pcr[j] = pc1m[l];
+ else
+ pcr[j] = pc1m[l-28];
+ }
+ for ( j = 0; j < 24; ++j )
+ {
+ if ( pcr[pc2[j]] != 0 )
+ kn[m] |= bigbyte[j];
+ if ( pcr[pc2[j+24]] != 0 )
+ kn[n] |= bigbyte[j];
+ }
+ }
+ cookey( kn, KnL );
+ }
+
+ private void cookey( int[] raw, int KnL[] )
+ {
+ int raw0, raw1;
+ int rawi, KnLi;
+ int i;
+
+ for ( i = 0, rawi = 0, KnLi = 0; i < 16; ++i )
+ {
+ raw0 = raw[rawi++];
+ raw1 = raw[rawi++];
+ KnL[KnLi] = (raw0 & 0x00fc0000) << 6;
+ KnL[KnLi] |= (raw0 & 0x00000fc0) << 10;
+ KnL[KnLi] |= (raw1 & 0x00fc0000) >>> 10;
+ KnL[KnLi] |= (raw1 & 0x00000fc0) >>> 6;
+ ++KnLi;
+ KnL[KnLi] = (raw0 & 0x0003f000) << 12;
+ KnL[KnLi] |= (raw0 & 0x0000003f) << 16;
+ KnL[KnLi] |= (raw1 & 0x0003f000) >>> 4;
+ KnL[KnLi] |= (raw1 & 0x0000003f);
+ ++KnLi;
+ }
+ }
+
+
+ // Block encryption routines.
+
+ private int[] tempInts = new int[2];
+
+ /// Encrypt a block of eight bytes.
+ public void encrypt( byte[] clearText, int clearOff, byte[] cipherText, int cipherOff )
+ {
+ squashBytesToInts( clearText, clearOff, tempInts, 0, 2 );
+ des( tempInts, tempInts, encryptKeys );
+ spreadIntsToBytes( tempInts, 0, cipherText, cipherOff, 2 );
+ }
+
+ /// Decrypt a block of eight bytes.
+ public void decrypt( byte[] cipherText, int cipherOff, byte[] clearText, int clearOff )
+ {
+ squashBytesToInts( cipherText, cipherOff, tempInts, 0, 2 );
+ des( tempInts, tempInts, decryptKeys );
+ spreadIntsToBytes( tempInts, 0, clearText, clearOff, 2 );
+ }
+
+ // Encrypt a text which is a multiple of 8 bytes.
+ public void encryptText(byte[] clearText, byte[] cipherText, byte[] key)
+ {
+ int i, j;
+
+ for(i=0; i<8; i++)
+ {
+ clearText[i] ^= key[i];
+ }
+ encrypt(clearText, 0, cipherText, 0);
+ for(i=8; i0; i-=8)
+ {
+ decrypt(cipherText, i, clearText, i);
+ for(j=0; j<8; j++)
+ {
+ clearText[i+j] ^= cipherText[i+j-8];
+ }
+ }
+ /* i = 0 */
+ decrypt(cipherText, 0, clearText, 0);
+ for(i=0; i<8; i++)
+ {
+ clearText[i] ^= key[i];
+ }
+ }
+
+ // The DES function.
+ private void des( int[] inInts, int[] outInts, int[] keys )
+ {
+ int fval, work, right, leftt;
+ int round;
+ int keysi = 0;
+
+ leftt = inInts[0];
+ right = inInts[1];
+
+ work = ((leftt >>> 4) ^ right) & 0x0f0f0f0f;
+ right ^= work;
+ leftt ^= (work << 4);
+
+ work = ((leftt >>> 16) ^ right) & 0x0000ffff;
+ right ^= work;
+ leftt ^= (work << 16);
+
+ work = ((right >>> 2) ^ leftt) & 0x33333333;
+ leftt ^= work;
+ right ^= (work << 2);
+
+ work = ((right >>> 8) ^ leftt) & 0x00ff00ff;
+ leftt ^= work;
+ right ^= (work << 8);
+ right = (right << 1) | ((right >>> 31) & 1);
+
+ work = (leftt ^ right) & 0xaaaaaaaa;
+ leftt ^= work;
+ right ^= work;
+ leftt = (leftt << 1) | ((leftt >>> 31) & 1);
+
+ for ( round = 0; round < 8; ++round )
+ {
+ work = (right << 28) | (right >>> 4);
+ work ^= keys[keysi++];
+ fval = SP7[ work & 0x0000003f ];
+ fval |= SP5[(work >>> 8) & 0x0000003f ];
+ fval |= SP3[(work >>> 16) & 0x0000003f ];
+ fval |= SP1[(work >>> 24) & 0x0000003f ];
+ work = right ^ keys[keysi++];
+ fval |= SP8[ work & 0x0000003f ];
+ fval |= SP6[(work >>> 8) & 0x0000003f ];
+ fval |= SP4[(work >>> 16) & 0x0000003f ];
+ fval |= SP2[(work >>> 24) & 0x0000003f ];
+ leftt ^= fval;
+ work = (leftt << 28) | (leftt >>> 4);
+ work ^= keys[keysi++];
+ fval = SP7[ work & 0x0000003f ];
+ fval |= SP5[(work >>> 8) & 0x0000003f ];
+ fval |= SP3[(work >>> 16) & 0x0000003f ];
+ fval |= SP1[(work >>> 24) & 0x0000003f ];
+ work = leftt ^ keys[keysi++];
+ fval |= SP8[ work & 0x0000003f ];
+ fval |= SP6[(work >>> 8) & 0x0000003f ];
+ fval |= SP4[(work >>> 16) & 0x0000003f ];
+ fval |= SP2[(work >>> 24) & 0x0000003f ];
+ right ^= fval;
+ }
+
+ right = (right << 31) | (right >>> 1);
+ work = (leftt ^ right) & 0xaaaaaaaa;
+ leftt ^= work;
+ right ^= work;
+ leftt = (leftt << 31) | (leftt >>> 1);
+ work = ((leftt >>> 8) ^ right) & 0x00ff00ff;
+ right ^= work;
+ leftt ^= (work << 8);
+ work = ((leftt >>> 2) ^ right) & 0x33333333;
+ right ^= work;
+ leftt ^= (work << 2);
+ work = ((right >>> 16) ^ leftt) & 0x0000ffff;
+ leftt ^= work;
+ right ^= (work << 16);
+ work = ((right >>> 4) ^ leftt) & 0x0f0f0f0f;
+ leftt ^= work;
+ right ^= (work << 4);
+ outInts[0] = right;
+ outInts[1] = leftt;
+ }
+
+
+ // Tables, permutations, S-boxes, etc.
+
+ private static byte[] bytebit = {
+ (byte)0x01, (byte)0x02, (byte)0x04, (byte)0x08,
+ (byte)0x10, (byte)0x20, (byte)0x40, (byte)0x80
+ };
+ private static int[] bigbyte = {
+ 0x800000, 0x400000, 0x200000, 0x100000,
+ 0x080000, 0x040000, 0x020000, 0x010000,
+ 0x008000, 0x004000, 0x002000, 0x001000,
+ 0x000800, 0x000400, 0x000200, 0x000100,
+ 0x000080, 0x000040, 0x000020, 0x000010,
+ 0x000008, 0x000004, 0x000002, 0x000001
+ };
+ private static byte[] pc1 = {
+ (byte)56, (byte)48, (byte)40, (byte)32, (byte)24, (byte)16, (byte) 8,
+ (byte) 0, (byte)57, (byte)49, (byte)41, (byte)33, (byte)25, (byte)17,
+ (byte) 9, (byte) 1, (byte)58, (byte)50, (byte)42, (byte)34, (byte)26,
+ (byte)18, (byte)10, (byte) 2, (byte)59, (byte)51, (byte)43, (byte)35,
+ (byte)62, (byte)54, (byte)46, (byte)38, (byte)30, (byte)22, (byte)14,
+ (byte) 6, (byte)61, (byte)53, (byte)45, (byte)37, (byte)29, (byte)21,
+ (byte)13, (byte) 5, (byte)60, (byte)52, (byte)44, (byte)36, (byte)28,
+ (byte)20, (byte)12, (byte) 4, (byte)27, (byte)19, (byte)11, (byte)3
+ };
+ private static int[] totrot = {
+ 1, 2, 4, 6, 8, 10, 12, 14, 15, 17, 19, 21, 23, 25, 27, 28
+ };
+
+ private static byte[] pc2 = {
+ (byte)13, (byte)16, (byte)10, (byte)23, (byte) 0, (byte) 4,
+ (byte) 2, (byte)27, (byte)14, (byte) 5, (byte)20, (byte) 9,
+ (byte)22, (byte)18, (byte)11, (byte)3 , (byte)25, (byte) 7,
+ (byte)15, (byte) 6, (byte)26, (byte)19, (byte)12, (byte) 1,
+ (byte)40, (byte)51, (byte)30, (byte)36, (byte)46, (byte)54,
+ (byte)29, (byte)39, (byte)50, (byte)44, (byte)32, (byte)47,
+ (byte)43, (byte)48, (byte)38, (byte)55, (byte)33, (byte)52,
+ (byte)45, (byte)41, (byte)49, (byte)35, (byte)28, (byte)31,
+ };
+
+ private static int[] SP1 = {
+ 0x01010400, 0x00000000, 0x00010000, 0x01010404,
+ 0x01010004, 0x00010404, 0x00000004, 0x00010000,
+ 0x00000400, 0x01010400, 0x01010404, 0x00000400,
+ 0x01000404, 0x01010004, 0x01000000, 0x00000004,
+ 0x00000404, 0x01000400, 0x01000400, 0x00010400,
+ 0x00010400, 0x01010000, 0x01010000, 0x01000404,
+ 0x00010004, 0x01000004, 0x01000004, 0x00010004,
+ 0x00000000, 0x00000404, 0x00010404, 0x01000000,
+ 0x00010000, 0x01010404, 0x00000004, 0x01010000,
+ 0x01010400, 0x01000000, 0x01000000, 0x00000400,
+ 0x01010004, 0x00010000, 0x00010400, 0x01000004,
+ 0x00000400, 0x00000004, 0x01000404, 0x00010404,
+ 0x01010404, 0x00010004, 0x01010000, 0x01000404,
+ 0x01000004, 0x00000404, 0x00010404, 0x01010400,
+ 0x00000404, 0x01000400, 0x01000400, 0x00000000,
+ 0x00010004, 0x00010400, 0x00000000, 0x01010004
+ };
+ private static int[] SP2 = {
+ 0x80108020, 0x80008000, 0x00008000, 0x00108020,
+ 0x00100000, 0x00000020, 0x80100020, 0x80008020,
+ 0x80000020, 0x80108020, 0x80108000, 0x80000000,
+ 0x80008000, 0x00100000, 0x00000020, 0x80100020,
+ 0x00108000, 0x00100020, 0x80008020, 0x00000000,
+ 0x80000000, 0x00008000, 0x00108020, 0x80100000,
+ 0x00100020, 0x80000020, 0x00000000, 0x00108000,
+ 0x00008020, 0x80108000, 0x80100000, 0x00008020,
+ 0x00000000, 0x00108020, 0x80100020, 0x00100000,
+ 0x80008020, 0x80100000, 0x80108000, 0x00008000,
+ 0x80100000, 0x80008000, 0x00000020, 0x80108020,
+ 0x00108020, 0x00000020, 0x00008000, 0x80000000,
+ 0x00008020, 0x80108000, 0x00100000, 0x80000020,
+ 0x00100020, 0x80008020, 0x80000020, 0x00100020,
+ 0x00108000, 0x00000000, 0x80008000, 0x00008020,
+ 0x80000000, 0x80100020, 0x80108020, 0x00108000
+ };
+ private static int[] SP3 = {
+ 0x00000208, 0x08020200, 0x00000000, 0x08020008,
+ 0x08000200, 0x00000000, 0x00020208, 0x08000200,
+ 0x00020008, 0x08000008, 0x08000008, 0x00020000,
+ 0x08020208, 0x00020008, 0x08020000, 0x00000208,
+ 0x08000000, 0x00000008, 0x08020200, 0x00000200,
+ 0x00020200, 0x08020000, 0x08020008, 0x00020208,
+ 0x08000208, 0x00020200, 0x00020000, 0x08000208,
+ 0x00000008, 0x08020208, 0x00000200, 0x08000000,
+ 0x08020200, 0x08000000, 0x00020008, 0x00000208,
+ 0x00020000, 0x08020200, 0x08000200, 0x00000000,
+ 0x00000200, 0x00020008, 0x08020208, 0x08000200,
+ 0x08000008, 0x00000200, 0x00000000, 0x08020008,
+ 0x08000208, 0x00020000, 0x08000000, 0x08020208,
+ 0x00000008, 0x00020208, 0x00020200, 0x08000008,
+ 0x08020000, 0x08000208, 0x00000208, 0x08020000,
+ 0x00020208, 0x00000008, 0x08020008, 0x00020200
+ };
+ private static int[] SP4 = {
+ 0x00802001, 0x00002081, 0x00002081, 0x00000080,
+ 0x00802080, 0x00800081, 0x00800001, 0x00002001,
+ 0x00000000, 0x00802000, 0x00802000, 0x00802081,
+ 0x00000081, 0x00000000, 0x00800080, 0x00800001,
+ 0x00000001, 0x00002000, 0x00800000, 0x00802001,
+ 0x00000080, 0x00800000, 0x00002001, 0x00002080,
+ 0x00800081, 0x00000001, 0x00002080, 0x00800080,
+ 0x00002000, 0x00802080, 0x00802081, 0x00000081,
+ 0x00800080, 0x00800001, 0x00802000, 0x00802081,
+ 0x00000081, 0x00000000, 0x00000000, 0x00802000,
+ 0x00002080, 0x00800080, 0x00800081, 0x00000001,
+ 0x00802001, 0x00002081, 0x00002081, 0x00000080,
+ 0x00802081, 0x00000081, 0x00000001, 0x00002000,
+ 0x00800001, 0x00002001, 0x00802080, 0x00800081,
+ 0x00002001, 0x00002080, 0x00800000, 0x00802001,
+ 0x00000080, 0x00800000, 0x00002000, 0x00802080
+ };
+ private static int[] SP5 = {
+ 0x00000100, 0x02080100, 0x02080000, 0x42000100,
+ 0x00080000, 0x00000100, 0x40000000, 0x02080000,
+ 0x40080100, 0x00080000, 0x02000100, 0x40080100,
+ 0x42000100, 0x42080000, 0x00080100, 0x40000000,
+ 0x02000000, 0x40080000, 0x40080000, 0x00000000,
+ 0x40000100, 0x42080100, 0x42080100, 0x02000100,
+ 0x42080000, 0x40000100, 0x00000000, 0x42000000,
+ 0x02080100, 0x02000000, 0x42000000, 0x00080100,
+ 0x00080000, 0x42000100, 0x00000100, 0x02000000,
+ 0x40000000, 0x02080000, 0x42000100, 0x40080100,
+ 0x02000100, 0x40000000, 0x42080000, 0x02080100,
+ 0x40080100, 0x00000100, 0x02000000, 0x42080000,
+ 0x42080100, 0x00080100, 0x42000000, 0x42080100,
+ 0x02080000, 0x00000000, 0x40080000, 0x42000000,
+ 0x00080100, 0x02000100, 0x40000100, 0x00080000,
+ 0x00000000, 0x40080000, 0x02080100, 0x40000100
+ };
+ private static int[] SP6 = {
+ 0x20000010, 0x20400000, 0x00004000, 0x20404010,
+ 0x20400000, 0x00000010, 0x20404010, 0x00400000,
+ 0x20004000, 0x00404010, 0x00400000, 0x20000010,
+ 0x00400010, 0x20004000, 0x20000000, 0x00004010,
+ 0x00000000, 0x00400010, 0x20004010, 0x00004000,
+ 0x00404000, 0x20004010, 0x00000010, 0x20400010,
+ 0x20400010, 0x00000000, 0x00404010, 0x20404000,
+ 0x00004010, 0x00404000, 0x20404000, 0x20000000,
+ 0x20004000, 0x00000010, 0x20400010, 0x00404000,
+ 0x20404010, 0x00400000, 0x00004010, 0x20000010,
+ 0x00400000, 0x20004000, 0x20000000, 0x00004010,
+ 0x20000010, 0x20404010, 0x00404000, 0x20400000,
+ 0x00404010, 0x20404000, 0x00000000, 0x20400010,
+ 0x00000010, 0x00004000, 0x20400000, 0x00404010,
+ 0x00004000, 0x00400010, 0x20004010, 0x00000000,
+ 0x20404000, 0x20000000, 0x00400010, 0x20004010
+ };
+ private static int[] SP7 = {
+ 0x00200000, 0x04200002, 0x04000802, 0x00000000,
+ 0x00000800, 0x04000802, 0x00200802, 0x04200800,
+ 0x04200802, 0x00200000, 0x00000000, 0x04000002,
+ 0x00000002, 0x04000000, 0x04200002, 0x00000802,
+ 0x04000800, 0x00200802, 0x00200002, 0x04000800,
+ 0x04000002, 0x04200000, 0x04200800, 0x00200002,
+ 0x04200000, 0x00000800, 0x00000802, 0x04200802,
+ 0x00200800, 0x00000002, 0x04000000, 0x00200800,
+ 0x04000000, 0x00200800, 0x00200000, 0x04000802,
+ 0x04000802, 0x04200002, 0x04200002, 0x00000002,
+ 0x00200002, 0x04000000, 0x04000800, 0x00200000,
+ 0x04200800, 0x00000802, 0x00200802, 0x04200800,
+ 0x00000802, 0x04000002, 0x04200802, 0x04200000,
+ 0x00200800, 0x00000000, 0x00000002, 0x04200802,
+ 0x00000000, 0x00200802, 0x04200000, 0x00000800,
+ 0x04000002, 0x04000800, 0x00000800, 0x00200002
+ };
+ private static int[] SP8 = {
+ 0x10001040, 0x00001000, 0x00040000, 0x10041040,
+ 0x10000000, 0x10001040, 0x00000040, 0x10000000,
+ 0x00040040, 0x10040000, 0x10041040, 0x00041000,
+ 0x10041000, 0x00041040, 0x00001000, 0x00000040,
+ 0x10040000, 0x10000040, 0x10001000, 0x00001040,
+ 0x00041000, 0x00040040, 0x10040040, 0x10041000,
+ 0x00001040, 0x00000000, 0x00000000, 0x10040040,
+ 0x10000040, 0x10001000, 0x00041040, 0x00040000,
+ 0x00041040, 0x00040000, 0x10041000, 0x00001000,
+ 0x00000040, 0x10040040, 0x00001000, 0x00041040,
+ 0x10001000, 0x00000040, 0x10000040, 0x10040000,
+ 0x10040040, 0x10000000, 0x00040000, 0x10001040,
+ 0x00000000, 0x10041040, 0x00040040, 0x10000040,
+ 0x10040000, 0x10001000, 0x10001040, 0x00000000,
+ 0x10041040, 0x00041000, 0x00041000, 0x00001040,
+ 0x00001040, 0x00040040, 0x10000000, 0x10041000
+ };
+
+ // Routines taken from other parts of the Acme utilities.
+
+ /// Squash bytes down to ints.
+ public static void squashBytesToInts( byte[] inBytes, int inOff, int[] outInts, int outOff, int intLen )
+ {
+ for ( int i = 0; i < intLen; ++i )
+ outInts[outOff + i] =
+ ( ( inBytes[inOff + i * 4 ] & 0xff ) << 24 ) |
+ ( ( inBytes[inOff + i * 4 + 1] & 0xff ) << 16 ) |
+ ( ( inBytes[inOff + i * 4 + 2] & 0xff ) << 8 ) |
+ ( inBytes[inOff + i * 4 + 3] & 0xff );
+ }
+
+ /// Spread ints into bytes.
+ public static void spreadIntsToBytes( int[] inInts, int inOff, byte[] outBytes, int outOff, int intLen )
+ {
+ for ( int i = 0; i < intLen; ++i )
+ {
+ outBytes[outOff + i * 4 ] = (byte) ( inInts[inOff + i] >>> 24 );
+ outBytes[outOff + i * 4 + 1] = (byte) ( inInts[inOff + i] >>> 16 );
+ outBytes[outOff + i * 4 + 2] = (byte) ( inInts[inOff + i] >>> 8 );
+ outBytes[outOff + i * 4 + 3] = (byte) inInts[inOff + i];
+ }
+ }
+ }
diff --git a/app/src/main/java/android/androidVNC/FitToScreenScaling.java b/app/src/main/java/android/androidVNC/FitToScreenScaling.java
new file mode 100644
index 0000000..2fb7a20
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/FitToScreenScaling.java
@@ -0,0 +1,57 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import android.widget.ImageView.ScaleType;
+
+import com.vectras.vm.R;
+
+/**
+ * @author Michael A. MacDonald
+ */
+class FitToScreenScaling extends AbstractScaling {
+
+ /**
+ * @param id
+ * @param scaleType
+ */
+ FitToScreenScaling() {
+ super(R.id.itemFitToScreen, ScaleType.FIT_CENTER);
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractScaling#isAbleToPan()
+ */
+ @Override
+ boolean isAbleToPan() {
+ return false;
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractScaling#isValidInputMode(int)
+ */
+ @Override
+ boolean isValidInputMode(int mode) {
+ return mode == R.id.itemInputFitToScreen;
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractScaling#getDefaultHandlerId()
+ */
+ @Override
+ int getDefaultHandlerId() {
+ return R.id.itemInputFitToScreen;
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractScaling#setCanvasScaleType(android.androidVNC.VncCanvas)
+ */
+ @Override
+ public void setScaleTypeForActivity(VncCanvasActivity activity) {
+ super.setScaleTypeForActivity(activity);
+ activity.vncCanvas.absoluteXPosition = activity.vncCanvas.absoluteYPosition = 0;
+ activity.vncCanvas.scrollTo(0, 0);
+ }
+
+}
diff --git a/app/src/main/java/android/androidVNC/FullBufferBitmapData.java b/app/src/main/java/android/androidVNC/FullBufferBitmapData.java
new file mode 100644
index 0000000..668b40c
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/FullBufferBitmapData.java
@@ -0,0 +1,221 @@
+/**
+ * Copyright (c) 2010 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.widget.ImageView;
+
+import com.vectras.qemu.Config;
+
+/**
+ * @author Michael A. MacDonald
+ *
+ */
+class FullBufferBitmapData extends AbstractBitmapData {
+
+ int xoffset;
+ int yoffset;
+
+ /**
+ * @author Michael A. MacDonald
+ *
+ */
+ class Drawable extends AbstractBitmapDrawable {
+
+ /**
+ * @param data
+ */
+ public Drawable(AbstractBitmapData data) {
+ super(data);
+ // TODO Auto-generated constructor stub
+ }
+
+ /* (non-Javadoc)
+ * @see android.graphics.drawable.DrawableContainer#draw(android.graphics.Canvas)
+ */
+ @Override
+ public void draw(Canvas canvas) {
+ if (vncCanvas.getScaleType() == ImageView.ScaleType.FIT_CENTER)
+ {
+ //canvas.drawBitmap(data.bitmapPixels, 0, data.framebufferwidth, xoffset, yoffset, framebufferwidth, framebufferheight, false, null);
+
+ //XXX; Limbo: for Hardware accelerated surfaces we have to stop using the above deprecated method and use a bitmap-backed method
+ // this fixes the issue with Nougat Devices displaying black screen for 24bit color mode C24bit
+ Bitmap bitmapTmp = Bitmap.createBitmap(framebufferwidth, framebufferheight, Config.bitmapConfig);
+ bitmapTmp.setPixels(bitmapPixels, 0, data.framebufferwidth, 0, 0, framebufferwidth, framebufferheight);
+ canvas.drawBitmap(bitmapTmp, xoffset, yoffset, null);
+
+ }
+ else
+ {
+ float scale = vncCanvas.getScale();
+ int xo = xoffset < 0 ? 0 : xoffset;
+ int yo = yoffset < 0 ? 0 : yoffset;
+ /*
+ if (scale == 1 || scale <= 0)
+ {
+ */
+ int drawWidth = vncCanvas.getVisibleWidth();
+ if (drawWidth + xo > data.framebufferwidth)
+ drawWidth = data.framebufferwidth - xo;
+ int drawHeight = vncCanvas.getVisibleHeight();
+ if (drawHeight + yo > data.framebufferheight)
+ drawHeight = data.framebufferheight - yo;
+
+
+ //canvas.drawBitmap(data.bitmapPixels, offset(xo, yo), data.framebufferwidth, xo, yo, drawWidth, drawHeight, false, null);
+
+ //XXX; for Hardware accelerated surfaces we have to stop using the above deprecated method and use a bitmap-backed method
+ // this fixes the issue with Nougat Devices displaying black screen for 24bit color mode C24bit
+ Bitmap bitmapTmp = Bitmap.createBitmap(framebufferwidth, framebufferheight, Config.bitmapConfig);
+ bitmapTmp.setPixels(bitmapPixels, offset(xo, yo), data.framebufferwidth, 0, 0, drawWidth, drawHeight);
+ canvas.drawBitmap(bitmapTmp, xo, yo, null);
+
+ /*
+ }
+ else
+ {
+ int scalewidth = (int)(vncCanvas.getVisibleWidth() / scale + 1);
+ if (scalewidth + xo > data.framebufferwidth)
+ scalewidth = data.framebufferwidth - xo;
+ int scaleheight = (int)(vncCanvas.getVisibleHeight() / scale + 1);
+ if (scaleheight + yo > data.framebufferheight)
+ scaleheight = data.framebufferheight - yo;
+ canvas.drawBitmap(data.bitmapPixels, offset(xo, yo), data.framebufferwidth, xo, yo, scalewidth, scaleheight, false, null);
+ }
+ */
+ }
+ if(data.vncCanvas.connection.getUseLocalCursor())
+ {
+ setCursorRect(data.vncCanvas.mouseX, data.vncCanvas.mouseY);
+ clipRect.set(cursorRect);
+ if (canvas.clipRect(cursorRect))
+ {
+ drawCursor(canvas);
+ }
+ }
+ }
+ }
+
+ /**
+ * Multiply this times total number of pixels to get estimate of process size with all buffers plus
+ * safety factor
+ */
+ static final int CAPACITY_MULTIPLIER = 7;
+
+ /**
+ * @param p
+ * @param c
+ */
+ public FullBufferBitmapData(RfbProto p, VncCanvas c, int capacity) {
+ super(p, c);
+ framebufferwidth=rfb.framebufferWidth;
+ framebufferheight=rfb.framebufferHeight;
+ bitmapwidth=framebufferwidth;
+ bitmapheight=framebufferheight;
+ android.util.Log.i("FBBM", "bitmapsize = ("+bitmapwidth+","+bitmapheight+")");
+ bitmapPixels = new int[framebufferwidth * framebufferheight];
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#copyRect(android.graphics.Rect, android.graphics.Rect, android.graphics.Paint)
+ */
+ @Override
+ void copyRect(Rect src, Rect dest, Paint paint) {
+ // TODO copy rect working?
+ throw new RuntimeException( "copyrect Not implemented");
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#createDrawable()
+ */
+ @Override
+ AbstractBitmapDrawable createDrawable() {
+ return new Drawable(this);
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#drawRect(int, int, int, int, android.graphics.Paint)
+ */
+ @Override
+ void drawRect(int x, int y, int w, int h, Paint paint) {
+ int color = paint.getColor();
+ int offset = offset(x,y);
+ if (w > 10)
+ {
+ for (int j = 0; j < h; j++, offset += framebufferwidth)
+ {
+ Arrays.fill(bitmapPixels, offset, offset + w, color);
+ }
+ }
+ else
+ {
+ for (int j = 0; j < h; j++, offset += framebufferwidth - w)
+ {
+ for (int k = 0; k < w; k++, offset++)
+ {
+ bitmapPixels[offset] = color;
+ }
+ }
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#offset(int, int)
+ */
+ @Override
+ int offset(int x, int y) {
+ return x + y * framebufferwidth;
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#scrollChanged(int, int)
+ */
+ @Override
+ void scrollChanged(int newx, int newy) {
+ xoffset = newx;
+ yoffset = newy;
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#syncScroll()
+ */
+ @Override
+ void syncScroll() {
+ // Don't need to do anything here
+
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#updateBitmap(int, int, int, int)
+ */
+ @Override
+ void updateBitmap(int x, int y, int w, int h) {
+ // Don't need to do anything here
+
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#validDraw(int, int, int, int)
+ */
+ @Override
+ boolean validDraw(int x, int y, int w, int h) {
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#writeFullUpdateRequest(boolean)
+ */
+ @Override
+ void writeFullUpdateRequest(boolean incremental) throws IOException {
+ rfb.writeFramebufferUpdateRequest(0, 0, framebufferwidth, framebufferheight, incremental);
+ }
+
+}
diff --git a/app/src/main/java/android/androidVNC/InStream.java b/app/src/main/java/android/androidVNC/InStream.java
new file mode 100644
index 0000000..cbbda06
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/InStream.java
@@ -0,0 +1,155 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+//
+// rdr::InStream marshalls data from a buffer stored in RDR (RFB Data
+// Representation).
+//
+package android.androidVNC;
+
+
+abstract public class InStream {
+
+ // check() ensures there is buffer data for at least one item of size
+ // itemSize bytes. Returns the number of items in the buffer (up to a
+ // maximum of nItems).
+
+ public final int check(int itemSize, int nItems) throws Exception {
+ if (ptr + itemSize * nItems > end) {
+ if (ptr + itemSize > end)
+ return overrun(itemSize, nItems);
+
+ nItems = (end - ptr) / itemSize;
+ }
+ return nItems;
+ }
+
+ public final void check(int itemSize) throws Exception {
+ if (ptr + itemSize > end)
+ overrun(itemSize, 1);
+ }
+
+ // readU/SN() methods read unsigned and signed N-bit integers.
+
+ public final int readS8() throws Exception {
+ check(1); return b[ptr++];
+ }
+
+ public final int readS16() throws Exception {
+ check(2); int b0 = b[ptr++];
+ int b1 = b[ptr++] & 0xff; return b0 << 8 | b1;
+ }
+
+ public final int readS32() throws Exception {
+ check(4); int b0 = b[ptr++];
+ int b1 = b[ptr++] & 0xff;
+ int b2 = b[ptr++] & 0xff;
+ int b3 = b[ptr++] & 0xff;
+ return b0 << 24 | b1 << 16 | b2 << 8 | b3;
+ }
+
+ public final int readU8() throws Exception {
+ return readS8() & 0xff;
+ }
+
+ public final int readU16() throws Exception {
+ return readS16() & 0xffff;
+ }
+
+ public final int readU32() throws Exception {
+ return readS32() & 0xffffffff;
+ }
+
+ public final void skip(int bytes) throws Exception {
+ while (bytes > 0) {
+ int n = check(1, bytes);
+ ptr += n;
+ bytes -= n;
+ }
+ }
+
+ // readBytes() reads an exact number of bytes into an array at an offset.
+
+ public void readBytes(byte[] data, int offset, int length) throws Exception {
+ int offsetEnd = offset + length;
+ while (offset < offsetEnd) {
+ int n = check(1, offsetEnd - offset);
+ System.arraycopy(b, ptr, data, offset, n);
+ ptr += n;
+ offset += n;
+ }
+ }
+
+ // readOpaqueN() reads a quantity "without byte-swapping". Because java has
+ // no byte-ordering, we just use big-endian.
+
+ public final int readOpaque8() throws Exception {
+ return readU8();
+ }
+
+ public final int readOpaque16() throws Exception {
+ return readU16();
+ }
+
+ public final int readOpaque32() throws Exception {
+ return readU32();
+ }
+
+ public final int readOpaque24A() throws Exception {
+ check(3); int b0 = b[ptr++];
+ int b1 = b[ptr++]; int b2 = b[ptr++];
+ return b0 << 24 | b1 << 16 | b2 << 8;
+ }
+
+ public final int readOpaque24B() throws Exception {
+ check(3); int b0 = b[ptr++];
+ int b1 = b[ptr++]; int b2 = b[ptr++];
+ return b0 << 16 | b1 << 8 | b2;
+ }
+
+ // pos() returns the position in the stream.
+
+ abstract public int pos();
+
+ // bytesAvailable() returns true if at least one byte can be read from the
+ // stream without blocking. i.e. if false is returned then readU8() would
+ // block.
+
+ public boolean bytesAvailable() { return end != ptr; }
+
+ // getbuf(), getptr(), getend() and setptr() are "dirty" methods which allow
+ // you to manipulate the buffer directly. This is useful for a stream which
+ // is a wrapper around an underlying stream.
+
+ public final byte[] getbuf() { return b; }
+ public final int getptr() { return ptr; }
+ public final int getend() { return end; }
+ public final void setptr(int p) { ptr = p; }
+
+ // overrun() is implemented by a derived class to cope with buffer overrun.
+ // It ensures there are at least itemSize bytes of buffer data. Returns
+ // the number of items in the buffer (up to a maximum of nItems). itemSize
+ // is supposed to be "small" (a few bytes).
+
+ abstract protected int overrun(int itemSize, int nItems) throws Exception;
+
+ protected InStream() {}
+ protected byte[] b;
+ protected int ptr;
+ protected int end;
+}
diff --git a/app/src/main/java/android/androidVNC/LargeBitmapData.java b/app/src/main/java/android/androidVNC/LargeBitmapData.java
new file mode 100644
index 0000000..68f7613
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/LargeBitmapData.java
@@ -0,0 +1,319 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import java.io.IOException;
+
+import com.antlersoft.android.drawing.OverlappingCopy;
+import com.antlersoft.android.drawing.RectList;
+import com.antlersoft.util.ObjectPool;
+import com.vectras.qemu.Config;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+
+/**
+ * @author Michael A. MacDonald
+ *
+ */
+class LargeBitmapData extends AbstractBitmapData {
+
+ /**
+ * Multiply this times total number of pixels to get estimate of process size with all buffers plus
+ * safety factor
+ */
+ static final int CAPACITY_MULTIPLIER = 21;
+
+ int xoffset;
+ int yoffset;
+ int scrolledToX;
+ int scrolledToY;
+ private Rect bitmapRect;
+ private Paint defaultPaint;
+ private RectList invalidList;
+ private RectList pendingList;
+
+ /**
+ * Pool of temporary rectangle objects. Need to synchronize externally access from
+ * multiple threads.
+ */
+ private static ObjectPool rectPool = new ObjectPool() {
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.util.ObjectPool#itemForPool()
+ */
+ @Override
+ protected Rect itemForPool() {
+ return new Rect();
+ }
+ };
+
+ class LargeBitmapDrawable extends AbstractBitmapDrawable
+ {
+ LargeBitmapDrawable()
+ {
+ super(LargeBitmapData.this);
+ }
+ /* (non-Javadoc)
+ * @see android.graphics.drawable.DrawableContainer#draw(android.graphics.Canvas)
+ */
+ @Override
+ public void draw(Canvas canvas) {
+ //android.util.Log.i("LBM", "Drawing "+xoffset+" "+yoffset);
+ int xoff, yoff;
+ synchronized ( LargeBitmapData.this )
+ {
+ xoff=xoffset;
+ yoff=yoffset;
+ }
+ draw(canvas, xoff, yoff);
+ }
+ }
+
+ /**
+ *
+ * @param p Protocol implementation
+ * @param c View that will display screen
+ * @param displayWidth
+ * @param displayHeight
+ * @param capacity Max process heap size in bytes
+ */
+ LargeBitmapData(RfbProto p, VncCanvas c, int displayWidth, int displayHeight, int capacity)
+ {
+ super(p,c);
+ double scaleMultiplier = Math.sqrt((double)(capacity * 1024 * 1024) / (double)(CAPACITY_MULTIPLIER * framebufferwidth * framebufferheight));
+ if (scaleMultiplier > 1)
+ scaleMultiplier = 1;
+ bitmapwidth=(int)((double)framebufferwidth * scaleMultiplier);
+ if (bitmapwidth < displayWidth)
+ bitmapwidth = displayWidth;
+ bitmapheight=(int)((double)framebufferheight * scaleMultiplier);
+ if (bitmapheight < displayHeight)
+ bitmapheight = displayHeight;
+ android.util.Log.i("LBM", "bitmapsize = ("+bitmapwidth+","+bitmapheight+")");
+ mbitmap = Bitmap.createBitmap(bitmapwidth, bitmapheight, Config.bitmapConfig);
+ memGraphics = new Canvas(mbitmap);
+ bitmapPixels = new int[bitmapwidth * bitmapheight];
+ invalidList = new RectList(rectPool);
+ pendingList = new RectList(rectPool);
+ bitmapRect=new Rect(0,0,bitmapwidth,bitmapheight);
+ defaultPaint = new Paint();
+ }
+
+ @Override
+ AbstractBitmapDrawable createDrawable()
+ {
+ return new LargeBitmapDrawable();
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#copyRect(android.graphics.Rect, android.graphics.Rect, android.graphics.Paint)
+ */
+ @Override
+ void copyRect(Rect src, Rect dest, Paint paint) {
+ // TODO copy rect working?
+ throw new RuntimeException( "copyrect Not implemented");
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#drawRect(int, int, int, int, android.graphics.Paint)
+ */
+ @Override
+ void drawRect(int x, int y, int w, int h, Paint paint) {
+ x-=xoffset;
+ y-=yoffset;
+ memGraphics.drawRect(x, y, x+w, y+h, paint);
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#offset(int, int)
+ */
+ @Override
+ int offset(int x, int y) {
+ return (y - yoffset) * bitmapwidth + x - xoffset;
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#scrollChanged(int, int)
+ */
+ @Override
+ synchronized void scrollChanged(int newx, int newy) {
+ //android.util.Log.i("LBM","scroll "+newx+" "+newy);
+ int newScrolledToX = scrolledToX;
+ int newScrolledToY = scrolledToY;
+ int visibleWidth = vncCanvas.getVisibleWidth();
+ int visibleHeight = vncCanvas.getVisibleHeight();
+ if (newx - xoffset < 0 )
+ {
+ newScrolledToX = newx + visibleWidth / 2 - bitmapwidth / 2;
+ if (newScrolledToX < 0)
+ newScrolledToX = 0;
+ }
+ else if (newx - xoffset + visibleWidth > bitmapwidth)
+ {
+ newScrolledToX = newx + visibleWidth / 2 - bitmapwidth / 2;
+ if (newScrolledToX + bitmapwidth > framebufferwidth)
+ newScrolledToX = framebufferwidth - bitmapwidth;
+ }
+ if (newy - yoffset < 0 )
+ {
+ newScrolledToY = newy + visibleHeight / 2 - bitmapheight / 2;
+ if (newScrolledToY < 0)
+ newScrolledToY = 0;
+ }
+ else if (newy - yoffset + visibleHeight > bitmapheight)
+ {
+ newScrolledToY = newy + visibleHeight / 2 - bitmapheight / 2;
+ if (newScrolledToY + bitmapheight > framebufferheight)
+ newScrolledToY = framebufferheight - bitmapheight;
+ }
+ if (newScrolledToX != scrolledToX || newScrolledToY != scrolledToY)
+ {
+ scrolledToX = newScrolledToX;
+ scrolledToY = newScrolledToY;
+ if ( waitingForInput)
+ syncScroll();
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#updateBitmap(int, int, int, int)
+ */
+ @Override
+ void updateBitmap(int x, int y, int w, int h) {
+ mbitmap.setPixels(bitmapPixels, offset(x,y), bitmapwidth, x-xoffset, y-yoffset, w, h);
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#validDraw(int, int, int, int)
+ */
+ @Override
+ synchronized boolean validDraw(int x, int y, int w, int h) {
+ //android.util.Log.i("LBM", "Validate Drawing "+x+" "+y+" "+w+" "+h+" "+xoffset+" "+yoffset+" "+(x-xoffset>=0 && x-xoffset+w<=bitmapwidth && y-yoffset>=0 && y-yoffset+h<=bitmapheight));
+ boolean result = x-xoffset>=0 && x-xoffset+w<=bitmapwidth && y-yoffset>=0 && y-yoffset+h<=bitmapheight;
+ ObjectPool.Entry entry = rectPool.reserve();
+ Rect r = entry.get();
+ r.set(x, y, x+w, y+h);
+ pendingList.subtract(r);
+ if ( ! result)
+ {
+ invalidList.add(r);
+ }
+ else
+ invalidList.subtract(r);
+ rectPool.release(entry);
+ return result;
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#writeFullUpdateRequest(boolean)
+ */
+ @Override
+ synchronized void writeFullUpdateRequest(boolean incremental) throws IOException {
+ if (! incremental) {
+ ObjectPool.Entry entry = rectPool.reserve();
+ Rect r = entry.get();
+ r.left=xoffset;
+ r.top=yoffset;
+ r.right=xoffset + bitmapwidth;
+ r.bottom=yoffset + bitmapheight;
+ pendingList.add(r);
+ invalidList.add(r);
+ rectPool.release(entry);
+ }
+ rfb.writeFramebufferUpdateRequest(xoffset, yoffset, bitmapwidth, bitmapheight, incremental);
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#syncScroll()
+ */
+ @Override
+ synchronized void syncScroll() {
+
+ int deltaX = xoffset - scrolledToX;
+ int deltaY = yoffset - scrolledToY;
+ xoffset=scrolledToX;
+ yoffset=scrolledToY;
+ bitmapRect.top=scrolledToY;
+ bitmapRect.bottom=scrolledToY+bitmapheight;
+ bitmapRect.left=scrolledToX;
+ bitmapRect.right=scrolledToX+bitmapwidth;
+ invalidList.intersect(bitmapRect);
+ if ( deltaX != 0 || deltaY != 0)
+ {
+ boolean didOverlapping = false;
+ if (Math.abs(deltaX) < bitmapwidth && Math.abs(deltaY) < bitmapheight) {
+ ObjectPool.Entry sourceEntry = rectPool.reserve();
+ ObjectPool.Entry addedEntry = rectPool.reserve();
+ try
+ {
+ Rect added = addedEntry.get();
+ Rect sourceRect = sourceEntry.get();
+ sourceRect.set(deltaX<0 ? -deltaX : 0,
+ deltaY<0 ? -deltaY : 0,
+ deltaX<0 ? bitmapwidth : bitmapwidth - deltaX,
+ deltaY < 0 ? bitmapheight : bitmapheight - deltaY);
+ if (! invalidList.testIntersect(sourceRect)) {
+ didOverlapping = true;
+ OverlappingCopy.Copy(mbitmap, memGraphics, defaultPaint, sourceRect, deltaX + sourceRect.left, deltaY + sourceRect.top, rectPool);
+ // Write request for side pixels
+ if (deltaX != 0) {
+ added.left = deltaX < 0 ? bitmapRect.right + deltaX : bitmapRect.left;
+ added.right = added.left + Math.abs(deltaX);
+ added.top = bitmapRect.top;
+ added.bottom = bitmapRect.bottom;
+ invalidList.add(added);
+ }
+ if (deltaY != 0) {
+ added.left = deltaX < 0 ? bitmapRect.left : bitmapRect.left + deltaX;
+ added.top = deltaY < 0 ? bitmapRect.bottom + deltaY : bitmapRect.top;
+ added.right = added.left + bitmapwidth - Math.abs(deltaX);
+ added.bottom = added.top + Math.abs(deltaY);
+ invalidList.add(added);
+ }
+ }
+ }
+ finally {
+ rectPool.release(addedEntry);
+ rectPool.release(sourceEntry);
+ }
+ }
+ if (! didOverlapping)
+ {
+ try
+ {
+ //android.util.Log.i("LBM","update req "+xoffset+" "+yoffset);
+ mbitmap.eraseColor(Color.GREEN);
+ writeFullUpdateRequest(false);
+ }
+ catch ( IOException ioe)
+ {
+ // TODO log this
+ }
+ }
+ }
+ int size = pendingList.getSize();
+ for (int i=0; i {
+ public int keySym;
+ int mouseButtons;
+ int keyEvent;
+ String name;
+ boolean isMouse;
+ boolean isKeyEvent;
+
+ MetaKeyBase(int mouseButtons, String name)
+ {
+ this.mouseButtons = mouseButtons;
+ this.name = name;
+ this.isMouse = true;
+ this.isKeyEvent = false;
+ }
+
+ MetaKeyBase(String name, int keySym, int keyEvent)
+ {
+ this.name = name;
+ this.keySym = keySym;
+ this.keyEvent = keyEvent;
+ this.isMouse = false;
+ this.isKeyEvent = true;
+ }
+
+ MetaKeyBase(String name, int keySym)
+ {
+ this.name = name;
+ this.keySym = keySym;
+ this.isMouse = false;
+ this.isKeyEvent = false;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Comparable#compareTo(java.lang.Object)
+ */
+ @Override
+ public int compareTo(MetaKeyBase another) {
+ return name.compareTo(another.name);
+ }
+}
diff --git a/app/src/main/java/android/androidVNC/MetaKeyBean.java b/app/src/main/java/android/androidVNC/MetaKeyBean.java
new file mode 100644
index 0000000..c5d82b5
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/MetaKeyBean.java
@@ -0,0 +1,267 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import android.view.KeyEvent;
+
+/**
+ * @author Michael A. MacDonald
+ *
+ */
+public class MetaKeyBean {
+ int keySym;
+ int metaFlags;
+
+ static final ArrayList allKeys;
+ static final String[] allKeysNames;
+ public static final HashMap keysByKeyCode;
+ static final HashMap keysByMouseButton;
+ static final HashMap keysByKeySym;
+ static final MetaKeyBean keyCtrlAltDel;
+ static final MetaKeyBean keyCtrlC;
+ static final MetaKeyBean keyArrowLeft;
+ static final MetaKeyBean keyArrowRight;
+ static final MetaKeyBean keyArrowUp;
+ static final MetaKeyBean keyArrowDown;
+
+ static {
+ allKeys = new ArrayList();
+
+ allKeys.add(new MetaKeyBase("Hangul", 0xff31));
+ allKeys.add(new MetaKeyBase("Hangul_Start", 0xff32));
+ allKeys.add(new MetaKeyBase("Hangul_End", 0xff33));
+ allKeys.add(new MetaKeyBase("Hangul_Hanja", 0xff34));
+ allKeys.add(new MetaKeyBase("Kana_Shift", 0xff2e));
+ allKeys.add(new MetaKeyBase("Right_Alt", 0xffea));
+
+ allKeys.add(new MetaKeyBase(VncCanvas.MOUSE_BUTTON_LEFT, "Mouse Left"));
+ allKeys.add(new MetaKeyBase(VncCanvas.MOUSE_BUTTON_MIDDLE,
+ "Mouse Middle"));
+ allKeys.add(new MetaKeyBase(VncCanvas.MOUSE_BUTTON_RIGHT, "Mouse Right"));
+ allKeys.add(new MetaKeyBase(VncCanvas.MOUSE_BUTTON_SCROLL_DOWN,
+ "Mouse Scroll Down"));
+ allKeys.add(new MetaKeyBase(VncCanvas.MOUSE_BUTTON_SCROLL_UP,
+ "Mouse Scroll Up"));
+
+ allKeys.add(new MetaKeyBase("Home", 0xFF50));
+ allKeys.add(new MetaKeyBase("Arrow Left", 0xFF51));
+ allKeys.add(new MetaKeyBase("Arrow Up", 0xFF52));
+ allKeys.add(new MetaKeyBase("Arrow Right", 0xFF53));
+ allKeys.add(new MetaKeyBase("Arrow Down", 0xFF54));
+ allKeys.add(new MetaKeyBase("Page Up", 0xFF55));
+ allKeys.add(new MetaKeyBase("Page Down", 0xFF56));
+ allKeys.add(new MetaKeyBase("End", 0xFF57));
+ allKeys.add(new MetaKeyBase("Insert", 0xFF63));
+ allKeys.add(new MetaKeyBase("Delete", 0xFFFF, KeyEvent.KEYCODE_DEL));
+ allKeys.add(new MetaKeyBase("Num Lock", 0xFF7F));
+ allKeys.add(new MetaKeyBase("Break", 0xFF6b));
+ allKeys.add(new MetaKeyBase("Scroll Lock", 0xFF14));
+ allKeys.add(new MetaKeyBase("Print Scrn", 0xFF15));
+ allKeys.add(new MetaKeyBase("Escape", 0xFF1B));
+ allKeys.add(new MetaKeyBase("Enter", 0xFF0D, KeyEvent.KEYCODE_ENTER));
+ allKeys.add(new MetaKeyBase("Tab", 0xFF09, KeyEvent.KEYCODE_TAB));
+ allKeys.add(new MetaKeyBase("BackSpace", 0xFF08));
+ allKeys.add(new MetaKeyBase("Space", 0x020, KeyEvent.KEYCODE_SPACE));
+
+ StringBuilder sb = new StringBuilder(" ");
+ for (int i = 0; i < 26; i++) {
+ sb.setCharAt(0, (char) ('A' + i));
+ allKeys.add(new MetaKeyBase(sb.toString(), 'a' + i,
+ KeyEvent.KEYCODE_A + i));
+ }
+
+ for (int i = 0; i < 10; i++) {
+ sb.setCharAt(0, (char) ('0' + i));
+ allKeys.add(new MetaKeyBase(sb.toString(), '0' + i,
+ KeyEvent.KEYCODE_0 + i));
+ }
+
+ for (int i = 0; i < 12; i++) {
+ sb.setLength(0);
+ sb.append('F');
+ if (i < 9)
+ sb.append(' ');
+ sb.append(Integer.toString(i + 1));
+ allKeys.add(new MetaKeyBase(sb.toString(), 0xFFBE + i));
+ }
+
+ java.util.Collections.sort(allKeys);
+ allKeysNames = new String[allKeys.size()];
+ keysByKeyCode = new HashMap();
+ keysByMouseButton = new HashMap();
+ keysByKeySym = new HashMap();
+ for (int i = 0; i < allKeysNames.length; ++i) {
+ MetaKeyBase b = allKeys.get(i);
+ allKeysNames[i] = b.name;
+ if (b.isKeyEvent)
+ keysByKeyCode.put(b.keyEvent, b);
+ if (b.isMouse)
+ keysByMouseButton.put(b.mouseButtons, b);
+ else
+ keysByKeySym.put(b.keySym, b);
+ }
+ keyCtrlAltDel = new MetaKeyBean(0, VncCanvas.CTRL_MASK
+ | VncCanvas.ALT_MASK, keysByKeyCode.get(KeyEvent.KEYCODE_DEL));
+ keyCtrlC = new MetaKeyBean(0, VncCanvas.CTRL_MASK,
+ keysByKeyCode.get(KeyEvent.KEYCODE_C));
+ keyArrowLeft = new MetaKeyBean(0, 0, keysByKeySym.get(0xFF51));
+ keyArrowUp = new MetaKeyBean(0, 0, keysByKeySym.get(0xFF52));
+ keyArrowRight = new MetaKeyBean(0, 0, keysByKeySym.get(0xFF53));
+ keyArrowDown = new MetaKeyBean(0, 0, keysByKeySym.get(0xFF54));
+ }
+
+ private boolean _regenDesc;
+ private boolean mouseClick;
+
+ MetaKeyBean() {
+ }
+
+ MetaKeyBean(MetaKeyBean toCopy) {
+ _regenDesc = true;
+ if (toCopy.isMouseClick())
+ setMouseButtons(toCopy.getMouseButtons());
+ else
+ setKeySym(toCopy.getKeySym());
+ setMetaListId(toCopy.getMetaListId());
+ setMetaFlags(toCopy.getMetaFlags());
+ }
+
+ private void setMetaListId(Object metaListId) {
+
+
+ }
+
+ private Object getMetaListId() {
+
+ return null;
+ }
+
+ MetaKeyBean(long listId, int metaFlags, MetaKeyBase base) {
+
+ setKeyBase(base);
+ setMetaFlags(metaFlags);
+ _regenDesc = true;
+ }
+
+ public String getKeyDesc() {
+ if (_regenDesc) {
+ synchronized (this) {
+ if (_regenDesc) {
+ StringBuilder sb = new StringBuilder();
+ int meta = getMetaFlags();
+ if (0 != (meta & VncCanvas.SHIFT_MASK)) {
+ sb.append("Shift");
+ }
+ if (0 != (meta & VncCanvas.CTRL_MASK)) {
+ if (sb.length() > 0)
+ sb.append('-');
+ sb.append("Ctrl");
+ }
+ if (0 != (meta & VncCanvas.ALT_MASK)) {
+ if (sb.length() > 0)
+ sb.append('-');
+ sb.append("Alt");
+ }
+ if (sb.length() > 0)
+ sb.append(' ');
+ MetaKeyBase base;
+ if (isMouseClick())
+ base = keysByMouseButton.get(getMouseButtons());
+ else
+ base = keysByKeySym.get(getKeySym());
+ sb.append(base.name);
+ setKeyDesc(sb.toString());
+ }
+ }
+ }
+ return getKeyDesc();
+ }
+
+ public void setKeyDesc(String arg_keyDesc) {
+ _regenDesc = false;
+ }
+
+ public void setKeySym(int arg_keySym) {
+ if (arg_keySym != getKeySym() || isMouseClick()) {
+ setMouseClick(false);
+ _regenDesc = true;
+
+ }
+ this.keySym = arg_keySym ;
+ }
+
+ int getKeySym() {
+
+ return keySym;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractMetaKeyBean#setMetaFlags(int)
+ */
+ public void setMetaFlags(int arg_metaFlags) {
+ if (arg_metaFlags != getMetaFlags()) {
+ _regenDesc = true;
+ }
+ this.metaFlags = arg_metaFlags;
+ }
+
+ int getMetaFlags() {
+
+ return this.metaFlags;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractMetaKeyBean#setMouseButtons(int)
+ */
+ public void setMouseButtons(int arg_mouseButtons) {
+ if (arg_mouseButtons != getMouseButtons() || !isMouseClick()) {
+ setMouseClick(true);
+ _regenDesc = true;
+ }
+ }
+
+ private void setMouseClick(boolean b) {
+
+ this.mouseClick = b;
+ }
+
+ boolean isMouseClick() {
+
+ return false;
+ }
+
+ int getMouseButtons() {
+
+ return 0;
+ }
+
+ void setKeyBase(MetaKeyBase base) {
+ if (base.isMouse) {
+ setMouseButtons(base.mouseButtons);
+ } else {
+ setKeySym(base.keySym);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof MetaKeyBean) {
+ return getKeyDesc().equals(((MetaKeyBean) o).getKeyDesc());
+ }
+ return false;
+ }
+
+}
diff --git a/app/src/main/java/android/androidVNC/MetaList.java b/app/src/main/java/android/androidVNC/MetaList.java
new file mode 100644
index 0000000..f4b3d89
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/MetaList.java
@@ -0,0 +1,77 @@
+// This class was generated from android.androidVNC.IMetaList by a tool
+// Do not edit this file directly! PLX THX
+package android.androidVNC;
+
+public class MetaList {
+
+ public static final String GEN_TABLE_NAME = "META_LIST";
+ public static final int GEN_COUNT = 2;
+
+ // Field constants
+ public static final String GEN_FIELD__ID = "_id";
+ public static final int GEN_ID__ID = 0;
+ public static final String GEN_FIELD_NAME = "NAME";
+ public static final int GEN_ID_NAME = 1;
+
+ // SQL Command for creating the table
+ public static String GEN_CREATE = "CREATE TABLE META_LIST (" +
+ "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
+ "NAME TEXT" +
+ ")";
+
+ // Members corresponding to defined fields
+ private long gen__Id;
+ private java.lang.String gen_name;
+
+
+ public String Gen_tableName() { return GEN_TABLE_NAME; }
+
+ // Field accessors
+ public long get_Id() { return gen__Id; }
+ public void set_Id(long arg__Id) { gen__Id = arg__Id; }
+ public java.lang.String getName() { return gen_name; }
+ public void setName(java.lang.String arg_name) { gen_name = arg_name; }
+
+ public android.content.ContentValues Gen_getValues() {
+ android.content.ContentValues values=new android.content.ContentValues();
+ values.put(GEN_FIELD__ID,Long.toString(this.gen__Id));
+ values.put(GEN_FIELD_NAME,this.gen_name);
+ return values;
+ }
+
+ /**
+ * Return an array that gives the column index in the cursor for each field defined
+ * @param cursor Database cursor over some columns, possibly including this table
+ * @return array of column indices; -1 if the column with that id is not in cursor
+ */
+ public int[] Gen_columnIndices(android.database.Cursor cursor) {
+ int[] result=new int[GEN_COUNT];
+ result[0] = cursor.getColumnIndex(GEN_FIELD__ID);
+ // Make compatible with database generated by older version of plugin with uppercase column name
+ if (result[0] == -1) {
+ result[0] = cursor.getColumnIndex("_ID");
+ }
+ result[1] = cursor.getColumnIndex(GEN_FIELD_NAME);
+ return result;
+ }
+
+ /**
+ * Populate one instance from a cursor
+ */
+ public void Gen_populate(android.database.Cursor cursor,int[] columnIndices) {
+ if ( columnIndices[GEN_ID__ID] >= 0 && ! cursor.isNull(columnIndices[GEN_ID__ID])) {
+ gen__Id = cursor.getLong(columnIndices[GEN_ID__ID]);
+ }
+ if ( columnIndices[GEN_ID_NAME] >= 0 && ! cursor.isNull(columnIndices[GEN_ID_NAME])) {
+ gen_name = cursor.getString(columnIndices[GEN_ID_NAME]);
+ }
+ }
+
+ /**
+ * Populate one instance from a ContentValues
+ */
+ public void Gen_populate(android.content.ContentValues values) {
+ gen__Id = values.getAsLong(GEN_FIELD__ID);
+ gen_name = values.getAsString(GEN_FIELD_NAME);
+ }
+}
diff --git a/app/src/main/java/android/androidVNC/MouseMover.java b/app/src/main/java/android/androidVNC/MouseMover.java
new file mode 100644
index 0000000..618ab09
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/MouseMover.java
@@ -0,0 +1,54 @@
+/**
+ * Copyright 2010 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import android.os.Handler;
+import android.os.SystemClock;
+import android.view.MotionEvent;
+
+/**
+ * Specialization of panner that moves the mouse instead of panning the screen
+ *
+ * @author Michael A. MacDonald
+ *
+ */
+class MouseMover extends Panner {
+
+ public MouseMover(VncCanvasActivity act, Handler hand) {
+ super(act, hand);
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Runnable#run()
+ */
+ @Override
+ public void run() {
+ long interval = SystemClock.uptimeMillis() - lastSent;
+ lastSent += interval;
+ double scale = (double)interval / 50.0;
+ VncCanvas canvas = activity.vncCanvas;
+ //Log.v(TAG, String.format("panning %f %d %d", scale, (int)((double)velocity.x * scale), (int)((double)velocity.y * scale)));
+ if ( canvas.processPointerEvent((int)(canvas.mouseX + ((double)velocity.x * scale)),
+ (int)(canvas.mouseY + ((double)velocity.y * scale)),
+ MotionEvent.ACTION_MOVE, 0,
+ false, false, false, false))
+ {
+ if (updater.updateVelocity(velocity, interval))
+ {
+ handler.postDelayed(this, 50);
+ }
+ else
+ {
+ //Log.v(TAG, "Updater requests stop");
+ stop();
+ }
+ }
+ else
+ {
+ //Log.v(TAG, "Panning failed");
+ stop();
+ }
+ }
+
+}
diff --git a/app/src/main/java/android/androidVNC/OneToOneScaling.java b/app/src/main/java/android/androidVNC/OneToOneScaling.java
new file mode 100644
index 0000000..5c6f0b8
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/OneToOneScaling.java
@@ -0,0 +1,57 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import android.widget.ImageView.ScaleType;
+
+import com.vectras.vm.R;
+
+/**
+ * @author Michael A. MacDonald
+ */
+class OneToOneScaling extends AbstractScaling {
+
+ /**
+ * @param id
+ * @param scaleType
+ */
+ public OneToOneScaling() {
+ super(R.id.itemOneToOne,ScaleType.CENTER);
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractScaling#getDefaultHandlerId()
+ */
+ @Override
+ int getDefaultHandlerId() {
+ return R.id.itemInputTouchPanTrackballMouse;
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractScaling#isAbleToPan()
+ */
+ @Override
+ boolean isAbleToPan() {
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractScaling#isValidInputMode(int)
+ */
+ @Override
+ boolean isValidInputMode(int mode) {
+ return mode != R.id.itemInputFitToScreen;
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractScaling#setScaleTypeForActivity(android.androidVNC.VncCanvasActivity)
+ */
+ @Override
+ public void setScaleTypeForActivity(VncCanvasActivity activity) {
+ super.setScaleTypeForActivity(activity);
+ activity.vncCanvas.scrollToAbsolute();
+ activity.vncCanvas.pan(0,0);
+ }
+
+}
diff --git a/app/src/main/java/android/androidVNC/Panner.java b/app/src/main/java/android/androidVNC/Panner.java
new file mode 100644
index 0000000..4298f1a
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/Panner.java
@@ -0,0 +1,104 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import android.graphics.PointF;
+import android.os.Handler;
+import android.os.SystemClock;
+
+/**
+ * Handles panning the screen continuously over a period of time
+ * @author Michael A. MacDonald
+ */
+class Panner implements Runnable {
+
+ VncCanvasActivity activity;
+ Handler handler;
+ PointF velocity;
+ long lastSent;
+ VelocityUpdater updater;
+
+ private static final String TAG = "PANNER";
+
+ /**
+ * Specify how the panning velocity changes over time
+ * @author Michael A. MacDonald
+ */
+ interface VelocityUpdater {
+ /**
+ * Called approximately every 50 ms to update the velocity of panning
+ * @param p X and Y components to update
+ * @param interval Milliseconds since last update
+ * @return False if the panning should stop immediately; true otherwise
+ */
+ boolean updateVelocity(PointF p, long interval);
+ }
+
+ static class DefaultUpdater implements VelocityUpdater {
+
+ static DefaultUpdater instance = new DefaultUpdater();
+
+ /**
+ * Don't change velocity
+ */
+ @Override
+ public boolean updateVelocity(PointF p, long interval) {
+ return true;
+ }
+
+ }
+
+ Panner(VncCanvasActivity act, Handler hand) {
+ activity = act;
+ velocity = new PointF();
+ handler = hand;
+ }
+
+ void stop()
+ {
+ handler.removeCallbacks(this);
+ }
+
+ void start(float xv, float yv, VelocityUpdater update)
+ {
+ if (update == null)
+ update = DefaultUpdater.instance;
+ updater = update;
+ velocity.x = xv;
+ velocity.y = yv;
+ //Log.v(TAG, String.format("pan start %f %f", velocity.x, velocity.y));
+ lastSent = SystemClock.uptimeMillis();
+
+ handler.postDelayed(this, 50);
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Runnable#run()
+ */
+ @Override
+ public void run() {
+ long interval = SystemClock.uptimeMillis() - lastSent;
+ lastSent += interval;
+ double scale = (double)interval / 50.0;
+ //Log.v(TAG, String.format("panning %f %d %d", scale, (int)((double)velocity.x * scale), (int)((double)velocity.y * scale)));
+ if ( activity.vncCanvas.pan((int)((double)velocity.x * scale), (int)((double)velocity.y * scale)))
+ {
+ if (updater.updateVelocity(velocity, interval))
+ {
+ handler.postDelayed(this, 50);
+ }
+ else
+ {
+ //Log.v(TAG, "Updater requests stop");
+ stop();
+ }
+ }
+ else
+ {
+ //Log.v(TAG, "Panning failed");
+ stop();
+ }
+ }
+
+}
diff --git a/app/src/main/java/android/androidVNC/RepeaterDialog.java b/app/src/main/java/android/androidVNC/RepeaterDialog.java
new file mode 100644
index 0000000..b33e0d5
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/RepeaterDialog.java
@@ -0,0 +1,59 @@
+/**
+ *
+ */
+package android.androidVNC;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.os.Bundle;
+import android.text.Html;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import com.vectras.vm.R;
+
+/**
+ * @author Michael A. MacDonald
+ *
+ */
+class RepeaterDialog extends Dialog {
+ private EditText _repeaterId;
+ androidVNC _configurationDialog;
+
+ RepeaterDialog(androidVNC context) {
+ super(context);
+ setOwnerActivity((Activity)context);
+ _configurationDialog = context;
+ }
+
+ /* (non-Javadoc)
+ * @see android.app.Dialog#onCreate(android.os.Bundle)
+ */
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setTitle(R.string.repeater_dialog_title);
+
+ setContentView(R.layout.repeater_dialog);
+ _repeaterId=(EditText)findViewById(R.id.textRepeaterInfo);
+ ((TextView)findViewById(R.id.textRepeaterCaption)).setText(Html.fromHtml(getContext().getString(R.string.repeater_caption)));
+ ((Button)findViewById(R.id.buttonSaveRepeater)).setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ _configurationDialog.updateRepeaterInfo(true, _repeaterId.getText().toString());
+ dismiss();
+ }
+ });
+ ((Button)findViewById(R.id.buttonClearRepeater)).setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ _configurationDialog.updateRepeaterInfo(false, "");
+ dismiss();
+ }
+ });
+ }
+}
diff --git a/app/src/main/java/android/androidVNC/RfbProto.java b/app/src/main/java/android/androidVNC/RfbProto.java
new file mode 100644
index 0000000..c9e9b90
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/RfbProto.java
@@ -0,0 +1,1416 @@
+//
+// Copyright (C) 2001-2004 HorizonLive.com, Inc. All Rights Reserved.
+// Copyright (C) 2001-2006 Constantin Kaplinsky. All Rights Reserved.
+// Copyright (C) 2000 Tridia Corporation. All Rights Reserved.
+// Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
+//
+// This is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This software is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this software; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+// USA.
+//
+
+//
+// RfbProto.java
+//
+
+package android.androidVNC;
+
+import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Log;
+
+import com.vectras.qemu.Config;
+
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.Socket;
+
+//- import java.awt.*;
+//- import java.awt.event.*;
+//- import java.util.zip.*;
+
+/**
+ * Access the RFB protocol through a socket.
+ *
+ * This class has no knowledge of the android-specific UI; it sees framebuffer updates
+ * and input events as defined in the RFB protocol.
+ *
+ */
+public class RfbProto {
+
+ final static String TAG = "RfbProto";
+
+ final static String
+ versionMsg_3_3 = "RFB 003.003\n",
+ versionMsg_3_7 = "RFB 003.007\n",
+ versionMsg_3_8 = "RFB 003.008\n";
+
+ // Vendor signatures: standard VNC/RealVNC, TridiaVNC, and TightVNC
+ final static String
+ StandardVendor = "STDV",
+ TridiaVncVendor = "TRDV",
+ TightVncVendor = "TGHT";
+
+ // Security types
+ final static int
+ SecTypeInvalid = 0,
+ SecTypeNone = 1,
+ SecTypeVncAuth = 2,
+ SecTypeTight = 16,
+ SecTypeUltra34 = 0xfffffffa;
+
+ // Supported tunneling types
+ final static int
+ NoTunneling = 0;
+ final static String
+ SigNoTunneling = "NOTUNNEL";
+
+ // Supported authentication types
+ final static int
+ AuthNone = 1,
+ AuthVNC = 2,
+ AuthUnixLogin = 129,
+ AuthUltra = 17;
+ final static String
+ SigAuthNone = "NOAUTH__",
+ SigAuthVNC = "VNCAUTH_",
+ SigAuthUnixLogin = "ULGNAUTH";
+
+ // VNC authentication results
+ final static int
+ VncAuthOK = 0,
+ VncAuthFailed = 1,
+ VncAuthTooMany = 2;
+
+ // Server-to-client messages
+ final static int
+ FramebufferUpdate = 0,
+ SetColourMapEntries = 1,
+ Bell = 2,
+ ServerCutText = 3,
+ TextChat = 11;
+
+ // Client-to-server messages
+ final static int
+ SetPixelFormat = 0,
+ FixColourMapEntries = 1,
+ SetEncodings = 2,
+ FramebufferUpdateRequest = 3,
+ KeyboardEvent = 4,
+ PointerEvent = 5,
+ ClientCutText = 6;
+
+ // Supported encodings and pseudo-encodings
+ final static int
+ EncodingRaw = 0,
+ EncodingCopyRect = 1,
+ EncodingRRE = 2,
+ EncodingCoRRE = 4,
+ EncodingHextile = 5,
+ EncodingZlib = 6,
+ EncodingTight = 7,
+ EncodingZRLE = 16,
+ EncodingCompressLevel0 = 0xFFFFFF00,
+ EncodingQualityLevel0 = 0xFFFFFFE0,
+ EncodingXCursor = 0xFFFFFF10,
+ EncodingRichCursor = 0xFFFFFF11,
+ EncodingPointerPos = 0xFFFFFF18,
+ EncodingLastRect = 0xFFFFFF20,
+ EncodingNewFBSize = 0xFFFFFF21;
+ final static String
+ SigEncodingRaw = "RAW_____",
+ SigEncodingCopyRect = "COPYRECT",
+ SigEncodingRRE = "RRE_____",
+ SigEncodingCoRRE = "CORRE___",
+ SigEncodingHextile = "HEXTILE_",
+ SigEncodingZlib = "ZLIB____",
+ SigEncodingTight = "TIGHT___",
+ SigEncodingZRLE = "ZRLE____",
+ SigEncodingCompressLevel0 = "COMPRLVL",
+ SigEncodingQualityLevel0 = "JPEGQLVL",
+ SigEncodingXCursor = "X11CURSR",
+ SigEncodingRichCursor = "RCHCURSR",
+ SigEncodingPointerPos = "POINTPOS",
+ SigEncodingLastRect = "LASTRECT",
+ SigEncodingNewFBSize = "NEWFBSIZ";
+
+ final static int MaxNormalEncoding = 255;
+
+ // Contstants used in the Hextile decoder
+ final static int
+ HextileRaw = 1,
+ HextileBackgroundSpecified = 2,
+ HextileForegroundSpecified = 4,
+ HextileAnySubrects = 8,
+ HextileSubrectsColoured = 16;
+
+ // Contstants used in the Tight decoder
+ final static int TightMinToCompress = 12;
+ final static int
+ TightExplicitFilter = 0x04,
+ TightFill = 0x08,
+ TightJpeg = 0x09,
+ TightMaxSubencoding = 0x09,
+ TightFilterCopy = 0x00,
+ TightFilterPalette = 0x01,
+ TightFilterGradient = 0x02;
+
+ // Constants used for UltraVNC chat extension
+ final static int
+ CHAT_OPEN = -1,
+ CHAT_CLOSE = -2,
+ CHAT_FINISHED = -3;
+
+ String host;
+ int port;
+ Socket sock;
+ DataInputStream is;
+ LimboOutputStream os;
+ private OutputStream sos;
+
+ //Limbo: need to send the network operations of the Main UI
+ // but also use a mechanism like IntentService to keep
+ // the network message queue intact
+ class LimboOutputStream {
+
+ OutputStream os;
+ HandlerThread rfbQueueThread = null;
+ Handler handler = null;
+
+ public LimboOutputStream(OutputStream sos) {
+ os = sos;
+
+ rfbQueueThread = new HandlerThread("RfbQueue");
+ rfbQueueThread.start();
+ handler = new Handler(rfbQueueThread.getLooper());
+ }
+
+ public void write(final int data) throws IOException {
+ final int data0 = data;
+
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ sos.write(data0);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ }
+
+ public void write(final byte[] bytes) throws IOException {
+ final byte [] buffer0 = java.util.Arrays.copyOf(bytes, bytes.length);
+
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ sos.write(buffer0);
+ } catch (IOException e) {
+ Log.w(TAG, "Error while sending VNC data");
+ if(Config.debug)
+ e.printStackTrace();
+ }
+ }
+ });
+ }
+
+ public void write(final byte[] buffer, final int offset, final int count) throws IOException {
+ final byte [] buffer0 = java.util.Arrays.copyOf(buffer, count);
+ final int offset0 = offset;
+ final int count0 = count;
+
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ sos.write(buffer0, offset0, count0);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ }
+
+ public void close() {
+ rfbQueueThread.interrupt();
+ handler.removeCallbacks(null);
+ handler.getLooper().quit();
+ }
+
+ }
+
+ DH dh;
+ long dh_resp;
+
+ //- SessionRecorder rec;
+ boolean inNormalProtocol = false;
+ //- VncViewer viewer;
+
+ // Java on UNIX does not call keyPressed() on some keys, for example
+ // swedish keys To prevent our workaround to produce duplicate
+ // keypresses on JVMs that actually works, keep track of if
+ // keyPressed() for a "broken" key was called or not.
+ boolean brokenKeyPressed = false;
+
+ // This will be set to true on the first framebuffer update
+ // containing Zlib-, ZRLE- or Tight-encoded data.
+ boolean wereZlibUpdates = false;
+
+ // This will be set to false if the startSession() was called after
+ // we have received at least one Zlib-, ZRLE- or Tight-encoded
+ // framebuffer update.
+ boolean recordFromBeginning = true;
+
+ // This fields are needed to show warnings about inefficiently saved
+ // sessions only once per each saved session file.
+ boolean zlibWarningShown;
+ boolean tightWarningShown;
+
+ // Before starting to record each saved session, we set this field
+ // to 0, and increment on each framebuffer update. We don't flush
+ // the SessionRecorder data into the file before the second update.
+ // This allows us to write initial framebuffer update with zero
+ // timestamp, to let the player show initial desktop before
+ // playback.
+ int numUpdatesInSession;
+
+ // Measuring network throughput.
+ boolean timing;
+ long timeWaitedIn100us;
+ long timedKbits;
+
+ // Protocol version and TightVNC-specific protocol options.
+ int serverMajor, serverMinor;
+ int clientMajor, clientMinor;
+ boolean protocolTightVNC;
+ CapsContainer tunnelCaps, authCaps;
+ CapsContainer serverMsgCaps, clientMsgCaps;
+ CapsContainer encodingCaps;
+
+ // If true, informs that the RFB socket was closed.
+ private boolean closed;
+
+
+ //
+ // Constructor. Make TCP connection to RFB server.
+ //
+
+ LocalSocket localSocket = null;
+ public static boolean allow_external = false;
+
+ //-RfbProto(String h, int p, VncViewer v) throws IOException {
+ RfbProto(String h, int p) throws IOException{
+ //- viewer = v;
+ host = h;
+ port = p;
+
+ /*
+ if (viewer.socketFactory == null) {
+ sock = new Socket(host, port);
+ } else {
+ try {
+ Class factoryClass = Class.forName(viewer.socketFactory);
+ SocketFactory factory = (SocketFactory)factoryClass.newInstance();
+ //- if (viewer.inAnApplet)
+ //- sock = factory.createSocket(host, port, viewer);
+ //- else
+ sock = factory.createSocket(host, port, viewer.mainArgs);
+ }
+ catch(Exception e) {
+ e.printStackTrace();
+ throw new IOException(e.getMessage());
+ }
+ } */
+ //+
+
+ if(!allow_external) {
+ localSocket = new LocalSocket();
+ String localVNCSocketPath = Config.getLocalVNCSocketPath();
+ LocalSocketAddress localSocketAddr = new LocalSocketAddress(localVNCSocketPath, LocalSocketAddress.Namespace.FILESYSTEM);
+ localSocket.connect(localSocketAddr);
+ is = new DataInputStream(new BufferedInputStream(localSocket.getInputStream(),
+ 32768));
+ sos = localSocket.getOutputStream();
+
+ } else {
+ sock = new Socket(host, port);
+ is = new DataInputStream(new BufferedInputStream(sock.getInputStream(),
+ 16384));
+ sos = sock.getOutputStream();
+ }
+
+ os = new LimboOutputStream(sos);
+
+ timing = false;
+ timeWaitedIn100us = 5;
+ timedKbits = 0;
+ }
+
+
+ synchronized void close() {
+
+ try {
+ os.close();
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+
+ try {
+ sock.close();
+ closed = true;
+ //- System.out.println("RFB socket closed");
+ Log.v(TAG, "RFB socket closed");
+ /*-
+ if (rec != null) {
+ rec.close();
+ rec = null;
+
+ } */
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+
+ }
+
+ synchronized boolean closed() {
+ return closed;
+ }
+
+ //
+ // Read server's protocol version message
+ //
+
+ void readVersionMsg() throws Exception {
+
+ byte[] b = new byte[12];
+
+ readFully(b);
+
+ if ((b[0] != 'R') || (b[1] != 'F') || (b[2] != 'B') || (b[3] != ' ')
+ || (b[4] < '0') || (b[4] > '9') || (b[5] < '0') || (b[5] > '9')
+ || (b[6] < '0') || (b[6] > '9') || (b[7] != '.')
+ || (b[8] < '0') || (b[8] > '9') || (b[9] < '0') || (b[9] > '9')
+ || (b[10] < '0') || (b[10] > '9') || (b[11] != '\n'))
+ {
+ Log.i(TAG,new String(b));
+ throw new Exception("Host " + host + " port " + port +
+ " is not an RFB server");
+ }
+
+ serverMajor = (b[4] - '0') * 100 + (b[5] - '0') * 10 + (b[6] - '0');
+ serverMinor = (b[8] - '0') * 100 + (b[9] - '0') * 10 + (b[10] - '0');
+
+ if (serverMajor < 3) {
+ throw new Exception("RFB server does not support protocol version 3");
+ }
+ }
+
+
+ //
+ // Write our protocol version message
+ //
+
+ synchronized void writeVersionMsg() throws IOException {
+ clientMajor = 3;
+ if (serverMajor > 3 || serverMinor >= 8) {
+ clientMinor = 8;
+ os.write(versionMsg_3_8.getBytes());
+ } else if (serverMinor >= 7) {
+ clientMinor = 7;
+ os.write(versionMsg_3_7.getBytes());
+ } else {
+ clientMinor = 3;
+ os.write(versionMsg_3_3.getBytes());
+ }
+ protocolTightVNC = false;
+ }
+
+
+ //
+ // Negotiate the authentication scheme.
+ //
+
+ int negotiateSecurity(int bitPref) throws Exception {
+ return (clientMinor >= 7) ?
+ selectSecurityType(bitPref) : readSecurityType(bitPref);
+ }
+
+ //
+ // Read security type from the server (protocol version 3.3).
+ //
+
+ int readSecurityType(int bitPref) throws Exception {
+ int secType = is.readInt();
+
+ switch (secType) {
+ case SecTypeInvalid:
+ readConnFailedReason();
+ return SecTypeInvalid; // should never be executed
+ case SecTypeNone:
+ case SecTypeVncAuth:
+ return secType;
+ case SecTypeUltra34:
+ if((bitPref & 1) == 1)
+ return secType;
+ throw new Exception("Username required.");
+ default:
+ throw new Exception("Unknown security type from RFB server: " + secType);
+ }
+ }
+
+ //
+ // Select security type from the server's list (protocol versions 3.7/3.8).
+ //
+
+ int selectSecurityType(int bitPref) throws Exception {
+ int secType = SecTypeInvalid;
+
+ // Read the list of security types.
+ int nSecTypes = is.readUnsignedByte();
+ if (nSecTypes == 0) {
+ readConnFailedReason();
+ return SecTypeInvalid; // should never be executed
+ }
+ byte[] secTypes = new byte[nSecTypes];
+ readFully(secTypes);
+
+ // Find out if the server supports TightVNC protocol extensions
+ for (int i = 0; i < nSecTypes; i++) {
+ if (secTypes[i] == SecTypeTight) {
+ protocolTightVNC = true;
+ os.write(SecTypeTight);
+ return SecTypeTight;
+ }
+ }
+
+ // Find first supported security type.
+ for (int i = 0; i < nSecTypes; i++) {
+ if (secTypes[i] == SecTypeNone || secTypes[i] == SecTypeVncAuth) {
+ secType = secTypes[i];
+ break;
+ }
+ }
+
+ if (secType == SecTypeInvalid) {
+ throw new Exception("Server did not offer supported security type");
+ } else {
+ os.write(secType);
+ }
+
+ return secType;
+ }
+
+ //
+ // Perform "no authentication".
+ //
+
+ void authenticateNone() throws Exception {
+ if (clientMinor >= 8)
+ readSecurityResult("No authentication");
+ }
+
+ //
+ // Perform standard VNC Authentication.
+ //
+
+ void authenticateVNC(String pw) throws Exception {
+ byte[] challenge = new byte[16];
+ readFully(challenge);
+
+ if (pw.length() > 8)
+ pw = pw.substring(0, 8); // Truncate to 8 chars
+
+ // Truncate password on the first zero byte.
+ int firstZero = pw.indexOf(0);
+ if (firstZero != -1)
+ pw = pw.substring(0, firstZero);
+
+ byte[] key = {0, 0, 0, 0, 0, 0, 0, 0};
+ System.arraycopy(pw.getBytes(), 0, key, 0, pw.length());
+
+ DesCipher des = new DesCipher(key);
+
+ des.encrypt(challenge, 0, challenge, 0);
+ des.encrypt(challenge, 8, challenge, 8);
+
+ os.write(challenge);
+
+ readSecurityResult("VNC authentication");
+ }
+
+ //
+ // Read security result.
+ // Throws an exception on authentication failure.
+ //
+
+ void readSecurityResult(String authType) throws Exception {
+ int securityResult = is.readInt();
+
+ switch (securityResult) {
+ case VncAuthOK:
+ System.out.println(authType + ": success");
+ break;
+ case VncAuthFailed:
+ if (clientMinor >= 8)
+ readConnFailedReason();
+ throw new Exception(authType + ": failed");
+ case VncAuthTooMany:
+ throw new Exception(authType + ": failed, too many tries");
+ default:
+ throw new Exception(authType + ": unknown result " + securityResult);
+ }
+ }
+
+ //
+ // Read the string describing the reason for a connection failure,
+ // and throw an exception.
+ //
+
+ void readConnFailedReason() throws Exception {
+ int reasonLen = is.readInt();
+ byte[] reason = new byte[reasonLen];
+ readFully(reason);
+ String reasonString = new String(reason);
+ Log.v(TAG, reasonString);
+ throw new Exception(reasonString);
+ }
+
+ void prepareDH() throws Exception
+ {
+ long gen = is.readLong();
+ long mod = is.readLong();
+ dh_resp = is.readLong();
+
+ dh = new DH(gen,mod);
+ long pub = dh.createInterKey();
+
+ os.write(DH.longToBytes(pub));
+ }
+
+ void authenticateDH(String us,String pw) throws Exception
+ {
+ long key = dh.createEncryptionKey(dh_resp);
+
+ DesCipher des = new DesCipher(DH.longToBytes(key));
+
+ byte user[] = new byte[256];
+ byte passwd[] = new byte[64];
+ int i;
+ System.arraycopy(us.getBytes(),0,user,0,us.length());
+ if(us.length() < 256)
+ {
+ for(i=us.length(); i<256; i++)
+ {
+ user[i]=0;
+ }
+ }
+ System.arraycopy(pw.getBytes(),0,passwd,0,pw.length());
+ if(pw.length() < 64)
+ {
+ for(i=pw.length(); i<64; i++)
+ {
+ passwd[i]=0;
+ }
+ }
+
+ des.encryptText(user,user,DH.longToBytes(key));
+ des.encryptText(passwd,passwd,DH.longToBytes(key));
+
+ os.write(user);
+ os.write(passwd);
+
+ readSecurityResult("VNC authentication");
+ }
+ //
+ // Initialize capability lists (TightVNC protocol extensions).
+ //
+
+ void initCapabilities() {
+ tunnelCaps = new CapsContainer();
+ authCaps = new CapsContainer();
+ serverMsgCaps = new CapsContainer();
+ clientMsgCaps = new CapsContainer();
+ encodingCaps = new CapsContainer();
+
+ // Supported authentication methods
+ authCaps.add(AuthNone, StandardVendor, SigAuthNone,
+ "No authentication");
+ authCaps.add(AuthVNC, StandardVendor, SigAuthVNC,
+ "Standard VNC password authentication");
+
+ // Supported encoding types
+ encodingCaps.add(EncodingCopyRect, StandardVendor,
+ SigEncodingCopyRect, "Standard CopyRect encoding");
+ encodingCaps.add(EncodingRRE, StandardVendor,
+ SigEncodingRRE, "Standard RRE encoding");
+ encodingCaps.add(EncodingCoRRE, StandardVendor,
+ SigEncodingCoRRE, "Standard CoRRE encoding");
+ encodingCaps.add(EncodingHextile, StandardVendor,
+ SigEncodingHextile, "Standard Hextile encoding");
+ encodingCaps.add(EncodingZRLE, StandardVendor,
+ SigEncodingZRLE, "Standard ZRLE encoding");
+ encodingCaps.add(EncodingZlib, TridiaVncVendor,
+ SigEncodingZlib, "Zlib encoding");
+ encodingCaps.add(EncodingTight, TightVncVendor,
+ SigEncodingTight, "Tight encoding");
+
+ // Supported pseudo-encoding types
+ encodingCaps.add(EncodingCompressLevel0, TightVncVendor,
+ SigEncodingCompressLevel0, "Compression level");
+ encodingCaps.add(EncodingQualityLevel0, TightVncVendor,
+ SigEncodingQualityLevel0, "JPEG quality level");
+ encodingCaps.add(EncodingXCursor, TightVncVendor,
+ SigEncodingXCursor, "X-style cursor shape update");
+ encodingCaps.add(EncodingRichCursor, TightVncVendor,
+ SigEncodingRichCursor, "Rich-color cursor shape update");
+ encodingCaps.add(EncodingPointerPos, TightVncVendor,
+ SigEncodingPointerPos, "Pointer position update");
+ encodingCaps.add(EncodingLastRect, TightVncVendor,
+ SigEncodingLastRect, "LastRect protocol extension");
+ encodingCaps.add(EncodingNewFBSize, TightVncVendor,
+ SigEncodingNewFBSize, "Framebuffer size change");
+ }
+
+ //
+ // Setup tunneling (TightVNC protocol extensions)
+ //
+
+ void setupTunneling() throws IOException {
+ int nTunnelTypes = is.readInt();
+ if (nTunnelTypes != 0) {
+ readCapabilityList(tunnelCaps, nTunnelTypes);
+
+ // We don't support tunneling yet.
+ writeInt(NoTunneling);
+ }
+ }
+
+ //
+ // Negotiate authentication scheme (TightVNC protocol extensions)
+ //
+
+ int negotiateAuthenticationTight() throws Exception {
+ int nAuthTypes = is.readInt();
+ if (nAuthTypes == 0)
+ return AuthNone;
+
+ readCapabilityList(authCaps, nAuthTypes);
+ for (int i = 0; i < authCaps.numEnabled(); i++) {
+ int authType = authCaps.getByOrder(i);
+ if (authType == AuthNone || authType == AuthVNC) {
+ writeInt(authType);
+ return authType;
+ }
+ }
+ throw new Exception("No suitable authentication scheme found");
+ }
+
+ //
+ // Read a capability list (TightVNC protocol extensions)
+ //
+
+ void readCapabilityList(CapsContainer caps, int count) throws IOException {
+ int code;
+ byte[] vendor = new byte[4];
+ byte[] name = new byte[8];
+ for (int i = 0; i < count; i++) {
+ code = is.readInt();
+ readFully(vendor);
+ readFully(name);
+ caps.enable(new CapabilityInfo(code, vendor, name));
+ }
+ }
+
+ //
+ // Write a 32-bit integer into the output stream.
+ //
+
+ byte[] writeIntBuffer = new byte[4];
+ void writeInt(int value) throws IOException {
+ writeIntBuffer[0] = (byte) ((value >> 24) & 0xff);
+ writeIntBuffer[1] = (byte) ((value >> 16) & 0xff);
+ writeIntBuffer[2] = (byte) ((value >> 8) & 0xff);
+ writeIntBuffer[3] = (byte) (value & 0xff);
+ os.write(writeIntBuffer);
+ }
+
+ //
+ // Write the client initialisation message
+ //
+
+ void writeClientInit() throws IOException {
+ /*- if (viewer.options.shareDesktop) {
+ os.write(1);
+ } else {
+ os.write(0);
+ }
+ viewer.options.disableShareDesktop();
+ */
+ os.write(0);
+ }
+
+
+ //
+ // Read the server initialisation message
+ //
+
+ String desktopName;
+ public int framebufferWidth, framebufferHeight;
+ int bitsPerPixel, depth;
+ boolean bigEndian, trueColour;
+ int redMax, greenMax, blueMax, redShift, greenShift, blueShift;
+
+ void readServerInit() throws IOException {
+ framebufferWidth = is.readUnsignedShort();
+ framebufferHeight = is.readUnsignedShort();
+ bitsPerPixel = is.readUnsignedByte();
+ depth = is.readUnsignedByte();
+ bigEndian = (is.readUnsignedByte() != 0);
+ trueColour = (is.readUnsignedByte() != 0);
+ redMax = is.readUnsignedShort();
+ greenMax = is.readUnsignedShort();
+ blueMax = is.readUnsignedShort();
+ redShift = is.readUnsignedByte();
+ greenShift = is.readUnsignedByte();
+ blueShift = is.readUnsignedByte();
+ byte[] pad = new byte[3];
+ readFully(pad);
+ int nameLength = is.readInt();
+ byte[] name = new byte[nameLength];
+ readFully(name);
+ desktopName = new String(name);
+
+ // Read interaction capabilities (TightVNC protocol extensions)
+ if (protocolTightVNC) {
+ int nServerMessageTypes = is.readUnsignedShort();
+ int nClientMessageTypes = is.readUnsignedShort();
+ int nEncodingTypes = is.readUnsignedShort();
+ is.readUnsignedShort();
+ readCapabilityList(serverMsgCaps, nServerMessageTypes);
+ readCapabilityList(clientMsgCaps, nClientMessageTypes);
+ readCapabilityList(encodingCaps, nEncodingTypes);
+ }
+
+ inNormalProtocol = true;
+ }
+
+
+ //
+ // Create session file and write initial protocol messages into it.
+ //
+ /*-
+ void startSession(String fname) throws IOException {
+ rec = new SessionRecorder(fname);
+ rec.writeHeader();
+ rec.write(versionMsg_3_3.getBytes());
+ rec.writeIntBE(SecTypeNone);
+ rec.writeShortBE(framebufferWidth);
+ rec.writeShortBE(framebufferHeight);
+ byte[] fbsServerInitMsg = {
+ 32, 24, 0, 1, 0,
+ (byte)0xFF, 0, (byte)0xFF, 0, (byte)0xFF,
+ 16, 8, 0, 0, 0, 0
+ };
+ rec.write(fbsServerInitMsg);
+ rec.writeIntBE(desktopName.length());
+ rec.write(desktopName.getBytes());
+ numUpdatesInSession = 0;
+
+ // FIXME: If there were e.g. ZRLE updates only, that should not
+ // affect recording of Zlib and Tight updates. So, actually
+ // we should maintain separate flags for Zlib, ZRLE and
+ // Tight, instead of one ``wereZlibUpdates'' variable.
+ //
+ if (wereZlibUpdates)
+ recordFromBeginning = false;
+
+ zlibWarningShown = false;
+ tightWarningShown = false;
+ }
+
+ //
+ // Close session file.
+ //
+
+ void closeSession() throws IOException {
+ if (rec != null) {
+ rec.close();
+ rec = null;
+ }
+ }
+ */
+
+ //
+ // Set new framebuffer size
+ //
+
+ void setFramebufferSize(int width, int height) {
+ framebufferWidth = width;
+ framebufferHeight = height;
+ }
+
+
+ //
+ // Read the server message type
+ //
+
+ int readServerMessageType() throws IOException {
+ int msgType = is.readUnsignedByte();
+
+ // If the session is being recorded:
+ /*-
+ if (rec != null) {
+ if (msgType == Bell) { // Save Bell messages in session files.
+ rec.writeByte(msgType);
+ if (numUpdatesInSession > 0)
+ rec.flush();
+ }
+ }
+ */
+
+ return msgType;
+ }
+
+
+ //
+ // Read a FramebufferUpdate message
+ //
+
+ int updateNRects;
+
+ void readFramebufferUpdate() throws IOException {
+ is.readByte();
+ updateNRects = is.readUnsignedShort();
+
+ // If the session is being recorded:
+ /*-
+ if (rec != null) {
+ rec.writeByte(FramebufferUpdate);
+ rec.writeByte(0);
+ rec.writeShortBE(updateNRects);
+ }
+ */
+
+ numUpdatesInSession++;
+ }
+
+ // Read a FramebufferUpdate rectangle header
+
+ int updateRectX, updateRectY, updateRectW, updateRectH, updateRectEncoding;
+
+ void readFramebufferUpdateRectHdr() throws Exception {
+ updateRectX = is.readUnsignedShort();
+ updateRectY = is.readUnsignedShort();
+ updateRectW = is.readUnsignedShort();
+ updateRectH = is.readUnsignedShort();
+ updateRectEncoding = is.readInt();
+
+ if (updateRectEncoding == EncodingZlib ||
+ updateRectEncoding == EncodingZRLE ||
+ updateRectEncoding == EncodingTight)
+ wereZlibUpdates = true;
+
+ // If the session is being recorded:
+ /*-
+ if (rec != null) {
+ if (numUpdatesInSession > 1)
+ rec.flush(); // Flush the output on each rectangle.
+ rec.writeShortBE(updateRectX);
+ rec.writeShortBE(updateRectY);
+ rec.writeShortBE(updateRectW);
+ rec.writeShortBE(updateRectH);
+ if (updateRectEncoding == EncodingZlib && !recordFromBeginning) {
+ // Here we cannot write Zlib-encoded rectangles because the
+ // decoder won't be able to reproduce zlib stream state.
+ if (!zlibWarningShown) {
+ System.out.println("Warning: Raw encoding will be used " +
+ "instead of Zlib in recorded session.");
+ zlibWarningShown = true;
+ }
+ rec.writeIntBE(EncodingRaw);
+ } else {
+ rec.writeIntBE(updateRectEncoding);
+ if (updateRectEncoding == EncodingTight && !recordFromBeginning &&
+ !tightWarningShown) {
+ System.out.println("Warning: Re-compressing Tight-encoded " +
+ "updates for session recording.");
+ tightWarningShown = true;
+ }
+ }
+ }
+ */
+
+ if (updateRectEncoding != RfbProto.EncodingPointerPos && ( updateRectEncoding < 0 || updateRectEncoding > MaxNormalEncoding ))
+ return;
+
+ if (updateRectX + updateRectW > framebufferWidth ||
+ updateRectY + updateRectH > framebufferHeight) {
+ throw new Exception("Framebuffer update rectangle too large: " +
+ updateRectW + "x" + updateRectH + " at (" +
+ updateRectX + "," + updateRectY + ")");
+ }
+ }
+
+ // Read CopyRect source X and Y.
+
+ int copyRectSrcX, copyRectSrcY;
+
+ void readCopyRect() throws IOException {
+ copyRectSrcX = is.readUnsignedShort();
+ copyRectSrcY = is.readUnsignedShort();
+
+ // If the session is being recorded:
+ /*-
+ if (rec != null) {
+ rec.writeShortBE(copyRectSrcX);
+ rec.writeShortBE(copyRectSrcY);
+ }
+ */
+ }
+
+
+ //
+ // Read a ServerCutText message
+ //
+
+ String readServerCutText() throws IOException {
+ byte[] pad = new byte[3];
+ readFully(pad);
+ int len = is.readInt();
+ byte[] text = new byte[len];
+ readFully(text);
+ return new String(text);
+ }
+
+
+ //
+ // Read an integer in compact representation (1..3 bytes).
+ // Such format is used as a part of the Tight encoding.
+ // Also, this method records data if session recording is active and
+ // the viewer's recordFromBeginning variable is set to true.
+ //
+
+ int readCompactLen() throws IOException {
+ int[] portion = new int[3];
+ portion[0] = is.readUnsignedByte();
+ int byteCount = 1;
+ int len = portion[0] & 0x7F;
+ if ((portion[0] & 0x80) != 0) {
+ portion[1] = is.readUnsignedByte();
+ byteCount++;
+ len |= (portion[1] & 0x7F) << 7;
+ if ((portion[1] & 0x80) != 0) {
+ portion[2] = is.readUnsignedByte();
+ byteCount++;
+ len |= (portion[2] & 0xFF) << 14;
+ }
+ }
+ /*-
+ if (rec != null && recordFromBeginning)
+ for (int i = 0; i < byteCount; i++)
+ rec.writeByte(portion[i]);
+ */
+ return len;
+ }
+
+
+ //
+ // Write a FramebufferUpdateRequest message
+ //
+
+ byte[] framebufferUpdateRequest = new byte[10];
+ synchronized void writeFramebufferUpdateRequest(int x, int y, int w, int h,
+ boolean incremental)
+ throws IOException
+ {
+ framebufferUpdateRequest[0] = (byte) FramebufferUpdateRequest;
+ framebufferUpdateRequest[1] = (byte) (incremental ? 1 : 0);
+ framebufferUpdateRequest[2] = (byte) ((x >> 8) & 0xff);
+ framebufferUpdateRequest[3] = (byte) (x & 0xff);
+ framebufferUpdateRequest[4] = (byte) ((y >> 8) & 0xff);
+ framebufferUpdateRequest[5] = (byte) (y & 0xff);
+ framebufferUpdateRequest[6] = (byte) ((w >> 8) & 0xff);
+ framebufferUpdateRequest[7] = (byte) (w & 0xff);
+ framebufferUpdateRequest[8] = (byte) ((h >> 8) & 0xff);
+ framebufferUpdateRequest[9] = (byte) (h & 0xff);
+
+ os.write(framebufferUpdateRequest);
+ }
+
+
+ //
+ // Write a SetPixelFormat message
+ //
+
+ synchronized void writeSetPixelFormat(int bitsPerPixel, int depth, boolean bigEndian,
+ boolean trueColour,
+ int redMax, int greenMax, int blueMax,
+ int redShift, int greenShift, int blueShift, boolean fGreyScale) // sf@2005)
+ throws IOException
+ {
+ byte[] b = new byte[20];
+
+ b[0] = (byte) SetPixelFormat;
+ b[4] = (byte) bitsPerPixel;
+ b[5] = (byte) depth;
+ b[6] = (byte) (bigEndian ? 1 : 0);
+ b[7] = (byte) (trueColour ? 1 : 0);
+ b[8] = (byte) ((redMax >> 8) & 0xff);
+ b[9] = (byte) (redMax & 0xff);
+ b[10] = (byte) ((greenMax >> 8) & 0xff);
+ b[11] = (byte) (greenMax & 0xff);
+ b[12] = (byte) ((blueMax >> 8) & 0xff);
+ b[13] = (byte) (blueMax & 0xff);
+ b[14] = (byte) redShift;
+ b[15] = (byte) greenShift;
+ b[16] = (byte) blueShift;
+ b[17] = (byte) (fGreyScale ? 1 : 0); // sf@2005
+
+ os.write(b);
+ }
+
+
+ //
+ // Write a FixColourMapEntries message. The values in the red, green and
+ // blue arrays are from 0 to 65535.
+ //
+
+ synchronized void writeFixColourMapEntries(int firstColour, int nColours,
+ int[] red, int[] green, int[] blue)
+ throws IOException
+ {
+ byte[] b = new byte[6 + nColours * 6];
+
+ b[0] = (byte) FixColourMapEntries;
+ b[2] = (byte) ((firstColour >> 8) & 0xff);
+ b[3] = (byte) (firstColour & 0xff);
+ b[4] = (byte) ((nColours >> 8) & 0xff);
+ b[5] = (byte) (nColours & 0xff);
+
+ for (int i = 0; i < nColours; i++) {
+ b[6 + i * 6] = (byte) ((red[i] >> 8) & 0xff);
+ b[6 + i * 6 + 1] = (byte) (red[i] & 0xff);
+ b[6 + i * 6 + 2] = (byte) ((green[i] >> 8) & 0xff);
+ b[6 + i * 6 + 3] = (byte) (green[i] & 0xff);
+ b[6 + i * 6 + 4] = (byte) ((blue[i] >> 8) & 0xff);
+ b[6 + i * 6 + 5] = (byte) (blue[i] & 0xff);
+ }
+
+ os.write(b);
+ }
+
+
+ //
+ // Write a SetEncodings message
+ //
+
+ synchronized void writeSetEncodings(int[] encs, int len) throws IOException {
+ byte[] b = new byte[4 + 4 * len];
+
+ b[0] = (byte) SetEncodings;
+ b[2] = (byte) ((len >> 8) & 0xff);
+ b[3] = (byte) (len & 0xff);
+
+ for (int i = 0; i < len; i++) {
+ b[4 + 4 * i] = (byte) ((encs[i] >> 24) & 0xff);
+ b[5 + 4 * i] = (byte) ((encs[i] >> 16) & 0xff);
+ b[6 + 4 * i] = (byte) ((encs[i] >> 8) & 0xff);
+ b[7 + 4 * i] = (byte) (encs[i] & 0xff);
+ }
+
+ os.write(b);
+ }
+
+
+ //
+ // Write a ClientCutText message
+ //
+
+ synchronized void writeClientCutText(String text) throws IOException {
+ byte[] b = new byte[8 + text.length()];
+
+ b[0] = (byte) ClientCutText;
+ b[4] = (byte) ((text.length() >> 24) & 0xff);
+ b[5] = (byte) ((text.length() >> 16) & 0xff);
+ b[6] = (byte) ((text.length() >> 8) & 0xff);
+ b[7] = (byte) (text.length() & 0xff);
+
+ System.arraycopy(text.getBytes(), 0, b, 8, text.length());
+
+ os.write(b);
+ }
+
+
+ //
+ // A buffer for putting pointer and keyboard events before being sent. This
+ // is to ensure that multiple RFB events generated from a single Java Event
+ // will all be sent in a single network packet. The maximum possible
+ // length is 4 modifier down events, a single key event followed by 4
+ // modifier up events i.e. 9 key events or 72 bytes.
+ //
+
+ byte[] eventBuf = new byte[72];
+ int eventBufLen;
+
+
+ /**
+ * Write a pointer event message. We may need to send modifier key events
+ * around it to set the correct modifier state.
+ * @param x
+ * @param y
+ * @param modifiers
+ * @param pointerMask
+ * @throws IOException
+ */
+ synchronized void writePointerEvent( int x, int y, int modifiers, int pointerMask) throws IOException
+ {
+ eventBufLen = 0;
+ writeModifierKeyEvents(modifiers);
+
+ eventBuf[eventBufLen++] = (byte) PointerEvent;
+ eventBuf[eventBufLen++] = (byte) pointerMask;
+ eventBuf[eventBufLen++] = (byte) ((x >> 8) & 0xff);
+ eventBuf[eventBufLen++] = (byte) (x & 0xff);
+ eventBuf[eventBufLen++] = (byte) ((y >> 8) & 0xff);
+ eventBuf[eventBufLen++] = (byte) (y & 0xff);
+
+ //
+ // Always release all modifiers after an "up" event
+ //
+
+ if (pointerMask == 0) {
+ writeModifierKeyEvents(0);
+ }
+
+ os.write(eventBuf, 0, eventBufLen);
+ }
+
+ void writeCtrlAltDel() throws IOException {
+ final int DELETE = 0xffff;
+ final int CTRLALT = VncCanvas.CTRL_MASK | VncCanvas.ALT_MASK;
+ try {
+ // Press
+ eventBufLen = 0;
+ writeModifierKeyEvents(CTRLALT);
+ writeKeyEvent(DELETE, true);
+ os.write(eventBuf, 0, eventBufLen);
+
+ // Release
+ eventBufLen = 0;
+ writeModifierKeyEvents(CTRLALT);
+ writeKeyEvent(DELETE, false);
+
+ // Reset VNC server modifiers state
+ writeModifierKeyEvents(0);
+ os.write(eventBuf, 0, eventBufLen);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ //
+ // Write a key event message. We may need to send modifier key events
+ // around it to set the correct modifier state. Also we need to translate
+ // from the Java key values to the X keysym values used by the RFB protocol.
+ //
+ public synchronized void writeKeyEvent(int keySym, int metaState, boolean down) throws IOException {
+// Log.i("writeKeyEvent","key = " + keySym + " metastate = " + metaState + " down = " + down);
+ eventBufLen = 0;
+ if (down)
+ writeModifierKeyEvents(metaState);
+ if (keySym != 0)
+ writeKeyEvent(keySym, down);
+
+ // Always release all modifiers after an "up" event
+ if (!down)
+ writeModifierKeyEvents(0);
+
+ os.write(eventBuf, 0, eventBufLen);
+ }
+
+
+
+
+ //
+ // Add a raw key event with the given X keysym to eventBuf.
+ //
+
+ private void writeKeyEvent(int keysym, boolean down) {
+ eventBuf[eventBufLen++] = (byte) KeyboardEvent;
+ eventBuf[eventBufLen++] = (byte) (down ? 1 : 0);
+ eventBuf[eventBufLen++] = (byte) 0;
+ eventBuf[eventBufLen++] = (byte) 0;
+ eventBuf[eventBufLen++] = (byte) ((keysym >> 24) & 0xff);
+ eventBuf[eventBufLen++] = (byte) ((keysym >> 16) & 0xff);
+ eventBuf[eventBufLen++] = (byte) ((keysym >> 8) & 0xff);
+ eventBuf[eventBufLen++] = (byte) (keysym & 0xff);
+ }
+
+
+ //
+ // Write key events to set the correct modifier state.
+ //
+
+ int oldModifiers = 0;
+
+ void writeModifierKeyEvents(int newModifiers) {
+ if ((newModifiers & VncCanvas.CTRL_MASK) != (oldModifiers & VncCanvas.CTRL_MASK))
+ writeKeyEvent(0xffe3, (newModifiers & VncCanvas.CTRL_MASK) != 0);
+
+ if ((newModifiers & VncCanvas.SHIFT_MASK) != (oldModifiers & VncCanvas.SHIFT_MASK))
+ writeKeyEvent(0xffe1, (newModifiers & VncCanvas.SHIFT_MASK) != 0);
+
+ if ((newModifiers & VncCanvas.META_MASK) != (oldModifiers & VncCanvas.META_MASK))
+ writeKeyEvent(0xffe7, (newModifiers & VncCanvas.META_MASK) != 0);
+
+ if ((newModifiers & VncCanvas.ALT_MASK) != (oldModifiers & VncCanvas.ALT_MASK))
+ writeKeyEvent(0xffe9, (newModifiers & VncCanvas.ALT_MASK) != 0);
+
+ oldModifiers = newModifiers;
+ }
+ //
+ // Compress and write the data into the recorded session file. This
+ // method assumes the recording is on (rec != null).
+ //
+
+
+ public void startTiming() {
+ timing = true;
+
+ // Carry over up to 1s worth of previous rate for smoothing.
+
+ if (timeWaitedIn100us > 10000) {
+ timedKbits = timedKbits * 10000 / timeWaitedIn100us;
+ timeWaitedIn100us = 10000;
+ }
+ }
+
+ public void stopTiming() {
+ timing = false;
+ if (timeWaitedIn100us < timedKbits/2)
+ timeWaitedIn100us = timedKbits/2; // upper limit 20Mbit/s
+ }
+
+ public long kbitsPerSecond() {
+ return timedKbits * 10000 / timeWaitedIn100us;
+ }
+
+ public long timeWaited() {
+ return timeWaitedIn100us;
+ }
+
+ public void readFully(byte b[]) throws IOException {
+ readFully(b, 0, b.length);
+ }
+
+ public void readFully(byte b[], int off, int len) throws IOException {
+ long before = 0;
+ timing = false; // for test
+
+ if (timing)
+ before = System.currentTimeMillis();
+
+ is.readFully(b, off, len);
+
+ if (timing) {
+ long after = System.currentTimeMillis();
+ long newTimeWaited = (after - before) * 10;
+ int newKbits = len * 8 / 1000;
+
+ // limit rate to between 10kbit/s and 40Mbit/s
+
+ if (newTimeWaited > newKbits*1000) newTimeWaited = newKbits*1000;
+ if (newTimeWaited < newKbits/4) newTimeWaited = newKbits/4;
+
+ timeWaitedIn100us += newTimeWaited;
+ timedKbits += newKbits;
+ }
+ }
+
+ synchronized void writeOpenChat() throws Exception {
+ os.write(TextChat); // byte type
+ os.write(0); // byte pad 1
+ os.write(0); // byte pad 2
+ os.write(0); // byte pad 2
+ writeInt(CHAT_OPEN); // int message length
+ }
+
+ synchronized void writeCloseChat() throws Exception {
+ os.write(TextChat); // byte type
+ os.write(0); // byte pad 1
+ os.write(0); // byte pad 2
+ os.write(0); // byte pad 2
+ writeInt(CHAT_CLOSE); // int message length
+ }
+
+ synchronized void writeFinishedChat() throws Exception {
+ os.write(TextChat); // byte type
+ os.write(0); // byte pad 1
+ os.write(0); // byte pad 2
+ os.write(0); // byte pad 2
+ writeInt(CHAT_FINISHED); // int message length
+ }
+
+ String readTextChatMsg() throws Exception {
+ byte[] pad = new byte[3];
+ readFully(pad);
+ int len = is.readInt();
+ if (len == CHAT_OPEN) {
+ // Remote user requests chat
+ ///viewer.openChat();
+ // Respond to chat request
+ writeOpenChat();
+ return null;
+ } else if (len == CHAT_CLOSE) {
+ // Remote user ends chat
+ ///viewer.closeChat();
+ return null;
+ } else if (len == CHAT_FINISHED) {
+ // Remote user says chat finished.
+ // Not sure why I should care about this state.
+ return null;
+ } else {
+ // Remote user sends message!!
+ if (len > 0) {
+ byte[] msg = new byte[len];
+ readFully(msg);
+ return new String(msg);
+ }
+ }
+ return null;
+ }
+
+ public synchronized void writeChatMessage(String msg) throws Exception {
+ os.write(TextChat); // byte type
+ os.write(0); // byte pad 1
+ os.write(0); // byte pad 2
+ os.write(0); // byte pad 2
+ byte [] bytes = msg.getBytes("8859_1");
+ byte [] outgoing = bytes;
+ if (bytes.length > 4096) {
+ outgoing = new byte[4096];
+ System.arraycopy(bytes, 0, outgoing, 0, 4096);
+ }
+ writeInt(outgoing.length); // int message length
+ os.write(outgoing); // message
+ }
+}
diff --git a/app/src/main/java/android/androidVNC/Utils.java b/app/src/main/java/android/androidVNC/Utils.java
new file mode 100644
index 0000000..d18f5eb
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/Utils.java
@@ -0,0 +1,75 @@
+package android.androidVNC;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.AlertDialog;
+import android.app.ActivityManager.MemoryInfo;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.net.Uri;
+import android.text.Html;
+
+public class Utils {
+
+ public static void showYesNoPrompt(Context _context, String title, String message, OnClickListener onYesListener, OnClickListener onNoListener) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(_context);
+ builder.setTitle(title);
+ builder.setIcon(android.R.drawable.ic_dialog_info); // lame icon
+ builder.setMessage(message);
+ builder.setCancelable(false);
+ builder.setPositiveButton("Yes", onYesListener);
+ builder.setNegativeButton("No", onNoListener);
+ builder.show();
+ }
+
+ private static final Intent docIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://code.google.com/p/android-vnc-viewer/wiki/Documentation"));
+
+ public static ActivityManager getActivityManager(Context context)
+ {
+ ActivityManager result = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
+ if (result == null)
+ throw new UnsupportedOperationException("Could not retrieve ActivityManager");
+ return result;
+ }
+
+ public static MemoryInfo getMemoryInfo(Context _context) {
+ MemoryInfo info = new MemoryInfo();
+ getActivityManager(_context).getMemoryInfo(info);
+ return info;
+ }
+
+ public static void showDocumentation(Context c) {
+ c.startActivity(docIntent);
+ }
+
+ private static int nextNoticeID = 0;
+ public static int nextNoticeID() {
+ nextNoticeID++;
+ return nextNoticeID;
+ }
+
+ public static void showErrorMessage(Context _context, String message) {
+ showMessage(_context, "Error!", message, android.R.drawable.ic_dialog_alert, null);
+ }
+
+ public static void showFatalErrorMessage(final Context _context, String message) {
+ showMessage(_context, "Error!", message, android.R.drawable.ic_dialog_alert, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ ((Activity) _context).finish();
+ }
+ });
+ }
+
+ public static void showMessage(Context _context, String title, String message, int icon, DialogInterface.OnClickListener ackHandler) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(_context);
+ builder.setTitle(title);
+ builder.setMessage(Html.fromHtml(message));
+ builder.setCancelable(false);
+ builder.setPositiveButton("Acknowledged", ackHandler);
+ builder.setIcon(icon);
+ builder.show();
+ }
+}
diff --git a/app/src/main/java/android/androidVNC/VncCanvas.java b/app/src/main/java/android/androidVNC/VncCanvas.java
new file mode 100644
index 0000000..eae0823
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/VncCanvas.java
@@ -0,0 +1,2078 @@
+//
+// Copyright (C) 2010 Michael A. MacDonald
+// Copyright (C) 2004 Horizon Wimba. All Rights Reserved.
+// Copyright (C) 2001-2003 HorizonLive.com, Inc. All Rights Reserved.
+// Copyright (C) 2001,2002 Constantin Kaplinsky. All Rights Reserved.
+// Copyright (C) 2000 Tridia Corporation. All Rights Reserved.
+// Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
+//
+// This is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This software is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this software; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+// USA.
+//
+//
+// VncCanvas is a subclass of android.view.SurfaceView which draws a VNC
+// desktop on it.
+//
+package android.androidVNC;
+
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Display;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.antlersoft.android.bc.BCFactory;
+import com.vectras.qemu.Config;
+import com.vectras.vm.utils.UIUtils;
+
+import java.io.IOException;
+import java.util.zip.Inflater;
+
+import androidx.appcompat.widget.AppCompatImageView;
+
+public class VncCanvas extends AppCompatImageView {
+
+ private final static String TAG = "VncCanvas";
+ private final static boolean LOCAL_LOGV = true;
+ public AbstractScaling scaling;
+ // Available to activity
+ public int mouseX, mouseY;
+ // Connection parameters
+ ConnectionBean connection;
+ // Runtime control flags
+ private boolean maintainConnection = true;
+ private boolean showDesktopInfo = true;
+ private boolean repaintsEnabled = true;
+ /**
+ * Use camera button as meta key for right mouse button
+ */
+ boolean cameraButtonDown = false;
+ // Keep track when a seeming key press was the result of a menu shortcut
+ int lastKeyDown;
+ boolean afterMenu;
+ // Color Model settings
+ private COLORMODEL pendingColorModel = COLORMODEL.C24bit;
+ public COLORMODEL colorModel = null;
+ private int bytesPerPixel = 0;
+ private int[] colorPalette = null;
+ // VNC protocol connection
+ public RfbProto rfb;
+ // Internal bitmap data
+ AbstractBitmapData bitmapData;
+ public Handler handler = new Handler();
+ // VNC Encoding parameters
+ private boolean useCopyRect = false; // TODO CopyRect is not working
+ private int preferredEncoding = -1;
+ // Unimplemented VNC encoding parameters
+ private boolean requestCursorUpdates = false;
+ private boolean ignoreCursorUpdates = true;
+ // Unimplemented TIGHT encoding parameters
+ private int compressLevel = -1;
+ private int jpegQuality = -1;
+ // Used to determine if encoding update is necessary
+ private int[] encodingsSaved = new int[20];
+ private int nEncodingsSaved = 0;
+ // ZRLE encoder's data.
+ private byte[] zrleBuf;
+ private int[] zrleTilePixels;
+ private ZlibInStream zrleInStream;
+ // Zlib encoder's data.
+ private byte[] zlibBuf;
+ private Inflater zlibInflater;
+ private MouseScrollRunnable scrollRunnable;
+ private Paint handleRREPaint;
+ /**
+ * Position of the top left portion of the visible part of the
+ * screen, in full-frame coordinates
+ */
+ int absoluteXPosition = 0, absoluteYPosition = 0;
+ public boolean mouseDown;
+
+ /**
+ * Constructor used by the inflation apparatus
+ *
+ * @param context
+ */
+ public VncCanvas(final Context context, AttributeSet attrs) {
+ super(context, attrs);
+ scrollRunnable = new MouseScrollRunnable();
+ handleRREPaint = new Paint();
+ handleRREPaint.setStyle(Style.FILL);
+ }
+
+ public VncCanvas(Context context) {
+ super(context);
+ }
+
+ public static int retries;
+ public static int MAX_RETRIES = 5;
+
+ /**
+ * Create a view showing a VNC connection
+ *
+ * @param context
+ * Containing context (activity)
+ * @param bean
+ * Connection settings
+ * @param setModes
+ * Callback to run on UI thread after connection is set up
+ */
+ void initializeVncCanvas(ConnectionBean bean, final Runnable setModes) {
+ connection = bean;
+ this.pendingColorModel = COLORMODEL.valueOf(bean.getColorModel());
+
+ setOnGenericMotionListener(new VNCGenericMotionListener_API12());
+ setOnTouchListener(new VNCOnTouchListener());
+
+ // Startup the RFB thread with a nifty progess dialog
+ final ProgressDialog pd = ProgressDialog.show(getContext(), "Connecting to VM Console", "Please wait...", true,
+ true, new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ closeConnection();
+ handler.post(new Runnable() {
+ public void run() {
+ Utils.showErrorMessage(getContext(), "VNC connection aborted!");
+ }
+ });
+ }
+ });
+ final Display display = pd.getWindow().getWindowManager().getDefaultDisplay();
+ Thread t = new Thread() {
+
+ public void run() {
+ try {
+ int width = 0;
+ int height = 0;
+ Point size = new Point();
+ display.getSize(size);
+ width = size.x;
+ height = size.y;
+
+ connectAndAuthenticate(connection.getUserName(), connection.getPassword());
+ doProtocolInitialisation(width, height);
+ handler.post(new Runnable() {
+ public void run() {
+ // pd.setMessage("Downloading first frame.\nPlease
+ // wait...");
+ }
+ });
+ processNormalProtocol(getContext(), pd, setModes);
+ } catch (Throwable e) {
+ if (maintainConnection) {
+ Log.e(TAG, e.toString());
+ if(Config.debug)
+ e.printStackTrace();
+ // Ensure we dismiss the progress dialog
+ // before we fatal error finish
+ if (pd.isShowing()) {
+ pd.dismiss();
+ }
+ if (e instanceof OutOfMemoryError) {
+ // TODO Not sure if this will happen but...
+ // figure out how to gracefully notify the user
+ // Instantiating an alert dialog here doesn't work
+ // because we are out of memory. :(
+ } else if (e instanceof ArrayIndexOutOfBoundsException || e instanceof java.net.ConnectException
+ || e instanceof java.io.IOException) {
+ // Retry
+ if (retries < MAX_RETRIES) {
+ retries++;
+ reload();
+ }
+ } else {
+ String error = "VNC connection failed!";
+ if (e.getMessage() != null && (e.getMessage().indexOf("authentication") > -1)) {
+ error = "VNC authentication failed!";
+ }
+ final String error_ = error + " " + e.getLocalizedMessage();
+ handler.post(new Runnable() {
+ public void run() {
+ Utils.showFatalErrorMessage(getContext(), error_);
+ }
+ });
+ }
+ }
+ }
+ }
+ };
+ t.start();
+ }
+
+ public void reload() {
+ Log.d(TAG, "Reconnecting...");
+ Activity activity = ((Activity) getContext());
+ Intent data = new Intent();
+ activity.setResult(Config.VNC_RESET_RESULT_CODE, data);
+ activity.finish();
+
+ }
+
+ void connectAndAuthenticate(String us, String pw) throws Exception {
+ Log.i(TAG, "Connecting to " + connection.getAddress() + ", port " + connection.getPort() + "...");
+
+ rfb = new RfbProto(connection.getAddress(), connection.getPort());
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "Connected to server");
+
+ }
+
+ rfb.readVersionMsg();
+ Log.i(TAG, "RFB server supports protocol version " + rfb.serverMajor + "." + rfb.serverMinor);
+
+ rfb.writeVersionMsg();
+ Log.i(TAG, "Using RFB protocol version " + rfb.clientMajor + "." + rfb.clientMinor);
+
+ int bitPref = 0;
+ if (connection.getUserName().length() > 0) {
+ bitPref |= 1;
+ }
+ Log.d("debug", "bitPref=" + bitPref);
+ int secType = rfb.negotiateSecurity(bitPref);
+ int authType;
+ if (secType == RfbProto.SecTypeTight) {
+ rfb.initCapabilities();
+ rfb.setupTunneling();
+ authType = rfb.negotiateAuthenticationTight();
+ } else if (secType == RfbProto.SecTypeUltra34) {
+ rfb.prepareDH();
+ authType = RfbProto.AuthUltra;
+ } else {
+ authType = secType;
+ }
+
+ switch (authType) {
+ case RfbProto.AuthNone:
+ Log.i(TAG, "No authentication needed");
+ rfb.authenticateNone();
+ break;
+ case RfbProto.AuthVNC:
+ Log.i(TAG, "VNC authentication needed");
+ rfb.authenticateVNC(pw);
+ break;
+ case RfbProto.AuthUltra:
+ rfb.authenticateDH(us, pw);
+ break;
+ default:
+ throw new Exception("Unknown authentication scheme " + authType);
+ }
+ }
+
+ void doProtocolInitialisation(int dx, int dy) throws IOException {
+ rfb.writeClientInit();
+ rfb.readServerInit();
+
+ Log.i(TAG, "Desktop name is " + rfb.desktopName);
+ Log.i(TAG, "Desktop size is " + rfb.framebufferWidth + " x " + rfb.framebufferHeight);
+
+ boolean useFull = false;
+ int capacity = BCFactory.getInstance().getBCActivityManager()
+ .getMemoryClass(Utils.getActivityManager(getContext()));
+ if (connection.getForceFull() == BitmapImplHint.AUTO) {
+ if (rfb.framebufferWidth * rfb.framebufferHeight * FullBufferBitmapData.CAPACITY_MULTIPLIER <= capacity
+ * 1024 * 1024) {
+ useFull = true;
+ }
+ } else {
+ useFull = (connection.getForceFull() == BitmapImplHint.FULL);
+ }
+ if (!useFull) {
+ bitmapData = new LargeBitmapData(rfb, this, dx, dy, capacity);
+ } else {
+ bitmapData = new FullBufferBitmapData(rfb, this, capacity);
+ }
+ mouseX = rfb.framebufferWidth / 2;
+ mouseY = rfb.framebufferHeight / 2;
+
+ setPixelFormat();
+ }
+
+ private void setPixelFormat() throws IOException {
+ pendingColorModel.setPixelFormat(rfb);
+ bytesPerPixel = pendingColorModel.bpp();
+ colorPalette = pendingColorModel.palette();
+ colorModel = pendingColorModel;
+ pendingColorModel = null;
+ }
+
+ public void setColorModel(COLORMODEL cm) {
+ // Only update if color model changes
+ if (colorModel == null || !colorModel.equals(cm)) {
+ pendingColorModel = cm;
+ }
+ }
+
+ public boolean isColorModel(COLORMODEL cm) {
+ return (colorModel != null) && colorModel.equals(cm);
+ }
+
+ private void mouseFollowPan() {
+ if (scaling.isAbleToPan()) {
+ int scrollx = absoluteXPosition;
+ int scrolly = absoluteYPosition;
+ int width = getVisibleWidth();
+ int height = getVisibleHeight();
+ // Log.i(TAG,"scrollx " + scrollx + " scrolly " + scrolly +
+ // " mouseX " + mouseX +" Y " + mouseY + " w " + width + " h " +
+ // height);
+ if (mouseX < scrollx || mouseX >= scrollx + width || mouseY < scrolly || mouseY >= scrolly + height) {
+ // Log.i(TAG,"warp to " + scrollx+width/2 + "," + scrolly +
+ // height/2);
+ warpMouse(scrollx + width / 2, scrolly + height / 2);
+ }
+ }
+ }
+
+ public void processNormalProtocol(final Context context, ProgressDialog pd, final Runnable setModes)
+ throws Exception {
+ try {
+ bitmapData.writeFullUpdateRequest(false);
+
+ handler.post(setModes);
+ connected();
+ //
+ // main dispatch loop
+ //
+ while (maintainConnection) {
+ bitmapData.syncScroll();
+ // Read message type from the server.
+ int msgType = rfb.readServerMessageType();
+ bitmapData.doneWaiting();
+ // Process the message depending on its type.
+ switch (msgType) {
+ case RfbProto.FramebufferUpdate:
+ rfb.readFramebufferUpdate();
+
+ for (int i = 0; i < rfb.updateNRects; i++) {
+ rfb.readFramebufferUpdateRectHdr();
+ int rx = rfb.updateRectX, ry = rfb.updateRectY;
+ int rw = rfb.updateRectW, rh = rfb.updateRectH;
+
+ if (rfb.updateRectEncoding == RfbProto.EncodingLastRect) {
+ Log.v(TAG, "rfb.EncodingLastRect");
+ break;
+ }
+
+ if (rfb.updateRectEncoding == RfbProto.EncodingNewFBSize) {
+ rfb.setFramebufferSize(rw, rh);
+ // - updateFramebufferSize();
+ Log.v(TAG, "rfb.EncodingNewFBSize");
+ reload();
+ break;
+ }
+
+ if (rfb.updateRectEncoding == RfbProto.EncodingXCursor
+ || rfb.updateRectEncoding == RfbProto.EncodingRichCursor) {
+ // - handleCursorShapeUpdate(rfb.updateRectEncoding,
+ // rx,
+ // ry, rw, rh);
+ Log.v(TAG, "rfb.EncodingCursor");
+ continue;
+
+ }
+
+ if (rfb.updateRectEncoding == RfbProto.EncodingPointerPos) {
+ // This never actually happens
+ mouseX = rx;
+ mouseY = ry;
+ Log.v(TAG, "rfb.EncodingPointerPos");
+ continue;
+ }
+
+ rfb.startTiming();
+
+ switch (rfb.updateRectEncoding) {
+ case RfbProto.EncodingRaw:
+ handleRawRect(rx, ry, rw, rh);
+ break;
+ case RfbProto.EncodingCopyRect:
+ handleCopyRect(rx, ry, rw, rh);
+ Log.v(TAG, "CopyRect is Buggy!");
+ break;
+ case RfbProto.EncodingRRE:
+ handleRRERect(rx, ry, rw, rh);
+ break;
+ case RfbProto.EncodingCoRRE:
+ handleCoRRERect(rx, ry, rw, rh);
+ break;
+ case RfbProto.EncodingHextile:
+ handleHextileRect(rx, ry, rw, rh);
+ break;
+ case RfbProto.EncodingZRLE:
+ handleZRLERect(rx, ry, rw, rh);
+ break;
+ case RfbProto.EncodingZlib:
+ handleZlibRect(rx, ry, rw, rh);
+ break;
+ default:
+ Log.e(TAG, "Unknown RFB rectangle encoding " + rfb.updateRectEncoding + " (0x"
+ + Integer.toHexString(rfb.updateRectEncoding) + ")");
+ }
+
+ rfb.stopTiming();
+
+ // Hide progress dialog
+ if (pd.isShowing()) {
+ pd.dismiss();
+ }
+ }
+
+ boolean fullUpdateNeeded = false;
+
+ if (pendingColorModel != null) {
+ setPixelFormat();
+ fullUpdateNeeded = true;
+ }
+
+ setEncodings(true);
+ bitmapData.writeFullUpdateRequest(!fullUpdateNeeded);
+
+ break;
+
+ case RfbProto.SetColourMapEntries:
+ throw new Exception("Can't handle SetColourMapEntries message");
+
+ case RfbProto.Bell:
+ handler.post(new Runnable() {
+ public void run() {
+ UIUtils.toastShort(context, "VNC Beep");
+ }
+ });
+ break;
+
+ case RfbProto.ServerCutText:
+ String s = rfb.readServerCutText();
+ if (s != null && s.length() > 0) {
+ // TODO implement cut & paste
+ }
+ break;
+
+ case RfbProto.TextChat:
+ // UltraVNC extension
+ String msg = rfb.readTextChatMsg();
+ if (msg != null && msg.length() > 0) {
+ // TODO implement chat interface
+ }
+ break;
+
+ default:
+ throw new Exception("Unknown RFB message type " + msgType);
+ }
+ }
+ } catch (Exception e) {
+ throw e;
+ } finally {
+ Log.v(TAG, "Closing VNC Connection");
+ rfb.close();
+ }
+ }
+
+ /**
+ * Apply scroll offset and scaling to convert touch-space coordinates to the
+ * corresponding point on the full frame.
+ *
+ * @param e
+ * MotionEvent with the original, touch space coordinates. This
+ * event is altered in place.
+ * @return e -- The same event passed in, with the coordinates mapped
+ */
+ MotionEvent changeTouchCoordinatesToFullFrame(MotionEvent e) {
+ // Log.v(TAG, String.format("tap at %f,%f", e.getX(), e.getY()));
+ float scale = getScale();
+
+ // Adjust coordinates for Android notification bar.
+ e.offsetLocation(0, -1f * getTop());
+
+ e.setLocation(absoluteXPosition + e.getX() / scale, absoluteYPosition + e.getY() / scale);
+
+ return e;
+ }
+
+ public void onDestroy() {
+ Log.v(TAG, "Cleaning up resources");
+ if (bitmapData != null) {
+ bitmapData.dispose();
+ }
+ bitmapData = null;
+ }
+
+ /**
+ * Warp the mouse to x, y in the RFB coordinates
+ *
+ * @param x
+ * @param y
+ */
+ void warpMouse(int x, int y) {
+ bitmapData.invalidateMousePosition();
+ mouseX = x;
+ mouseY = y;
+ bitmapData.invalidateMousePosition();
+ try {
+ rfb.writePointerEvent(x, y, 0, MOUSE_BUTTON_NONE);
+ } catch (IOException ioe) {
+ Log.w(TAG, ioe);
+ }
+ }
+
+ /*
+ * f(x,s) is a function that returns the coordinate in screen/scroll space
+ * corresponding to the coordinate x in full-frame space with scaling s.
+ *
+ * This function returns the difference between f(x,s1) and f(x,s2)
+ *
+ * f(x,s) = (x - i/2) * s + ((i - w)/2)) * s = s (x - i/2 + i/2 + w/2) = s
+ * (x + w/2)
+ *
+ *
+ * f(x,s) = (x - ((i - w)/2)) * s
+ *
+ * @param oldscaling
+ *
+ * @param scaling
+ *
+ * @param imageDim
+ *
+ * @param windowDim
+ *
+ * @param offset
+ *
+ * @return
+ */
+ /**
+ * Change to Canvas's scroll position to match the absoluteXPosition
+ */
+ void scrollToAbsolute() {
+ float scale = getScale();
+ try {
+ scrollTo((int) ((absoluteXPosition + ((float) getWidth() - getImageWidth()) / 2) * scale),
+ (int) ((absoluteYPosition + ((float) getHeight() - getImageHeight()) / 2) * scale));
+ } catch (Exception e) {
+ Log.v("VNC", "Error: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Make sure mouse is visible on displayable part of screen
+ */
+ void panToMouse() {
+ if (!connection.getFollowMouse()) {
+ return;
+ }
+
+ if (scaling != null && !scaling.isAbleToPan()) {
+ return;
+ }
+
+ int x = mouseX;
+ int y = mouseY;
+ boolean panned = false;
+ int w = getVisibleWidth();
+ int h = getVisibleHeight();
+ int iw = getImageWidth();
+ int ih = getImageHeight();
+
+ int newX = absoluteXPosition;
+ int newY = absoluteYPosition;
+
+ if (x - newX >= w - 5) {
+ newX = x - w + 5;
+ if (newX + w > iw) {
+ newX = iw - w;
+ }
+ } else if (x < newX + 5) {
+ newX = x - 5;
+ if (newX < 0) {
+ newX = 0;
+ }
+ }
+ if (newX != absoluteXPosition) {
+ absoluteXPosition = newX;
+ panned = true;
+ }
+ if (y - newY >= h - 5) {
+ newY = y - h + 5;
+ if (newY + h > ih) {
+ newY = ih - h;
+ }
+ } else if (y < newY + 5) {
+ newY = y - 5;
+ if (newY < 0) {
+ newY = 0;
+ }
+ }
+ if (newY != absoluteYPosition) {
+ absoluteYPosition = newY;
+ panned = true;
+ }
+ if (panned) {
+ scrollToAbsolute();
+ }
+ }
+
+ /**
+ * Pan by a number of pixels (relative pan)
+ *
+ * @param dX
+ * @param dY
+ * @return True if the pan changed the view (did not move view out of
+ * bounds); false otherwise
+ */
+ boolean pan(int dX, int dY) {
+
+ double scale = getScale();
+
+ double sX = (double) dX / scale;
+ double sY = (double) dY / scale;
+
+ if (absoluteXPosition + sX < 0) // dX = diff to 0
+ {
+ sX = -absoluteXPosition;
+ }
+ if (absoluteYPosition + sY < 0) {
+ sY = -absoluteYPosition;
+ }
+
+ // Prevent panning right or below desktop image
+
+ Point outSize = new Point();
+ int height = 0;
+ int width = 0;
+ VncCanvasActivity.display.getSize(outSize);
+ height = outSize.y;
+ width = outSize.x;
+
+ if (absoluteXPosition + getVisibleWidth() + sX > getImageWidth()) {
+ sX = getImageWidth() - getVisibleWidth() - absoluteXPosition;
+ }
+ if (absoluteYPosition + getVisibleHeight() + sY > getImageHeight() + height * .6) {
+ sY = getImageHeight() + height * .6 - getVisibleHeight() - absoluteYPosition;
+ }
+
+ absoluteXPosition += sX;
+ absoluteYPosition += sY;
+ if (sX != 0.0 || sY != 0.0) {
+ scrollToAbsolute();
+ return true;
+ }
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.view.View#onScrollChanged(int, int, int, int)
+ */
+ @Override
+ protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+ super.onScrollChanged(l, t, oldl, oldt);
+ bitmapData.scrollChanged(absoluteXPosition, absoluteYPosition);
+ mouseFollowPan();
+ }
+
+ void handleRawRect(int x, int y, int w, int h) throws IOException {
+ handleRawRect(x, y, w, h, true);
+ }
+
+ byte[] handleRawRectBuffer = new byte[128];
+
+ void handleRawRect(int x, int y, int w, int h, boolean paint) throws IOException {
+ boolean valid = bitmapData.validDraw(x, y, w, h);
+ int[] pixels = bitmapData.bitmapPixels;
+ if (bytesPerPixel == 1) {
+ // 1 byte per pixel. Use palette lookup table.
+ if (w > handleRawRectBuffer.length) {
+ handleRawRectBuffer = new byte[w];
+ }
+ int i, offset;
+ for (int dy = y; dy < y + h; dy++) {
+ rfb.readFully(handleRawRectBuffer, 0, w);
+ if (!valid) {
+ continue;
+ }
+ offset = bitmapData.offset(x, dy);
+ for (i = 0; i < w; i++) {
+ pixels[offset + i] = colorPalette[0xFF & handleRawRectBuffer[i]];
+ }
+ }
+ } else {
+ // 4 bytes per pixel (argb) 24-bit color
+
+ final int l = w * 4;
+ if (l > handleRawRectBuffer.length) {
+ handleRawRectBuffer = new byte[l];
+ }
+ int i, offset;
+ for (int dy = y; dy < y + h; dy++) {
+ rfb.readFully(handleRawRectBuffer, 0, l);
+ if (!valid) {
+ continue;
+ }
+ offset = bitmapData.offset(x, dy);
+ for (i = 0; i < w; i++) {
+ final int idx = i * 4;
+ pixels[offset + i] = // 0xFF << 24 |
+ (handleRawRectBuffer[idx + 2] & 0xff) << 16 | (handleRawRectBuffer[idx + 1] & 0xff) << 8
+ | (handleRawRectBuffer[idx] & 0xff);
+ }
+ }
+ }
+
+ if (!valid) {
+ return;
+ }
+
+ bitmapData.updateBitmap(x, y, w, h);
+
+ if (paint) {
+ reDraw();
+ }
+ }
+
+ private Runnable reDraw = new Runnable() {
+ public void run() {
+ if (showDesktopInfo) {
+ // Show a Toast with the desktop info on first frame draw.
+ showDesktopInfo = false;
+ showConnectionInfo();
+ }
+ if (bitmapData != null) {
+ bitmapData.updateView(VncCanvas.this);
+ }
+ }
+ };
+
+ private void reDraw() {
+ if (repaintsEnabled) {
+ handler.post(reDraw);
+ }
+ }
+
+ public void disableRepaints() {
+ repaintsEnabled = false;
+ }
+
+ public void enableRepaints() {
+ repaintsEnabled = true;
+ }
+
+ public void showConnectionInfo() {
+ String msg = rfb.desktopName;
+ int idx = rfb.desktopName.indexOf("(");
+ if (idx > -1) {
+ // Breakup actual desktop name from IP addresses for improved
+ // readability
+ String dn = rfb.desktopName.substring(0, idx).trim();
+ String ip = rfb.desktopName.substring(idx).trim();
+ msg = dn + "\n" + ip;
+ }
+ msg += "\n" + rfb.framebufferWidth + "x" + rfb.framebufferHeight;
+ String enc = getEncoding();
+ // Encoding might not be set when we display this message
+ if (enc != null && !enc.equals("")) {
+ msg += ", " + getEncoding() + " encoding, " + colorModel.toString();
+ } else {
+ msg += ", " + colorModel.toString();
+ }
+ // Toast.makeText(getContext(), msg, Toast.LENGTH_LONG).show();
+ }
+
+ private String getEncoding() {
+ switch (preferredEncoding) {
+ case RfbProto.EncodingRaw:
+ return "RAW";
+ case RfbProto.EncodingTight:
+ return "TIGHT";
+ case RfbProto.EncodingCoRRE:
+ return "CoRRE";
+ case RfbProto.EncodingHextile:
+ return "HEXTILE";
+ case RfbProto.EncodingRRE:
+ return "RRE";
+ case RfbProto.EncodingZlib:
+ return "ZLIB";
+ case RfbProto.EncodingZRLE:
+ return "ZRLE";
+ }
+ return "";
+ }
+
+ // Useful shortcuts for modifier masks.
+ final static int CTRL_MASK = KeyEvent.META_SYM_ON;
+ final static int SHIFT_MASK = KeyEvent.META_SHIFT_ON;
+ final static int META_MASK = 0;
+ final static int ALT_MASK = KeyEvent.META_ALT_ON;
+ private static final int MOUSE_BUTTON_NONE = 0;
+ static final int MOUSE_BUTTON_LEFT = 1;
+ static final int MOUSE_BUTTON_MIDDLE = 2;
+ static final int MOUSE_BUTTON_RIGHT = 4;
+ static final int MOUSE_BUTTON_SCROLL_UP = 8;
+ static final int MOUSE_BUTTON_SCROLL_DOWN = 16;
+ /**
+ * Current state of "mouse" buttons Alt meta means use second mouse button 0
+ * = none 1 = default button 2 = second button
+ */
+ private int pointerMask = MOUSE_BUTTON_NONE;
+ private boolean ALT_PRESSED = false;
+ private boolean CTRL_PRESSED = false;
+
+ /**
+ * Convert a motion event to a format suitable for sending over the wire
+ *
+ * @param evt
+ * motion event; x and y must already have been converted from
+ * screen coordinates to remote frame buffer coordinates.
+ * cameraButton flag is interpreted as second mouse button
+ * @param downEvent
+ * True if "mouse button" (touch or trackball button) is down
+ * when this happens
+ * @return true if event was actually sent
+ */
+ public boolean processPointerEvent(MotionEvent evt, boolean downEvent) {
+ return processPointerEvent(evt, downEvent, cameraButtonDown);
+ }
+
+ /**
+ * Convert a motion event to a format suitable for sending over the wire
+ *
+ * @param evt
+ * motion event; x and y must already have been converted from
+ * screen coordinates to remote frame buffer coordinates.
+ * @param downEvent
+ * True if "mouse button" (touch or trackball button) is down
+ * when this happens
+ * @param useRightButton
+ * If true, event is interpreted as happening with right mouse
+ * button
+ * @return true if event was actually sent
+ */
+ public boolean processPointerEvent(MotionEvent evt, boolean downEvent, boolean useRightButton) {
+ boolean useMiddleButton = false;
+
+ if(evt.getButtonState() == MotionEvent.BUTTON_SECONDARY){
+ useRightButton = true;
+ } else if(evt.getButtonState() == MotionEvent.BUTTON_TERTIARY){
+ useMiddleButton = true;
+ }
+
+ //XXX: not reliable with laptop trackpads
+// if(Config.mouseMode == Config.MouseMode.External
+// && MotionEvent.TOOL_TYPE_FINGER == evt.getToolType(0))
+// return true;
+//
+// if(Config.mouseMode == Config.MouseMode.Trackpad
+// && MotionEvent.TOOL_TYPE_MOUSE == evt.getToolType(0))
+// return true;
+
+ return processPointerEvent((int) evt.getX(), (int) evt.getY(), evt.getAction(), evt.getMetaState(), downEvent,
+ useRightButton, useMiddleButton, false);
+ }
+
+ boolean processPointerEvent(int x, int y, int action,
+ int modifiers, boolean mouseIsDown, boolean useRightButton,
+ boolean useMiddleButton, boolean scrollUp) {
+ //Log.v("Limbo", "processPointerEvent: " + x + ", " + y + ", "
+ // + action + ", " + modifiers + ", " + mouseIsDown + ", "
+ // + useRightButton + ", " + useMiddleButton + ", " + scrollUp
+ //);
+
+ if (rfb != null && rfb.inNormalProtocol) {
+ if (action == MotionEvent.ACTION_DOWN || (mouseIsDown && action == MotionEvent.ACTION_MOVE)) {
+ if (useRightButton) {
+ // Log.v("Limbo", "Right Button Down");
+ pointerMask |= MOUSE_BUTTON_RIGHT;
+ } else if (useMiddleButton) {
+ pointerMask |= MOUSE_BUTTON_MIDDLE;
+ }else {
+ //Log.v("Limbo", "Left Button Down: x=" + x + ", y=" + y);
+ pointerMask |= MOUSE_BUTTON_LEFT;
+ }
+ } else if (action == MotionEvent.ACTION_SCROLL) {
+ // Log.v("Limbo", "Button Up");
+ if(scrollUp)
+ pointerMask |= MOUSE_BUTTON_SCROLL_UP;
+ else
+ pointerMask |= MOUSE_BUTTON_SCROLL_DOWN;
+ } else if (action == MotionEvent.ACTION_UP) {
+ // Log.v("Limbo", "Button Up");
+ //pointerMask = 0;
+ if (useRightButton) {
+ // Log.v("Limbo", "Right Button Down");
+ pointerMask &= ~MOUSE_BUTTON_RIGHT;
+ } else if (useMiddleButton) {
+ pointerMask &= ~MOUSE_BUTTON_MIDDLE;
+ }else {
+ //Log.v("Limbo", "Left Button Down: x=" + x + ", y=" + y);
+ //XXX: Mouse middle click cannot always be detected so we
+ // reset all buttons (left, middle, click) to be safe
+ pointerMask = 0;
+ }
+ }
+ bitmapData.invalidateMousePosition();
+ mouseX = x;
+ mouseY = y;
+ if (mouseX < 0) {
+ mouseX = 0;
+ } else if (mouseX >= rfb.framebufferWidth) {
+ mouseX = rfb.framebufferWidth - 1;
+ }
+ if (mouseY < 0) {
+ mouseY = 0;
+ } else if (mouseY >= rfb.framebufferHeight) {
+ mouseY = rfb.framebufferHeight - 1;
+ }
+ bitmapData.invalidateMousePosition();
+ try {
+ rfb.writePointerEvent(mouseX, mouseY, modifiers, pointerMask);
+ if (action == MotionEvent.ACTION_SCROLL) {
+ rfb.writePointerEvent(mouseX, mouseY, 0, 0);
+ pointerMask = 0;
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ panToMouse();
+ return true;
+ }
+
+ return false;
+ }
+
+ private int isSpecialKey(int key) {
+ switch (key) {
+ case '!':
+ return '1';
+ case '@':
+ return '2';
+ case '#':
+ return '3';
+ case '$':
+ return '4';
+ case '%':
+ return '5';
+ case '^':
+ return '6';
+ case '&':
+ return '7';
+ case '*':
+ return '8';
+ case '(':
+ return '9';
+ case ')':
+ return '0';
+ case '_':
+ return '-';
+ case '+':
+ return '=';
+ case '~':
+ return '`';
+ case '{':
+ return '[';
+ case '}':
+ return ']';
+ case '|':
+ return '\\';
+ case '\"':
+ return '\'';
+ case ':':
+ return ';';
+ case '<':
+ return ',';
+ case '>':
+ return '.';
+ case '?':
+ return '/';
+ default:
+ return -1;
+
+ } }
+
+ /**
+ * Moves the scroll while the volume key is held down
+ *
+ * @author Michael A. MacDonald
+ */
+ class MouseScrollRunnable implements Runnable {
+
+ int delay = 100;
+ int scrollButton = 0;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Runnable#run()
+ */
+ @Override
+ public void run() {
+ try {
+ rfb.writePointerEvent(mouseX, mouseY, 0, scrollButton);
+ rfb.writePointerEvent(mouseX, mouseY, 0, 0);
+
+ handler.postDelayed(this, delay);
+ } catch (IOException ioe) {
+ }
+ }
+ }
+
+ public synchronized boolean processLocalKeyEvent(int keyCode, KeyEvent evt) {
+ if (keyCode == KeyEvent.KEYCODE_MENU) // Ignore menu key
+ {
+ return true;
+ }
+ if (keyCode == KeyEvent.KEYCODE_CAMERA) {
+ cameraButtonDown = (evt.getAction() != KeyEvent.ACTION_UP);
+ } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
+ int mouseChange = keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ? MOUSE_BUTTON_SCROLL_DOWN
+ : MOUSE_BUTTON_SCROLL_UP;
+ if (evt.getAction() == KeyEvent.ACTION_DOWN) {
+ // If not auto-repeat
+ if (scrollRunnable.scrollButton != mouseChange) {
+ pointerMask |= mouseChange;
+ scrollRunnable.scrollButton = mouseChange;
+ handler.postDelayed(scrollRunnable, 200);
+ }
+ } else {
+ handler.removeCallbacks(scrollRunnable);
+ scrollRunnable.scrollButton = 0;
+ pointerMask &= ~mouseChange;
+ }
+ try {
+ rfb.writePointerEvent(mouseX, mouseY, evt.getMetaState(), pointerMask);
+ } catch (IOException ioe) {
+ // TODO: do something with exception
+ }
+ return true;
+ }
+ if (rfb != null && rfb.inNormalProtocol) {
+ boolean down = (evt.getAction() == KeyEvent.ACTION_DOWN);
+ int key;
+ int metaState = evt.getMetaState();
+ metaState = 0;
+ // Log.v("Key Pressed", keyCode + ", metaState = " + metaState);
+
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_BACK:
+ // key = 0xff1b;
+ return false;
+ // break;
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ key = 0xff51;
+ break;
+ case KeyEvent.KEYCODE_DPAD_UP:
+ key = 0xff52;
+ break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ key = 0xff53;
+ break;
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ key = 0xff54;
+ break;
+ case KeyEvent.KEYCODE_DEL:
+ key = 0xff08;
+ break;
+ case KeyEvent.KEYCODE_FORWARD_DEL:
+ key = MetaKeyBean.keysByKeyCode.get(KeyEvent.KEYCODE_DEL).keySym;
+ break;
+ case KeyEvent.KEYCODE_ALT_LEFT:
+ case KeyEvent.KEYCODE_ALT_RIGHT:
+ this.ALT_PRESSED = true;
+ return true;
+ case KeyEvent.KEYCODE_MOVE_HOME:
+ key = 0xFF50;
+ break;
+ case KeyEvent.KEYCODE_INSERT:
+ key = 0xFF63;
+ break;
+ case KeyEvent.KEYCODE_MOVE_END:
+ key = 0xFF57;
+ break;
+ case KeyEvent.KEYCODE_PAGE_DOWN:
+ key = 0xFF56;
+ break;
+ case KeyEvent.KEYCODE_PAGE_UP:
+ key = 0xFF55;
+ break;
+ case KeyEvent.KEYCODE_ENTER:
+ key = 0xff0d;
+ break;
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ key = 0xff0d;
+ break;
+ case KeyEvent.KEYCODE_TAB:
+ key = 0xFF09;
+ break;
+ case 111: // ESCAPE
+ key = 0xff1b;
+ break;
+ default:
+ key = evt.getUnicodeChar();
+ //Log.v("unicode", "Unicode Char for " + evt.getKeyCode() + " is " + key);
+
+ //ΧΧΧ: Workaround for some chars not recognized by QEMU
+ int specialKey = isSpecialKey(key);
+ if (specialKey != -1) {
+ key = specialKey;
+ metaState = metaState | VncCanvas.SHIFT_MASK;
+ } else if (keyCode >= 131 && keyCode <= 142) {
+ // Function Key pressed
+ key = 0xFFBE + keyCode - 131;
+ } else if (key == 0){
+ //Key is a meta combination or unknown
+ key = evt.getUnicodeChar(0);
+ }
+
+ break;
+ }
+
+ if ((evt.getMetaState() & KeyEvent.META_CTRL_ON) == KeyEvent.META_CTRL_ON) {
+ // Log.v("meta", "setting ctrl mask");
+ metaState = metaState | VncCanvas.CTRL_MASK;
+ }
+
+ if ((evt.getMetaState() & KeyEvent.META_ALT_ON) == KeyEvent.META_ALT_ON || this.ALT_PRESSED) {
+ // Log.v("meta", "setting alt mask");
+ metaState = metaState | VncCanvas.ALT_MASK;
+ }
+
+ if ((evt.getMetaState() & KeyEvent.META_SHIFT_ON) == KeyEvent.META_SHIFT_ON) {
+ // Log.v("meta", "setting shift mask");
+ metaState = metaState | VncCanvas.SHIFT_MASK;
+ }
+ try {
+ if (afterMenu) {
+ afterMenu = false;
+ if (!down && key != lastKeyDown) {
+ return true;
+ }
+ }
+ if (down) {
+ lastKeyDown = key;
+ }
+
+ rfb.writeKeyEvent(key, metaState, down);
+ this.ALT_PRESSED = false;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public void closeConnection() {
+ maintainConnection = false;
+ }
+
+ public void sendMetaKey1(int key, int flags) {
+ try {
+ rfb.writeKeyEvent(key, flags, true);
+ rfb.writeKeyEvent(key, flags, false);
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ }
+
+ }
+
+ public void sendText(String s) {
+ int l = s.length();
+ for (int i = 0; i < l; i++) {
+ char c = s.charAt(i);
+ int meta = 0;
+ int keysym = c;
+ if (Character.isISOControl(c)) {
+ if (c == '\n') {
+ keysym = MetaKeyBean.keysByKeyCode.get(KeyEvent.KEYCODE_ENTER).keySym;
+ } else {
+ continue;
+ }
+ }
+ try {
+ rfb.writeKeyEvent(keysym, meta, true);
+ rfb.writeKeyEvent(keysym, meta, false);
+ } catch (IOException ioe) {
+ // TODO: log this
+ }
+ }
+ }
+
+ void sendMetaKey(MetaKeyBean meta) {
+ if (meta.isMouseClick()) {
+ try {
+ rfb.writePointerEvent(mouseX, mouseY, meta.getMetaFlags(), meta.getMouseButtons());
+ rfb.writePointerEvent(mouseX, mouseY, meta.getMetaFlags(), 0);
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ }
+ } else {
+ try {
+ rfb.writeKeyEvent(meta.getKeySym(), meta.getMetaFlags(), true);
+ rfb.writeKeyEvent(meta.getKeySym(), meta.getMetaFlags(), false);
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ }
+ }
+ }
+
+ float getScale() {
+ if (scaling == null) {
+ return 1;
+ }
+ return scaling.getScale();
+ }
+
+ public int getVisibleWidth() {
+ return (int) ((double) getWidth() / getScale() + 0.5);
+ }
+
+ public int getVisibleHeight() {
+ return (int) ((double) getHeight() / getScale() + 0.5);
+ }
+
+ public int getImageWidth() {
+ return bitmapData.framebufferwidth;
+ }
+
+ public int getImageHeight() {
+ return bitmapData.framebufferheight;
+ }
+
+ public int getCenteredXOffset() {
+ int xoffset = (bitmapData.framebufferwidth - getWidth()) / 2;
+ return xoffset;
+ }
+
+ public int getCenteredYOffset() {
+ int yoffset = (bitmapData.framebufferheight - getHeight()) / 2;
+ return yoffset;
+ }
+
+ /**
+ * Additional Encodings
+ *
+ */
+ private void setEncodings(boolean autoSelectOnly) {
+ if (rfb == null || !rfb.inNormalProtocol) {
+ return;
+ }
+
+ if (preferredEncoding == -1) {
+ // Preferred format is ZRLE
+ preferredEncoding = RfbProto.EncodingZRLE;
+ } else {
+ // Auto encoder selection is not enabled.
+ if (autoSelectOnly) {
+ return;
+ }
+ }
+
+ int[] encodings = new int[20];
+ int nEncodings = 0;
+
+ encodings[nEncodings++] = preferredEncoding;
+ if (useCopyRect) {
+ encodings[nEncodings++] = RfbProto.EncodingCopyRect;
+ }
+ // if (preferredEncoding != RfbProto.EncodingTight)
+ // encodings[nEncodings++] = RfbProto.EncodingTight;
+ if (preferredEncoding != RfbProto.EncodingZRLE) {
+ encodings[nEncodings++] = RfbProto.EncodingZRLE;
+ }
+ if (preferredEncoding != RfbProto.EncodingHextile) {
+ encodings[nEncodings++] = RfbProto.EncodingHextile;
+ }
+ if (preferredEncoding != RfbProto.EncodingZlib) {
+ encodings[nEncodings++] = RfbProto.EncodingZlib;
+ }
+ if (preferredEncoding != RfbProto.EncodingCoRRE) {
+ encodings[nEncodings++] = RfbProto.EncodingCoRRE;
+ }
+ if (preferredEncoding != RfbProto.EncodingRRE) {
+ encodings[nEncodings++] = RfbProto.EncodingRRE;
+ }
+
+ if (compressLevel >= 0 && compressLevel <= 9) {
+ encodings[nEncodings++] = RfbProto.EncodingCompressLevel0 + compressLevel;
+ }
+ if (jpegQuality >= 0 && jpegQuality <= 9) {
+ encodings[nEncodings++] = RfbProto.EncodingQualityLevel0 + jpegQuality;
+ }
+
+ if (requestCursorUpdates) {
+ encodings[nEncodings++] = RfbProto.EncodingXCursor;
+ encodings[nEncodings++] = RfbProto.EncodingRichCursor;
+ if (!ignoreCursorUpdates) {
+ encodings[nEncodings++] = RfbProto.EncodingPointerPos;
+ }
+ }
+
+ encodings[nEncodings++] = RfbProto.EncodingLastRect;
+ encodings[nEncodings++] = RfbProto.EncodingNewFBSize;
+
+ boolean encodingsWereChanged = false;
+ if (nEncodings != nEncodingsSaved) {
+ encodingsWereChanged = true;
+ } else {
+ for (int i = 0; i < nEncodings; i++) {
+ if (encodings[i] != encodingsSaved[i]) {
+ encodingsWereChanged = true;
+ break;
+ }
+ }
+ }
+
+ if (encodingsWereChanged) {
+ try {
+ rfb.writeSetEncodings(encodings, nEncodings);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ encodingsSaved = encodings;
+ nEncodingsSaved = nEncodings;
+ }
+ }
+
+ //
+ // Handle a CopyRect rectangle.
+ //
+ final Paint handleCopyRectPaint = new Paint();
+
+ private void handleCopyRect(int x, int y, int w, int h) throws IOException {
+
+ /**
+ * This does not work properly yet.
+ */
+ rfb.readCopyRect();
+ if (!bitmapData.validDraw(x, y, w, h)) {
+ return;
+ }
+ // Source Coordinates
+ int leftSrc = rfb.copyRectSrcX;
+ int topSrc = rfb.copyRectSrcY;
+ int rightSrc = topSrc + w;
+ int bottomSrc = topSrc + h;
+
+ // Change
+ int dx = x - rfb.copyRectSrcX;
+ int dy = y - rfb.copyRectSrcY;
+
+ // Destination Coordinates
+ int leftDest = leftSrc + dx;
+ int topDest = topSrc + dy;
+ int rightDest = rightSrc + dx;
+ int bottomDest = bottomSrc + dy;
+
+ bitmapData.copyRect(new Rect(leftSrc, topSrc, rightSrc, bottomSrc),
+ new Rect(leftDest, topDest, rightDest, bottomDest), handleCopyRectPaint);
+
+ reDraw();
+ }
+
+ byte[] bg_buf = new byte[4];
+ byte[] rre_buf = new byte[128];
+
+ //
+ // Handle an RRE-encoded rectangle.
+ //
+
+ private void handleRRERect(int x, int y, int w, int h) throws IOException {
+ boolean valid = bitmapData.validDraw(x, y, w, h);
+ int nSubrects = rfb.is.readInt();
+
+ rfb.readFully(bg_buf, 0, bytesPerPixel);
+ int pixel;
+ if (bytesPerPixel == 1) {
+ pixel = colorPalette[0xFF & bg_buf[0]];
+ } else {
+ pixel = Color.rgb(bg_buf[2] & 0xFF, bg_buf[1] & 0xFF, bg_buf[0] & 0xFF);
+ }
+ handleRREPaint.setColor(pixel);
+ if (valid) {
+ bitmapData.drawRect(x, y, w, h, handleRREPaint);
+ }
+
+ int len = nSubrects * (bytesPerPixel + 8);
+ if (len > rre_buf.length) {
+ rre_buf = new byte[len];
+ }
+
+ rfb.readFully(rre_buf, 0, len);
+ if (!valid) {
+ return;
+ }
+
+ int sx, sy, sw, sh;
+
+ int i = 0;
+ for (int j = 0; j < nSubrects; j++) {
+ if (bytesPerPixel == 1) {
+ pixel = colorPalette[0xFF & rre_buf[i++]];
+ } else {
+ pixel = Color.rgb(rre_buf[i + 2] & 0xFF, rre_buf[i + 1] & 0xFF, rre_buf[i] & 0xFF);
+ i += 4;
+ }
+ sx = x + ((rre_buf[i] & 0xff) << 8) + (rre_buf[i + 1] & 0xff);
+ i += 2;
+ sy = y + ((rre_buf[i] & 0xff) << 8) + (rre_buf[i + 1] & 0xff);
+ i += 2;
+ sw = ((rre_buf[i] & 0xff) << 8) + (rre_buf[i + 1] & 0xff);
+ i += 2;
+ sh = ((rre_buf[i] & 0xff) << 8) + (rre_buf[i + 1] & 0xff);
+ i += 2;
+
+ handleRREPaint.setColor(pixel);
+ bitmapData.drawRect(sx, sy, sw, sh, handleRREPaint);
+ }
+
+ reDraw();
+ }
+
+ //
+ // Handle a CoRRE-encoded rectangle.
+ //
+ private void handleCoRRERect(int x, int y, int w, int h) throws IOException {
+ boolean valid = bitmapData.validDraw(x, y, w, h);
+ int nSubrects = rfb.is.readInt();
+
+ rfb.readFully(bg_buf, 0, bytesPerPixel);
+ int pixel;
+ if (bytesPerPixel == 1) {
+ pixel = colorPalette[0xFF & bg_buf[0]];
+ } else {
+ pixel = Color.rgb(bg_buf[2] & 0xFF, bg_buf[1] & 0xFF, bg_buf[0] & 0xFF);
+ }
+ handleRREPaint.setColor(pixel);
+ if (valid) {
+ bitmapData.drawRect(x, y, w, h, handleRREPaint);
+ }
+
+ int len = nSubrects * (bytesPerPixel + 8);
+ if (len > rre_buf.length) {
+ rre_buf = new byte[len];
+ }
+
+ rfb.readFully(rre_buf, 0, len);
+ if (!valid) {
+ return;
+ }
+
+ int sx, sy, sw, sh;
+ int i = 0;
+
+ for (int j = 0; j < nSubrects; j++) {
+ if (bytesPerPixel == 1) {
+ pixel = colorPalette[0xFF & rre_buf[i++]];
+ } else {
+ pixel = Color.rgb(rre_buf[i + 2] & 0xFF, rre_buf[i + 1] & 0xFF, rre_buf[i] & 0xFF);
+ i += 4;
+ }
+ sx = x + (rre_buf[i++] & 0xFF);
+ sy = y + (rre_buf[i++] & 0xFF);
+ sw = rre_buf[i++] & 0xFF;
+ sh = rre_buf[i++] & 0xFF;
+
+ handleRREPaint.setColor(pixel);
+ bitmapData.drawRect(sx, sy, sw, sh, handleRREPaint);
+ }
+
+ reDraw();
+ }
+
+ //
+ // Handle a Hextile-encoded rectangle.
+ //
+ // These colors should be kept between handleHextileSubrect() calls.
+ private int hextile_bg, hextile_fg;
+
+ private void handleHextileRect(int x, int y, int w, int h) throws IOException {
+
+ hextile_bg = Color.BLACK;
+ hextile_fg = Color.BLACK;
+
+ for (int ty = y; ty < y + h; ty += 16) {
+ int th = 16;
+ if (y + h - ty < 16) {
+ th = y + h - ty;
+ }
+
+ for (int tx = x; tx < x + w; tx += 16) {
+ int tw = 16;
+ if (x + w - tx < 16) {
+ tw = x + w - tx;
+ }
+
+ handleHextileSubrect(tx, ty, tw, th);
+ }
+
+ // Finished with a row of tiles, now let's show it.
+ reDraw();
+ }
+ }
+
+ //
+ // Handle one tile in the Hextile-encoded data.
+ //
+ Paint handleHextileSubrectPaint = new Paint();
+ byte[] backgroundColorBuffer = new byte[4];
+
+ private void handleHextileSubrect(int tx, int ty, int tw, int th) throws IOException {
+
+ int subencoding = rfb.is.readUnsignedByte();
+
+ // Is it a raw-encoded sub-rectangle?
+ if ((subencoding & RfbProto.HextileRaw) != 0) {
+ handleRawRect(tx, ty, tw, th, false);
+ return;
+ }
+
+ boolean valid = bitmapData.validDraw(tx, ty, tw, th);
+ // Read and draw the background if specified.
+ if (bytesPerPixel > backgroundColorBuffer.length) {
+ throw new RuntimeException("impossible colordepth");
+ }
+ if ((subencoding & RfbProto.HextileBackgroundSpecified) != 0) {
+ rfb.readFully(backgroundColorBuffer, 0, bytesPerPixel);
+ if (bytesPerPixel == 1) {
+ hextile_bg = colorPalette[0xFF & backgroundColorBuffer[0]];
+ } else {
+ hextile_bg = Color.rgb(backgroundColorBuffer[2] & 0xFF, backgroundColorBuffer[1] & 0xFF,
+ backgroundColorBuffer[0] & 0xFF);
+ }
+ }
+ handleHextileSubrectPaint.setColor(hextile_bg);
+ handleHextileSubrectPaint.setStyle(Paint.Style.FILL);
+ if (valid) {
+ bitmapData.drawRect(tx, ty, tw, th, handleHextileSubrectPaint);
+ }
+
+ // Read the foreground color if specified.
+ if ((subencoding & RfbProto.HextileForegroundSpecified) != 0) {
+ rfb.readFully(backgroundColorBuffer, 0, bytesPerPixel);
+ if (bytesPerPixel == 1) {
+ hextile_fg = colorPalette[0xFF & backgroundColorBuffer[0]];
+ } else {
+ hextile_fg = Color.rgb(backgroundColorBuffer[2] & 0xFF, backgroundColorBuffer[1] & 0xFF,
+ backgroundColorBuffer[0] & 0xFF);
+ }
+ }
+
+ // Done with this tile if there is no sub-rectangles.
+ if ((subencoding & RfbProto.HextileAnySubrects) == 0) {
+ return;
+ }
+
+ int nSubrects = rfb.is.readUnsignedByte();
+ int bufsize = nSubrects * 2;
+ if ((subencoding & RfbProto.HextileSubrectsColoured) != 0) {
+ bufsize += nSubrects * bytesPerPixel;
+ }
+ if (rre_buf.length < bufsize) {
+ rre_buf = new byte[bufsize];
+ }
+ rfb.readFully(rre_buf, 0, bufsize);
+
+ int b1, b2, sx, sy, sw, sh;
+ int i = 0;
+ if ((subencoding & RfbProto.HextileSubrectsColoured) == 0) {
+
+ // Sub-rectangles are all of the same color.
+ handleHextileSubrectPaint.setColor(hextile_fg);
+ for (int j = 0; j < nSubrects; j++) {
+ b1 = rre_buf[i++] & 0xFF;
+ b2 = rre_buf[i++] & 0xFF;
+ sx = tx + (b1 >> 4);
+ sy = ty + (b1 & 0xf);
+ sw = (b2 >> 4) + 1;
+ sh = (b2 & 0xf) + 1;
+ if (valid) {
+ bitmapData.drawRect(sx, sy, sw, sh, handleHextileSubrectPaint);
+ }
+ }
+ } else if (bytesPerPixel == 1) {
+
+ // BGR233 (8-bit color) version for colored sub-rectangles.
+ for (int j = 0; j < nSubrects; j++) {
+ hextile_fg = colorPalette[0xFF & rre_buf[i++]];
+ b1 = rre_buf[i++] & 0xFF;
+ b2 = rre_buf[i++] & 0xFF;
+ sx = tx + (b1 >> 4);
+ sy = ty + (b1 & 0xf);
+ sw = (b2 >> 4) + 1;
+ sh = (b2 & 0xf) + 1;
+ handleHextileSubrectPaint.setColor(hextile_fg);
+ if (valid) {
+ bitmapData.drawRect(sx, sy, sw, sh, handleHextileSubrectPaint);
+ }
+ }
+
+ } else {
+
+ // Full-color (24-bit) version for colored sub-rectangles.
+ for (int j = 0; j < nSubrects; j++) {
+ hextile_fg = Color.rgb(rre_buf[i + 2] & 0xFF, rre_buf[i + 1] & 0xFF, rre_buf[i] & 0xFF);
+ i += 4;
+ b1 = rre_buf[i++] & 0xFF;
+ b2 = rre_buf[i++] & 0xFF;
+ sx = tx + (b1 >> 4);
+ sy = ty + (b1 & 0xf);
+ sw = (b2 >> 4) + 1;
+ sh = (b2 & 0xf) + 1;
+ handleHextileSubrectPaint.setColor(hextile_fg);
+ if (valid) {
+ bitmapData.drawRect(sx, sy, sw, sh, handleHextileSubrectPaint);
+ }
+ }
+
+ }
+ }
+
+ //
+ // Handle a ZRLE-encoded rectangle.
+ //
+ Paint handleZRLERectPaint = new Paint();
+ int[] handleZRLERectPalette = new int[128];
+
+ private void handleZRLERect(int x, int y, int w, int h) throws Exception {
+
+ if (zrleInStream == null) {
+ zrleInStream = new ZlibInStream();
+ }
+
+ int nBytes = rfb.is.readInt();
+ if (nBytes > 64 * 1024 * 1024) {
+ throw new Exception("ZRLE decoder: illegal compressed data size");
+ }
+
+ if (zrleBuf == null || zrleBuf.length < nBytes) {
+ zrleBuf = new byte[nBytes + 4096];
+ }
+
+ rfb.readFully(zrleBuf, 0, nBytes);
+
+ zrleInStream.setUnderlying(new MemInStream(zrleBuf, 0, nBytes), nBytes);
+
+ boolean valid = bitmapData.validDraw(x, y, w, h);
+
+ for (int ty = y; ty < y + h; ty += 64) {
+
+ int th = Math.min(y + h - ty, 64);
+
+ for (int tx = x; tx < x + w; tx += 64) {
+
+ int tw = Math.min(x + w - tx, 64);
+
+ int mode = zrleInStream.readU8();
+ boolean rle = (mode & 128) != 0;
+ int palSize = mode & 127;
+
+ readZrlePalette(handleZRLERectPalette, palSize);
+
+ if (palSize == 1) {
+ int pix = handleZRLERectPalette[0];
+ int c = (bytesPerPixel == 1) ? colorPalette[0xFF & pix] : (0xFF000000 | pix);
+ handleZRLERectPaint.setColor(c);
+ handleZRLERectPaint.setStyle(Paint.Style.FILL);
+ if (valid) {
+ bitmapData.drawRect(tx, ty, tw, th, handleZRLERectPaint);
+ }
+ continue;
+ }
+
+ if (!rle) {
+ if (palSize == 0) {
+ readZrleRawPixels(tw, th);
+ } else {
+ readZrlePackedPixels(tw, th, handleZRLERectPalette, palSize);
+ }
+ } else {
+ if (palSize == 0) {
+ readZrlePlainRLEPixels(tw, th);
+ } else {
+ readZrlePackedRLEPixels(tw, th, handleZRLERectPalette);
+ }
+ }
+ if (valid) {
+ handleUpdatedZrleTile(tx, ty, tw, th);
+ }
+ }
+ }
+
+ zrleInStream.reset();
+
+ reDraw();
+ }
+
+ //
+ // Handle a Zlib-encoded rectangle.
+ //
+ byte[] handleZlibRectBuffer = new byte[128];
+
+ private void handleZlibRect(int x, int y, int w, int h) throws Exception {
+ boolean valid = bitmapData.validDraw(x, y, w, h);
+ int nBytes = rfb.is.readInt();
+
+ if (zlibBuf == null || zlibBuf.length < nBytes) {
+ zlibBuf = new byte[nBytes * 2];
+ }
+
+ rfb.readFully(zlibBuf, 0, nBytes);
+
+ if (zlibInflater == null) {
+ zlibInflater = new Inflater();
+ }
+ zlibInflater.setInput(zlibBuf, 0, nBytes);
+
+ int[] pixels = bitmapData.bitmapPixels;
+
+ if (bytesPerPixel == 1) {
+ // 1 byte per pixel. Use palette lookup table.
+ if (w > handleZlibRectBuffer.length) {
+ handleZlibRectBuffer = new byte[w];
+ }
+ int i, offset;
+ for (int dy = y; dy < y + h; dy++) {
+ zlibInflater.inflate(handleZlibRectBuffer, 0, w);
+ if (!valid) {
+ continue;
+ }
+ offset = bitmapData.offset(x, dy);
+ for (i = 0; i < w; i++) {
+ pixels[offset + i] = colorPalette[0xFF & handleZlibRectBuffer[i]];
+ }
+ }
+ } else {
+ // 24-bit color (ARGB) 4 bytes per pixel.
+ final int l = w * 4;
+ if (l > handleZlibRectBuffer.length) {
+ handleZlibRectBuffer = new byte[l];
+ }
+ int i, offset;
+ for (int dy = y; dy < y + h; dy++) {
+ zlibInflater.inflate(handleZlibRectBuffer, 0, l);
+ if (!valid) {
+ continue;
+ }
+ offset = bitmapData.offset(x, dy);
+ for (i = 0; i < w; i++) {
+ final int idx = i * 4;
+ pixels[offset + i] = (handleZlibRectBuffer[idx + 2] & 0xFF) << 16
+ | (handleZlibRectBuffer[idx + 1] & 0xFF) << 8 | (handleZlibRectBuffer[idx] & 0xFF);
+ }
+ }
+ }
+ if (!valid) {
+ return;
+ }
+ bitmapData.updateBitmap(x, y, w, h);
+
+ reDraw();
+ }
+
+ private int readPixel(InStream is) throws Exception {
+ int pix;
+ if (bytesPerPixel == 1) {
+ pix = is.readU8();
+ } else {
+ int p1 = is.readU8();
+ int p2 = is.readU8();
+ int p3 = is.readU8();
+ pix = (p3 & 0xFF) << 16 | (p2 & 0xFF) << 8 | (p1 & 0xFF);
+ }
+ return pix;
+ }
+
+ byte[] readPixelsBuffer = new byte[128];
+
+ private void readPixels(InStream is, int[] dst, int count) throws Exception {
+ if (bytesPerPixel == 1) {
+ if (count > readPixelsBuffer.length) {
+ readPixelsBuffer = new byte[count];
+ }
+ is.readBytes(readPixelsBuffer, 0, count);
+ for (int i = 0; i < count; i++) {
+ dst[i] = (int) readPixelsBuffer[i] & 0xFF;
+ }
+ } else {
+ final int l = count * 3;
+ if (l > readPixelsBuffer.length) {
+ readPixelsBuffer = new byte[l];
+ }
+ is.readBytes(readPixelsBuffer, 0, l);
+ for (int i = 0; i < count; i++) {
+ final int idx = i * 3;
+ dst[i] = ((readPixelsBuffer[idx + 2] & 0xFF) << 16 | (readPixelsBuffer[idx + 1] & 0xFF) << 8
+ | (readPixelsBuffer[idx] & 0xFF));
+ }
+ }
+ }
+
+ private void readZrlePalette(int[] palette, int palSize) throws Exception {
+ readPixels(zrleInStream, palette, palSize);
+ }
+
+ private void readZrleRawPixels(int tw, int th) throws Exception {
+ int len = tw * th;
+ if (zrleTilePixels == null || len > zrleTilePixels.length) {
+ zrleTilePixels = new int[len];
+ }
+ readPixels(zrleInStream, zrleTilePixels, tw * th); // /
+ }
+
+ private void readZrlePackedPixels(int tw, int th, int[] palette, int palSize) throws Exception {
+
+ int bppp = ((palSize > 16) ? 8 : ((palSize > 4) ? 4 : ((palSize > 2) ? 2 : 1)));
+ int ptr = 0;
+ int len = tw * th;
+ if (zrleTilePixels == null || len > zrleTilePixels.length) {
+ zrleTilePixels = new int[len];
+ }
+
+ for (int i = 0; i < th; i++) {
+ int eol = ptr + tw;
+ int b = 0;
+ int nbits = 0;
+
+ while (ptr < eol) {
+ if (nbits == 0) {
+ b = zrleInStream.readU8();
+ nbits = 8;
+ }
+ nbits -= bppp;
+ int index = (b >> nbits) & ((1 << bppp) - 1) & 127;
+ if (bytesPerPixel == 1) {
+ if (index >= colorPalette.length) {
+ Log.e(TAG, "zrlePlainRLEPixels palette lookup out of bounds " + index + " (0x"
+ + Integer.toHexString(index) + ")");
+ }
+ zrleTilePixels[ptr++] = colorPalette[0xFF & palette[index]];
+ } else {
+ zrleTilePixels[ptr++] = palette[index];
+ }
+ }
+ }
+ }
+
+ private void readZrlePlainRLEPixels(int tw, int th) throws Exception {
+ int ptr = 0;
+ int end = ptr + tw * th;
+ if (zrleTilePixels == null || end > zrleTilePixels.length) {
+ zrleTilePixels = new int[end];
+ }
+ while (ptr < end) {
+ int pix = readPixel(zrleInStream);
+ int len = 1;
+ int b;
+ do {
+ b = zrleInStream.readU8();
+ len += b;
+ } while (b == 255);
+
+ if (!(len <= end - ptr)) {
+ throw new Exception("ZRLE decoder: assertion failed" + " (len <= end-ptr)");
+ }
+
+ if (bytesPerPixel == 1) {
+ while (len-- > 0) {
+ zrleTilePixels[ptr++] = colorPalette[0xFF & pix];
+ }
+ } else {
+ while (len-- > 0) {
+ zrleTilePixels[ptr++] = pix;
+ }
+ }
+ }
+ }
+
+ private void readZrlePackedRLEPixels(int tw, int th, int[] palette) throws Exception {
+
+ int ptr = 0;
+ int end = ptr + tw * th;
+ if (zrleTilePixels == null || end > zrleTilePixels.length) {
+ zrleTilePixels = new int[end];
+ }
+ while (ptr < end) {
+ int index = zrleInStream.readU8();
+ int len = 1;
+ if ((index & 128) != 0) {
+ int b;
+ do {
+ b = zrleInStream.readU8();
+ len += b;
+ } while (b == 255);
+
+ if (!(len <= end - ptr)) {
+ throw new Exception("ZRLE decoder: assertion failed" + " (len <= end - ptr)");
+ }
+ }
+
+ index &= 127;
+ int pix = palette[index];
+
+ if (bytesPerPixel == 1) {
+ while (len-- > 0) {
+ zrleTilePixels[ptr++] = colorPalette[0xFF & pix];
+ }
+ } else {
+ while (len-- > 0) {
+ zrleTilePixels[ptr++] = pix;
+ }
+ }
+ }
+ }
+
+ //
+ // Copy pixels from zrleTilePixels8 or zrleTilePixels24, then update.
+ //
+ private void handleUpdatedZrleTile(int x, int y, int w, int h) {
+ int offsetSrc = 0;
+ int[] destPixels = bitmapData.bitmapPixels;
+ for (int j = 0; j < h; j++) {
+ System.arraycopy(zrleTilePixels, offsetSrc, destPixels, bitmapData.offset(x, y + j), w);
+ offsetSrc += w;
+ }
+
+ bitmapData.updateBitmap(x, y, w, h);
+ }
+
+ public void connected() {
+ VncCanvasActivity activity = (VncCanvasActivity) VncCanvas.this.getContext();
+ activity.onConnected();
+
+ }
+
+
+ class VNCOnTouchListener implements View.OnTouchListener {
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ // TODO Auto-generated method stub
+ //Log.i("VNCOnTouchListener", "onTouch");
+ if(Config.mouseMode == Config.MouseMode.Trackpad) {
+ return false;
+ }
+ return processPointerEvent(event, event.getAction() == MotionEvent.ACTION_DOWN);
+ }
+
+
+ }
+
+ class VNCGenericMotionListener_API12 implements View.OnGenericMotionListener {
+ private VncCanvas mSurface;
+
+ // Generic Motion (mouse hover, joystick...) events go here
+ @Override
+ public boolean onGenericMotion(View v, MotionEvent event) {
+ float x, y;
+ int action;
+
+ switch (event.getSource()) {
+ case InputDevice.SOURCE_JOYSTICK:
+ case InputDevice.SOURCE_GAMEPAD:
+ case InputDevice.SOURCE_DPAD:
+ return true;
+ case InputDevice.SOURCE_MOUSE:
+ if(Config.mouseMode == Config.MouseMode.Trackpad)
+ break;
+
+ action = event.getActionMasked();
+ //Log.d("SDL", "onGenericMotion, action = " + action + "," + event.getX() + ", " + event.getY());
+ switch (action) {
+ case MotionEvent.ACTION_SCROLL:
+ x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
+ y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
+ //Log.d("SDL", "Mouse Scroll: " +event.getX() + ":" + event.getY() + " => " + x + "," + y);
+
+ //TODO:
+ //SDLActivity.onSDLNativeMouse(0, action, x, y);
+ //processPointerEvent(event,false);
+
+ // Log.v("Limbo", "Button Up");
+ boolean scrollUp=false;
+ if (y > 0)
+ scrollUp = true;
+ else if (y < 0)
+ scrollUp = false;
+
+ return processPointerEvent((int) event.getX(), (int) event.getY(), event.getAction(), event.getMetaState(), false,
+ false, false, scrollUp);
+ //return true;
+
+ case MotionEvent.ACTION_HOVER_MOVE:
+ if(Config.processMouseHistoricalEvents) {
+ final int historySize = event.getHistorySize();
+ for (int h = 0; h < historySize; h++) {
+ float ex = event.getHistoricalX(h);
+ float ey = event.getHistoricalY(h);
+ float ep = event.getHistoricalPressure(h);
+ processHoverMouse(event, ex, ey, ep, action);
+ }
+ }
+
+ float ex = event.getX();
+ float ey = event.getY();
+ float ep = event.getPressure();
+ processHoverMouse(event, ex, ey, ep, action);
+ return true;
+
+ case MotionEvent.ACTION_UP:
+
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ // Event was not managed
+ return false;
+ }
+
+ private void processHoverMouse(MotionEvent event, float x,float y,float p, int action) {
+
+ //Log.d("VncCanvas", "Mouse Hover: " + x + "," + y);
+
+ if(Config.mouseMode == Config.MouseMode.External) {
+
+// float x_margin = (SDLActivity.width - LimboSDLActivity.vm_width * LimboSDLActivity.height / (float) LimboSDLActivity.vm_height) / 2;
+// if (x < x_margin) {
+// return;
+// } else if (x > SDLActivity.width - x_margin) {
+// return;
+// }
+
+ //TODO:
+ //SDLActivity.onSDLNativeMouse(0, action, x, y);
+ processPointerEvent(event, false, false);
+ }
+// else if (Config.mouseMode == Config.MouseMode.External_Alt){
+// processHoverMouseAlt(x, y, p, action);
+// }
+ }
+
+ }
+}
diff --git a/app/src/main/java/android/androidVNC/VncCanvasActivity.java b/app/src/main/java/android/androidVNC/VncCanvasActivity.java
new file mode 100644
index 0000000..f36ffd3
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/VncCanvasActivity.java
@@ -0,0 +1,1854 @@
+/*
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+//
+// CanvasView is the Activity for showing VNC Desktop.
+//
+package android.androidVNC;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnDismissListener;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.PointF;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.Display;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.Toast;
+import android.widget.ZoomControls;
+
+import com.antlersoft.android.bc.BCFactory;
+import com.vectras.qemu.Config;
+import com.vectras.qemu.MainActivityCommon;
+import com.vectras.vm.R;
+import com.vectras.vm.utils.UIUtils;
+
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+public class VncCanvasActivity extends AppCompatActivity {
+
+ static Display display = null;
+ public static Activity activity;
+
+ /**
+ * @author Michael A. MacDonald
+ */
+ class ZoomInputHandler extends AbstractGestureInputHandler {
+
+ /**
+ * In drag mode (entered with long press) you process mouse events
+ * without sending them through the gesture detector
+ */
+ private boolean dragMode;
+ /**
+ * Key handler delegate that handles DPad-based mouse motion
+ */
+ private DPadMouseKeyHandler keyHandler;
+
+ /**
+ * @param c
+ */
+ ZoomInputHandler() {
+ super(VncCanvasActivity.this);
+ keyHandler = new DPadMouseKeyHandler(VncCanvasActivity.this, vncCanvas.handler);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#getHandlerDescription()
+ */
+ @Override
+ public CharSequence getHandlerDescription() {
+ return getResources().getString(R.string.input_mode_touch_pan_zoom_mouse);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#getName()
+ */
+ @Override
+ public String getName() {
+ return TOUCH_ZOOM_MODE;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * android.androidVNC.VncCanvasActivity.ZoomInputHandler#onKeyDown(int,
+ * android.view.KeyEvent)
+ */
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent evt) {
+ return keyHandler.onKeyDown(keyCode, evt);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * android.androidVNC.VncCanvasActivity.ZoomInputHandler#onKeyUp(int,
+ * android.view.KeyEvent)
+ */
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent evt) {
+ return keyHandler.onKeyUp(keyCode, evt);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * android.androidVNC.AbstractInputHandler#onTrackballEvent(android.
+ * view.MotionEvent)
+ */
+ @Override
+ public boolean onTrackballEvent(MotionEvent evt) {
+ return trackballMouse(evt);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * android.view.GestureDetector.SimpleOnGestureListener#onDown(android
+ * .view.MotionEvent)
+ */
+ @Override
+ public boolean onDown(MotionEvent e) {
+ panner.stop();
+ return true;
+ }
+
+ /**
+ * Divide stated fling velocity by this amount to get initial velocity
+ * per pan interval
+ */
+ static final float FLING_FACTOR = 8;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * android.view.GestureDetector.SimpleOnGestureListener#onFling(android
+ * .view.MotionEvent, android.view.MotionEvent, float, float)
+ */
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+ showZoomer(false);
+ panner.start(-(velocityX / FLING_FACTOR), -(velocityY / FLING_FACTOR), new Panner.VelocityUpdater() {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.Panner.VelocityUpdater#updateVelocity
+ * (android.graphics.Point, long)
+ */
+ @Override
+ public boolean updateVelocity(PointF p, long interval) {
+ double scale = Math.pow(0.8, interval / 50.0);
+ p.x *= scale;
+ p.y *= scale;
+ return (Math.abs(p.x) > 0.5 || Math.abs(p.y) > 0.5);
+ }
+ });
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * android.androidVNC.AbstractGestureInputHandler#onTouchEvent(android
+ * .view.MotionEvent)
+ */
+ @Override
+ public boolean onTouchEvent(MotionEvent e) {
+ // MK
+ if (e.getAction() == MotionEvent.ACTION_CANCEL)
+ return true;
+
+ if (dragMode) {
+ vncCanvas.changeTouchCoordinatesToFullFrame(e);
+ if (e.getAction() == MotionEvent.ACTION_UP) {
+ dragMode = false;
+ }
+ return vncCanvas.processPointerEvent(e, true);
+ } else {
+ return super.onTouchEvent(e);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * android.view.GestureDetector.SimpleOnGestureListener#onLongPress(
+ * android.view.MotionEvent)
+ */
+ @Override
+ public void onLongPress(MotionEvent e) {
+// showZoomer(true);
+// BCFactory.getInstance().getBCHaptic().performLongPressHaptic(vncCanvas);
+// dragMode = true;
+// vncCanvas.processPointerEvent(vncCanvas.changeTouchCoordinatesToFullFrame(e), true);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * android.view.GestureDetector.SimpleOnGestureListener#onScroll(android
+ * .view.MotionEvent, android.view.MotionEvent, float, float)
+ */
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+ if (inScaling) {
+ return false;
+ }
+ showZoomer(false);
+ return vncCanvas.pan((int) distanceX, (int) distanceY);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.view.GestureDetector.SimpleOnGestureListener#
+ * onSingleTapConfirmed (android.view.MotionEvent)
+ */
+ @Override
+ public boolean onSingleTapConfirmed(MotionEvent e) {
+ vncCanvas.changeTouchCoordinatesToFullFrame(e);
+ vncCanvas.processPointerEvent(e, true);
+ e.setAction(MotionEvent.ACTION_UP);
+ return vncCanvas.processPointerEvent(e, false);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * android.view.GestureDetector.SimpleOnGestureListener#onDoubleTap(
+ * android.view.MotionEvent)
+ */
+ @Override
+ public boolean onDoubleTap(MotionEvent e) {
+ vncCanvas.changeTouchCoordinatesToFullFrame(e);
+ vncCanvas.processPointerEvent(e, true, true);
+ e.setAction(MotionEvent.ACTION_UP);
+ return vncCanvas.processPointerEvent(e, false, true);
+ }
+ }
+
+ public class TouchpadInputHandler extends AbstractGestureInputHandler {
+
+ /**
+ * In drag mode (entered with long press) you process mouse events
+ * without sending them through the gesture detector
+ */
+ private boolean dragMode;
+ float dragX, dragY;
+ /**
+ * Key handler delegate that handles DPad-based mouse motion
+ */
+ private DPadMouseKeyHandler keyHandler;
+
+ TouchpadInputHandler() {
+ super(VncCanvasActivity.this);
+ keyHandler = new DPadMouseKeyHandler(VncCanvasActivity.this, vncCanvas.handler);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#getHandlerDescription()
+ */
+ @Override
+ public CharSequence getHandlerDescription() {
+ return getResources().getString(R.string.input_mode_touchpad);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#getName()
+ */
+ @Override
+ public String getName() {
+ return TOUCHPAD_MODE;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * android.androidVNC.VncCanvasActivity.ZoomInputHandler#onKeyDown(int,
+ * android.view.KeyEvent)
+ */
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent evt) {
+ return keyHandler.onKeyDown(keyCode, evt);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * android.androidVNC.VncCanvasActivity.ZoomInputHandler#onKeyUp(int,
+ * android.view.KeyEvent)
+ */
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent evt) {
+ return keyHandler.onKeyUp(keyCode, evt);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * android.androidVNC.AbstractInputHandler#onTrackballEvent(android.
+ * view.MotionEvent)
+ */
+ @Override
+ public boolean onTrackballEvent(MotionEvent evt) {
+ return trackballMouse(evt);
+ }
+
+ /**
+ * scale down delta when it is small. This will allow finer control when
+ * user is making a small movement on touch screen. Scale up delta when
+ * delta is big. This allows fast mouse movement when user is flinging.
+ *
+ * @param deltaX
+ * @return
+ */
+ private float fineCtrlScale(float delta) {
+ float sign = (delta > 0) ? 1 : -1;
+ delta = Math.abs(delta);
+ if (delta >= 1 && delta <= 3) {
+ delta = 1;
+ } else if (delta <= 10) {
+ delta *= 0.34;
+ } else if (delta <= 30) {
+ delta *= delta / 30;
+ } else if (delta <= 90) {
+ delta *= (delta / 30);
+ } else {
+ delta *= 3.0;
+ }
+ return sign * delta;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * android.view.GestureDetector.SimpleOnGestureListener#onLongPress(
+ * android.view.MotionEvent)
+ */
+ @Override
+ public void onLongPress(MotionEvent e) {
+ if(Config.enableDragOnLongPress)
+ dragPointer(e);
+ }
+
+ private void dragPointer(MotionEvent e) {
+
+ showZoomer(true);
+ BCFactory.getInstance().getBCHaptic().performLongPressHaptic(vncCanvas);
+ dragMode = true;
+ dragX = e.getX();
+ dragY = e.getY();
+ // send a mouse down event to the remote without moving the mouse.
+ remoteMouseStayPut(e);
+ vncCanvas.processPointerEvent(e, true);
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * android.view.GestureDetector.SimpleOnGestureListener#onScroll(android
+ * .view.MotionEvent, android.view.MotionEvent, float, float)
+ */
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+ //LIMBO: Disable this for now
+// if (BCFactory.getInstance().getBCMotionEvent().getPointerCount(e2) > 1) {
+// if (inScaling) {
+// return false;
+// }
+// showZoomer(true);
+// return vncCanvas.pan((int) distanceX, (int) distanceY);
+// } else {
+ // compute the relative movement offset on the remote screen.
+ float deltaX = -distanceX * vncCanvas.getScale();
+ float deltaY = -distanceY * vncCanvas.getScale();
+ deltaX = fineCtrlScale(deltaX);
+ deltaY = fineCtrlScale(deltaY);
+
+ // compute the absolution new mouse pos on the remote site.
+ float newRemoteX = vncCanvas.mouseX + deltaX;
+ float newRemoteY = vncCanvas.mouseY + deltaY;
+
+ if (dragMode) {
+ if (e2.getAction() == MotionEvent.ACTION_UP) {
+ dragMode = false;
+ }
+ dragX = e2.getX();
+ dragY = e2.getY();
+ e2.setLocation(newRemoteX, newRemoteY);
+ return vncCanvas.processPointerEvent(e2, true);
+ } else {
+ e2.setLocation(newRemoteX, newRemoteY);
+ vncCanvas.processPointerEvent(e2, false);
+ }
+// }
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * android.androidVNC.AbstractGestureInputHandler#onTouchEvent(android
+ * .view.MotionEvent)
+ */
+ @Override
+ public boolean onTouchEvent(MotionEvent e) {
+
+ // MK
+ if (e.getAction() == MotionEvent.ACTION_CANCEL)
+ return true;
+
+ if (Config.mouseMode == Config.MouseMode.External) {
+ return true;
+ }
+ // if (e.getPointerCount() > 1) {
+ // // Log.v("Limbo", "Detected 2 finger tap in onTouchEvent");
+ // rightClick(e);
+ // return true;
+ // }
+
+ // compute the relative movement offset on the remote screen.
+ float deltaX = (e.getX() - dragX) * vncCanvas.getScale();
+ float deltaY = (e.getY() - dragY) * vncCanvas.getScale();
+ dragX = e.getX();
+ dragY = e.getY();
+ deltaX = fineCtrlScale(deltaX);
+ deltaY = fineCtrlScale(deltaY);
+
+ // compute the absolution new mouse pos on the remote site.
+ float newRemoteX = vncCanvas.mouseX + deltaX;
+ float newRemoteY = vncCanvas.mouseY + deltaY;
+
+ if (dragMode) {
+ boolean down = false;
+ if (e.getAction() == MotionEvent.ACTION_UP) {
+ dragMode = false;
+ } else if (e.getAction() == MotionEvent.ACTION_DOWN) {
+ down = true;
+ }
+
+ e.setLocation(newRemoteX, newRemoteY);
+ vncCanvas.processPointerEvent(e, down);
+ return super.onTouchEvent(e);
+
+ } else if (!Config.enableDragOnLongPress && e.getAction() == MotionEvent.ACTION_MOVE) {
+ e.setLocation(newRemoteX, newRemoteY);
+ return vncCanvas.processPointerEvent(e, false);
+ } else {
+ return super.onTouchEvent(e);
+ }
+ }
+
+ public boolean rightClick(final MotionEvent e) {
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ remoteMouseStayPut(e);
+ // One
+ // Log.v("Double Click", "One");
+ vncCanvas.processPointerEvent(e, true, true);
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException ex) {
+ Logger.getLogger(VncCanvasActivity.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ e.setAction(MotionEvent.ACTION_UP);
+ vncCanvas.processPointerEvent(e, false, true);
+ }
+ });
+ // t.setPriority(Thread.MAX_PRIORITY);
+ t.start();
+ return true;
+
+ }
+
+ /**
+ * Modify the event so that it does not move the mouse on the remote
+ * server.
+ *
+ * @param e
+ */
+ private void remoteMouseStayPut(MotionEvent e) {
+ e.setLocation(vncCanvas.mouseX, vncCanvas.mouseY);
+
+ }
+
+ /*
+ * (non-Javadoc) confirmed single tap: do a single mouse click on remote
+ * without moving the mouse.
+ *
+ * @see android.view.GestureDetector.SimpleOnGestureListener#
+ * onSingleTapConfirmed (android.view.MotionEvent)
+ */
+
+ @Override
+ public boolean onSingleTapConfirmed(MotionEvent e) {
+ singleClick(e);
+ return true;
+ // boolean multiTouch =
+ // (BCFactory.getInstance().getBCMotionEvent().getPointerCount(e) >
+ // 1);
+ // remoteMouseStayPut(e);
+ // vncCanvas.processPointerEvent(e, true, multiTouch ||
+ // vncCanvas.cameraButtonDown);
+ // e.setAction(MotionEvent.ACTION_UP);
+ // return vncCanvas.processPointerEvent(e, false, multiTouch ||
+ // vncCanvas.cameraButtonDown);
+ }
+
+ private boolean singleClick(final MotionEvent e) {
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ remoteMouseStayPut(e);
+ // One
+ // Log.v("Double Click", "One");
+ vncCanvas.processPointerEvent(e, true, false);
+ try {
+ Thread.sleep(50);
+ } catch (InterruptedException ex) {
+ Logger.getLogger(VncCanvasActivity.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ e.setAction(MotionEvent.ACTION_UP);
+ vncCanvas.processPointerEvent(e, false, false);
+ }
+ });
+ // t.setPriority(Thread.MAX_PRIORITY);
+ t.start();
+ return true;
+
+ }
+
+ private boolean middleClick(final MotionEvent e) {
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ remoteMouseStayPut(e);
+ // One
+ // Log.v("Double Click", "One");
+ //vncCanvas.processPointerEvent(e, true, false);
+ vncCanvas.processPointerEvent((int) e.getX(), (int) e.getY(), e.getAction(), 0, true, false, true, false);
+ try {
+ Thread.sleep(50);
+ } catch (InterruptedException ex) {
+ Logger.getLogger(VncCanvasActivity.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ e.setAction(MotionEvent.ACTION_UP);
+ //vncCanvas.processPointerEvent(e, false, false);
+ vncCanvas.processPointerEvent((int) e.getX(), (int) e.getY(), e.getAction(), 0, false, false, true, false);
+ }
+ });
+ // t.setPriority(Thread.MAX_PRIORITY);
+ t.start();
+ return true;
+
+ }
+
+ /*
+ * (non-Javadoc) double tap: do two left mouse right mouse clicks on
+ * remote without moving the mouse.
+ *
+ * @see
+ * android.view.GestureDetector.SimpleOnGestureListener#onDoubleTap(
+ * android.view.MotionEvent)
+ */
+ @Override
+ public boolean onDoubleTap(MotionEvent e) {
+ if(!Config.enableDragOnLongPress)
+ processDoubleTap(e);
+ else
+ doubleClick(e);
+ return false;
+
+ }
+
+ private void processDoubleTap(final MotionEvent e) {
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ try {
+ Thread.sleep(400);
+ } catch (InterruptedException e1) {
+ e1.printStackTrace();
+ }
+
+ if (vncCanvas.mouseDown) {
+// panner.stop();
+ dragPointer(e);
+ } else
+ doubleClick(e);
+
+ }
+ });
+ t.start();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * android.view.GestureDetector.SimpleOnGestureListener#onDown(android
+ * .view.MotionEvent)
+ */
+ @Override
+ public boolean onDown(MotionEvent e) {
+ panner.stop();
+ return true;
+ }
+
+ private Object doubleClickLock = new Object();
+
+ private boolean doubleClick(final MotionEvent e1) {
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ synchronized (doubleClickLock) {
+
+ //XXX: We make a copy of the event because we have some
+ // race condition here updating mouseX, mouseY
+ MotionEvent event = MotionEvent.obtain(e1.getDownTime(),
+ e1.getEventTime(), e1.getAction(),
+ e1.getX(), e1.getY(), e1.getMetaState());
+
+ remoteMouseStayPut(event);
+ // One
+ // Log.v("Double Click", "One");
+ vncCanvas.processPointerEvent(event, true, false);
+ try {
+ Thread.sleep(50);
+ } catch (InterruptedException ex) {
+ Logger.getLogger(VncCanvasActivity.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ event.setAction(MotionEvent.ACTION_UP);
+ vncCanvas.processPointerEvent(event, false, false);
+ try {
+ Thread.sleep(50);
+ } catch (InterruptedException ex) {
+ Logger.getLogger(VncCanvasActivity.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ // Two
+ // Log.v("Double Click", "Two");
+ event.setAction(MotionEvent.ACTION_DOWN);
+ vncCanvas.processPointerEvent(event, true, false);
+ try {
+ Thread.sleep(50);
+ } catch (InterruptedException ex) {
+ Logger.getLogger(VncCanvasActivity.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ event.setAction(MotionEvent.ACTION_UP);
+ vncCanvas.processPointerEvent(event, false, false);
+ }
+ }
+ });
+ // t.setPriority(Thread.MAX_PRIORITY);
+ t.start();
+ return true;
+
+ }
+ }
+
+ private final static String TAG = "VncCanvasActivity";
+ public AbstractInputHandler inputHandler;
+ public VncCanvas vncCanvas;
+
+ public MenuItem[] inputModeMenuItems;
+ public AbstractInputHandler inputModeHandlers[];
+ public ConnectionBean connection;
+ public boolean trackballButtonDown;
+ public static final int inputModeIds[] = { R.id.itemInputFitToScreen, R.id.itemInputTouchpad, R.id.itemInputMouse,
+ R.id.itemInputPan, R.id.itemInputTouchPanTrackballMouse, R.id.itemInputDPadPanTouchMouse,
+ R.id.itemInputTouchPanZoomMouse };
+ ZoomControls zoomer;
+ Panner panner;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+
+ super.onCreate(icicle);
+ activity = this;
+
+ Intent i = getIntent();
+ connection = new ConnectionBean();
+ Uri data = i.getData();
+ if ((data != null) && (data.getScheme().equals("vnc"))) {
+ String host = data.getHost();
+ // This should not happen according to Uri contract, but bug
+ // introduced in Froyo (2.2)
+ // has made this parsing of host necessary
+ int index = host.indexOf(':');
+ int port;
+ if (index != -1) {
+ try {
+ port = Integer.parseInt(host.substring(index + 1));
+ } catch (NumberFormatException nfe) {
+ port = 0;
+ }
+ host = host.substring(0, index);
+ } else {
+ port = data.getPort();
+ }
+ if (host.equals(VncConstants.CONNECTION)) {
+ ConnectionBean bean = new ConnectionBean();
+ if (bean != null) {
+ bean.setConnectionId(connection.get_Id());
+ }
+ } else {
+ connection.setAddress(host);
+ connection.setNickname(connection.getAddress());
+ connection.setPort(port);
+ List path = data.getPathSegments();
+ if (path.size() >= 1) {
+ connection.setColorModel(path.get(1));
+ }
+ if (path.size() >= 2) {
+ connection.setPassword(path.get(1));
+ }
+ }
+ } else {
+
+ Bundle extras = i.getExtras();
+
+ if (connection.getPort() == 0) {
+ connection.setPort(5901);
+ }
+
+ // Parse a HOST:PORT entry
+ String host = connection.getAddress();
+ // if (host.indexOf(':') > -1) {
+ // String p = host.substring(host.indexOf(':') + 1);
+ // try {
+ // connection.setPort(Integer.parseInt(p));
+ // } catch (Exception e) {
+ // }
+ // connection.setAddress(host.substring(0, host.indexOf(':')));
+ // }
+ }
+ connection.setPassword(MainActivityCommon.getVnc_passwd());
+ setContentView();
+
+ vncCanvas = (VncCanvas) findViewById(R.id.vnc_canvas);
+ zoomer = (ZoomControls) findViewById(R.id.zoomer);
+
+ vncCanvas.initializeVncCanvas(connection, new Runnable() {
+ public void run() {
+ setModes();
+ }
+ });
+ zoomer.hide();
+ zoomer.setOnZoomInClickListener(new View.OnClickListener() {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.view.View.OnClickListener#onClick(android.view.View)
+ */
+ @Override
+ public void onClick(View v) {
+ showZoomer(true);
+ vncCanvas.scaling.zoomIn(VncCanvasActivity.this);
+
+ }
+ });
+ zoomer.setOnZoomOutClickListener(new View.OnClickListener() {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.view.View.OnClickListener#onClick(android.view.View)
+ */
+ @Override
+ public void onClick(View v) {
+ showZoomer(true);
+ vncCanvas.scaling.zoomOut(VncCanvasActivity.this);
+
+ }
+ });
+ zoomer.setOnZoomInClickListener(new View.OnClickListener() {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.view.View.OnClickListener#onClick(android.view.View)
+ */
+ @Override
+ public void onClick(View v) {
+ InputMethodManager inputMgr = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+ inputMgr.toggleSoftInput(0, 0);
+ }
+ });
+ zoomer.setOnZoomOutClickListener(new View.OnClickListener() {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.view.View.OnClickListener#onClick(android.view.View)
+ */
+ @Override
+ public void onClick(View v) {
+ InputMethodManager inputMgr = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+ inputMgr.toggleSoftInput(0, 0);
+ }
+ });
+ panner = new Panner(this, vncCanvas.handler);
+
+ inputHandler = getInputHandlerById(R.id.itemInputFitToScreen);
+
+ display = ((WindowManager) this.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
+ }
+
+ public void setContentView() {
+ setContentView(R.layout.activity_vnc);
+ }
+
+ /**
+ * Set modes on start to match what is specified in the ConnectionBean;
+ * color mode (already done) scaling, input mode
+ */
+ void setModes() {
+ AbstractInputHandler handler = getInputHandlerByName(connection.getInputMode());
+ AbstractScaling.getByScaleType(connection.getScaleMode()).setScaleTypeForActivity(this);
+ this.inputHandler = handler;
+ showPanningState();
+ }
+
+ ConnectionBean getConnection() {
+ return connection;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.app.Activity#onPrepareDialog(int, android.app.Dialog)
+ */
+ @Override
+ protected void onPrepareDialog(int id, Dialog dialog) {
+ super.onPrepareDialog(id, dialog);
+ if (dialog instanceof ConnectionSettable) {
+ ((ConnectionSettable) dialog).setConnection(connection);
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ // ignore orientation/keyboard change
+ super.onConfigurationChanged(newConfig);
+ }
+
+ @Override
+ protected void onStop() {
+ vncCanvas.disableRepaints();
+ super.onStop();
+ }
+
+ @Override
+ protected void onRestart() {
+ vncCanvas.enableRepaints();
+ super.onRestart();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.vnccanvasactivitymenu, menu);
+
+ if (vncCanvas.scaling != null) {
+ menu.findItem(vncCanvas.scaling.getId()).setChecked(true);
+ }
+
+ Menu inputMenu = menu.findItem(R.id.itemInputMode).getSubMenu();
+
+ inputModeMenuItems = new MenuItem[inputModeIds.length];
+ for (int i = 0; i < inputModeIds.length; i++) {
+ inputModeMenuItems[i] = inputMenu.findItem(inputModeIds[i]);
+ }
+ updateInputMenu();
+ return true;
+ }
+
+ /**
+ * Change the input mode sub-menu to reflect change in scaling
+ */
+ public void updateInputMenu() {
+ if (inputModeMenuItems == null || vncCanvas.scaling == null) {
+ return;
+ }
+ for (MenuItem item : inputModeMenuItems) {
+ item.setEnabled(vncCanvas.scaling.isValidInputMode(item.getItemId()));
+ if (getInputHandlerById(item.getItemId()) == inputHandler) {
+ item.setChecked(true);
+ }
+ }
+ }
+
+
+
+ /**
+ * If id represents an input handler, return that; otherwise return null
+ *
+ * @param id
+ * @return
+ */
+ public AbstractInputHandler getInputHandlerById(int id) {
+ if (inputModeHandlers == null) {
+ inputModeHandlers = new AbstractInputHandler[inputModeIds.length];
+ }
+ for (int i = 0; i < inputModeIds.length; ++i) {
+ if (inputModeIds[i] == id) {
+ if (inputModeHandlers[i] == null) {
+ if (id == R.id.itemInputFitToScreen)
+ inputModeHandlers[i] = new FitToScreenMode();
+ else if (id == R.id.itemInputPan)
+ inputModeHandlers[i] = new PanMode();
+ else if (id == R.id.itemInputMouse)
+ inputModeHandlers[i] = new MouseMode();
+
+ else if (id == R.id.itemInputTouchPanTrackballMouse)
+ inputModeHandlers[i] = new TouchPanTrackballMouse();
+ else if (id == R.id.itemInputDPadPanTouchMouse)
+ inputModeHandlers[i] = new DPadPanTouchMouseMode();
+
+ else if (id == R.id.itemInputTouchPanZoomMouse)
+ inputModeHandlers[i] = new ZoomInputHandler();
+
+ else if (id == R.id.itemInputTouchpad)
+ inputModeHandlers[i] = new TouchpadInputHandler();
+ }
+
+ return inputModeHandlers[i];
+ }
+ }
+ return null;
+ }
+
+ AbstractInputHandler getInputHandlerByName(String name) {
+ AbstractInputHandler result = null;
+ for (int id : inputModeIds) {
+ AbstractInputHandler handler = getInputHandlerById(id);
+ if (handler.getName().equals(name)) {
+ result = handler;
+ break;
+ }
+ }
+ if (result == null) {
+ result = getInputHandlerById(R.id.itemInputTouchPanZoomMouse);
+ }
+ return result;
+ }
+
+ int getModeIdFromHandler(AbstractInputHandler handler) {
+ for (int id : inputModeIds) {
+ if (handler == getInputHandlerById(id)) {
+ return id;
+ }
+ }
+ return R.id.itemInputTouchPanZoomMouse;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ vncCanvas.afterMenu = true;
+ if (item.getItemId() == R.id.itemSpecialKeys) {
+ showDialog(R.layout.metakey);
+ return true;
+ }else if (item.getItemId() == R.id.itemColorMode) {
+ selectColorModel();
+ return true;
+ }
+ // Following sets one of the scaling options
+ else if (item.getItemId() == R.id.itemZoomable ||
+ item.getItemId() == R.id.itemOneToOne ||
+ item.getItemId() == R.id.itemFitToScreen) {
+ AbstractScaling.getById(item.getItemId()).setScaleTypeForActivity(this);
+ item.setChecked(true);
+ showPanningState();
+ return true;
+ }
+ // case R.id.itemCenterMouse:
+ // vncCanvas.warpMouse(vncCanvas.absoluteXPosition
+ // + vncCanvas.getVisibleWidth() / 2,
+ // vncCanvas.absoluteYPosition + vncCanvas.getVisibleHeight()
+ // / 2);
+ // return true;
+ else if (item.getItemId() == R.id.itemReconnect){
+ vncCanvas.closeConnection();
+ vncCanvas.reload();
+ return true;
+ } else if (item.getItemId() == R.id.itemDisconnect){
+ vncCanvas.closeConnection();
+ finish();
+ return true;
+ } else if (item.getItemId() == R.id.itemEnterText){
+ showDialog(R.layout.entertext);
+ return true;
+ }else if (item.getItemId() == R.id.itemCtrlC) {
+ vncCanvas.sendMetaKey(MetaKeyBean.keyCtrlC);
+ return true;
+ }else if (item.getItemId() == R.id.itemCtrlAltDel){
+ vncCanvas.sendMetaKey(MetaKeyBean.keyCtrlAltDel);
+ return true;
+ }
+ // case R.id.itemFollowMouse:
+ // vncCanvas.panToMouse();
+ // return true;
+ // case R.id.itemFollowPan:
+ // return true;
+ // case R.id.itemArrowLeft:
+ // vncCanvas.sendMetaKey(MetaKeyBean.keyArrowLeft);
+ // return true;
+ // case R.id.itemArrowUp:
+ // vncCanvas.sendMetaKey(MetaKeyBean.keyArrowUp);
+ // return true;
+ // case R.id.itemArrowRight:
+ // vncCanvas.sendMetaKey(MetaKeyBean.keyArrowRight);
+ // return true;
+ // case R.id.itemArrowDown:
+ // vncCanvas.sendMetaKey(MetaKeyBean.keyArrowDown);
+ // return true;
+ else if (item.getItemId() == R.id.itemSendKeyAgain) {
+ return true;
+ }else if (item.getItemId() == R.id.itemOpenDoc){
+ Utils.showDocumentation(this);
+ return true;
+ }else {
+ AbstractInputHandler input = getInputHandlerById(item.getItemId());
+ if (input != null) {
+ inputHandler = input;
+ connection.setInputMode(input.getName());
+ item.setChecked(true);
+ showPanningState();
+ return true;
+ }
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private MetaKeyBean lastSentKey;
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (isFinishing()) {
+ vncCanvas.closeConnection();
+ vncCanvas.onDestroy();
+
+
+ }
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent evt) {
+ if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
+ MotionEvent e = MotionEvent.obtain(1000, 1000, MotionEvent.ACTION_DOWN, vncCanvas.mouseX, vncCanvas.mouseY,
+ 0);
+ ((TouchpadInputHandler) this.inputHandler).rightClick(e);
+ return true;
+ } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
+ MotionEvent e = MotionEvent.obtain(1000, 1000, MotionEvent.ACTION_DOWN, vncCanvas.mouseX, vncCanvas.mouseY,
+ 0);
+ ((TouchpadInputHandler) this.inputHandler).middleClick(e);
+ return true;
+ } else if (keyCode == KeyEvent.KEYCODE_MENU) {
+ return super.onKeyDown(keyCode, evt);
+ }
+
+ return inputHandler.onKeyDown(keyCode, evt);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent evt) {
+ if (keyCode == KeyEvent.KEYCODE_MENU) {
+ return super.onKeyUp(keyCode, evt);
+ }
+
+ return inputHandler.onKeyUp(keyCode, evt);
+ }
+
+ public void showPanningState() {
+ // Toast.makeText(this, inputHandler.getHandlerDescription(),
+ // Toast.LENGTH_SHORT).show();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.app.Activity#onTrackballEvent(android.view.MotionEvent)
+ */
+ @Override
+ public boolean onTrackballEvent(MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ trackballButtonDown = true;
+ break;
+ case MotionEvent.ACTION_UP:
+ trackballButtonDown = false;
+ break;
+ }
+ return inputHandler.onTrackballEvent(event);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ // MK
+ if (event.getAction() == MotionEvent.ACTION_CANCEL)
+ return true;
+
+ if(event.getAction() == MotionEvent.ACTION_DOWN)
+ vncCanvas.mouseDown = true;
+ else if(event.getAction() == MotionEvent.ACTION_UP)
+ vncCanvas.mouseDown = false;
+
+ return inputHandler.onTouchEvent(event);
+ }
+
+ protected void selectColorModel() {
+ // Stop repainting the desktop
+ // because the display is composited!
+ vncCanvas.disableRepaints();
+
+ String[] choices = new String[COLORMODEL.values().length];
+ int currentSelection = -1;
+ for (int i = 0; i < choices.length; i++) {
+ COLORMODEL cm = COLORMODEL.values()[i];
+ choices[i] = cm.toString();
+ if (vncCanvas.isColorModel(cm)) {
+ currentSelection = i;
+ }
+ }
+
+ final Dialog dialog = new Dialog(this);
+ dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+ ListView list = new ListView(this);
+ list.setAdapter(new ArrayAdapter(this, android.R.layout.simple_list_item_checked, choices));
+ list.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+ list.setItemChecked(currentSelection, true);
+ list.setOnItemClickListener(new OnItemClickListener() {
+ public void onItemClick(AdapterView> arg0, View arg1, int arg2, long arg3) {
+ if (dialog.isShowing()) {
+ dialog.dismiss();
+ }
+ COLORMODEL cm = COLORMODEL.values()[arg2];
+ vncCanvas.setColorModel(cm);
+ connection.setColorModel(cm.nameString());
+ UIUtils.toastShort(VncCanvasActivity.this, "Updating Color Model to " + cm.toString());
+ }
+ });
+ dialog.setOnDismissListener(new OnDismissListener() {
+ @Override
+ public void onDismiss(DialogInterface arg0) {
+ Log.i(TAG, "Color Model Selector dismissed");
+ // Restore desktop repaints
+ vncCanvas.enableRepaints();
+ }
+ });
+ dialog.setContentView(list);
+ dialog.show();
+ }
+
+ float panTouchX, panTouchY;
+
+ /**
+ * Pan based on touch motions
+ *
+ * @param event
+ */
+ private boolean pan(MotionEvent event) {
+ float curX = event.getX();
+ float curY = event.getY();
+ int dX = (int) (panTouchX - curX);
+ int dY = (int) (panTouchY - curY);
+
+ return vncCanvas.pan(dX, dY);
+ }
+
+ boolean defaultKeyDownHandler(int keyCode, KeyEvent evt) {
+ if (vncCanvas.processLocalKeyEvent(keyCode, evt)) {
+ return true;
+ }
+ return super.onKeyDown(keyCode, evt);
+ }
+
+ boolean defaultKeyUpHandler(int keyCode, KeyEvent evt) {
+ if (vncCanvas.processLocalKeyEvent(keyCode, evt)) {
+ return true;
+ }
+ return super.onKeyUp(keyCode, evt);
+ }
+
+ boolean touchPan(MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ panTouchX = event.getX();
+ panTouchY = event.getY();
+ break;
+ case MotionEvent.ACTION_MOVE:
+ pan(event);
+ panTouchX = event.getX();
+ panTouchY = event.getY();
+ break;
+ case MotionEvent.ACTION_UP:
+ pan(event);
+ break;
+ }
+ return true;
+ }
+
+ private static int convertTrackballDelta(double delta) {
+ return (int) Math.pow(Math.abs(delta) * 6.01, 2.5) * (delta < 0.0 ? -1 : 1);
+ }
+
+ boolean trackballMouse(MotionEvent evt) {
+ // MK
+ if (evt.getAction() == MotionEvent.ACTION_CANCEL)
+ return false;
+
+ int dx = convertTrackballDelta(evt.getX());
+ int dy = convertTrackballDelta(evt.getY());
+
+ evt.offsetLocation(vncCanvas.mouseX + dx - evt.getX(), vncCanvas.mouseY + dy - evt.getY());
+
+ if (vncCanvas.processPointerEvent(evt, trackballButtonDown)) {
+ return true;
+ }
+ return VncCanvasActivity.super.onTouchEvent(evt);
+ }
+
+ long hideZoomAfterMs;
+ static final long ZOOM_HIDE_DELAY_MS = 2500;
+ HideZoomRunnable hideZoomInstance = new HideZoomRunnable();
+
+ private void showZoomer(boolean force) {
+
+ if (force || zoomer.getVisibility() != View.VISIBLE) {
+ // zoomer.show();
+ hideZoomAfterMs = SystemClock.uptimeMillis() + ZOOM_HIDE_DELAY_MS;
+ vncCanvas.handler.postAtTime(hideZoomInstance, hideZoomAfterMs + 10);
+ }
+ }
+
+ private class HideZoomRunnable implements Runnable {
+
+ public void run() {
+ if (SystemClock.uptimeMillis() >= hideZoomAfterMs) {
+ zoomer.hide();
+ }
+ }
+ }
+
+ /**
+ * Touches and dpad (trackball) pan the screen
+ *
+ * @author Michael A. MacDonald
+ *
+ */
+ class PanMode implements AbstractInputHandler {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#onKeyDown(int,
+ * android.view.KeyEvent)
+ */
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent evt) {
+ // DPAD KeyDown events are move MotionEvents in Panning Mode
+ final int dPos = 100;
+ boolean result = false;
+ // MK
+ if (evt.getAction() == MotionEvent.ACTION_CANCEL)
+ return true;
+
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ result = true;
+ break;
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ onTouchEvent(MotionEvent.obtain(1, System.currentTimeMillis(), MotionEvent.ACTION_MOVE,
+ panTouchX + dPos, panTouchY, 0));
+ result = true;
+ break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ onTouchEvent(MotionEvent.obtain(1, System.currentTimeMillis(), MotionEvent.ACTION_MOVE,
+ panTouchX - dPos, panTouchY, 0));
+ result = true;
+ break;
+ case KeyEvent.KEYCODE_DPAD_UP:
+ onTouchEvent(MotionEvent.obtain(1, System.currentTimeMillis(), MotionEvent.ACTION_MOVE, panTouchX,
+ panTouchY + dPos, 0));
+ result = true;
+ break;
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ onTouchEvent(MotionEvent.obtain(1, System.currentTimeMillis(), MotionEvent.ACTION_MOVE, panTouchX,
+ panTouchY - dPos, 0));
+ result = true;
+ break;
+ default:
+ result = defaultKeyDownHandler(keyCode, evt);
+ break;
+ }
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#onKeyUp(int,
+ * android.view.KeyEvent)
+ */
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent evt) {
+ // Ignore KeyUp events for DPAD keys in Panning Mode; trackball
+ // button switches to mouse mode
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ inputHandler = getInputHandlerById(R.id.itemInputMouse);
+ connection.setInputMode(inputHandler.getName());
+ updateInputMenu();
+ showPanningState();
+ return true;
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ return true;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ return true;
+ case KeyEvent.KEYCODE_DPAD_UP:
+ return true;
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ return true;
+ }
+ return defaultKeyUpHandler(keyCode, evt);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * android.androidVNC.AbstractInputHandler#onTouchEvent(android.view
+ * .MotionEvent)
+ */
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ // MK
+ if (event.getAction() == MotionEvent.ACTION_CANCEL)
+ return true;
+
+ return touchPan(event);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * android.androidVNC.AbstractInputHandler#onTrackballEvent(android.
+ * view.MotionEvent)
+ */
+ @Override
+ public boolean onTrackballEvent(MotionEvent evt) {
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#handlerDescription()
+ */
+ @Override
+ public CharSequence getHandlerDescription() {
+ return getResources().getText(R.string.input_mode_panning);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#getName()
+ */
+ @Override
+ public String getName() {
+ return "PAN_MODE";
+ }
+ }
+
+ /**
+ * The touchscreen pans the screen; the trackball moves and clicks the
+ * mouse.
+ *
+ * @author Michael A. MacDonald
+ *
+ */
+ public class TouchPanTrackballMouse implements AbstractInputHandler {
+
+ private DPadMouseKeyHandler keyHandler = new DPadMouseKeyHandler(VncCanvasActivity.this, vncCanvas.handler);
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#onKeyDown(int,
+ * android.view.KeyEvent)
+ */
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent evt) {
+ return keyHandler.onKeyDown(keyCode, evt);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#onKeyUp(int,
+ * android.view.KeyEvent)
+ */
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent evt) {
+ return keyHandler.onKeyUp(keyCode, evt);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * android.androidVNC.AbstractInputHandler#onTouchEvent(android.view
+ * .MotionEvent)
+ */
+ @Override
+ public boolean onTouchEvent(MotionEvent evt) {
+ // MK
+ if (evt.getAction() == MotionEvent.ACTION_CANCEL)
+ return true;
+
+ return touchPan(evt);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * android.androidVNC.AbstractInputHandler#onTrackballEvent(android.
+ * view.MotionEvent)
+ */
+ @Override
+ public boolean onTrackballEvent(MotionEvent evt) {
+ return trackballMouse(evt);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#handlerDescription()
+ */
+ @Override
+ public CharSequence getHandlerDescription() {
+ return getResources().getText(R.string.input_mode_touchpad_pan_trackball_mouse);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#getName()
+ */
+ @Override
+ public String getName() {
+ return "TOUCH_PAN_TRACKBALL_MOUSE";
+ }
+ }
+
+ public static final String FIT_SCREEN_NAME = "FIT_SCREEN";
+ /**
+ * Internal name for default input mode with Zoom scaling
+ */
+ public static final String TOUCH_ZOOM_MODE = "TOUCH_ZOOM_MODE";
+ public static final String TOUCHPAD_MODE = "TOUCHPAD_MODE";
+
+ /**
+ * In fit-to-screen mode, no panning. Trackball and touchscreen work as
+ * mouse.
+ *
+ * @author Michael A. MacDonald
+ *
+ */
+ public class FitToScreenMode implements AbstractInputHandler {
+
+ private DPadMouseKeyHandler keyHandler = new DPadMouseKeyHandler(VncCanvasActivity.this, vncCanvas.handler);
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#onKeyDown(int,
+ * android.view.KeyEvent)
+ */
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent evt) {
+ return keyHandler.onKeyDown(keyCode, evt);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#onKeyUp(int,
+ * android.view.KeyEvent)
+ */
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent evt) {
+ return keyHandler.onKeyUp(keyCode, evt);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * android.androidVNC.AbstractInputHandler#onTouchEvent(android.view
+ * .MotionEvent)
+ */
+ @Override
+ public boolean onTouchEvent(MotionEvent evt) {
+ // MK
+ if (evt.getAction() == MotionEvent.ACTION_CANCEL)
+ return true;
+
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * android.androidVNC.AbstractInputHandler#onTrackballEvent(android.
+ * view.MotionEvent)
+ */
+ @Override
+ public boolean onTrackballEvent(MotionEvent evt) {
+ return trackballMouse(evt);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#handlerDescription()
+ */
+ @Override
+ public CharSequence getHandlerDescription() {
+ return getResources().getText(R.string.input_mode_fit_to_screen);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#getName()
+ */
+ @Override
+ public String getName() {
+ return FIT_SCREEN_NAME;
+ }
+ }
+
+ /**
+ * Touch screen controls, clicks the mouse.
+ *
+ * @author Michael A. MacDonald
+ *
+ */
+ class MouseMode implements AbstractInputHandler {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#onKeyDown(int,
+ * android.view.KeyEvent)
+ */
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent evt) {
+ if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
+ return true;
+ }
+ return defaultKeyDownHandler(keyCode, evt);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#onKeyUp(int,
+ * android.view.KeyEvent)
+ */
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent evt) {
+ if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
+ inputHandler = getInputHandlerById(R.id.itemInputPan);
+ showPanningState();
+ connection.setInputMode(inputHandler.getName());
+ updateInputMenu();
+ return true;
+ }
+ return defaultKeyUpHandler(keyCode, evt);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * android.androidVNC.AbstractInputHandler#onTouchEvent(android.view
+ * .MotionEvent)
+ */
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ // Mouse Pointer Control Mode
+ // Pointer event is absolute coordinates.
+
+ // MK
+ if (event.getAction() == MotionEvent.ACTION_CANCEL)
+ return true;
+
+ vncCanvas.changeTouchCoordinatesToFullFrame(event);
+ if (vncCanvas.processPointerEvent(event, true)) {
+ return true;
+ }
+ return VncCanvasActivity.super.onTouchEvent(event);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * android.androidVNC.AbstractInputHandler#onTrackballEvent(android.
+ * view.MotionEvent)
+ */
+ @Override
+ public boolean onTrackballEvent(MotionEvent evt) {
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#handlerDescription()
+ */
+ @Override
+ public CharSequence getHandlerDescription() {
+ return getResources().getText(R.string.input_mode_mouse);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#getName()
+ */
+ @Override
+ public String getName() {
+ return "MOUSE";
+ }
+ }
+
+ /**
+ * Touch screen controls, clicks the mouse. DPad pans the screen
+ *
+ * @author Michael A. MacDonald
+ *
+ */
+ class DPadPanTouchMouseMode implements AbstractInputHandler {
+
+ private boolean isPanning;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#onKeyDown(int,
+ * android.view.KeyEvent)
+ */
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent evt) {
+ int xv = 0;
+ int yv = 0;
+ boolean result = true;
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ // xv = -1;
+ vncCanvas.sendMetaKey(MetaKeyBean.keyArrowLeft);
+ return result;
+ // break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ // xv = 1;
+ vncCanvas.sendMetaKey(MetaKeyBean.keyArrowRight);
+ return result;
+ // break;
+ case KeyEvent.KEYCODE_DPAD_UP:
+ // yv = -1;
+ vncCanvas.sendMetaKey(MetaKeyBean.keyArrowUp);
+ return result;
+ // break;
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ // yv = 1;
+ vncCanvas.sendMetaKey(MetaKeyBean.keyArrowDown);
+ return result;
+ // break;
+ default:
+ result = defaultKeyDownHandler(keyCode, evt);
+ break;
+ }
+ if ((xv != 0 || yv != 0) && !isPanning) {
+ final int x = xv;
+ final int y = yv;
+ isPanning = true;
+ panner.start(x, y, new Panner.VelocityUpdater() {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * android.androidVNC.Panner.VelocityUpdater#updateVelocity
+ * (android.graphics.Point, long)
+ */
+ @Override
+ public boolean updateVelocity(PointF p, long interval) {
+ double scale = (2.0 * (double) interval / 50.0);
+ if (Math.abs(p.x) < 500) {
+ p.x += (int) (scale * x);
+ }
+ if (Math.abs(p.y) < 500) {
+ p.y += (int) (scale * y);
+ }
+ return true;
+ }
+ });
+ vncCanvas.pan(x, y);
+ }
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#onKeyUp(int,
+ * android.view.KeyEvent)
+ */
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent evt) {
+ boolean result = false;
+
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ case KeyEvent.KEYCODE_DPAD_UP:
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ panner.stop();
+ isPanning = false;
+ result = true;
+ break;
+ default:
+ result = defaultKeyUpHandler(keyCode, evt);
+ break;
+ }
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * android.androidVNC.AbstractInputHandler#onTouchEvent(android.view
+ * .MotionEvent)
+ */
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ // Mouse Pointer Control Mode
+ // Pointer event is absolute coordinates.
+
+ // MK
+ if (event.getAction() == MotionEvent.ACTION_CANCEL)
+ return true;
+
+ vncCanvas.changeTouchCoordinatesToFullFrame(event);
+ if (vncCanvas.processPointerEvent(event, true)) {
+ return true;
+ }
+ return VncCanvasActivity.super.onTouchEvent(event);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * android.androidVNC.AbstractInputHandler#onTrackballEvent(android.
+ * view.MotionEvent)
+ */
+ @Override
+ public boolean onTrackballEvent(MotionEvent evt) {
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#handlerDescription()
+ */
+ @Override
+ public CharSequence getHandlerDescription() {
+ return getResources().getText(R.string.input_mode_dpad_pan_touchpad_mouse);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#getName()
+ */
+ @Override
+ public String getName() {
+ return "DPAD_PAN_TOUCH_MOUSE";
+ }
+ }
+
+ public void onConnected() {
+
+ }
+}
diff --git a/app/src/main/java/android/androidVNC/VncConstants.java b/app/src/main/java/android/androidVNC/VncConstants.java
new file mode 100644
index 0000000..7aaa22b
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/VncConstants.java
@@ -0,0 +1,8 @@
+package android.androidVNC;
+
+/**
+ * Keys for intent values
+ */
+public class VncConstants {
+ public static final String CONNECTION = "android.androidVNC.CONNECTION";
+}
diff --git a/app/src/main/java/android/androidVNC/ZlibInStream.java b/app/src/main/java/android/androidVNC/ZlibInStream.java
new file mode 100644
index 0000000..91103a1
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/ZlibInStream.java
@@ -0,0 +1,112 @@
+package android.androidVNC;
+/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+//
+// A ZlibInStream reads from a zlib.io.InputStream
+//
+
+public class ZlibInStream extends InStream {
+
+ static final int defaultBufSize = 16384;
+
+ public ZlibInStream(int bufSize_) {
+ bufSize = bufSize_;
+ b = new byte[bufSize];
+ ptr = end = ptrOffset = 0;
+ inflater = new java.util.zip.Inflater();
+ }
+
+ public ZlibInStream() { this(defaultBufSize); }
+
+ public void setUnderlying(InStream is, int bytesIn_) {
+ underlying = is;
+ bytesIn = bytesIn_;
+ ptr = end = 0;
+ }
+
+ public void reset() throws Exception {
+ ptr = end = 0;
+ if (underlying == null) return;
+
+ while (bytesIn > 0) {
+ decompress();
+ end = 0; // throw away any data
+ }
+ underlying = null;
+ }
+
+ public int pos() { return ptrOffset + ptr; }
+
+ protected int overrun(int itemSize, int nItems) throws Exception {
+ if (itemSize > bufSize)
+ throw new Exception("ZlibInStream overrun: max itemSize exceeded");
+ if (underlying == null)
+ throw new Exception("ZlibInStream overrun: no underlying stream");
+
+ if (end - ptr != 0)
+ System.arraycopy(b, ptr, b, 0, end - ptr);
+
+ ptrOffset += ptr;
+ end -= ptr;
+ ptr = 0;
+
+ while (end < itemSize) {
+ decompress();
+ }
+
+ if (itemSize * nItems > end)
+ nItems = end / itemSize;
+
+ return nItems;
+ }
+
+ // decompress() calls the decompressor once. Note that this won't
+ // necessarily generate any output data - it may just consume some input
+ // data. Returns false if wait is false and we would block on the underlying
+ // stream.
+
+ private void decompress() throws Exception {
+ try {
+ underlying.check(1);
+ int avail_in = underlying.getend() - underlying.getptr();
+ if (avail_in > bytesIn)
+ avail_in = bytesIn;
+
+ if (inflater.needsInput()) {
+ inflater.setInput(underlying.getbuf(), underlying.getptr(), avail_in);
+ }
+
+ int n = inflater.inflate(b, end, bufSize - end);
+
+ end += n;
+ if (inflater.needsInput()) {
+ bytesIn -= avail_in;
+ underlying.setptr(underlying.getptr() + avail_in);
+ }
+ } catch (java.util.zip.DataFormatException e) {
+ throw new Exception("ZlibInStream: inflate failed");
+ }
+ }
+
+ private InStream underlying;
+ private int bufSize;
+ private int ptrOffset;
+ private java.util.zip.Inflater inflater;
+ private int bytesIn;
+}
diff --git a/app/src/main/java/android/androidVNC/ZoomScaling.java b/app/src/main/java/android/androidVNC/ZoomScaling.java
new file mode 100644
index 0000000..45e39c5
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/ZoomScaling.java
@@ -0,0 +1,187 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import android.graphics.Matrix;
+import android.widget.ImageView.ScaleType;
+
+import com.vectras.vm.R;
+
+/**
+ * @author Michael A. MacDonald
+ */
+class ZoomScaling extends AbstractScaling {
+
+ static final String TAG = "ZoomScaling";
+
+ private Matrix matrix;
+ int canvasXOffset;
+ int canvasYOffset;
+ float scaling;
+ float minimumScale;
+
+ /**
+ * @param id
+ * @param scaleType
+ */
+ public ZoomScaling() {
+ super(R.id.itemZoomable, ScaleType.MATRIX);
+ matrix = new Matrix();
+ scaling = 1;
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractScaling#getDefaultHandlerId()
+ */
+ @Override
+ int getDefaultHandlerId() {
+ return R.id.itemInputTouchPanZoomMouse;
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractScaling#isAbleToPan()
+ */
+ @Override
+ boolean isAbleToPan() {
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractScaling#isValidInputMode(int)
+ */
+ @Override
+ boolean isValidInputMode(int mode) {
+ return mode != R.id.itemInputFitToScreen;
+ }
+
+ /**
+ * Call after scaling and matrix have been changed to resolve scrolling
+ * @param activity
+ */
+ private void resolveZoom(VncCanvasActivity activity)
+ {
+ activity.vncCanvas.scrollToAbsolute();
+ activity.vncCanvas.pan(0,0);
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractScaling#zoomIn(android.androidVNC.VncCanvasActivity)
+ */
+ @Override
+ void zoomIn(VncCanvasActivity activity) {
+ resetMatrix();
+ standardizeScaling();
+ scaling += 0.25;
+ if (scaling > 4.0)
+ {
+ scaling = (float)4.0;
+ activity.zoomer.setIsZoomInEnabled(false);
+ }
+ activity.zoomer.setIsZoomOutEnabled(false); //disable Zoomer
+ matrix.postScale(scaling, scaling);
+ //Log.v(TAG,String.format("before set matrix scrollx = %d scrolly = %d", activity.vncCanvas.getScrollX(), activity.vncCanvas.getScrollY()));
+ activity.vncCanvas.setImageMatrix(matrix);
+ resolveZoom(activity);
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractScaling#getScale()
+ */
+ @Override
+ float getScale() {
+ return scaling;
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractScaling#zoomOut(android.androidVNC.VncCanvasActivity)
+ */
+ @Override
+ void zoomOut(VncCanvasActivity activity) {
+ resetMatrix();
+ standardizeScaling();
+ scaling -= 0.25;
+ if (scaling < minimumScale)
+ {
+ scaling = minimumScale;
+ activity.zoomer.setIsZoomOutEnabled(false);
+ }
+ activity.zoomer.setIsZoomInEnabled(true);
+ matrix.postScale(scaling, scaling);
+ //Log.v(TAG,String.format("before set matrix scrollx = %d scrolly = %d", activity.vncCanvas.getScrollX(), activity.vncCanvas.getScrollY()));
+ activity.vncCanvas.setImageMatrix(matrix);
+ //Log.v(TAG,String.format("after set matrix scrollx = %d scrolly = %d", activity.vncCanvas.getScrollX(), activity.vncCanvas.getScrollY()));
+ resolveZoom(activity);
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractScaling#adjust(android.androidVNC.VncCanvasActivity, float, float, float)
+ */
+ @Override
+ void adjust(VncCanvasActivity activity, float scaleFactor, float fx,
+ float fy) {
+ float newScale = scaleFactor * scaling;
+ if (scaleFactor < 1)
+ {
+ if (newScale < minimumScale)
+ {
+ newScale = minimumScale;
+ activity.zoomer.setIsZoomOutEnabled(false);
+ }
+ activity.zoomer.setIsZoomInEnabled(true);
+ }
+ else
+ {
+ if (newScale > 4)
+ {
+ newScale = 4;
+ activity.zoomer.setIsZoomInEnabled(false);
+ }
+ activity.zoomer.setIsZoomOutEnabled(true);
+ }
+ // ax is the absolute x of the focus
+ int xPan = activity.vncCanvas.absoluteXPosition;
+ float ax = (fx / scaling) + xPan;
+ float newXPan = (scaling * xPan - scaling * ax + newScale * ax)/newScale;
+ int yPan = activity.vncCanvas.absoluteYPosition;
+ float ay = (fy / scaling) + yPan;
+ float newYPan = (scaling * yPan - scaling * ay + newScale * ay)/newScale;
+ resetMatrix();
+ scaling = newScale;
+ matrix.postScale(scaling, scaling);
+ activity.vncCanvas.setImageMatrix(matrix);
+ resolveZoom(activity);
+ activity.vncCanvas.pan((int)(newXPan - xPan), (int)(newYPan - yPan));
+ }
+
+ private void resetMatrix()
+ {
+ matrix.reset();
+ matrix.preTranslate(canvasXOffset, canvasYOffset);
+ }
+
+ /**
+ * Set scaling to one of the clicks on the zoom scale
+ */
+ private void standardizeScaling()
+ {
+ scaling = ((float)((int)(scaling * 4))) / 4;
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractScaling#setScaleTypeForActivity(android.androidVNC.VncCanvasActivity)
+ */
+ @Override
+ public void setScaleTypeForActivity(VncCanvasActivity activity) {
+ super.setScaleTypeForActivity(activity);
+ scaling = (float)1.0;
+ minimumScale = activity.vncCanvas.bitmapData.getMinimumScale();
+ canvasXOffset = -activity.vncCanvas.getCenteredXOffset();
+ canvasYOffset = -activity.vncCanvas.getCenteredYOffset();
+ resetMatrix();
+ activity.vncCanvas.setImageMatrix(matrix);
+ // Reset the pan position to (0,0)
+ resolveZoom(activity);
+ }
+
+}
diff --git a/app/src/main/java/android/androidVNC/androidVNC.java b/app/src/main/java/android/androidVNC/androidVNC.java
new file mode 100644
index 0000000..aa7996c
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/androidVNC.java
@@ -0,0 +1,306 @@
+/*
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+//
+// androidVNC is the Activity for setting VNC server IP and port.
+//
+
+package android.androidVNC;
+
+import java.util.ArrayList;
+
+import android.app.Activity;
+import android.app.ActivityManager.MemoryInfo;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.RadioGroup;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import com.vectras.vm.R;
+
+public class androidVNC extends Activity {
+ private EditText ipText;
+ private EditText portText;
+ private EditText passwordText;
+ private Button goButton;
+ private TextView repeaterText;
+ private RadioGroup groupForceFullScreen;
+ private Spinner colorSpinner;
+ private Spinner spinnerConnection;
+ private ConnectionBean selected;
+ private EditText textNickname;
+ private EditText textUsername;
+ private CheckBox checkboxKeepPassword;
+ private CheckBox checkboxLocalCursor;
+ private boolean repeaterTextSet;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+
+ super.onCreate(icicle);
+ setContentView(R.layout.main_vnc);
+
+ ipText = (EditText) findViewById(R.id.textIP);
+ portText = (EditText) findViewById(R.id.textPORT);
+ passwordText = (EditText) findViewById(R.id.textPASSWORD);
+ textNickname = (EditText) findViewById(R.id.textNickname);
+ textUsername = (EditText) findViewById(R.id.textUsername);
+ goButton = (Button) findViewById(R.id.buttonGO);
+ ((Button) findViewById(R.id.buttonRepeater))
+ .setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ showDialog(R.layout.repeater_dialog);
+ }
+ });
+ ((Button) findViewById(R.id.buttonImportExport))
+ .setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ showDialog(R.layout.importexport);
+ }
+ });
+ colorSpinner = (Spinner) findViewById(R.id.colorformat);
+ COLORMODEL[] models = COLORMODEL.values();
+ ArrayAdapter colorSpinnerAdapter = new ArrayAdapter(
+ this, android.R.layout.simple_spinner_item, models);
+ groupForceFullScreen = (RadioGroup) findViewById(R.id.groupForceFullScreen);
+ checkboxKeepPassword = (CheckBox) findViewById(R.id.checkboxKeepPassword);
+ checkboxLocalCursor = (CheckBox) findViewById(R.id.checkboxUseLocalCursor);
+ colorSpinner.setAdapter(colorSpinnerAdapter);
+ colorSpinner.setSelection(0);
+ spinnerConnection = (Spinner) findViewById(R.id.spinnerConnection);
+ spinnerConnection
+ .setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView> ad, View view,
+ int itemIndex, long id) {
+ selected = (ConnectionBean) ad.getSelectedItem();
+ updateViewFromSelected();
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView> ad) {
+ selected = null;
+ }
+ });
+ spinnerConnection
+ .setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.widget.AdapterView.OnItemLongClickListener#
+ * onItemLongClick(android.widget.AdapterView,
+ * android.view.View, int, long)
+ */
+ @Override
+ public boolean onItemLongClick(AdapterView> arg0,
+ View arg1, int arg2, long arg3) {
+ spinnerConnection.setSelection(arg2);
+ selected = (ConnectionBean) spinnerConnection
+ .getItemAtPosition(arg2);
+ canvasStart();
+ return true;
+ }
+
+ });
+ repeaterText = (TextView) findViewById(R.id.textRepeaterId);
+ goButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ canvasStart();
+ }
+ });
+
+ }
+
+ protected void onDestroy() {
+
+ super.onDestroy();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.app.Activity#onCreateDialog(int)
+ */
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ return new RepeaterDialog(this);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.app.Activity#onCreateOptionsMenu(android.view.Menu)
+ */
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.androidvncmenu, menu);
+ return true;
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem)
+ */
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == R.id.itemSaveAsCopy){
+ updateSelectedFromView();
+ arriveOnPage();
+ } else if (item.getItemId() == R.id.itemDeleteConnection ||
+ item.getItemId() == R.id.itemOpenDoc) {
+ Utils.showDocumentation(this);
+ }
+ return true;
+ }
+
+ private void updateViewFromSelected() {
+ if (selected == null)
+ return;
+ ipText.setText(selected.getAddress());
+ portText.setText(Integer.toString(selected.getPort()));
+ if (selected.getPassword().length() > 0) {
+ passwordText.setText(selected.getPassword());
+ }
+ groupForceFullScreen
+ .check(selected.getForceFull() == BitmapImplHint.AUTO ? R.id.radioForceFullScreenAuto
+ : (selected.getForceFull() == BitmapImplHint.FULL ? R.id.radioForceFullScreenOn
+ : R.id.radioForceFullScreenOff));
+ checkboxLocalCursor.setChecked(selected.getUseLocalCursor());
+ textNickname.setText(selected.getNickname());
+ COLORMODEL cm = COLORMODEL.valueOf(selected.getColorModel());
+ COLORMODEL[] colors = COLORMODEL.values();
+ for (int i = 0; i < colors.length; ++i) {
+ if (colors[i] == cm) {
+ colorSpinner.setSelection(i);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Called when changing view to match selected connection or from Repeater
+ * dialog to update the repeater information shown.
+ *
+ * @param repeaterId
+ * If null or empty, show text for not using repeater
+ */
+ void updateRepeaterInfo(boolean useRepeater, String repeaterId) {
+ if (useRepeater) {
+ repeaterText.setText(repeaterId);
+ repeaterTextSet = true;
+ } else {
+ repeaterText.setText(getText(R.string.repeater_empty_text));
+ repeaterTextSet = false;
+ }
+ }
+
+ private void updateSelectedFromView() {
+ if (selected == null) {
+ return;
+ }
+ selected.setAddress(ipText.getText().toString());
+ try {
+ selected.setPort(Integer.parseInt(portText.getText().toString()));
+ } catch (NumberFormatException nfe) {
+
+ }
+ selected.setNickname(textNickname.getText().toString());
+
+ selected.setForceFull(groupForceFullScreen.getCheckedRadioButtonId() == R.id.radioForceFullScreenAuto ? BitmapImplHint.AUTO
+ : (groupForceFullScreen.getCheckedRadioButtonId() == R.id.radioForceFullScreenOn ? BitmapImplHint.FULL
+ : BitmapImplHint.TILE));
+ selected.setPassword(passwordText.getText().toString());
+
+ selected.setUseLocalCursor(checkboxLocalCursor.isChecked());
+ selected.setColorModel(((COLORMODEL) colorSpinner.getSelectedItem())
+ .nameString());
+ }
+
+ protected void onStart() {
+ super.onStart();
+ arriveOnPage();
+ }
+
+ void arriveOnPage() {
+ ArrayList connections = new ArrayList();
+ connections.add(0, new ConnectionBean());
+ int connectionIndex = 0;
+
+ spinnerConnection.setAdapter(new ArrayAdapter(this,
+ android.R.layout.simple_spinner_item, connections
+ .toArray(new ConnectionBean[connections.size()])));
+ spinnerConnection.setSelection(connectionIndex, false);
+ selected = connections.get(connectionIndex);
+ updateViewFromSelected();
+ }
+
+ protected void onStop() {
+ super.onStop();
+ if (selected == null) {
+ return;
+ }
+ updateSelectedFromView();
+
+ }
+
+
+
+ private void canvasStart() {
+ if (selected == null)
+ return;
+ MemoryInfo info = Utils.getMemoryInfo(this);
+ if (info.lowMemory) {
+ // Low Memory situation. Prompt.
+ Utils.showYesNoPrompt(
+ this,
+ "Continue?",
+ "Android reports low system memory.\nContinue with VNC connection?",
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ vnc();
+ }
+ }, null);
+ } else
+ vnc();
+ }
+
+ private void vnc() {
+ updateSelectedFromView();
+ Intent intent = new Intent(this, VncCanvasActivity.class);
+ startActivity(intent);
+ }
+
+}
diff --git a/app/src/main/java/com/antlersoft/android/bc/BCActivityManagerDefault.java b/app/src/main/java/com/antlersoft/android/bc/BCActivityManagerDefault.java
new file mode 100644
index 0000000..a5d9b60
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/android/bc/BCActivityManagerDefault.java
@@ -0,0 +1,21 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package com.antlersoft.android.bc;
+
+import android.app.ActivityManager;
+
+/**
+ * @author Michael A. MacDonald
+ */
+class BCActivityManagerDefault implements IBCActivityManager {
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.IBCActivityManager#getMemoryClass(android.app.ActivityManager)
+ */
+ @Override
+ public int getMemoryClass(ActivityManager am) {
+ return 16;
+ }
+
+}
diff --git a/app/src/main/java/com/antlersoft/android/bc/BCActivityManagerV5.java b/app/src/main/java/com/antlersoft/android/bc/BCActivityManagerV5.java
new file mode 100644
index 0000000..a764c76
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/android/bc/BCActivityManagerV5.java
@@ -0,0 +1,21 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package com.antlersoft.android.bc;
+
+import android.app.ActivityManager;
+
+/**
+ * @author Michael A. MacDonald
+ */
+public class BCActivityManagerV5 implements IBCActivityManager {
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.IBCActivityManager#getMemoryClass(android.app.ActivityManager)
+ */
+ @Override
+ public int getMemoryClass(ActivityManager am) {
+ return am.getMemoryClass();
+ }
+
+}
diff --git a/app/src/main/java/com/antlersoft/android/bc/BCFactory.java b/app/src/main/java/com/antlersoft/android/bc/BCFactory.java
new file mode 100644
index 0000000..d9cf102
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/android/bc/BCFactory.java
@@ -0,0 +1,191 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package com.antlersoft.android.bc;
+
+/**
+ * Create interface implementations appropriate to the current version of the SDK;
+ * implementations can allow use of higher-level SDK calls in .apk's that will still run
+ * on lower-level SDK's
+ * @author Michael A. MacDonald
+ */
+public class BCFactory {
+
+ private static BCFactory _theInstance = new BCFactory();
+
+ private IBCActivityManager bcActivityManager;
+ private IBCHaptic bcHaptic;
+ private IBCMotionEvent bcMotionEvent;
+ private IBCStorageContext bcStorageContext;
+
+ /**
+ * This is here so checking the static doesn't get optimized away;
+ * note we can't use SDK_INT because that is too new
+ * @return sdk version
+ */
+ int getSdkVersion()
+ {
+ try
+ {
+ return android.os.Build.VERSION.SDK_INT;
+ }
+ catch (NumberFormatException nfe)
+ {
+ return 1;
+ }
+ }
+
+ /**
+ * Return the implementation of IBCActivityManager appropriate for this SDK level
+ * @return
+ */
+ public IBCActivityManager getBCActivityManager()
+ {
+ if (bcActivityManager == null)
+ {
+ synchronized (this)
+ {
+ if (bcActivityManager == null)
+ {
+ if (getSdkVersion() >= 5)
+ {
+ try
+ {
+ bcActivityManager = (IBCActivityManager)getClass().getClassLoader().loadClass("com.antlersoft.android.bc.BCActivityManagerV5").newInstance();
+ }
+ catch (Exception ie)
+ {
+ bcActivityManager = new BCActivityManagerDefault();
+ throw new RuntimeException("Error instantiating", ie);
+ }
+ }
+ else
+ {
+ bcActivityManager = new BCActivityManagerDefault();
+ }
+ }
+ }
+ }
+ return bcActivityManager;
+ }
+
+
+
+ /**
+ * Return the implementation of IBCHaptic appropriate for this SDK level
+ *
+ * Since we dropped support of SDK levels prior to 3, there is only one version at the moment.
+ * @return
+ */
+ public IBCHaptic getBCHaptic()
+ {
+ if (bcHaptic == null)
+ {
+ synchronized (this)
+ {
+ if (bcHaptic == null)
+ {
+ try
+ {
+ bcHaptic = (IBCHaptic)getClass().getClassLoader().loadClass("com.antlersoft.android.bc.BCHapticDefault").newInstance();
+ }
+ catch (Exception ie)
+ {
+ throw new RuntimeException("Error instantiating", ie);
+ }
+ }
+ }
+ }
+ return bcHaptic;
+ }
+
+ /**
+ * Return the implementation of IBCMotionEvent appropriate for this SDK level
+ * @return
+ */
+ public IBCMotionEvent getBCMotionEvent()
+ {
+ if (bcMotionEvent == null)
+ {
+ synchronized (this)
+ {
+ if (bcMotionEvent == null)
+ {
+ if (getSdkVersion() >= 5)
+ {
+ try
+ {
+ bcMotionEvent = (IBCMotionEvent)getClass().getClassLoader().loadClass("com.antlersoft.android.bc.BCMotionEvent5").newInstance();
+ }
+ catch (Exception ie)
+ {
+ throw new RuntimeException("Error instantiating", ie);
+ }
+ }
+ else
+ {
+ try
+ {
+ bcMotionEvent = (IBCMotionEvent)getClass().getClassLoader().loadClass("com.antlersoft.android.bc.BCMotionEvent4").newInstance();
+ }
+ catch (Exception ie)
+ {
+ throw new RuntimeException("Error instantiating", ie);
+ }
+ }
+ }
+ }
+ }
+ return bcMotionEvent;
+ }
+
+ /**
+ *
+ * @return An implementation of IBCStorageContext appropriate for the running Android release
+ */
+ public IBCStorageContext getStorageContext()
+ {
+ if (bcStorageContext == null)
+ {
+ synchronized (this)
+ {
+ if (bcStorageContext == null)
+ {
+ if (getSdkVersion() >= 8)
+ {
+ try
+ {
+ bcStorageContext = (IBCStorageContext)getClass().getClassLoader().loadClass("com.antlersoft.android.bc.BCStorageContext8").newInstance();
+ }
+ catch (Exception ie)
+ {
+ throw new RuntimeException("Error instantiating", ie);
+ }
+ }
+ else
+ {
+ try
+ {
+ bcStorageContext = (IBCStorageContext)getClass().getClassLoader().loadClass("com.antlersoft.android.bc.BCStorageContext7").newInstance();
+ }
+ catch (Exception ie)
+ {
+ throw new RuntimeException("Error instantiating", ie);
+ }
+ }
+ }
+ }
+ }
+ return bcStorageContext;
+ }
+
+ /**
+ * Returns the only instance of this class, which manages the SDK specific interface
+ * implementations
+ * @return Factory instance
+ */
+ public static BCFactory getInstance()
+ {
+ return _theInstance;
+ }
+}
diff --git a/app/src/main/java/com/antlersoft/android/bc/BCHapticDefault.java b/app/src/main/java/com/antlersoft/android/bc/BCHapticDefault.java
new file mode 100644
index 0000000..c632c05
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/android/bc/BCHapticDefault.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package com.antlersoft.android.bc;
+
+import android.view.View;
+import android.view.HapticFeedbackConstants;
+
+/**
+ * Implementation for SDK version >= 3
+ * @author Michael A. MacDonald
+ */
+class BCHapticDefault implements IBCHaptic {
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.IBCHaptic#performLongPressHaptic(android.view.View)
+ */
+ @Override
+ public boolean performLongPressHaptic(View v) {
+ return v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING|HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING
+ );
+ }
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.IBCHaptic#setIsHapticEnabled(android.view.View, boolean)
+ */
+/*
+ * @Override
+ public boolean setIsHapticEnabled(View v, boolean enabled) {
+ return v.setHapticFeedbackEnabled(hapticFeedbackEnabled)
+ }
+*/
+}
diff --git a/app/src/main/java/com/antlersoft/android/bc/BCMotionEvent4.java b/app/src/main/java/com/antlersoft/android/bc/BCMotionEvent4.java
new file mode 100644
index 0000000..9e7eab2
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/android/bc/BCMotionEvent4.java
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2010 Michael A. MacDonald
+ */
+package com.antlersoft.android.bc;
+
+import android.view.MotionEvent;
+
+/**
+ * Pre-sdk 5 version; add fake multi-touch sensing later?
+ *
+ * @author Michael A. MacDonald
+ *
+ */
+class BCMotionEvent4 implements IBCMotionEvent {
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.IBCMotionEvent#getPointerCount(android.view.MotionEvent)
+ */
+ @Override
+ public int getPointerCount(MotionEvent evt) {
+ return 1;
+ }
+
+}
diff --git a/app/src/main/java/com/antlersoft/android/bc/BCMotionEvent5.java b/app/src/main/java/com/antlersoft/android/bc/BCMotionEvent5.java
new file mode 100644
index 0000000..af15236
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/android/bc/BCMotionEvent5.java
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) 2010 Michael A. MacDonald
+ */
+package com.antlersoft.android.bc;
+
+import android.view.MotionEvent;
+
+/**
+ * @author Michael A. MacDonald
+ *
+ */
+class BCMotionEvent5 implements IBCMotionEvent {
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.IBCMotionEvent#getPointerCount(android.view.MotionEvent)
+ */
+ @Override
+ public int getPointerCount(MotionEvent evt) {
+ return evt.getPointerCount();
+ }
+
+}
diff --git a/app/src/main/java/com/antlersoft/android/bc/BCStorageContext7.java b/app/src/main/java/com/antlersoft/android/bc/BCStorageContext7.java
new file mode 100644
index 0000000..2758783
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/android/bc/BCStorageContext7.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2011 Michael A. MacDonald
+ */
+package com.antlersoft.android.bc;
+
+import java.io.File;
+
+import android.content.Context;
+
+import android.os.Environment;
+
+/**
+ * @author Michael A. MacDonald
+ *
+ */
+public class BCStorageContext7 implements IBCStorageContext {
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.IBCStorageContext#getExternalStorageDir(android.content.Context, java.lang.String)
+ */
+ @Override
+ public File getExternalStorageDir(Context context, String type) {
+ File f = Environment.getExternalStorageDirectory();
+ f = new File(f, "Android/data/android.androidVNC/files");
+ if (type != null)
+ f=new File(f, type);
+ f.mkdirs();
+ return f;
+ }
+
+}
diff --git a/app/src/main/java/com/antlersoft/android/bc/BCStorageContext8.java b/app/src/main/java/com/antlersoft/android/bc/BCStorageContext8.java
new file mode 100644
index 0000000..3a7988f
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/android/bc/BCStorageContext8.java
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2010 Michael A. MacDonald
+ */
+package com.antlersoft.android.bc;
+
+import java.io.File;
+
+import android.content.Context;
+
+/**
+ * @author Michael A. MacDonald
+ *
+ */
+class BCStorageContext8 implements IBCStorageContext {
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.IBCStorageContext#getExternalStorageDir(android.content.Context, java.lang.String)
+ */
+ @Override
+ public File getExternalStorageDir(Context context, String type) {
+ return context.getExternalFilesDir(type);
+ }
+
+}
diff --git a/app/src/main/java/com/antlersoft/android/bc/IBCActivityManager.java b/app/src/main/java/com/antlersoft/android/bc/IBCActivityManager.java
new file mode 100644
index 0000000..103c19e
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/android/bc/IBCActivityManager.java
@@ -0,0 +1,13 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package com.antlersoft.android.bc;
+
+import android.app.ActivityManager;
+
+/**
+ * @author Michael A. MacDonald
+ */
+public interface IBCActivityManager {
+ public int getMemoryClass(ActivityManager am);
+}
diff --git a/app/src/main/java/com/antlersoft/android/bc/IBCHaptic.java b/app/src/main/java/com/antlersoft/android/bc/IBCHaptic.java
new file mode 100644
index 0000000..4a53f7c
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/android/bc/IBCHaptic.java
@@ -0,0 +1,20 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package com.antlersoft.android.bc;
+
+import android.view.View;
+
+/**
+ * Access the Haptic interfaces added in version 3 without breaking compatibility
+ * @author Michael A. MacDonald
+ */
+public interface IBCHaptic {
+ public boolean performLongPressHaptic(View v);
+ /**
+ * Set whether haptic feedback is enabled on the view
+ * @param enabled
+ * @return Old value of setting
+ */
+ //public boolean setIsHapticEnabled(View v, boolean enabled);
+}
diff --git a/app/src/main/java/com/antlersoft/android/bc/IBCMotionEvent.java b/app/src/main/java/com/antlersoft/android/bc/IBCMotionEvent.java
new file mode 100644
index 0000000..166f23a
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/android/bc/IBCMotionEvent.java
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2010 Michael A. MacDonald
+ */
+package com.antlersoft.android.bc;
+
+import android.view.MotionEvent;
+
+/**
+ * Access to SDK-dependent features of MotionEvent
+ *
+ * @see android.view.MotionEvent
+ *
+ * @author Michael A. MacDonald
+ *
+ */
+public interface IBCMotionEvent {
+ /**
+ * Obtain the number of pointers active in the event
+ * @see android.view.MotionEvent#getPointerCount()
+ * @param evt
+ * @return number of pointers
+ */
+ int getPointerCount(MotionEvent evt);
+}
diff --git a/app/src/main/java/com/antlersoft/android/bc/IBCStorageContext.java b/app/src/main/java/com/antlersoft/android/bc/IBCStorageContext.java
new file mode 100644
index 0000000..69d6ab6
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/android/bc/IBCStorageContext.java
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2010 Michael A. MacDonald
+ */
+package com.antlersoft.android.bc;
+
+import android.content.Context;
+
+import java.io.File;
+
+/**
+ * Provides a way to access the directory on external storage as returned by
+ * Context.getExternal... added in API 8 that will work with earlier API releases.
+ * @author Michael A. MacDonald
+ *
+ */
+public interface IBCStorageContext {
+ /**
+ *
+ * @param context Context within the application with which the storage will be associated
+ * @param type May be null; if specified, references a sub-directory within the base directory
+ * for the app in the external storage
+ * @return File representing abstract path of storage directory; refer to android.os.Environment to
+ * see if the path is actually accessible
+ */
+ public File getExternalStorageDir(Context context, String type);
+}
diff --git a/app/src/main/java/com/antlersoft/android/drawing/OverlappingCopy.java b/app/src/main/java/com/antlersoft/android/drawing/OverlappingCopy.java
new file mode 100644
index 0000000..96e15fa
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/android/drawing/OverlappingCopy.java
@@ -0,0 +1,136 @@
+package com.antlersoft.android.drawing;
+
+import com.antlersoft.util.ObjectPool;
+import com.antlersoft.util.SafeObjectPool;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+
+public class OverlappingCopy
+{
+ private static SafeObjectPool ocRectPool = new SafeObjectPool() {
+ @Override
+ protected Rect itemForPool()
+ {
+ return new Rect();
+ }
+ };
+ private static void transformRect(Rect source, Rect transformedSource, int deltaX, int deltaY)
+ {
+ transformedSource.set(deltaX < 0 ? source.right * -1 : source.left,
+ deltaY < 0 ? source.bottom * -1 : source.top,
+ deltaX < 0 ? source.left * -1 : source.right,
+ deltaY < 0 ? source.top * -1 : source.bottom);
+ }
+ private static void copyTransformedRect(Rect stepSourceRect, Rect stepDestRect, int deltaX, int deltaY, Bitmap data, Canvas bitmapBackedCanvas, Paint paint)
+ {
+ transformRect(stepSourceRect,stepSourceRect,deltaX,deltaY);
+ stepDestRect.set(stepSourceRect);
+ stepDestRect.offset(deltaX,deltaY);
+ bitmapBackedCanvas.drawBitmap(data, stepSourceRect, stepDestRect, paint);
+ }
+ public static void Copy(Bitmap data, Canvas bitmapBackedCanvas, Paint paint, Rect source, int destX, int destY)
+ {
+ Copy(data,bitmapBackedCanvas,paint,source,destX,destY,ocRectPool);
+ }
+ public static void Copy(Bitmap data, Canvas bitmapBackedCanvas, Paint paint, Rect source, int destX, int destY, ObjectPool rectPool)
+ {
+ //android.util.Log.i("LBM","Copy "+source.toString()+" to "+destX+","+destY);
+ int deltaX = destX - source.left;
+ int deltaY = destY - source.top;
+ int absDeltaX = deltaX < 0 ? -deltaX : deltaX;
+ int absDeltaY = deltaY < 0 ? -deltaY : deltaY;
+
+ // Look for degenerate case
+ if (absDeltaX == 0 && absDeltaY == 0)
+ return;
+ // Look for non-overlap case
+ if (absDeltaX >= source.right - source.left || absDeltaY >= source.bottom - source.top)
+ {
+ // Non-overlapping copy
+ ObjectPool.Entry entry = rectPool.reserve();
+ Rect dest = entry.get();
+ dest.set(source.left + deltaX, source.top + deltaY, source.right + deltaX, source.bottom + deltaY);
+ bitmapBackedCanvas.drawBitmap(data, source, dest, paint);
+ rectPool.release(entry);
+ return;
+ }
+ // Determine coordinate transform so that dest rectangle is always down and to the right.
+ ObjectPool.Entry transformedSourceEntry = rectPool.reserve();
+ Rect transformedSource = transformedSourceEntry.get();
+ transformRect(source,transformedSource,deltaX,deltaY);
+ ObjectPool.Entry transformedDestEntry = rectPool.reserve();
+ Rect transformedDest = transformedDestEntry.get();
+ transformedDest.set(transformedSource);
+ transformedDest.offset(absDeltaX, absDeltaY);
+ ObjectPool.Entry intersectEntry = rectPool.reserve();
+ Rect intersect = intersectEntry.get();
+ intersect.setIntersect(transformedSource, transformedDest);
+
+ boolean xStepDone = false;
+ int xStepWidth;
+ int yStepHeight;
+ if (absDeltaX > absDeltaY)
+ {
+ xStepWidth = absDeltaX;
+ yStepHeight = source.bottom - source.top - absDeltaY;
+ }
+ else
+ {
+ xStepWidth = source.right - source.left - absDeltaX;
+ yStepHeight = absDeltaY;
+ }
+
+ ObjectPool.Entry stepSourceEntry = rectPool.reserve();
+ Rect stepSourceRect = stepSourceEntry.get();
+ ObjectPool.Entry stepDestEntry = rectPool.reserve();
+ Rect stepDestRect = stepDestEntry.get();
+
+ for (int xStep = 0; ! xStepDone; xStep++)
+ {
+ int stepRight = intersect.right - xStep * xStepWidth;
+ int stepLeft = stepRight - xStepWidth;
+ if (stepLeft <= intersect.left)
+ {
+ stepLeft = intersect.left;
+ xStepDone = true;
+ }
+ boolean yStepDone = false;
+ for (int yStep = 0; ! yStepDone; yStep++)
+ {
+ int stepBottom = intersect.bottom - yStep * yStepHeight;
+ int stepTop = stepBottom - yStepHeight;
+ if (stepTop <= intersect.top)
+ {
+ stepTop = intersect.top;
+ yStepDone = true;
+ }
+ stepSourceRect.set(stepLeft,stepTop,stepRight,stepBottom);
+ //android.util.Log.i("LBM","Copy transformed "+stepSourceRect.toString()+" "+deltaX+" "+deltaY);
+ copyTransformedRect(stepSourceRect, stepDestRect, deltaX, deltaY, data, bitmapBackedCanvas, paint);
+ }
+ }
+ if (absDeltaX>0)
+ {
+ // Copy left edge
+ stepSourceRect.set(transformedSource.left,transformedSource.top,intersect.left,transformedSource.bottom);
+ copyTransformedRect(stepSourceRect, stepDestRect, deltaX, deltaY, data, bitmapBackedCanvas, paint);
+ }
+ if (absDeltaY>0)
+ {
+ // Copy top excluding left edge
+ stepSourceRect.set(intersect.left,transformedSource.top,transformedSource.right,intersect.top);
+ copyTransformedRect(stepSourceRect, stepDestRect, deltaX, deltaY, data, bitmapBackedCanvas, paint);
+ }
+
+ rectPool.release(stepDestEntry);
+ rectPool.release(stepSourceEntry);
+ rectPool.release(intersectEntry);
+ rectPool.release(transformedDestEntry);
+ rectPool.release(transformedSourceEntry);
+ }
+}
+
+
diff --git a/app/src/main/java/com/antlersoft/android/drawing/RectList.java b/app/src/main/java/com/antlersoft/android/drawing/RectList.java
new file mode 100644
index 0000000..c1de320
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/android/drawing/RectList.java
@@ -0,0 +1,550 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package com.antlersoft.android.drawing;
+
+import android.graphics.Rect;
+
+import java.util.ArrayList;
+
+import com.antlersoft.util.ObjectPool;
+
+/**
+ * A list of rectangular regions that together represent an area of interest. Provides
+ * a set of operations that apply to the whole area, adding, changing and mutating the
+ * rectangles in the list as required.
+ *
+ * Invariants: None of the rectangles in the list overlap; no pair of rectangles in the list
+ * together make a single rectangle (none share a complete side)
+ *
+ *
+ * Instances of this class are not thread safe
+ *
+ * @author Michael A. MacDonald
+ *
+ */
+public class RectList {
+
+ enum OverlapType {
+ NONE,
+ SAME,
+ CONTAINS,
+ CONTAINED_BY,
+ COALESCIBLE,
+ PARTIAL
+ }
+
+ static final int LEFT = 1;
+ static final int TOP_LEFT = 2;
+ static final int TOP = 4;
+ static final int TOP_RIGHT = 8;
+ static final int RIGHT = 16;
+ static final int BOTTOM_RIGHT = 32;
+ static final int BOTTOM = 64;
+ static final int BOTTOM_LEFT = 128;
+
+ /**
+ * The part left over when one rectangle is subtracted from another
+ * @author Michael A. MacDonald
+ *
+ */
+ static class NonOverlappingPortion
+ {
+ Rect leftPortion;
+ Rect topLeftPortion;
+ Rect topPortion;
+ Rect topRightPortion;
+ Rect rightPortion;
+ Rect bottomRightPortion;
+ Rect bottomPortion;
+ Rect bottomLeftPortion;
+
+ int r1Owns;
+ int r2Owns;
+ int common;
+ int adjacent;
+ boolean horizontalOverlap;
+ boolean verticalOverlap;
+
+ Rect coalesced;
+
+ NonOverlappingPortion()
+ {
+ leftPortion = new Rect();
+ topLeftPortion = new Rect();
+ topPortion = new Rect();
+ topRightPortion = new Rect();
+ rightPortion = new Rect();
+ bottomRightPortion = new Rect();
+ bottomPortion = new Rect();
+ bottomLeftPortion = new Rect();
+ coalesced = new Rect();
+ }
+
+ void setCornerOwnership(int side1, int side2, int corner)
+ {
+ int combined = (side1 | side2);
+ if ((r1Owns & combined) == combined)
+ r1Owns |= corner;
+ else if ((r2Owns & combined) == combined)
+ r2Owns |= corner;
+ }
+
+ void setCornerOwnership()
+ {
+ setCornerOwnership(LEFT,TOP,TOP_LEFT);
+ setCornerOwnership(TOP,RIGHT,TOP_RIGHT);
+ setCornerOwnership(BOTTOM,RIGHT,BOTTOM_RIGHT);
+ setCornerOwnership(BOTTOM,LEFT,BOTTOM_LEFT);
+ }
+
+ /**
+ * Populates with the borders remaining when r2 is subtracted from r1
+ * @param r1
+ * @param r2
+ * @return
+ */
+ OverlapType overlap(Rect r1, Rect r2)
+ {
+ r1Owns = 0;
+ r2Owns = 0;
+ common = 0;
+ adjacent = 0;
+ OverlapType result = OverlapType.NONE;
+ horizontalOverlap = false;
+ verticalOverlap = false;
+
+ if (r1.left < r2.left)
+ {
+ leftPortion.left = topLeftPortion.left = bottomLeftPortion.left = r1.left;
+ if (r2.left < r1.right) {
+ leftPortion.right = topLeftPortion.right = bottomLeftPortion.right = topPortion.left = bottomPortion.left = r2.left;
+ horizontalOverlap = true;
+ } else {
+ leftPortion.right = topLeftPortion.right = bottomLeftPortion.right = topPortion.left = bottomPortion.left = r1.right;
+ if (r2.left == r1.right)
+ adjacent |= LEFT;
+ }
+ r1Owns |= LEFT;
+ }
+ else
+ {
+ leftPortion.left = topLeftPortion.left = bottomLeftPortion.left = r2.left;
+ if (r1.left < r2.right) {
+ leftPortion.right = topLeftPortion.right = bottomLeftPortion.right = topPortion.left = bottomPortion.left = r1.left;
+ horizontalOverlap = true;
+ } else {
+ leftPortion.right = topLeftPortion.right = bottomLeftPortion.right = topPortion.left = bottomPortion.left = r2.right;
+ if ( r1.left == r2.right)
+ adjacent |= RIGHT;
+ }
+ if (r2.left < r1.left)
+ r2Owns |= LEFT;
+ else
+ common |= LEFT;
+ }
+ if (r1.top < r2.top)
+ {
+ topPortion.top = topLeftPortion.top = topRightPortion.top = r1.top;
+ if (r2.top < r1.bottom) {
+ topPortion.bottom = topLeftPortion.bottom = topRightPortion.bottom = leftPortion.top = rightPortion.top = r2.top;
+ verticalOverlap = true;
+ } else {
+ topPortion.bottom = topLeftPortion.bottom = topRightPortion.bottom = leftPortion.top = rightPortion.top = r1.bottom;
+ if (r2.top == r1.bottom)
+ adjacent |= TOP;
+ }
+ r1Owns |= TOP;
+ }
+ else
+ {
+ topPortion.top = topLeftPortion.top = topRightPortion.top = r2.top;
+ if (r1.top < r2.bottom) {
+ topPortion.bottom = topLeftPortion.bottom = topRightPortion.bottom = leftPortion.top = rightPortion.top = r1.top;
+ verticalOverlap = true;
+ } else {
+ topPortion.bottom = topLeftPortion.bottom = topRightPortion.bottom = leftPortion.top = rightPortion.top = r2.bottom;
+ if (r1.top == r2.bottom)
+ adjacent |= BOTTOM;
+ }
+ if (r2.top < r1.top)
+ r2Owns |= TOP;
+ else
+ common |= TOP;
+ }
+ if (r1.right > r2.right)
+ {
+ rightPortion.right = topRightPortion.right = bottomRightPortion.right = r1.right;
+ if (r2.right > r1.left) {
+ rightPortion.left = topRightPortion.left = bottomRightPortion.left = topPortion.right = bottomPortion.right = r2.right;
+ horizontalOverlap = true;
+ } else {
+ rightPortion.left = topRightPortion.left = bottomRightPortion.left = topPortion.right = bottomPortion.right = r1.left;
+ if (r2.right == r1.left)
+ adjacent |= RIGHT;
+ }
+ r1Owns |= RIGHT;
+ }
+ else
+ {
+ rightPortion.right = topRightPortion.right = bottomRightPortion.right = r2.right;
+ if (r1.right > r2.left) {
+ rightPortion.left = topRightPortion.left = bottomRightPortion.left = topPortion.right = bottomPortion.right = r1.right;
+ horizontalOverlap = true;
+ } else {
+ rightPortion.left = topRightPortion.left = bottomRightPortion.left = topPortion.right = bottomPortion.right = r2.left;
+ if (r1.right==r2.left)
+ adjacent |= LEFT;
+ }
+ if (r2.right > r1.right)
+ r2Owns |= RIGHT;
+ else
+ common |= RIGHT;
+ }
+ if (r1.bottom > r2.bottom)
+ {
+ bottomPortion.bottom = bottomLeftPortion.bottom = bottomRightPortion.bottom = r1.bottom;
+ if (r2.bottom > r1.top) {
+ bottomPortion.top = bottomLeftPortion.top = bottomRightPortion.top = leftPortion.bottom = rightPortion.bottom = r2.bottom;
+ verticalOverlap = true;
+ } else {
+ bottomPortion.top = bottomLeftPortion.top = bottomRightPortion.top = leftPortion.bottom = rightPortion.bottom = r1.top;
+ if (r2.bottom==r1.top)
+ adjacent |= BOTTOM;
+ }
+ r1Owns |= BOTTOM;
+ }
+ else
+ {
+ bottomPortion.bottom = bottomLeftPortion.bottom = bottomRightPortion.bottom = r2.bottom;
+ if (r1.bottom > r2.top) {
+ bottomPortion.top = bottomLeftPortion.top = bottomRightPortion.top = leftPortion.bottom = rightPortion.bottom = r1.bottom;
+ verticalOverlap = true;
+ } else {
+ bottomPortion.top = bottomLeftPortion.top = bottomRightPortion.top = leftPortion.bottom = rightPortion.bottom = r2.top;
+ if (r1.bottom==r2.top)
+ adjacent |= TOP;
+ }
+ if (r2.bottom > r1.bottom)
+ r2Owns |= BOTTOM;
+ else
+ common |= BOTTOM;
+ }
+ if ( common == (LEFT|RIGHT|TOP|BOTTOM))
+ {
+ result = OverlapType.SAME;
+ }
+ else if ((common & (LEFT|RIGHT)) == (LEFT | RIGHT) && (verticalOverlap || (adjacent & (TOP | BOTTOM)) != 0))
+ {
+ result = OverlapType.COALESCIBLE;
+ coalesced.left = r1.left;
+ coalesced.right = r1.right;
+ coalesced.top = topPortion.top;
+ coalesced.bottom = bottomPortion.bottom;
+ }
+ else if ((common & (TOP | BOTTOM)) == (TOP | BOTTOM) && (horizontalOverlap || (adjacent & (LEFT | RIGHT)) != 0))
+ {
+ result = OverlapType.COALESCIBLE;
+ coalesced.left = leftPortion.left;
+ coalesced.right = rightPortion.right;
+ coalesced.top = r1.top;
+ coalesced.bottom = r1.bottom;
+ }
+ else if (verticalOverlap && horizontalOverlap) {
+ if (r2Owns == 0)
+ {
+ result = OverlapType.CONTAINED_BY;
+ }
+ else if (r1Owns == 0)
+ {
+ result = OverlapType.CONTAINS;
+ }
+ else
+ {
+ // Partial overlap, non coalescible case
+ result = OverlapType.PARTIAL;
+ setCornerOwnership();
+ }
+ }
+ return result;
+ }
+ }
+
+ /**
+ * Up to 8 Rect objects
+ * @author Michael A. MacDonald
+ *
+ */
+ static class NonOverlappingRects
+ {
+ ObjectPool.Entry[] rectEntries;
+ int count;
+ static final int MAX_RECTS = 8;
+
+ @SuppressWarnings("unchecked")
+ NonOverlappingRects()
+ {
+ rectEntries = new ObjectPool.Entry[MAX_RECTS];
+ }
+
+ private void addOwnedRect(int owner, int direction, ObjectPool pool, Rect r)
+ {
+ if ((owner & direction)==direction)
+ {
+ ObjectPool.Entry entry = pool.reserve();
+ rectEntries[count++] = entry;
+ entry.get().set(r);
+ }
+ }
+
+ void Populate(NonOverlappingPortion p, ObjectPool pool, int owner)
+ {
+ count = 0;
+ for (int i=0; i> list;
+ private ObjectPool pool;
+ private ObjectPool nonOverlappingRectsPool = new ObjectPool() {
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.util.ObjectPool#itemForPool()
+ */
+ @Override
+ protected NonOverlappingRects itemForPool() {
+ return new NonOverlappingRects();
+ }
+
+ };
+ private ObjectPool>> listRectsPool = new ObjectPool>>() {
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.util.ObjectPool#itemForPool()
+ */
+ @Override
+ protected ArrayList> itemForPool() {
+ return new ArrayList>(NonOverlappingRects.MAX_RECTS);
+ }
+ };
+ private NonOverlappingPortion nonOverlappingPortion;
+
+ public RectList(ObjectPool pool)
+ {
+ this.pool = pool;
+ list = new ArrayList>();
+ nonOverlappingPortion = new NonOverlappingPortion();
+ }
+
+ public int getSize()
+ {
+ return list.size();
+ }
+
+ public Rect get(int i)
+ {
+ return list.get(i).get();
+ }
+
+ /**
+ * Remove all rectangles from the list and release them from the pool
+ */
+ public void clear()
+ {
+ for (int i=list.size()-1; i>=0; i--)
+ {
+ ObjectPool.Entry r = list.get(i);
+ pool.release(r);
+ }
+ list.clear();
+ }
+
+ private void recursiveAdd(ObjectPool.Entry toAdd, int level)
+ {
+ if (level>=list.size())
+ {
+ list.add(toAdd);
+ return;
+ }
+ Rect addRect = toAdd.get();
+ ObjectPool.Entry thisEntry = list.get(level);
+ Rect thisRect = thisEntry.get();
+ switch (nonOverlappingPortion.overlap(thisRect, addRect))
+ {
+ case NONE :
+ recursiveAdd(toAdd,level + 1);
+ break;
+ case SAME :
+ case CONTAINS :
+ pool.release(toAdd);
+ break;
+ case CONTAINED_BY :
+ pool.release(thisEntry);
+ list.remove(level);
+ recursiveAdd(toAdd,level);
+ break;
+ case COALESCIBLE :
+ pool.release(thisEntry);
+ list.remove(level);
+ addRect.set(nonOverlappingPortion.coalesced);
+ recursiveAdd(toAdd,0);
+ break;
+ case PARTIAL :
+ pool.release(toAdd);
+ ObjectPool.Entry rectsEntry = nonOverlappingRectsPool.reserve();
+ NonOverlappingRects rects = rectsEntry.get();
+ rects.Populate(nonOverlappingPortion,pool,nonOverlappingPortion.r2Owns);
+ for (int i=0; i entry = pool.reserve();
+ Rect r = entry.get();
+ r.set(toAdd);
+ recursiveAdd(entry,0);
+ }
+
+ /**
+ * Change the rectangle of interest to include only those portions
+ * that fall inside bounds.
+ * @param bounds
+ */
+ public void intersect(Rect bounds)
+ {
+ int size = list.size();
+ ObjectPool.Entry>> listEntry = listRectsPool.reserve();
+ ArrayList> newList = listEntry.get();
+ newList.clear();
+ for (int i=0; i entry = list.get(i);
+ Rect rect = entry.get();
+ if (rect.intersect(bounds))
+ {
+ newList.add(entry);
+ }
+ else
+ pool.release(entry);
+ }
+ list.clear();
+ size = newList.size();
+ for (int i=0; i>> listEntry = listRectsPool.reserve();
+ ArrayList> newList = listEntry.get();
+ newList.clear();
+ for (int i=0; i entry = list.get(i);
+ Rect rect = entry.get();
+ switch (nonOverlappingPortion.overlap(rect, toSubtract))
+ {
+ case SAME:
+ pool.release(entry);
+ newList.clear();
+ list.remove(i);
+ return;
+ case CONTAINED_BY:
+ pool.release(entry);
+ list.remove(i);
+ i--;
+ size--;
+ break;
+ case NONE:
+ break;
+ case COALESCIBLE:
+ if (!nonOverlappingPortion.verticalOverlap || ! nonOverlappingPortion.horizontalOverlap)
+ break;
+ case CONTAINS :
+ nonOverlappingPortion.setCornerOwnership();
+ case PARTIAL :
+ {
+ ObjectPool.Entry rectsEntry = nonOverlappingRectsPool.reserve();
+ NonOverlappingRects rects = rectsEntry.get();
+ rects.Populate(nonOverlappingPortion, pool, nonOverlappingPortion.r1Owns);
+ pool.release(entry);
+ list.remove(i);
+ i--;
+ size--;
+ for (int j=0; j m_max_size)
+ {
+ m_max_size = Math.max(2 * m_max_size, new_size);
+ byte[] new_buffer = new byte[m_max_size];
+ System.arraycopy(m_buffer, 0, new_buffer, 0, result);
+ m_buffer = new_buffer;
+ }
+
+ return result;
+ }
+
+ public void release()
+ {
+ if (m_depth<1)
+ {
+ throw new IllegalStateException("release() without reserve()");
+ }
+ m_depth--;
+ }
+}
diff --git a/app/src/main/java/com/antlersoft/util/ObjectPool.java b/app/src/main/java/com/antlersoft/util/ObjectPool.java
new file mode 100644
index 0000000..e504309
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/util/ObjectPool.java
@@ -0,0 +1,58 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package com.antlersoft.util;
+
+/**
+ * A pool of reusable object of a given type. You get the object from a Entry, which you get
+ * by calling reserve(). When you are done with the object, you call release() passing the Entry.
+ *
+ * Failing to call release() does not leak memory--but you will not get the benefits
+ * of reusing the object. You will run into contention issues if you
+ * call release() while still holding a reference to the pool object.
+ * @author Michael A. MacDonald
+ *
+ */
+public abstract class ObjectPool {
+ public static class Entry {
+ S item;
+ Entry nextEntry;
+
+ Entry(S i, Entry n)
+ {
+ item = i;
+ nextEntry = n;
+ }
+
+ public S get() {
+ return item;
+ }
+ }
+
+ private Entry next;
+ public ObjectPool()
+ {
+ next = null;
+ }
+
+ public Entry reserve()
+ {
+ if (next == null)
+ {
+ next = new Entry(itemForPool(), null);
+ }
+ Entry result = next;
+ next = result.nextEntry;
+ result.nextEntry = null;
+
+ return result;
+ }
+
+ public void release(Entry entry)
+ {
+ entry.nextEntry = next;
+ next = entry;
+ }
+
+ protected abstract R itemForPool();
+}
diff --git a/app/src/main/java/com/antlersoft/util/SafeObjectPool.java b/app/src/main/java/com/antlersoft/util/SafeObjectPool.java
new file mode 100644
index 0000000..3d197ab
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/util/SafeObjectPool.java
@@ -0,0 +1,29 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package com.antlersoft.util;
+
+/**
+ * Synchronized object pool
+ * @author Michael A. MacDonald
+ *
+ */
+public abstract class SafeObjectPool extends ObjectPool {
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.util.ObjectPool#release(com.antlersoft.util.ObjectPool.Entry)
+ */
+ @Override
+ public synchronized void release(com.antlersoft.util.ObjectPool.Entry entry) {
+ super.release(entry);
+ }
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.util.ObjectPool#reserve()
+ */
+ @Override
+ public synchronized com.antlersoft.util.ObjectPool.Entry reserve() {
+ return super.reserve();
+ }
+
+}
diff --git a/app/src/main/java/com/vectras/qemu/Config.java b/app/src/main/java/com/vectras/qemu/Config.java
new file mode 100644
index 0000000..eb755a3
--- /dev/null
+++ b/app/src/main/java/com/vectras/qemu/Config.java
@@ -0,0 +1,185 @@
+/*
+Copyright (C) Max Kastanas 2012
+
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+package com.vectras.qemu;
+
+import android.androidVNC.COLORMODEL;
+import android.androidVNC.VncCanvasActivity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.Environment;
+import android.widget.ImageView.ScaleType;
+
+import com.vectras.vm.AppConfig;
+
+import java.util.Hashtable;
+import java.util.LinkedHashMap;
+
+/**
+ *
+ * @author dev
+ */
+public class Config {
+
+ // Constants
+ public static final int UI_VNC = 0;
+ public static final int UI_SDL = 1;
+ public static final int UI_SPICE = 2;
+ public static final int SDL_MOUSE_LEFT = 1;
+ public static final int SDL_MOUSE_MIDDLE = 2;
+ public static final int SDL_MOUSE_RIGHT = 3;
+ public static final int VNC_REQUEST_CODE = 1004;
+ public static final int VNC_RESET_RESULT_CODE = 1006;
+ public static final int SDL_REQUEST_CODE = 1007;
+ public static final String ACTION_START = "com.vectras.qemu.action.STARTVM";
+
+ // GUI Options
+ public static final boolean enable_SDL = true;
+ public static final boolean enable_SPICE = false;
+
+ public static final boolean enable_qemu_fullScreen = true;
+
+ // App config
+ public static final String APP_NAME = "Vectras Emulator";
+ public static String storagedir = null;
+
+ //Some OSes don't like emulated multi cores for QEMU 2.9.1 you can disable here
+ /// thought there is also the Disable TSC feature so you don't have to do it here
+ public static boolean enableSMPOnlyOnKVM = false;
+
+ //set to true if you need to debug native library loading
+ public static boolean loadNativeLibsEarly = false;
+
+ //XXX: QEMU 3.1.0 needs the libraries to be loaded from the main thread
+ public static boolean loadNativeLibsMainThread = true;
+
+ public static String wakeLockTag = "vectras:wakelock";
+ public static String wifiLockTag = "vectras:wifilock";
+
+ //this will be populated later
+ public static String cacheDir = null;
+
+ //we disable mouse modes for now
+ public static boolean disableMouseModes = true;
+
+ //double tap an hold is still buggy so we keep using the old-way trackpad
+ public static boolean enableDragOnLongPress = true;
+
+ //we need to define the configuration for the VNC client since we replaced some deprecated
+ // functions
+ public static Bitmap.Config bitmapConfig = Bitmap.Config.RGB_565;
+
+ //XXX set scaling to linear it's a tad slower but it's worth it
+ public static int SDLHintScale=1;
+ public static boolean viewLogInternally = true;
+
+
+ //XXX some archs don't support floppy or sd card
+ public static boolean enableEmulatedFloppy = true;
+ public static boolean enableEmulatedSDCard;
+ public static String destLogFilename = "vectraslog.txt";
+
+ public static String notificationChannelID = "vectras";
+ public static String notificationChannelName = "vectras";
+ public static boolean showToast = false;
+ public static boolean closeFileDescriptors = true;
+ public static String hda_path;
+ public static String extra_params;
+
+
+ public static final String getCacheDir(){
+ return cacheDir.toString();
+ }
+ public static final String getBasefileDir() {
+ return getCacheDir() + "/vectras/";
+ }
+
+ public static String getTmpFolder() {
+ return getBasefileDir() + "var/tmp"; // Do not modify
+ }
+ public static String machineFolder = "machines/";
+ public static String getMachineDir(){
+ return getBasefileDir() + machineFolder;
+ }
+ public static String logFilePath = null;
+
+
+ public static final String defaultDNSServer = "8.8.8.8";
+ public static String state_filename = "vm.state";
+
+ //QMP
+ public static String QMPServer = "127.0.0.1";
+ public static int QMPPort = 4444;
+
+ public static int MAX_DISPLAY_REFRESH_RATE = 100; //Hz
+
+ // VNC Defaults
+ public static String defaultVNCHost = "127.0.0.1";
+ public static final String defaultVNCUsername = "vectras";
+ public static final String defaultVNCPasswd = "";
+
+ //It seems that for new veersion of qemu it expectes a relative number
+ // so we stop using absolute port numbers
+ public static final int defaultVNCPort = 1;
+
+ public static final String defaultVNCColorMode = COLORMODEL.C24bit.nameString();
+ public static final ScaleType defaultFullscreenScaleMode = ScaleType.FIT_CENTER;
+ public static final ScaleType defaultScaleModeCenter = ScaleType.CENTER;
+ public static final String defaultInputMode = VncCanvasActivity.TOUCH_ZOOM_MODE;
+
+ //Keyboard Layout
+ public static String defaultKeyboardLayout = "en-us";
+
+
+ public static boolean enableToggleKeyboard = false;
+ // Debug
+ public static final boolean debug = false;
+ public static boolean debugQmp = false;
+
+ //remove in production
+ public static boolean debugStrictMode = false;
+
+ public static boolean processMouseHistoricalEvents = false;
+
+ public static String getLocalQMPSocketPath() {
+ return Config.getCacheDir()+"/qmpsocket";
+ }
+
+ public static String getLocalVNCSocketPath() {
+ return Config.getCacheDir()+"/vncsocket";
+ }
+
+ public static enum MouseMode {
+ Trackpad, External
+ }
+ public static MouseMode mouseMode = MouseMode.Trackpad;
+
+ //specify hd interface, alternative we don't need it right now
+ public static boolean enable_hd_if = false;
+ public static String hd_if_type = "ide";
+
+ //Change to true in prod if you want to be notified by default for new versions
+ public static boolean defaultCheckNewVersion = true;
+
+ public static final String sharedFolder = AppConfig.sharedFolder;
+
+ public static String machinename = "VECTRAS";
+ public static int paused = 0;
+ public static String ui = "VNC";
+ public static boolean maxPriority = false;
+}
diff --git a/app/src/main/java/com/vectras/qemu/MainActivityCommon.java b/app/src/main/java/com/vectras/qemu/MainActivityCommon.java
new file mode 100644
index 0000000..83e400e
--- /dev/null
+++ b/app/src/main/java/com/vectras/qemu/MainActivityCommon.java
@@ -0,0 +1,676 @@
+package com.vectras.qemu;
+
+import android.androidVNC.RfbProto;
+import android.androidVNC.VncCanvas;
+import android.app.Activity;
+import android.app.NotificationManager;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.StrictMode;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.vectras.qemu.jni.StartVM;
+import com.vectras.qemu.utils.FileInstaller;
+import com.vectras.qemu.utils.FileUtils;
+import com.vectras.qemu.utils.Machine;
+import com.vectras.qemu.utils.QmpClient;
+import com.vectras.vm.R;
+import com.vectras.vm.utils.UIUtils;
+
+import java.io.File;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.Enumeration;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class MainActivityCommon {
+
+ public static VMStatus currStatus = VMStatus.Ready;
+ public static boolean vmStarted = false;
+ public static StartVM vmexecutor;
+ public static String vnc_passwd = "vectras";
+ public static int vnc_allow_external = 1;
+ public static int qmp_allow_external = 0;
+ public static ProgressDialog progDialog;
+ public static View parent;
+ public static InstallerTask installerTaskTask;
+ public static boolean timeQuit = false;
+ public static Object lockTime = new Object();
+
+ public static final String TAG = "VECTRAS";
+
+ public static AppCompatActivity activity = null;
+
+ public static boolean libLoaded;
+
+
+ static public void onInstall(boolean force) {
+ FileInstaller.installFiles(activity, force);
+ }
+
+ public static String getVnc_passwd() {
+ return vnc_passwd;
+ }
+
+ public static void setVnc_passwd(String vnc_passwd) {
+ vnc_passwd = vnc_passwd;
+ }
+
+ public static String getLocalIpAddress() {
+ try {
+ for (Enumeration en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {
+ NetworkInterface intf = en.nextElement();
+ for (Enumeration 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;
+ }
+
+ // Start calling the JNI interface
+ public static void startvm(Activity activity, int UI) {
+ QmpClient.allow_external = (qmp_allow_external == 1);
+ vmexecutor.qmp_allow_external = qmp_allow_external;
+
+ if (UI == Config.UI_VNC) {
+ // disable sound card with VNC
+ vmexecutor.enablevnc = 1;
+ vmexecutor.enablespice = 0;
+ vmexecutor.sound_card = null;
+ vmexecutor.vnc_allow_external = vnc_allow_external;
+ RfbProto.allow_external = (vnc_allow_external == 1);
+ vmexecutor.vnc_passwd = vnc_passwd;
+ } else if (UI == Config.UI_SDL) {
+ vmexecutor.enablevnc = 0;
+ vmexecutor.enablespice = 0;
+ } else if (UI == Config.UI_SPICE) {
+ vmexecutor.vnc_allow_external = vnc_allow_external;
+ vmexecutor.vnc_passwd = vnc_passwd;
+ vmexecutor.enablevnc = 0;
+ vmexecutor.enablespice = 1;
+ }
+ vmexecutor.startvm(activity, UI);
+
+ }
+
+
+ public static void cleanup() {
+
+ vmStarted = false;
+
+ //XXX flush and close all file descriptors if we haven't already
+ FileUtils.close_fds();
+
+ ////XXX; we wait till fds flush and close
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ //set the exit code
+ MainSettingsManager.setExitCode(activity, 1);
+
+ //XXX: SDL seems to lock the keyboard events
+ // unless we finish the starting activity
+ activity.finish();
+
+ Log.v(TAG, "Exit");
+ //XXX: We exit here to force unload the native libs
+ System.exit(0);
+
+
+ }
+
+ public static void changeStatus(final VMStatus status_changed) {
+ activity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (status_changed == VMStatus.Running) {
+
+ vmStarted = true;
+ } else if (status_changed == VMStatus.Ready || status_changed == VMStatus.Stopped) {
+
+ } else if (status_changed == VMStatus.Saving) {
+
+ } else if (status_changed == VMStatus.Paused) {
+
+ }
+ }
+ });
+
+ }
+
+ public static void install(boolean force) {
+ progDialog = ProgressDialog.show(activity, "Please Wait", "Installing BIOS...", true);
+ installerTaskTask = new InstallerTask();
+ installerTaskTask.force = force;
+ installerTaskTask.execute();
+ }
+
+ public static void checkAndLoadLibs() {
+ if (Config.loadNativeLibsEarly)
+ if (Config.loadNativeLibsMainThread)
+ setupNativeLibs();
+ else
+ setupNativeLibsAsync();
+ }
+
+ public static void clearNotifications() {
+ NotificationManager notificationManager = (NotificationManager) activity.getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.cancelAll();
+ }
+
+ public static void setupNativeLibsAsync() {
+
+ Thread thread = new Thread(new Runnable() {
+ public void run() {
+ setupNativeLibs();
+ }
+ });
+ thread.setPriority(Thread.MIN_PRIORITY);
+ thread.start();
+
+ }
+
+ public static void savePendingEditText() {
+ View currentView = activity.getCurrentFocus();
+ if (currentView != null && currentView instanceof EditText) {
+ ((EditText) currentView).setFocusable(false);
+ }
+ }
+
+ public static void checkLog() {
+
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+
+ if (MainSettingsManager.getExitCode(activity) != 1) {
+ MainSettingsManager.setExitCode(activity, 1);
+ activity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ UIUtils.promptShowLog(activity);
+ }
+ });
+ }
+ }
+ });
+ t.start();
+ }
+
+ public static void setupFolders() {
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+
+ Config.cacheDir = activity.getCacheDir().getAbsolutePath();
+ Config.storagedir = Environment.getExternalStorageDirectory().toString();
+
+ // Create Temp folder
+ File folder = new File(Config.getTmpFolder());
+ if (!folder.exists())
+ folder.mkdirs();
+
+
+ }
+ });
+ t.start();
+ }
+
+ //XXX: sometimes this needs to be called from the main thread otherwise
+ // qemu crashes when it is started later
+ public static void setupNativeLibs() {
+
+ if (libLoaded)
+ return;
+
+ //Some devices need stl loaded upfront
+ //System.loadLibrary("stlport_shared");
+
+ //Compatibility lib
+ System.loadLibrary("compat-vectras");
+
+ //Glib deps
+ System.loadLibrary("compat-musl");
+
+
+ //Glib
+ System.loadLibrary("glib-2.0");
+
+ //Pixman for qemu
+ System.loadLibrary("pixman-1");
+
+ //Spice server
+ if (Config.enable_SPICE) {
+ System.loadLibrary("crypto");
+ System.loadLibrary("ssl");
+ System.loadLibrary("spice");
+ }
+
+ // //Load SDL library
+ if (Config.enable_SDL) {
+ System.loadLibrary("SDL2");
+ }
+
+ System.loadLibrary("compat-SDL2-ext");
+
+ //Vectras needed for vmexecutor
+ System.loadLibrary("vectras");
+
+ loadQEMULib();
+
+ libLoaded = true;
+ }
+
+ public static void loadQEMULib() {
+
+ try {
+ System.loadLibrary("qemu-system-i386");
+ } catch (Error ex) {
+ System.loadLibrary("qemu-system-x86_64");
+ }
+
+ }
+
+
+ public static void setupStrictMode() {
+
+ if (Config.debugStrictMode) {
+ StrictMode.setThreadPolicy(
+ new StrictMode.ThreadPolicy.Builder().detectDiskReads().detectDiskWrites().detectNetwork()
+ //.penaltyDeath()
+ .penaltyLog().build());
+ StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectLeakedSqlLiteObjects()
+ .detectLeakedClosableObjects().penaltyLog()
+ // .penaltyDeath()
+ .build());
+ }
+
+ }
+
+ public static void onLicense() {
+ PackageInfo pInfo = null;
+
+ try {
+ pInfo = activity.getPackageManager().getPackageInfo(activity.getClass().getPackage().getName(), PackageManager.GET_META_DATA);
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ return;
+ }
+
+ final PackageInfo finalPInfo = pInfo;
+ activity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+
+ }
+ });
+
+ }
+
+ // Main event function
+ // Retrives values from saved preferences
+ public static void onStartButton() {
+ if (MainService.isRunning) {
+ startvnc();
+ } else {
+ if (vmexecutor == null) {
+
+ try {
+ vmexecutor = new StartVM(activity);
+ } catch (Exception ex) {
+ UIUtils.toastLong(activity, "Error: " + ex);
+ return;
+
+ }
+ }
+ // dns
+ vmexecutor.dns_addr = Config.defaultDNSServer;
+
+ vmexecutor.paused = 0;
+
+ if (!vmStarted) {
+ UIUtils.toastShort(activity, "Starting VM");
+ //XXX: make sure that bios files are installed in case we ran out of space in the last
+ // run
+ FileInstaller.installFiles(activity, false);
+ } else {
+ UIUtils.toastShort(activity, "Connecting to VM");
+ }
+
+ if (Config.ui.equals("VNC")) {
+ vmexecutor.enableqmp = 1; // We enable qemu monitor
+ startVNC();
+
+ } else if (Config.ui.equals("SDL")) {
+ vmexecutor.enableqmp = 0; // We disable qemu monitor
+ startSDL();
+ } else {
+ vmexecutor.enableqmp = 1; // We enable qemu monitor
+ startSPICE();
+ }
+ }
+ }
+
+ public static String getLanguageCode(int index) {
+ // TODO: Add more languages from /assets/roms/keymaps
+ switch (index) {
+ case 0:
+ return "en-us";
+ case 1:
+ return "es";
+ case 2:
+ return "fr";
+ }
+ return null;
+ }
+
+ public static void startSDL() {
+
+ Thread tsdl = new Thread(new Runnable() {
+ public void run() {
+ startsdl();
+ }
+ });
+ if (Config.maxPriority)
+ tsdl.setPriority(Thread.MAX_PRIORITY);
+ tsdl.start();
+ }
+
+ public static void startVNC() {
+
+ VncCanvas.retries = 0;
+ if (!vmStarted) {
+
+ Thread tvm = new Thread(new Runnable() {
+ public void run() {
+ startvm(activity, Config.UI_VNC);
+ }
+ });
+ if (Config.maxPriority)
+ tvm.setPriority(Thread.MAX_PRIORITY);
+ tvm.start();
+ } else {
+ startvnc();
+ }
+
+
+ }
+
+ public static void startSPICE() {
+
+ if (!vmStarted) {
+
+ Thread tvm = new Thread(new Runnable() {
+ public void run() {
+ startvm(activity, Config.UI_SPICE);
+ }
+ });
+ if (Config.maxPriority)
+ tvm.setPriority(Thread.MAX_PRIORITY);
+ tvm.start();
+ }
+
+ }
+
+ public static void onStopButton(boolean exit) {
+ stopVM(exit);
+ }
+
+ public static void onRestartButton() {
+
+ execTimer();
+
+ Machine.resetVM(activity);
+ }
+
+ public static void onResumeButton() {
+
+ // TODO: This probably has no effect
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ resumevm();
+ }
+ });
+ t.start();
+ }
+
+ public static void toggleVisibility(View view) {
+ if (view.getVisibility() == View.VISIBLE) {
+ view.setVisibility(View.GONE);
+ } else if (view.getVisibility() == View.GONE || view.getVisibility() == View.INVISIBLE) {
+ view.setVisibility(View.VISIBLE);
+ }
+ }
+
+ public static boolean onKeyDown(int keyCode, KeyEvent event) {
+
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ activity.moveTaskToBack(true);
+ return true; // return
+ }
+
+ return false;
+ }
+
+
+ public static void startvnc() {
+
+ // Wait till Qemu settles
+ try {
+ Thread.sleep(2000);
+ } catch (InterruptedException ex) {
+ Logger.getLogger(activity.getClass().getName()).log(Level.SEVERE, null, ex);
+ }
+ if (MainSettingsManager.getVncExternal(activity)) {
+
+ } else {
+ connectLocally();
+ }
+ }
+
+ public static void promptConnectLocally(final Activity activity) {
+
+ new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ final AlertDialog alertDialog;
+ alertDialog = new AlertDialog.Builder(activity, R.style.MainDialogTheme).create();
+ alertDialog.setTitle("VNC Started");
+ TextView stateView = new TextView(activity);
+ stateView.setText("VNC Server started: " + getLocalIpAddress() + ":" + Config.defaultVNCPort + "\n"
+ + "Warning: VNC Connection is Unencrypted and not secure make sure you're on a private network!\n");
+
+ stateView.setPadding(20, 20, 20, 20);
+ alertDialog.setView(stateView);
+
+ alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "OK", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ alertDialog.dismiss();
+ }
+ });
+ alertDialog.setButton(DialogInterface.BUTTON_NEUTRAL, "Connect Locally", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ connectLocally();
+ }
+ });
+ alertDialog.show();
+ }
+ }, 100);
+
+
+ }
+
+ public static void connectLocally() {
+ //UIUtils.toastShort(MainActivity.this, "Connecting to VM Display");
+ Intent intent = getVNCIntent();
+ activity.startActivityForResult(intent, Config.VNC_REQUEST_CODE);
+ }
+
+ public static void startsdl() {
+
+ Intent intent = null;
+
+ intent = new Intent(activity, MainSDLActivity.class);
+
+ android.content.ContentValues values = new android.content.ContentValues();
+ activity.startActivityForResult(intent, Config.SDL_REQUEST_CODE);
+ }
+
+
+ public static void resumevm() {
+ if (vmexecutor != null) {
+ vmexecutor.resumevm();
+ UIUtils.toastShort(activity, "VM Reset");
+ } else {
+
+ UIUtils.toastShort(activity, "VM not running");
+ }
+
+ }
+
+ public static Intent getVNCIntent() {
+ return new Intent(activity, com.vectras.qemu.MainVNCActivity.class);
+
+ }
+
+
+ public static void goToSettings() {
+ Intent i = new Intent(activity, MainSettingsManager.class);
+ activity.startActivity(i);
+ }
+
+ public static void onViewLog() {
+
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ FileUtils.viewVectrasLog(activity);
+ }
+ });
+ t.start();
+ }
+
+ public static void goToURL(String url) {
+
+ Intent i = new Intent(Intent.ACTION_VIEW);
+ i.setData(Uri.parse(url));
+ activity.startActivity(i);
+
+ }
+
+ public static void stopVM(boolean exit) {
+ execTimer();
+ Machine.stopVM(activity);
+ }
+
+ public static void stopTimeListener() {
+
+ synchronized (lockTime) {
+ timeQuit = true;
+ lockTime.notifyAll();
+ }
+ }
+
+
+ public static void timer() {
+ //XXX: No timers just ping a few times
+ for (int i = 0; i < 3; i++) {
+ checkAndUpdateStatus(false);
+
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException ex) {
+ ex.printStackTrace();
+ }
+ }
+
+ }
+
+ public static void checkAndUpdateStatus(boolean force) {
+ if (vmexecutor != null) {
+ VMStatus status = checkStatus();
+ if (force || status != currStatus) {
+ currStatus = status;
+ changeStatus(status);
+ }
+ }
+ }
+
+ public static void execTimer() {
+
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ startTimer();
+ }
+ });
+ t.start();
+ }
+
+ public static void startTimer() {
+ stopTimeListener();
+
+ timeQuit = false;
+ try {
+ timer();
+ } catch (Exception ex) {
+ ex.printStackTrace();
+
+ }
+
+ }
+
+
+ public static enum VMStatus {
+ Ready, Stopped, Saving, Paused, Completed, Failed, Unknown, Running
+ }
+
+ public static VMStatus checkStatus() {
+ VMStatus state = VMStatus.Ready;
+ if (vmexecutor != null && libLoaded && vmexecutor.get_state().toUpperCase().equals("RUNNING")) {
+ state = VMStatus.Running;
+ }
+ return state;
+ }
+
+ public static class InstallerTask extends AsyncTask {
+ public boolean force;
+
+ @Override
+ protected Void doInBackground(Void... arg0) {
+ onInstall(force);
+ if (progDialog.isShowing()) {
+ progDialog.dismiss();
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void test) {
+
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/vectras/qemu/MainApplication.java b/app/src/main/java/com/vectras/qemu/MainApplication.java
new file mode 100644
index 0000000..bcf9d1a
--- /dev/null
+++ b/app/src/main/java/com/vectras/qemu/MainApplication.java
@@ -0,0 +1,20 @@
+package com.vectras.qemu;
+
+import android.app.Application;
+
+public class MainApplication extends Application {
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ try {
+ Class.forName("android.os.AsyncTask");
+ } catch (Throwable ignore) {
+ // ignored
+ }
+
+
+
+ }
+
+}
diff --git a/app/src/main/java/com/vectras/qemu/MainSDLActivity.java b/app/src/main/java/com/vectras/qemu/MainSDLActivity.java
new file mode 100644
index 0000000..1a25ff9
--- /dev/null
+++ b/app/src/main/java/com/vectras/qemu/MainSDLActivity.java
@@ -0,0 +1,1856 @@
+package com.vectras.qemu;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.media.AudioManager;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Vibrator;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.Gravity;
+import android.view.InputDevice;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+import android.widget.ScrollView;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+import android.widget.TextView;
+import androidx.appcompat.app.ActionBar;
+
+import com.vectras.qemu.utils.FileUtils;
+import com.vectras.qemu.utils.Machine;
+import com.vectras.qemu.utils.QmpClient;
+import com.vectras.vm.R;
+import com.vectras.vm.utils.UIUtils;
+
+import java.io.File;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.egl.EGLSurface;
+import org.json.JSONObject;
+import org.libsdl.app.SDLActivity;
+import org.libsdl.app.SDLControllerManager;
+
+/**
+ * SDL Activity
+ */
+public class MainSDLActivity extends SDLActivity {
+ public static final String TAG = "MainSDLActivity";
+
+ public static MainSDLActivity activity ;
+
+ public static final int KEYBOARD = 10000;
+ public static final int QUIT = 10001;
+ public static final int HELP = 10002;
+
+ private boolean monitorMode = false;
+ private boolean mouseOn = false;
+ private Object lockTime = new Object();
+ private boolean timeQuit = false;
+ private boolean once = true;
+ private boolean zoomable = false;
+ private String status = null;
+
+ public static int vm_width;
+ public static int vm_height;
+
+
+ private Thread timeListenerThread;
+
+ private ProgressDialog progDialog;
+ protected static ViewGroup mMainLayout;
+
+ public String cd_iso_path = null;
+
+ // HDD
+ public String hda_img_path = null;
+ public String hdb_img_path = null;
+ public String hdc_img_path = null;
+ public String hdd_img_path = null;
+
+ public String fda_img_path = null;
+ public String fdb_img_path = null;
+ public String cpu = null;
+
+ // Default Settings
+ public int memory = 128;
+ public String bootdevice = null;
+
+ // net
+ public String net_cfg = "None";
+ public int nic_num = 1;
+ public String vga_type = "std";
+ public String hd_cache = "default";
+ public String nic_driver = null;
+ public String soundcard = null;
+ public String lib = "libvectras.so";
+ public String lib_path = null;
+ public String snapshot_name = "vectras";
+ public int disableacpi = 0;
+ public int disablehpet = 0;
+ public int disabletsc = 0;
+ public int enableqmp = 0;
+ public int enablevnc = 0;
+ public String vnc_passwd = null;
+ public int vnc_allow_external = 0;
+ public String qemu_dev = null;
+ public String qemu_dev_value = null;
+
+ public String dns_addr = null;
+ public int restart = 0;
+
+ // This is what SDL runs in. It invokes SDL_main(), eventually
+ private static Thread mSDLThread;
+
+ // EGL private objects
+ private static EGLContext mEGLContext;
+ private static EGLSurface mEGLSurface;
+ private static EGLDisplay mEGLDisplay;
+ private static EGLConfig mEGLConfig;
+ private static int mGLMajor, mGLMinor;
+
+
+ private static Activity activity1;
+
+ // public static void showTextInput(int x, int y, int w, int h) {
+ // // Transfer the task to the main thread as a Runnable
+ // // mSingleton.commandHandler.post(new ShowTextInputHandler(x, y, w, h));
+ // }
+
+ public static void singleClick(final MotionEvent event, final int pointer_id) {
+
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ // Log.d("SDL", "Mouse Single Click");
+ try {
+ Thread.sleep(50);
+ } catch (InterruptedException ex) {
+ // Log.v("singletap", "Could not sleep");
+ }
+ MainActivityCommon.vmexecutor.onVectrasMouse(Config.SDL_MOUSE_LEFT, MotionEvent.ACTION_DOWN, 1,0, 0);
+ try {
+ Thread.sleep(50);
+ } catch (InterruptedException ex) {
+ // Log.v("singletap", "Could not sleep");
+ }
+ MainActivityCommon.vmexecutor.onVectrasMouse(Config.SDL_MOUSE_LEFT, MotionEvent.ACTION_UP, 1, 0, 0);
+ }
+ });
+ t.start();
+ }
+
+ public static void delayKey(int ms) {
+ try {
+ Thread.sleep(ms);
+ } catch (InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ public static void sendCtrlAltKey(int code) {
+ delayKey(100);
+ SDLActivity.onNativeKeyDown(KeyEvent.KEYCODE_CTRL_LEFT);
+ delayKey(100);
+ SDLActivity.onNativeKeyDown(KeyEvent.KEYCODE_ALT_LEFT);
+ delayKey(100);
+ if(code>=0) {
+ SDLActivity.onNativeKeyDown(code);
+ delayKey(100);
+ SDLActivity.onNativeKeyUp(code);
+ delayKey(100);
+ }
+ SDLActivity.onNativeKeyUp(KeyEvent.KEYCODE_ALT_LEFT);
+ delayKey(100);
+ SDLActivity.onNativeKeyUp(KeyEvent.KEYCODE_CTRL_LEFT);
+ }
+
+ public void stopTimeListener() {
+ Log.v("SaveVM", "Stopping Listener");
+ synchronized (this.lockTime) {
+ this.timeQuit = true;
+ this.lockTime.notifyAll();
+ }
+ }
+
+ public void onDestroy() {
+
+ // Now wait for the SDL thread to quit
+ Log.v("VectrasSDL", "Waiting for SDL thread to quit");
+ if (mSDLThread != null) {
+ try {
+ mSDLThread.join();
+ } catch (Exception e) {
+ Log.v("SDL", "Problem stopping thread: " + e);
+ }
+ mSDLThread = null;
+
+ Log.v("SDL", "Finished waiting for SDL thread");
+ }
+ this.stopTimeListener();
+
+ MainActivityCommon.vmexecutor.doStopVM(0);
+ super.onDestroy();
+ }
+
+ public void startTimeListener() {
+ this.stopTimeListener();
+ timeQuit = false;
+ try {
+ Log.v("Listener", "Time Listener Started...");
+
+ synchronized (lockTime) {
+ while (timeQuit == false) {
+ lockTime.wait();
+ }
+ lockTime.notifyAll();
+ }
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ Log.v("SaveVM", "Time listener thread error: " + ex.getMessage());
+ }
+ Log.v("Listener", "Time listener thread exited...");
+
+ }
+
+ public static boolean toggleKeyboardFlag = true;
+
+// @Override
+// public boolean onOptionsItemSelected(final MenuItem item) {
+// // Log.v("Vectras", "Inside Options Check");
+// super.onOptionsItemSelected(item);
+// if (item.getItemId() == R.id.itemReset) {
+// Machine.resetVM(activity);
+// } else if (item.getItemId() == R.id.itemShutdown) {
+// UIUtils.hideKeyboard(this, mSurface);
+// Machine.stopVM(activity);
+// } else if (item.getItemId() == R.id.itemMouse) {
+// onMouseMode();
+// } else if (item.getItemId() == this.KEYBOARD || item.getItemId() == R.id.itemKeyboard) {
+// //XXX: need to post after delay to work correctly
+// new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
+// @Override
+// public void run() {
+// toggleKeyboardFlag = UIUtils.onKeyboard(activity, toggleKeyboardFlag, mSurface);
+// }
+// }, 200);
+//
+// }
+// else if (item.getItemId() == R.id.itemMonitor) {
+// if (this.monitorMode) {
+// this.onVMConsole();
+// } else {
+// this.onMonitor();
+// }
+// } else if (item.getItemId() == R.id.itemVolume) {
+// this.onSelectMenuVol();
+// } else if (item.getItemId() == R.id.itemSaveState) {
+// this.promptPause(activity);
+// } else if (item.getItemId() == R.id.itemSaveSnapshot) {
+// //TODO:
+// //this.promptStateName(activity);
+// } else if (item.getItemId() == R.id.itemFitToScreen) {
+// onFitToScreen();
+// } else if (item.getItemId() == R.id.itemStretchToScreen) {
+// onStretchToScreen();
+// } else if (item.getItemId() == R.id.itemZoomIn) {
+// this.setZoomIn();
+// } else if (item.getItemId() == R.id.itemZoomOut) {
+// this.setZoomOut();
+// } else if (item.getItemId() == R.id.itemCtrlAltDel) {
+// this.onCtrlAltDel();
+// } else if (item.getItemId() == R.id.itemCtrlC) {
+// this.onCtrlC();
+// } else if (item.getItemId() == R.id.itemOneToOne) {
+// this.onNormalScreen();
+// } else if (item.getItemId() == R.id.itemZoomable) {
+// this.setZoomable();
+// } else if (item.getItemId() == this.QUIT) {
+// } else if (item.getItemId() == R.id.itemHelp) {
+// UIUtils.onHelp(this);
+// } else if (item.getItemId() == R.id.itemHideToolbar) {
+// this.onHideToolbar();
+// } else if (item.getItemId() == R.id.itemDisplay) {
+// this.onSelectMenuSDLDisplay();
+// } else if (item.getItemId() == R.id.itemViewLog) {
+// this.onViewLog();
+// }
+// // this.canvas.requestFocus();
+//
+//
+// this.invalidateOptionsMenu();
+// return true;
+// }
+
+ public void onViewLog() {
+ FileUtils.viewVectrasLog(this);
+ }
+
+ public void onHideToolbar(){
+ ActionBar bar = this.getSupportActionBar();
+ if (bar != null) {
+ bar.hide();
+ }
+ }
+
+
+ private void onMouseMode() {
+
+ String [] items = {"Trackpad Mouse (Phone)",
+ "Bluetooth/USB Mouse (Desktop mode)", //Physical mouse for Chromebook, Android x86 PC, or Bluetooth Mouse
+ };
+ final AlertDialog.Builder mBuilder = new AlertDialog.Builder(this);
+ mBuilder.setTitle("Mouse");
+ mBuilder.setSingleChoiceItems(items, Config.mouseMode.ordinal(), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int i) {
+ switch(i){
+ case 0:
+ setUIModeMobile(true);
+ break;
+ case 1:
+ promptSetUIModeDesktop(false);
+ break;
+ default:
+ break;
+ }
+ dialog.dismiss();
+ }
+ });
+ final AlertDialog alertDialog = mBuilder.create();
+ alertDialog.show();
+
+ }
+
+ public boolean checkVMResolutionFits() {
+ int width = mLayout.getWidth();
+ int height = mLayout.getHeight();
+ ActionBar bar = activity.getSupportActionBar();
+
+ if (!MainSettingsManager.getAlwaysShowMenuToolbar(MainSDLActivity.this)
+ && bar != null && bar.isShowing()) {
+ height += bar.getHeight();
+ }
+
+ if(vm_width < width && vm_height < height)
+ return true;
+
+ return false;
+ }
+
+ public void calibration() {
+ //XXX: No need to calibrate for SDL trackpad.
+ }
+
+ private void setUIModeMobile(boolean fitToScreen){
+
+ try {
+ UIUtils.setOrientation(this);
+ MotionEvent a = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
+
+ //TODO: needed?
+ //MainSDLActivity.singleClick(a, 0);
+ Config.mouseMode = Config.MouseMode.Trackpad;
+ MainSettingsManager.setDesktopMode(this, false);
+ MainActivityCommon.vmexecutor.setRelativeMouseMode(1);
+ if(Config.showToast)
+ UIUtils.toastShort(this.getApplicationContext(), "Trackpad Enabled");
+ if(fitToScreen)
+ onFitToScreen();
+ else
+ onNormalScreen();
+ calibration();
+ invalidateOptionsMenu();
+ }catch (Exception ex){
+ if(Config.debug)
+ ex.printStackTrace();
+ }
+
+ }
+
+ private void promptSetUIModeDesktop(final boolean mouseMethodAlt) {
+
+
+ final AlertDialog alertDialog;
+ alertDialog = new AlertDialog.Builder(activity, R.style.MainDialogTheme).create();
+ alertDialog.setTitle("Desktop Mode");
+
+ LinearLayout mLayout = new LinearLayout(this);
+ mLayout.setPadding(20,20,20,20);
+ mLayout.setOrientation(LinearLayout.VERTICAL);
+
+ TextView textView = new TextView(activity);
+ textView.setVisibility(View.VISIBLE);
+ String desktopInstructions = this.getString(R.string.desktopInstructions);
+ if(!checkVMResolutionFits()){
+ String resolutionWarning = "Warning: Machine resolution "
+ + vm_width+ "x" + vm_height +
+ " is too high for Desktop Mode. " +
+ "Scaling will be used and Mouse Alignment will not be accurate. " +
+ "Reduce display resolution within the Guest OS for better experience.\n\n";
+ desktopInstructions = resolutionWarning + desktopInstructions;
+ }
+ textView.setText(desktopInstructions);
+
+ ScrollView scrollView = new ScrollView(this);
+ scrollView.addView(textView);
+
+ LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+ mLayout.addView(scrollView, params);
+
+ alertDialog.setView(mLayout);
+
+ alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "OK", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ setUIModeDesktop();
+ alertDialog.dismiss();
+ }
+ });
+ alertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancel", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ alertDialog.dismiss();
+ }
+ });
+ alertDialog.show();
+
+ }
+
+ private void setUIModeDesktop() {
+
+ try {
+ MotionEvent a = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
+
+ //TODO: needed?
+ //MainSDLActivity.singleClick(a, 0);
+
+ //TODO: not needed?
+ //SDLActivity.onNativeMouseReset(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
+ //SDLActivity.onNativeMouseReset(0, 0, MotionEvent.ACTION_MOVE, vm_width, vm_height, 0);
+
+ Config.mouseMode = Config.MouseMode.External;
+ MainSettingsManager.setDesktopMode(this, true);
+ MainActivityCommon.vmexecutor.setRelativeMouseMode(0);
+ if(Config.showToast)
+ UIUtils.toastShort(MainSDLActivity.this, "External Mouse Enabled");
+ onNormalScreen();
+ calibration();
+ invalidateOptionsMenu();
+ }catch (Exception ex){
+ if(Config.debug)
+ ex.printStackTrace();
+ }
+ }
+
+ private void onCtrlAltDel() {
+
+ SDLActivity.onNativeKeyDown(KeyEvent.KEYCODE_CTRL_RIGHT);
+ SDLActivity.onNativeKeyDown(KeyEvent.KEYCODE_ALT_RIGHT);
+ SDLActivity.onNativeKeyDown(KeyEvent.KEYCODE_FORWARD_DEL);
+ SDLActivity.onNativeKeyUp(KeyEvent.KEYCODE_FORWARD_DEL);
+ SDLActivity.onNativeKeyUp(KeyEvent.KEYCODE_ALT_RIGHT);
+ SDLActivity.onNativeKeyUp(KeyEvent.KEYCODE_CTRL_RIGHT);
+ }
+
+ private void onCtrlC() {
+
+ SDLActivity.onNativeKeyDown(KeyEvent.KEYCODE_CTRL_RIGHT);
+ SDLActivity.onNativeKeyDown(KeyEvent.KEYCODE_C);
+ SDLActivity.onNativeKeyUp(KeyEvent.KEYCODE_C);
+ SDLActivity.onNativeKeyUp(KeyEvent.KEYCODE_CTRL_RIGHT);
+ }
+
+
+ //TODO: not working
+ private void onStretchToScreen() {
+
+
+ new Thread(new Runnable() {
+ public void run() {
+ Log.d(TAG, "onStretchToScreen");
+ screenMode = SDLScreenMode.Fullscreen;
+ sendCtrlAltKey(KeyEvent.KEYCODE_F); // not working
+ if(Config.showToast)
+ UIUtils.toastShort(activity, "Resizing, Please Wait");
+ resize(null);
+
+ }
+ }).start();
+
+ }
+
+ private void onFitToScreen() {
+ try {
+ UIUtils.setOrientation(this);
+ ActionBar bar = MainSDLActivity.this.getSupportActionBar();
+ if (bar != null && !MainSettingsManager.getAlwaysShowMenuToolbar(this)) {
+ bar.hide();
+ }
+ new Thread(new Runnable() {
+ public void run() {
+ Log.d(TAG, "onFitToScreen");
+ screenMode = SDLScreenMode.FitToScreen;
+ if(Config.showToast)
+ UIUtils.toastShort(activity, "Resizing, Please Wait");
+ resize(null);
+
+ }
+ }).start();
+ }catch (Exception ex){
+ if(Config.debug)
+ ex.printStackTrace();
+ }
+
+ }
+
+ private void onNormalScreen() {
+ try {
+ ActionBar bar = MainSDLActivity.this.getSupportActionBar();
+ if (bar != null && !MainSettingsManager.getAlwaysShowMenuToolbar(this)) {
+ bar.hide();
+ }
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
+ new Thread(new Runnable() {
+ public void run() {
+ Log.d(TAG, "onNormalScreen");
+ screenMode = SDLScreenMode.Normal;
+ if(Config.showToast)
+ UIUtils.toastShort(activity, "Resizing, Please Wait");
+ resize(null);
+
+ }
+ }).start();
+ }catch (Exception ex){
+ if(Config.debug)
+ ex.printStackTrace();
+ }
+
+ }
+
+ public void resize(final Configuration newConfig) {
+
+ //XXX: flag so no mouse events are processed
+ isResizing = true;
+
+ //XXX: This is needed so Nougat+ devices will update their layout
+ new Handler(Looper.getMainLooper()).post(new Runnable() {
+ @Override
+ public void run() {
+ ((ExSDLSurface) mSurface).getHolder().setFixedSize(1, 1);
+ setLayout(newConfig);
+
+ new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ //vsdl((VectrasSDLSurface) mSurface).doResize(false, newConfig);
+ }
+ }, 1000);
+ }
+ });
+
+ }
+
+ private void setZoomIn() {
+
+ new Thread(new Runnable() {
+ public void run() {
+ screenMode = SDLScreenMode.Normal;
+ sendCtrlAltKey(KeyEvent.KEYCODE_4);
+ }
+ }).start();
+
+ }
+
+ private void setZoomOut() {
+
+
+ new Thread(new Runnable() {
+ public void run() {
+ screenMode = SDLScreenMode.Normal;
+ sendCtrlAltKey(KeyEvent.KEYCODE_3);
+
+ }
+ }).start();
+
+ }
+
+ private void setZoomable() {
+
+ zoomable = true;
+
+ }
+
+// @Override
+// public boolean onPrepareOptionsMenu(Menu menu) {
+// menu.clear();
+// return this.setupMenu(menu);
+// }
+//
+// @Override
+// public boolean onCreateOptionsMenu(Menu menu) {
+// menu.clear();
+// return this.setupMenu(menu);
+// }
+//
+// public boolean setupMenu(Menu menu) {
+// // Log.v("Vectras", "Inside Options Created");
+// getMenuInflater().inflate(R.menu.sdlactivitymenu, menu);
+//
+// int maxMenuItemsShown = 4;
+// int actionShow = MenuItemCompat.SHOW_AS_ACTION_IF_ROOM;
+// if(UIUtils.isLandscapeOrientation(this)) {
+// maxMenuItemsShown = 6;
+// actionShow = MenuItemCompat.SHOW_AS_ACTION_ALWAYS;
+// }
+//
+// // if (vncCanvas.scaling != null) {
+// // menu.findItem(vncCanvas.scaling.getId()).setChecked(true);
+// // }
+//
+// // Remove snapshots for now
+// menu.removeItem(menu.findItem(R.id.itemSaveSnapshot).getItemId());
+//
+// // Remove Monitor console for SDL2 it creates 2 SDL windows and SDL for
+// // android supports only 1
+// menu.removeItem(menu.findItem(R.id.itemMonitor).getItemId());
+//
+// // Remove scaling for now
+// menu.removeItem(menu.findItem(R.id.itemScaling).getItemId());
+//
+// // Remove external mouse for now
+// menu.removeItem(menu.findItem(R.id.itemExternalMouse).getItemId());
+// //menu.removeItem(menu.findItem(R.id.itemUIMode).getItemId());
+//
+// menu.removeItem(menu.findItem(R.id.itemCtrlAltDel).getItemId());
+// menu.removeItem(menu.findItem(R.id.itemCtrlC).getItemId());
+//
+// if (MainSettingsManager.getAlwaysShowMenuToolbar(activity) || Config.mouseMode == Config.MouseMode.External) {
+// menu.removeItem(menu.findItem(R.id.itemHideToolbar).getItemId());
+// maxMenuItemsShown--;
+// }
+//
+// if (soundcard==null || soundcard.equals("None")) {
+// menu.removeItem(menu.findItem(R.id.itemVolume).getItemId());
+// maxMenuItemsShown--;
+// }
+//
+//
+//
+// for (int i = 0; i < menu.size() && i < maxMenuItemsShown; i++) {
+// MenuItemCompat.setShowAsAction(menu.getItem(i), actionShow);
+// }
+//
+// return true;
+//
+// }
+
+ private void onMonitor() {
+ new Thread(new Runnable() {
+ public void run() {
+ monitorMode = true;
+ // final KeyEvent altDown = new KeyEvent(downTime, eventTime,
+ // KeyEvent.ACTION_DOWN,
+ // KeyEvent.KEYCODE_2, 1, KeyEvent.META_ALT_LEFT_ON);
+ sendCtrlAltKey(KeyEvent.KEYCODE_2);
+ // sendCtrlAltKey(altDown);
+ Log.v("Vectras", "Monitor On");
+ }
+ }).start();
+
+ }
+
+ private void onVMConsole() {
+ monitorMode = false;
+ sendCtrlAltKey(KeyEvent.KEYCODE_1);
+ }
+
+
+ // FIXME: We need this to able to catch complex characters strings like
+ // grave and send it as text
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (event.getAction() == KeyEvent.ACTION_MULTIPLE && event.getKeyCode() == KeyEvent.KEYCODE_UNKNOWN) {
+ sendText(event.getCharacters().toString());
+ return true;
+ } else if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) {
+ this.onBackPressed();
+ return true;
+ } if (event.getKeyCode() == KeyEvent.KEYCODE_VOLUME_DOWN) {
+ // We emulate right click with volume down
+ if(event.getAction() == KeyEvent.ACTION_DOWN) {
+ MotionEvent e = MotionEvent.obtain(1000, 1000, MotionEvent.ACTION_DOWN, 0, 0, 0, 0, 0, 0, 0,
+ InputDevice.SOURCE_TOUCHSCREEN, 0);
+ rightClick(e, 0);
+ }
+ return true;
+ } else if (event.getKeyCode() == KeyEvent.KEYCODE_VOLUME_UP) {
+ // We emulate middle click with volume up
+ if(event.getAction() == KeyEvent.ACTION_DOWN) {
+ MotionEvent e = MotionEvent.obtain(1000, 1000, MotionEvent.ACTION_DOWN, 0, 0, 0, 0, 0, 0, 0,
+ InputDevice.SOURCE_TOUCHSCREEN, 0);
+ middleClick(e, 0);
+ }
+ return true;
+ } else {
+ return super.dispatchKeyEvent(event);
+ }
+
+ }
+
+ private static void sendText(String string) {
+
+ // Log.v("sendText", string);
+ KeyCharacterMap keyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
+ KeyEvent[] keyEvents = keyCharacterMap.getEvents(string.toCharArray());
+ if (keyEvents != null)
+ for (int i = 0; i < keyEvents.length; i++) {
+
+ if (keyEvents[i].getAction() == KeyEvent.ACTION_DOWN) {
+ // Log.v("sendText", "Up: " + keyEvents[i].getKeyCode());
+ SDLActivity.onNativeKeyDown(keyEvents[i].getKeyCode());
+ } else if (keyEvents[i].getAction() == KeyEvent.ACTION_UP) {
+ // Log.v("sendText", "Down: " + keyEvents[i].getKeyCode());
+ SDLActivity.onNativeKeyUp(keyEvents[i].getKeyCode());
+ }
+ }
+ }
+
+
+ // Setup
+ protected void onCreate(Bundle savedInstanceState) {
+ // Log.v("SDL", "onCreate()");
+ activity = this;
+
+ if (MainSettingsManager.getFullscreen(this))
+ getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
+ WindowManager.LayoutParams.FLAG_FULLSCREEN);
+
+ super.onCreate(savedInstanceState);
+ setupVolume();
+
+ mSingleton = this;
+
+ Log.v("SDL", "Max Mem = " + Runtime.getRuntime().maxMemory());
+
+ this.activity1 = this;
+
+ // So we can call stuff from static callbacks
+ mSingleton = this;
+
+ createUI(0, 0);
+
+ UIUtils.showHints(this);
+
+ this.resumeVM();
+
+ UIUtils.setOrientation(this);
+
+
+ }
+
+ private void createUI(int w, int h) {
+ //mSurface = new SDLSurface(this);
+
+ int width = w;
+ int height = h;
+ if (width == 0) {
+ width = RelativeLayout.LayoutParams.WRAP_CONTENT;
+ }
+ if (height == 0) {
+ height = RelativeLayout.LayoutParams.WRAP_CONTENT;
+ }
+
+ setContentView(R.layout.activity_sdl);
+
+ //TODO:
+ mLayout = (RelativeLayout) activity.findViewById(R.id.sdl_layout);
+ mMainLayout = (LinearLayout) activity.findViewById(R.id.main_layout);
+
+ RelativeLayout mLayout = (RelativeLayout) findViewById(R.id.sdl);
+ RelativeLayout.LayoutParams surfaceParams = new RelativeLayout.LayoutParams(width, height);
+ surfaceParams.addRule(RelativeLayout.CENTER_IN_PARENT);
+
+ mLayout.addView(mSurface, surfaceParams);
+
+ }
+
+ protected void onPause() {
+ Log.v("SDL", "onPause()");
+ MainService.updateServiceNotification(Config.machinename + ": VM Suspended");
+ super.onPause();
+
+ }
+
+
+ public void onSelectMenuVol() {
+
+ final AlertDialog alertDialog;
+ alertDialog = new AlertDialog.Builder(activity, R.style.MainDialogTheme).create();
+ alertDialog.setTitle("Volume");
+
+ LinearLayout.LayoutParams volParams = new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+
+ LinearLayout t = createVolumePanel();
+ t.setLayoutParams(volParams);
+
+ ScrollView s = new ScrollView(activity);
+ s.addView(t);
+ alertDialog.setView(s);
+ alertDialog.setButton(Dialog.BUTTON_POSITIVE, "OK", new DialogInterface.OnClickListener() {
+
+ public void onClick(DialogInterface dialog, int which) {
+ alertDialog.cancel();
+ }
+ });
+ alertDialog.show();
+
+ }
+
+ public LinearLayout createVolumePanel() {
+ LinearLayout layout = new LinearLayout (this);
+ layout.setPadding(20, 20, 20, 20);
+
+ LinearLayout.LayoutParams volparams = new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
+
+ SeekBar vol = new SeekBar(this);
+
+ int volume = 0;
+
+ //TODO:
+ vol.setMax(maxVolume);
+ volume = getCurrentVolume();
+
+ vol.setProgress(volume);
+ vol.setLayoutParams(volparams);
+
+ ((SeekBar) vol).setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
+
+ public void onProgressChanged(SeekBar s, int progress, boolean touch) {
+ //TODO:
+ setVolume(progress);
+ }
+
+ public void onStartTrackingTouch(SeekBar arg0) {
+
+ }
+
+ public void onStopTrackingTouch(SeekBar arg0) {
+
+ }
+ });
+
+ layout.addView(vol);
+
+ return layout;
+
+ }
+
+ protected void onResume() {
+ Log.v("SDL", "onResume()");
+ MainService.updateServiceNotification(Config.machinename + ": VM Running");
+ super.onResume();
+ }
+
+ // Messages from the SDLMain thread
+ static int COMMAND_CHANGE_TITLE = 1;
+ static int COMMAND_SAVEVM = 2;
+
+ public void loadLibraries() {
+ //XXX: override for the specific arch
+ }
+
+
+ public void promptPause(final Activity activity) {
+
+ final AlertDialog alertDialog;
+ alertDialog = new AlertDialog.Builder(activity, R.style.MainDialogTheme).create();
+ alertDialog.setTitle("Pause VM");
+ TextView stateView = new TextView(activity);
+ stateView.setText("This make take a while depending on the RAM size used");
+ stateView.setPadding(20, 20, 20, 20);
+ alertDialog.setView(stateView);
+
+ alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "Pause", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ onPauseVM();
+ return;
+ }
+ });
+ alertDialog.show();
+ }
+
+ private void onPauseVM() {
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ // Delete any previous state file
+ if (MainActivityCommon.vmexecutor.save_state_name != null) {
+ File file = new File(MainActivityCommon.vmexecutor.save_state_name);
+ if (file.exists()) {
+ file.delete();
+ }
+ }
+ if(Config.showToast)
+ UIUtils.toastShort(getApplicationContext(), "Please wait while saving VM State");
+ MainActivityCommon.vmexecutor.current_fd = MainActivityCommon.vmexecutor.get_fd(MainActivityCommon.vmexecutor.save_state_name);
+
+ String uri = "fd:" + MainActivityCommon.vmexecutor.current_fd;
+ String command = QmpClient.stop();
+ String msg = QmpClient.sendCommand(command);
+ command = QmpClient.migrate(false, false, uri);
+ msg = QmpClient.sendCommand(command);
+ if (msg != null) {
+ processMigrationResponse(msg);
+ }
+
+ // XXX: Instead we poll to see if migration is complete
+ new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ VMListener a = new VMListener();
+ a.execute();
+ }
+ }, 0);
+ }
+ });
+ t.start();
+
+ }
+
+ private void processMigrationResponse(String response) {
+ String errorStr = null;
+ try {
+ JSONObject object = new JSONObject(response);
+ errorStr = object.getString("error");
+ }catch (Exception ex) {
+ if(Config.debug)
+ ex.printStackTrace();
+ }
+ if (errorStr != null) {
+ String descStr = null;
+
+ try {
+ JSONObject descObj = new JSONObject(errorStr);
+ descStr = descObj.getString("desc");
+ }catch (Exception ex) {
+ if(Config.debug)
+ ex.printStackTrace();
+ }
+ final String descStr1 = descStr;
+
+ new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ Machine.pausedErrorVM(activity, descStr1);
+ }
+ }, 100);
+
+ }
+
+ }
+
+ private class VMListener extends AsyncTask {
+
+ @Override
+ protected Void doInBackground(Void... arg0) {
+ startTimeListener();
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void test) {
+
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ boolean res = false;
+ if(Config.mouseMode == Config.MouseMode.External){
+ return res;
+ }
+ //TODO:
+ //vsdlres = ((VectrasSDLSurface) this.mSurface).onTouchProcess(this.mSurface, event);
+ //vsdlres = ((VectrasSDLSurface) this.mSurface).onTouchEventProcess(event);
+ return true;
+ }
+
+ private void resumeVM() {
+ if(MainActivityCommon.vmexecutor == null){
+ return;
+ }
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ if (MainActivityCommon.vmexecutor.paused == 1) {
+
+ try {
+ Thread.sleep(4000);
+ } catch (InterruptedException ex) {
+ Logger.getLogger(MainVNCActivity.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ MainActivityCommon.vmexecutor.paused = 0;
+
+ String command = QmpClient.cont();
+ String msg = QmpClient.sendCommand(command);
+ new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ if(Config.mouseMode == Config.MouseMode.External)
+ setUIModeDesktop();
+ else
+ setUIModeMobile(screenMode == SDLScreenMode.FitToScreen);
+ }
+ }, 500);
+ }
+ }
+ });
+ t.start();
+
+ }
+
+ public void onBackPressed() {
+ if (!MainSettingsManager.getAlwaysShowMenuToolbar(activity)) {
+ ActionBar bar = this.getSupportActionBar();
+ if (bar != null) {
+ if (bar.isShowing())
+ bar.hide();
+ else
+ bar.show();
+ }
+ } else {
+ UIUtils.hideKeyboard(this, mSurface);
+ Machine.stopVM(activity);
+ }
+
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ this.invalidateOptionsMenu();
+ }
+
+ public void onSelectMenuSDLDisplay() {
+
+ final AlertDialog alertDialog;
+ alertDialog = new AlertDialog.Builder(activity, R.style.MainDialogTheme).create();
+ alertDialog.setTitle("Display");
+
+ LinearLayout.LayoutParams volParams = new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+
+ LinearLayout t = createSDLDisplayPanel();
+ t.setLayoutParams(volParams);
+
+ ScrollView s = new ScrollView(activity);
+ s.addView(t);
+ alertDialog.setView(s);
+ alertDialog.setButton(Dialog.BUTTON_POSITIVE, "OK", new DialogInterface.OnClickListener() {
+
+ public void onClick(DialogInterface dialog, int which) {
+ alertDialog.cancel();
+ }
+ });
+ alertDialog.show();
+
+ }
+
+
+ public LinearLayout createSDLDisplayPanel() {
+ LinearLayout layout = new LinearLayout(this);
+ layout.setOrientation(LinearLayout.VERTICAL);
+ layout.setPadding(20, 20, 20, 20);
+
+ LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+ int currRate = getCurrentSDLRefreshRate();
+
+ LinearLayout buttonsLayout = new LinearLayout(this);
+ buttonsLayout.setOrientation(LinearLayout.HORIZONTAL);
+ buttonsLayout.setGravity(Gravity.CENTER_HORIZONTAL);
+ Button displayMode = new Button (this);
+
+ displayMode.setText("Display Mode");
+ displayMode.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View view) {
+ onDisplayMode();
+ }
+ });
+ buttonsLayout.addView(displayMode);
+ layout.addView(buttonsLayout);
+
+ final TextView value = new TextView(this);
+ value.setText("Idle Refresh Rate: " + currRate+" Hz");
+ layout.addView(value);
+ value.setLayoutParams(params);
+
+ SeekBar rate = new SeekBar(this);
+ rate.setMax(Config.MAX_DISPLAY_REFRESH_RATE);
+
+ rate.setProgress(currRate);
+ rate.setLayoutParams(params);
+
+ ((SeekBar) rate).setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
+
+ public void onProgressChanged(SeekBar s, int progress, boolean touch) {
+ value.setText("Idle Refresh Rate: " + (progress+1)+" Hz");
+ }
+
+ public void onStartTrackingTouch(SeekBar arg0) {
+
+ }
+
+ public void onStopTrackingTouch(SeekBar arg0) {
+ int progress = arg0.getProgress()+1;
+ int refreshMs = 1000 / progress;
+ Log.v(TAG, "Changing idle refresh rate: (ms)" + refreshMs);
+ MainActivityCommon.vmexecutor.setsdlrefreshrate(refreshMs);
+ }
+ });
+
+
+ layout.addView(rate);
+
+ return layout;
+
+ }
+
+ public int getCurrentSDLRefreshRate() {
+ return 1000 / MainActivityCommon.vmexecutor.getsdlrefreshrate();
+ }
+
+
+
+ private void onDisplayMode() {
+
+ String [] items = {
+ "Normal (One-To-One)",
+ "Fit To Screen"
+// ,"Stretch To Screen" //Stretched
+ };
+ int currentScaleType = 0;
+ if(screenMode == SDLScreenMode.FitToScreen){
+ currentScaleType = 1;
+ } else if(screenMode == SDLScreenMode.Fullscreen)
+ currentScaleType = 2;
+
+ final AlertDialog.Builder mBuilder = new AlertDialog.Builder(this);
+ mBuilder.setTitle("Display Mode");
+ mBuilder.setSingleChoiceItems(items, currentScaleType, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int i) {
+ switch(i){
+ case 0:
+ onNormalScreen();
+ break;
+ case 1:
+ if(Config.mouseMode == Config.MouseMode.External){
+ UIUtils.toastShort(MainSDLActivity.this, "Fit to Screen Disabled under Desktop Mode");
+ dialog.dismiss();
+ return;
+ }
+ onFitToScreen();
+ break;
+ case 2:
+ if(Config.mouseMode == Config.MouseMode.External){
+ UIUtils.toastShort(MainSDLActivity.this, "Stretch Screen Disabled under Desktop Mode");
+ dialog.dismiss();
+ return;
+ }
+ onStretchToScreen();
+ break;
+ default:
+ break;
+ }
+ dialog.dismiss();
+ }
+ });
+ final AlertDialog alertDialog = mBuilder.create();
+ alertDialog.show();
+
+ }
+
+
+ @Override
+ protected synchronized void runSDLMain(){
+
+ //We go through the vm executor
+ MainActivityCommon.startvm(this, Config.UI_SDL);
+
+ //XXX: we hold the thread because SDLActivity will exit
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static void onVMResolutionChanged(int w, int h)
+ {
+ boolean refreshDisplay = false;
+
+ if(w!=vm_width || h!=vm_height)
+ refreshDisplay = true;
+ vm_width = w;
+ vm_height = h;
+
+ Log.v(TAG, "VM resolution changed to " + vm_width + "x" + vm_height);
+
+
+ if(refreshDisplay) {
+ activity.resize(null);
+ }
+
+ }
+
+ public static boolean isResizing = false;
+
+ public enum SDLScreenMode {
+ Normal,
+ FitToScreen,
+ Fullscreen //fullscreen not implemented yet
+ }
+
+ public SDLScreenMode screenMode = SDLScreenMode.FitToScreen;
+
+ private void setLayout(Configuration newConfig) {
+
+ boolean isLanscape =
+ (newConfig!=null && newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE)
+ || UIUtils.isLandscapeOrientation(this);
+
+ View vnc_canvas_layout = (View) this.findViewById(R.id.sdl_layout);
+ RelativeLayout.LayoutParams vnc_canvas_layout_params = null;
+ //normal 1-1
+ if(screenMode == SDLScreenMode.Normal) {
+ if (isLanscape) {
+ vnc_canvas_layout_params = new RelativeLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ );
+// vnc_canvas_layout_params.addRule(RelativeLayout.CENTER_IN_PARENT);
+ vnc_canvas_layout_params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
+ vnc_canvas_layout_params.addRule(RelativeLayout.CENTER_HORIZONTAL);
+
+ } else {
+ vnc_canvas_layout_params = new RelativeLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ );
+ vnc_canvas_layout_params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
+ vnc_canvas_layout_params.addRule(RelativeLayout.CENTER_HORIZONTAL);
+ }
+ } else {
+ //fittoscreen
+ if (isLanscape) {
+ vnc_canvas_layout_params = new RelativeLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT
+ );
+ vnc_canvas_layout_params.addRule(RelativeLayout.CENTER_IN_PARENT);
+ } else {
+
+ vnc_canvas_layout_params = new RelativeLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ );
+ vnc_canvas_layout_params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
+ vnc_canvas_layout_params.addRule(RelativeLayout.CENTER_HORIZONTAL);
+ }
+ }
+ vnc_canvas_layout.setLayoutParams(vnc_canvas_layout_params);
+
+ this.invalidateOptionsMenu();
+ }
+
+ /*public class VectrasSDLSurface extends ExSDLSurface implements View.OnKeyListener, View.OnTouchListener {
+
+ public boolean initialized = false;
+
+ public VectrasSDLSurface(Context context) {
+ super(context);
+ setOnKeyListener(this);
+ setOnTouchListener(this);
+ gestureDetector = new GestureDetector(activity, new GestureListener());
+ setOnGenericMotionListener(new SDLGenericMotionListener_API12());
+ }
+
+ public void surfaceChanged(SurfaceHolder holder,
+ int format, int width, int height) {
+ super.surfaceChanged(holder, format, width, height);
+ new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
+ @Override
+ public void run() {
+
+ SDLActivity.onNativeKeyDown(KeyEvent.KEYCODE_CTRL_LEFT);
+ SDLActivity.onNativeKeyUp(KeyEvent.KEYCODE_CTRL_LEFT);
+ }
+ }, 500);
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ super.surfaceCreated(holder);
+
+ new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
+ @Override
+ public void run() {
+
+ if(Config.mouseMode == Config.MouseMode.External)
+ setUIModeDesktop();
+ else
+ setUIModeMobile(screenMode == SDLScreenMode.FitToScreen);
+ }
+ },1000);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ Log.d(TAG,"Configuration changed");
+ resize(newConfig);
+ }
+
+
+
+ public synchronized void doResize(boolean reverse, final Configuration newConfig) {
+ //XXX: notify the UI not to process mouse motion
+ isResizing = true;
+ Log.v(TAG, "Resizing Display");
+
+ Display display = SDLActivity.mSingleton.getWindowManager().getDefaultDisplay();
+ int height = 0;
+ int width = 0;
+
+ Point size = new Point();
+ display.getSize(size);
+ int screen_width = size.x;
+ int screen_height = size.y;
+
+ final ActionBar bar = ((SDLActivity) activity).getSupportActionBar();
+
+ if(MainSDLActivity.mLayout != null) {
+ width = MainSDLActivity.mLayout.getWidth();
+ height = MainSDLActivity.mLayout.getHeight();
+ }
+
+ //native resolution for use with external mouse
+ if(screenMode != SDLScreenMode.Fullscreen && screenMode != SDLScreenMode.FitToScreen) {
+ width = MainSDLActivity.vm_width;
+ height = MainSDLActivity.vm_height;
+ }
+
+ if(reverse){
+ int temp = width;
+ width = height;
+ height = temp;
+ }
+
+ boolean portrait = SDLActivity.mSingleton.getResources()
+ .getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
+
+ if (portrait) {
+ if(Config.mouseMode != Config.MouseMode.External) {
+ int height_n = (int) (width / (MainSDLActivity.vm_width / (float) MainSDLActivity.vm_height));
+ Log.d(TAG, "Resizing portrait: " + width + " x " + height_n);
+ getHolder().setFixedSize(width, height_n);
+ }
+ } else {
+ if ( (screenMode == SDLScreenMode.Fullscreen || screenMode == SDLScreenMode.FitToScreen)
+ && !MainSettingsManager.getAlwaysShowMenuToolbar(MainSDLActivity.this)
+ && bar != null && bar.isShowing()) {
+ height += bar.getHeight();
+ }
+ Log.d(TAG, "Resizing landscape: " + width + " x " + height);
+ getHolder().setFixedSize(width, height);
+ }
+ initialized = true;
+
+ new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ isResizing = false;
+ }
+ }, 1000);
+
+
+
+ }
+
+ // XXX: SDL is missing some key codes in sdl2-keymap.h
+ // So we create them with a Shift Modifier
+ private boolean handleMissingKeys(int keyCode, int action) {
+
+ int keyCodeTmp = keyCode;
+ switch (keyCode) {
+ case 77:
+ keyCodeTmp = 9;
+ break;
+ case 81:
+ keyCodeTmp = 70;
+ break;
+ case 17:
+ keyCodeTmp = 15;
+ break;
+ case 18:
+ keyCodeTmp = 10;
+ break;
+ default:
+ return false;
+
+ }
+ if (action == KeyEvent.ACTION_DOWN) {
+ SDLActivity.onNativeKeyDown(59);
+ SDLActivity.onNativeKeyDown(keyCodeTmp);
+ } else {
+ SDLActivity.onNativeKeyUp(59);
+ SDLActivity.onNativeKeyUp(keyCodeTmp);
+ }
+ return true;
+
+ }
+
+ @Override
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+
+ if ((keyCode == KeyEvent.KEYCODE_BACK) || (keyCode == KeyEvent.KEYCODE_FORWARD)) {
+ // dismiss android back and forward keys
+ return true;
+ } else if (event.getKeyCode() == KeyEvent.KEYCODE_MENU) {
+ return false;
+ } else if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ //Log.v("SDL", "key down: " + keyCode);
+ if (!handleMissingKeys(keyCode, event.getAction()))
+ SDLActivity.onNativeKeyDown(keyCode);
+ return true;
+ } else if (event.getAction() == KeyEvent.ACTION_UP) {
+ //Log.v("SDL", "key up: " + keyCode);
+ if (!handleMissingKeys(keyCode, event.getAction()))
+ SDLActivity.onNativeKeyUp(keyCode);
+ return true;
+ } else {
+ return super.onKey(v, keyCode, event);
+ }
+
+ }
+
+ // Touch events
+ public boolean onTouchProcess(View v, MotionEvent event) {
+ int action = event.getAction();
+ float x = event.getX(0);
+ float y = event.getY(0);
+ float p = event.getPressure(0);
+
+ int relative = Config.mouseMode == Config.MouseMode.External? 0: 1;
+
+ int sdlMouseButton = 0;
+ if(event.getButtonState() == MotionEvent.BUTTON_PRIMARY)
+ sdlMouseButton = Config.SDL_MOUSE_LEFT;
+ else if(event.getButtonState() == MotionEvent.BUTTON_SECONDARY)
+ sdlMouseButton = Config.SDL_MOUSE_RIGHT;
+ else if(event.getButtonState() == MotionEvent.BUTTON_TERTIARY)
+ sdlMouseButton = Config.SDL_MOUSE_MIDDLE;
+
+
+ if (event.getAction() == MotionEvent.ACTION_MOVE) {
+
+ if (mouseUp) {
+ old_x = x;
+ old_y = y;
+ mouseUp = false;
+ }
+ if (action == MotionEvent.ACTION_MOVE) {
+ if(Config.mouseMode == Config.MouseMode.External) {
+ //Log.d("SDL", "onTouch Absolute Move by=" + action + ", X,Y=" + (x) + "," + (y) + " P=" + p);
+ MainActivityCommon.vmexecutor.onVectrasMouse(0, MotionEvent.ACTION_MOVE,0, x , y );
+ }else {
+ //Log.d("SDL", "onTouch Relative Moving by=" + action + ", X,Y=" + (x -
+// old_x) + "," + (y - old_y) + " P=" + p);
+ MainActivityCommon.vmexecutor.onVectrasMouse(0, MotionEvent.ACTION_MOVE,1, (x - old_x) * sensitivity_mult, (y - old_y) * sensitivity_mult);
+ }
+
+ }
+ // save current
+ old_x = x;
+ old_y = y;
+
+ }
+ else if (event.getAction() == event.ACTION_UP ) {
+ //Log.d("SDL", "onTouch Up: " + sdlMouseButton);
+ //XXX: it seems that the Button state is not available when Button up so
+ // we should release all mouse buttons to be safe since we don't know which one fired the event
+ if(sdlMouseButton == Config.SDL_MOUSE_MIDDLE
+ ||sdlMouseButton == Config.SDL_MOUSE_RIGHT
+ ) {
+ MainActivityCommon.vmexecutor.onVectrasMouse(sdlMouseButton, MotionEvent.ACTION_UP, relative, x, y);
+ } else if (sdlMouseButton != 0) {
+ MainActivityCommon.vmexecutor.onVectrasMouse(sdlMouseButton, MotionEvent.ACTION_UP, relative, x, y);
+ } else { // if we don't have inforamtion about which button we can make some guesses
+
+ //Or only the last one pressed
+ if (lastMouseButtonDown > 0) {
+ if(lastMouseButtonDown == Config.SDL_MOUSE_MIDDLE
+ ||lastMouseButtonDown == Config.SDL_MOUSE_RIGHT
+ ) {
+ MainActivityCommon.vmexecutor.onVectrasMouse(lastMouseButtonDown, MotionEvent.ACTION_UP, relative,x, y);
+ }else
+ MainActivityCommon.vmexecutor.onVectrasMouse(lastMouseButtonDown, MotionEvent.ACTION_UP, relative, x, y);
+ } else {
+ //ALl buttons
+ if (Config.mouseMode == Config.MouseMode.Trackpad) {
+ MainActivityCommon.vmexecutor.onVectrasMouse(Config.SDL_MOUSE_LEFT, MotionEvent.ACTION_UP, 1, 0, 0);
+ } else if (Config.mouseMode == Config.MouseMode.External) {
+ MainActivityCommon.vmexecutor.onVectrasMouse(Config.SDL_MOUSE_LEFT, MotionEvent.ACTION_UP, 0, x, y);
+ MainActivityCommon.vmexecutor.onVectrasMouse(Config.SDL_MOUSE_RIGHT, MotionEvent.ACTION_UP, 0, x, y);
+ MainActivityCommon.vmexecutor.onVectrasMouse(Config.SDL_MOUSE_MIDDLE, MotionEvent.ACTION_UP, 0, x, y);
+ }
+ }
+ }
+ lastMouseButtonDown = -1;
+ mouseUp = true;
+ }
+ else if (event.getAction() == event.ACTION_DOWN
+ && Config.mouseMode == Config.MouseMode.External
+ ) {
+
+ //XXX: Some touch events for touchscreen mode are primary so we force left mouse button
+ if(sdlMouseButton == 0 && MotionEvent.TOOL_TYPE_FINGER == event.getToolType(0)) {
+ sdlMouseButton = Config.SDL_MOUSE_LEFT;
+ }
+
+ MainActivityCommon.vmexecutor.onVectrasMouse(sdlMouseButton, MotionEvent.ACTION_DOWN, relative, x, y);
+ lastMouseButtonDown = sdlMouseButton;
+ }
+ return true;
+ }
+
+ public boolean onTouch(View v, MotionEvent event) {
+ boolean res = false;
+ if(Config.mouseMode == Config.MouseMode.External){
+ res = onTouchProcess(v,event);
+ res = onTouchEventProcess(event);
+ }
+ return res;
+ }
+
+ public boolean onTouchEvent(MotionEvent event) {
+ return false;
+ }
+
+ public boolean onTouchEventProcess(MotionEvent event) {
+ // Log.v("onTouchEvent",
+ // "Action=" + event.getAction() + ", X,Y=" + event.getX() + ","
+ // + event.getY() + " P=" + event.getPressure());
+ // MK
+ if (event.getAction() == MotionEvent.ACTION_CANCEL)
+ return true;
+
+ if (!firstTouch) {
+ firstTouch = true;
+ }
+ if (event.getPointerCount() > 1) {
+
+ // XXX: Vectras Legacy enable Right Click with 2 finger touch
+ // Log.v("Right Click",
+ // "Action=" + event.getAction() + ", X,Y=" + event.getX()
+ // + "," + event.getY() + " P=" + event.getPressure());
+ // rightClick(event);
+ return true;
+ } else
+ return gestureDetector.onTouchEvent(event);
+ }
+ }
+*/
+ public AudioManager am;
+ protected int maxVolume;
+
+ protected void setupVolume() {
+ if (am == null) {
+ am = (AudioManager) mSingleton.getSystemService(Context.AUDIO_SERVICE);
+ maxVolume = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ }
+ }
+
+ public void setVolume(int volume) {
+ if(am!=null)
+ am.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
+ }
+
+ protected int getCurrentVolume() {
+ int volumeTmp = 0;
+ if(am!=null)
+ volumeTmp = am.getStreamVolume(AudioManager.STREAM_MUSIC);
+ return volumeTmp;
+ }
+
+
+ //XXX: We want to suspend only when app is calling onPause()
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+
+ }
+
+ public boolean rightClick(final MotionEvent e, final int i) {
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ Log.d("SDL", "Mouse Right Click");
+ MainActivityCommon.vmexecutor.onVectrasMouse(Config.SDL_MOUSE_RIGHT, MotionEvent.ACTION_DOWN, 1, -1, -1);
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException ex) {
+// Log.v("SDLSurface", "Interrupted: " + ex);
+ }
+ MainActivityCommon.vmexecutor.onVectrasMouse(Config.SDL_MOUSE_RIGHT, MotionEvent.ACTION_UP, 1, -1, -1);
+ }
+ });
+ t.start();
+ return true;
+
+ }
+
+ public boolean middleClick(final MotionEvent e, final int i) {
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ Log.d("SDL", "Mouse Middle Click");
+ MainActivityCommon.vmexecutor.onVectrasMouse(Config.SDL_MOUSE_MIDDLE, MotionEvent.ACTION_DOWN, 1,-1, -1);
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException ex) {
+// Log.v("SDLSurface", "Interrupted: " + ex);
+ }
+ MainActivityCommon.vmexecutor.onVectrasMouse(Config.SDL_MOUSE_MIDDLE, MotionEvent.ACTION_UP, 1,-1, -1);
+ }
+ });
+ t.start();
+ return true;
+
+ }
+
+ private void doubleClick(final MotionEvent event, final int pointer_id) {
+
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ //Log.d("SDL", "Mouse Double Click");
+ for (int i = 0; i < 2; i++) {
+ MainActivityCommon.vmexecutor.onVectrasMouse(Config.SDL_MOUSE_LEFT, MotionEvent.ACTION_DOWN, 1, 0, 0);
+ try {
+ Thread.sleep(50);
+ } catch (InterruptedException ex) {
+ // Log.v("doubletap", "Could not sleep");
+ }
+ MainActivityCommon.vmexecutor.onVectrasMouse(Config.SDL_MOUSE_LEFT, MotionEvent.ACTION_UP, 1,0, 0);
+ try {
+ Thread.sleep(50);
+ } catch (InterruptedException ex) {
+ // Log.v("doubletap", "Could not sleep");
+ }
+ }
+ }
+ });
+ t.start();
+ }
+
+
+ int lastMouseButtonDown = -1;
+ public float old_x = 0;
+ public float old_y = 0;
+ private boolean mouseUp = true;
+ private float sensitivity_mult = (float) 1.0;
+ private boolean firstTouch = false;
+
+
+
+ private class GestureListener extends GestureDetector.SimpleOnGestureListener {
+
+ @Override
+ public boolean onDown(MotionEvent event) {
+ // Log.v("onDown", "Action=" + event.getAction() + ", X,Y=" + event.getX()
+ // + "," + event.getY() + " P=" + event.getPressure());
+ return true;
+ }
+
+ @Override
+ public void onLongPress(MotionEvent event) {
+ // Log.d("SDL", "Long Press Action=" + event.getAction() + ", X,Y="
+ // + event.getX() + "," + event.getY() + " P="
+ // + event.getPressure());
+ if(Config.mouseMode == Config.MouseMode.External)
+ return;
+
+ if(Config.enableDragOnLongPress)
+ dragPointer(event);
+ }
+
+ public boolean onSingleTapConfirmed(MotionEvent event) {
+ float x1 = event.getX();
+ float y1 = event.getY();
+
+ if(Config.mouseMode == Config.MouseMode.External)
+ return true;
+
+// Log.d("onSingleTapConfirmed", "Tapped at: (" + x1 + "," + y1 +
+// ")");
+
+ for (int i = 0; i < event.getPointerCount(); i++) {
+ int action = event.getAction();
+ float x = event.getX(i);
+ float y = event.getY(i);
+ float p = event.getPressure(i);
+
+ //Log.v("onSingleTapConfirmed", "Action=" + action + ", X,Y=" + x + "," + y + " P=" + p);
+ if (event.getAction() == event.ACTION_DOWN
+ && MotionEvent.TOOL_TYPE_FINGER == event.getToolType(0)) {
+ //Log.d("SDL", "onTouch Down: " + event.getButtonState());
+ MainSDLActivity.singleClick(event, i);
+ }
+ }
+ return true;
+
+ }
+
+ // event when double tap occurs
+ @Override
+ public boolean onDoubleTap(MotionEvent event) {
+// Log.d("onDoubleTap", "Tapped at: (" + event.getX() + "," + event.getY() + ")");
+
+ if(Config.mouseMode == Config.MouseMode.External
+ //&& MotionEvent.TOOL_TYPE_MOUSE == event.getToolType(0)
+ )
+ return true;
+
+ if(!Config.enableDragOnLongPress)
+ processDoubleTap(event);
+ else
+ doubleClick(event, 0);
+
+ return true;
+ }
+ }
+
+ private void dragPointer(MotionEvent event) {
+
+ MainActivityCommon.vmexecutor.onVectrasMouse(Config.SDL_MOUSE_LEFT, MotionEvent.ACTION_DOWN, 1, 0, 0);
+ Vibrator v = (Vibrator) activity.getSystemService(Context.VIBRATOR_SERVICE);
+ if (v.hasVibrator()) {
+ v.vibrate(100);
+ }
+ }
+
+ private void processDoubleTap(final MotionEvent event) {
+
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ try {
+ Thread.sleep(400);
+ } catch (InterruptedException e1) {
+ e1.printStackTrace();
+ }
+
+ if (!mouseUp) {
+ dragPointer(event);
+ } else {
+ // Log.v("onDoubleTap", "Action=" + action + ", X,Y=" + x + "," + y + " P=" + p);
+ doubleClick(event, 0);
+ }
+
+ }
+ });
+ t.start();
+
+ }
+
+ class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener {
+ private ExSDLSurface mSurface;
+
+ @Override
+ public boolean onGenericMotion(View v, MotionEvent event) {
+ float x, y;
+ int action;
+
+ switch (event.getSource()) {
+ case InputDevice.SOURCE_JOYSTICK:
+ case InputDevice.SOURCE_GAMEPAD:
+ case InputDevice.SOURCE_DPAD:
+ SDLControllerManager.handleJoystickMotionEvent(event);
+ return true;
+
+ case InputDevice.SOURCE_MOUSE:
+ if(Config.mouseMode == Config.MouseMode.Trackpad)
+ break;
+
+ action = event.getActionMasked();
+// Log.d("SDL", "onGenericMotion, action = " + action + "," + event.getX() + ", " + event.getY());
+ switch (action) {
+ case MotionEvent.ACTION_SCROLL:
+ x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
+ y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
+// Log.d("SDL", "Mouse Scroll: " + x + "," + y);
+ MainActivityCommon.vmexecutor.onVectrasMouse(0, action, 0, x, y);
+ return true;
+
+ case MotionEvent.ACTION_HOVER_MOVE:
+ if(Config.processMouseHistoricalEvents) {
+ final int historySize = event.getHistorySize();
+ for (int h = 0; h < historySize; h++) {
+ float ex = event.getHistoricalX(h);
+ float ey = event.getHistoricalY(h);
+ float ep = event.getHistoricalPressure(h);
+ processHoverMouse(ex, ey, ep, action);
+ }
+ }
+
+ float ex = event.getX();
+ float ey = event.getY();
+ float ep = event.getPressure();
+ processHoverMouse(ex, ey, ep, action);
+ return true;
+
+ case MotionEvent.ACTION_UP:
+
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ // Event was not managed
+ return false;
+ }
+
+ private void processHoverMouse(float x,float y,float p, int action) {
+
+
+
+ if(Config.mouseMode == Config.MouseMode.External) {
+ //Log.d("SDL", "Mouse Hover: " + x + "," + y);
+ MainActivityCommon.vmexecutor.onVectrasMouse(0, action, 0, x, y);
+ }
+ }
+
+ }
+
+ GestureDetector gestureDetector;
+
+}
diff --git a/app/src/main/java/com/vectras/qemu/MainService.java b/app/src/main/java/com/vectras/qemu/MainService.java
new file mode 100644
index 0000000..85c7fa3
--- /dev/null
+++ b/app/src/main/java/com/vectras/qemu/MainService.java
@@ -0,0 +1,236 @@
+package com.vectras.qemu;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.BitmapFactory;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiManager.WifiLock;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.util.Log;
+import android.view.View;
+
+import androidx.core.app.NotificationCompat;
+
+import com.vectras.qemu.jni.StartVM;
+import com.vectras.qemu.utils.FileUtils;
+import com.vectras.vm.Fragment.HomeFragment;
+import com.vectras.vm.MainActivity;
+import com.vectras.vm.R;
+
+public class MainService extends Service {
+
+ private static final String TAG = "MainService";
+ private static Notification mNotification;
+ private static WifiLock mWifiLock;
+ public static MainService service;
+ private static WakeLock mWakeLock;
+ public static boolean isRunning;
+ private NotificationManager mNotificationManager;
+
+ @Override
+ public IBinder onBind(Intent arg0) {
+
+ return null;
+ }
+
+ public static StartVM executor;
+ private static NotificationCompat.Builder builder;
+
+ public static final int notifID = 1000;
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ final String action = intent.getAction();
+ final Bundle b = intent.getExtras();
+ final int ui = b.getInt("ui", 0);
+
+ if (action.equals(Config.ACTION_START)) {
+ setUpAsForeground(Config.machinename + " VM Running");
+
+ FileUtils.startLogging();
+
+ scheduleTimer();
+
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+
+ //XXX: wait till logging starts capturing
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ Log.v(TAG, "Starting VM " + Config.machinename);
+
+ setupLocks();
+
+ if (ui == Config.UI_VNC) {
+
+ new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
+
+ @Override
+ public void run() {
+ MainActivityCommon.startvnc();
+ }
+ }, 2000);
+ }
+
+ //Start vm
+ String res = executor.startvm();
+
+ //VM has exited
+ MainActivityCommon.cleanup();
+
+ }
+ });
+ t.setName("StartVM");
+ t.start();
+
+
+ }
+
+
+ // Don't restart if killed
+ return START_NOT_STICKY;
+ }
+
+ private void scheduleTimer() {
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ try {
+ Thread.sleep(2000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ MainActivityCommon.startTimer();
+ }
+ });
+ t.start();
+ }
+
+
+ private void setUpAsForeground(String text) {
+ isRunning = true;
+ MainActivityCommon.vmStarted = true;
+ Class> clientClass = null;
+ if (Config.ui != null) {
+ if (Config.ui.equals("VNC")) {
+ if (MainSettingsManager.getVncExternal(MainActivityCommon.activity)) {
+ MainActivity.extVncLayout.setVisibility(View.VISIBLE);
+ MainActivity.appbar.setExpanded(true);
+ }
+ clientClass = MainVNCActivity.class;
+ } else if (Config.ui.equals("SDL")) {
+ clientClass = MainSDLActivity.class;
+ } else {
+ Log.e(TAG, "Unknown User Interface");
+ return;
+ }
+ } else {
+ // UIUtils.toastLong(service, "Machine UI is not set");
+ //using VNC by default
+ clientClass = MainVNCActivity.class;
+ }
+ Intent intent = new Intent(service.getApplicationContext(), clientClass);
+
+ PendingIntent pi = PendingIntent.getActivity(service.getApplicationContext(), 0, intent,
+ PendingIntent.FLAG_IMMUTABLE);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ NotificationChannel chan = new NotificationChannel(Config.notificationChannelID, Config.notificationChannelName, NotificationManager.IMPORTANCE_NONE);
+ NotificationManager notifService = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ notifService.createNotificationChannel(chan);
+ builder = new NotificationCompat.Builder(service, Config.notificationChannelID);
+
+ } else
+ builder = new NotificationCompat.Builder(service, "");
+ mNotification = builder.setContentIntent(pi).setContentTitle(getString(R.string.app_name)).setContentText(text)
+ .setSmallIcon(R.mipmap.ic_launcher)
+ .setLargeIcon(BitmapFactory.decodeResource(service.getResources(), R.mipmap.ic_launcher)).build();
+ mNotification.tickerText = text;
+ mNotification.flags |= Notification.FLAG_ONGOING_EVENT;
+
+ service.startForeground(notifID, mNotification);
+
+
+ }
+
+ public static void updateServiceNotification(String text) {
+ if (builder != null) {
+ builder.setContentText(text);
+ mNotification = builder.build();
+ // mNotification.tickerText = text ;
+
+ NotificationManager mNotificationManager = (NotificationManager)
+ service.getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
+ // mId allows you to update the notification later on.
+ mNotificationManager.notify(notifID, mNotification);
+ }
+ }
+
+ @Override
+ public void onCreate() {
+ Log.d(TAG, "Creating Service");
+ service = this;
+ }
+
+ private void setupLocks() {
+
+ mWifiLock = ((WifiManager) service.getApplicationContext().getSystemService(Context.WIFI_SERVICE))
+ .createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, Config.wifiLockTag);
+ mWifiLock.setReferenceCounted(false);
+
+ PowerManager pm = (PowerManager) service.getApplicationContext().getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Config.wakeLockTag);
+ mWakeLock.setReferenceCounted(false);
+
+ mNotificationManager = (NotificationManager) service.getApplicationContext().getSystemService(NOTIFICATION_SERVICE);
+
+ }
+
+ private static void releaseLocks() {
+
+ if (mWifiLock != null && mWifiLock.isHeld()) {
+ Log.d(TAG, "Release Wifi lock...");
+ mWifiLock.release();
+ }
+
+ if (mWakeLock != null && mWakeLock.isHeld()) {
+ Log.d(TAG, "Release Wake lock...");
+ mWakeLock.release();
+ }
+
+ }
+
+ public static void stopService() {
+
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ releaseLocks();
+ if (service != null) {
+ service.stopForeground(true);
+ service.stopSelf();
+ isRunning = false;
+ }
+
+ }
+ });
+ t.setName("StartVM");
+ t.start();
+
+
+ }
+
+}
diff --git a/app/src/main/java/com/vectras/qemu/MainSettingsManager.java b/app/src/main/java/com/vectras/qemu/MainSettingsManager.java
new file mode 100644
index 0000000..72db3dc
--- /dev/null
+++ b/app/src/main/java/com/vectras/qemu/MainSettingsManager.java
@@ -0,0 +1,585 @@
+/*
+Copyright (C) Max Kastanas 2012
+
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+package com.vectras.qemu;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.Environment;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceManager;
+import android.view.MenuItem;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragment;
+import androidx.preference.PreferenceFragmentCompat;
+import com.vectras.vm.R;
+
+import java.util.List;
+
+public class MainSettingsManager extends AppCompatActivity {
+
+ private static final String TAG = "SettingsActivity";
+ public static MainSettingsManager activity;
+ public static SharedPreferences sp;
+
+ public static int fragment = 0;
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_settings);
+ activity = this;
+
+ fragment = 0;
+
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setDisplayShowHomeEnabled(true);
+ toolbar.setTitle("Settings");
+ }
+ @Override
+ public boolean onOptionsItemSelected(@NonNull MenuItem item) {
+ if(item.getItemId()== android.R.id.home){
+ if (fragment == 0) {
+ finish();
+ } else {
+ MainFragment.mainFragment();
+ }
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ public static class MainFragment extends PreferenceFragmentCompat {
+
+ public static MainFragment fr;
+ @Override
+ public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) {
+ setPreferencesFromResource(R.xml.headers_preference, rootKey);
+ fr = MainFragment.this;
+ fragment = 0;
+ findPreference("app").setOnPreferenceClickListener(preference -> {
+ getFragmentManager().beginTransaction().replace(R.id.settingz,
+ new AppPreferencesFragment()).commit();
+ return false;
+ });
+ findPreference("userinterface").setOnPreferenceClickListener(preference -> {
+ getFragmentManager().beginTransaction().replace(R.id.settingz,
+ new UserInterfacePreferencesFragment()).commit();
+ return false;
+ });
+ findPreference("qemu").setOnPreferenceClickListener(preference -> {
+ getFragmentManager().beginTransaction().replace(R.id.settingz,
+ new QemuPreferencesFragment()).commit();
+ return false;
+ });
+ findPreference("vnc").setOnPreferenceClickListener(preference -> {
+ getFragmentManager().beginTransaction().replace(R.id.settingz,
+ new VncPreferencesFragment()).commit();
+ return false;
+ });
+ }
+ public static void mainFragment(){
+ fr.getFragmentManager().beginTransaction().replace(R.id.settingz,
+ new MainFragment()).commit();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ fragment = 0;
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ fragment = 0;
+ }
+
+ }
+
+ public static class AppPreferencesFragment extends PreferenceFragmentCompat
+ implements OnSharedPreferenceChangeListener {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ sp = getPreferenceScreen().getSharedPreferences();
+
+ Toolbar toolbar = (Toolbar) activity.findViewById(R.id.toolbar);
+ activity.setSupportActionBar(toolbar);
+ activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
+ toolbar.setTitle("APP SETTINGS");
+ fragment = 1;
+ }
+
+ @Override
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ setPreferencesFromResource(R.xml.settings, rootKey);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ fragment = 1;
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ }
+
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ sp = sharedPreferences;
+ }
+ }
+
+ public static class UserInterfacePreferencesFragment extends PreferenceFragmentCompat
+ implements OnSharedPreferenceChangeListener {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.userinterface);
+ sp = getPreferenceScreen().getSharedPreferences();
+
+ Toolbar toolbar = (Toolbar) activity.findViewById(R.id.toolbar);
+ activity.setSupportActionBar(toolbar);
+ activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
+ toolbar.setTitle("USER INTERFACE");
+ fragment = 2;
+ }
+
+ @Override
+ public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
+
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ fragment = 2;
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ }
+
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ sp = sharedPreferences;
+ }
+
+ }
+ public static class QemuPreferencesFragment extends PreferenceFragmentCompat
+ implements Preference.OnPreferenceChangeListener {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.qemu);
+ sp = getPreferenceScreen().getSharedPreferences();
+
+ Toolbar toolbar = (Toolbar) activity.findViewById(R.id.toolbar);
+ activity.setSupportActionBar(toolbar);
+ activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
+ toolbar.setTitle("QEMU");
+ fragment = 3;
+ }
+
+ @Override
+ public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
+
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ onMemory();
+ fragment = 3;
+ }
+
+ private void onMemory() {
+ //findPreference("memory").setEnabled(getCusRam(activity));
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ onMemory();
+ }
+
+ @Override
+ public boolean onPreferenceChange(@NonNull Preference preference, Object newValue) {
+ onMemory();
+ return true;
+ }
+ }
+ public static class VncPreferencesFragment extends PreferenceFragmentCompat
+ implements OnSharedPreferenceChangeListener {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.vnc);
+ sp = getPreferenceScreen().getSharedPreferences();
+
+ Toolbar toolbar = (Toolbar) activity.findViewById(R.id.toolbar);
+ activity.setSupportActionBar(toolbar);
+ activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
+ toolbar.setTitle("VNC");
+ fragment = 4;
+ }
+
+ @Override
+ public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
+
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ fragment = 4;
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ }
+
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ sp = sharedPreferences;
+ }
+
+ }
+ static String getDNSServer(Activity activity) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
+ return prefs.getString("dnsServer", Config.defaultDNSServer);
+ }
+
+ public static void setDNSServer(Activity activity, String dnsServer) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
+ SharedPreferences.Editor edit = prefs.edit();
+ edit.putString("dnsServer", dnsServer);
+ edit.apply();
+ }
+
+ public static boolean getVncExternal(Activity activity) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
+ return prefs.getBoolean("vncExternal", false);
+ }
+
+ public static void setVncExternal(Activity activity, boolean vncExternal) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
+ SharedPreferences.Editor edit = prefs.edit();
+ edit.putBoolean("vncExternal", vncExternal);
+ edit.apply();
+ }
+
+ public static int getOrientationSetting(Activity activity) {
+
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
+ int orientation = prefs.getInt("orientation", 0);
+ // UIUtils.log("Getting First time: " + firstTime);
+ return orientation;
+ }
+
+ public static void setOrientationSetting(Activity activity, int orientation) {
+
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
+ SharedPreferences.Editor edit = prefs.edit();
+ edit.putInt("orientation", orientation);
+ edit.apply();
+ }
+
+
+ public static boolean getPromptUpdateVersion(Activity activity) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
+ return prefs.getBoolean("updateVersionPrompt", Config.defaultCheckNewVersion);
+ }
+
+
+ public static void setPromptUpdateVersion(Activity activity, boolean flag) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
+ SharedPreferences.Editor edit = prefs.edit();
+ edit.putBoolean("updateVersionPrompt", flag);
+ edit.apply();
+ // UIUtils.log("Setting First time: ");
+ }
+
+ static boolean getPrio(Activity activity) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
+ return prefs.getBoolean("HighPrio", false);
+ }
+
+ public static void setPrio(Activity activity, boolean flag) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
+ SharedPreferences.Editor edit = prefs.edit();
+ edit.putBoolean("HighPrio", flag);
+ edit.apply();
+ // UIUtils.log("Setting First time: ");
+ }
+
+ public static boolean getAlwaysShowMenuToolbar(Activity activity) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
+ return prefs.getBoolean("AlwaysShowMenuToolbar", false);
+ }
+
+ public static void setAlwaysShowMenuToolbar(Activity activity, boolean flag) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
+ SharedPreferences.Editor edit = prefs.edit();
+ edit.putBoolean("AlwaysShowMenuToolbar", flag);
+ edit.apply();
+ // UIUtils.log("Setting First time: ");
+ }
+
+ public static boolean getFullscreen(Activity activity) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
+ return prefs.getBoolean("ShowFullscreen", true);
+ }
+
+ public static void setFullscreen(Activity activity, boolean flag) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
+ SharedPreferences.Editor edit = prefs.edit();
+ edit.putBoolean("ShowFullscreen", flag);
+ edit.apply();
+ // UIUtils.log("Setting First time: ");
+ }
+
+ public static boolean getDesktopMode(Activity activity) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
+ return prefs.getBoolean("DesktopMode", false);
+ }
+
+ public static void setDesktopMode(Activity activity, boolean flag) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
+ SharedPreferences.Editor edit = prefs.edit();
+ edit.putBoolean("DesktopMode", flag);
+ edit.apply();
+ // UIUtils.log("Setting First time: ");
+ }
+
+ public static boolean getEnableLegacyFileManager(Activity activity) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
+ return prefs.getBoolean("EnableLegacyFileManager", false);
+ }
+
+
+ public static void setEnableLegacyFileManager(Activity activity, boolean flag) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
+ SharedPreferences.Editor edit = prefs.edit();
+ edit.putBoolean("EnableLegacyFileManager", flag);
+ edit.apply();
+ // UIUtils.log("Setting First time: ");
+ }
+
+ public static String getLastDir(Context context) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ String imagesDir = prefs.getString("lastDir", null);
+ return imagesDir;
+ }
+
+ public static void setLastDir(Context context, String imagesPath) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ SharedPreferences.Editor edit = prefs.edit();
+ edit.putString("lastDir", imagesPath);
+ edit.commit();
+ }
+
+ public static String getImagesDir(Context context) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ String imagesDir = prefs.getString("imagesDir", null);
+ return imagesDir;
+ }
+
+ public static void setImagesDir(Context context, String imagesPath) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ SharedPreferences.Editor edit = prefs.edit();
+ edit.putString("imagesDir", imagesPath);
+ edit.commit();
+ }
+
+
+ public static String getExportDir(Context context) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ String imagesDir = prefs.getString("exportDir", null);
+ return imagesDir;
+ }
+
+ public static void setExportDir(Context context, String imagesPath) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ SharedPreferences.Editor edit = prefs.edit();
+ edit.putString("exportDir", imagesPath);
+ edit.commit();
+ }
+
+
+ public static String getSharedDir(Context context) {
+ String lastDir = Environment.getExternalStorageDirectory().getPath();
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ return prefs.getString("sharedDir", lastDir);
+ }
+
+ public static void setSharedDir(Context context, String lastDir) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ SharedPreferences.Editor edit = prefs.edit();
+ edit.putString("sharedDir", lastDir);
+ edit.apply();
+ // UIUtils.log("Setting First time: ");
+ }
+
+
+ public static Boolean getMTTCG(Context context) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ Boolean MTTCG = prefs.getBoolean("MTTCG", true);
+ return MTTCG;
+ }
+
+ public static void setMTTCG(Context context, Boolean MTTCG) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ SharedPreferences.Editor edit = prefs.edit();
+ edit.putBoolean("MTTCG", MTTCG);
+ edit.commit();
+ }
+
+ public static int getCpuCores(Context context) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ int cpuCores = prefs.getInt("cpuCores", 1);
+ return cpuCores;
+ }
+
+ public static void setCpuCores(Context context, int cpuCores) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ SharedPreferences.Editor edit = prefs.edit();
+ edit.putInt("cpuCores", cpuCores);
+ edit.commit();
+ }
+
+ public static int getExitCode(Context context) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ int exitCode = prefs.getInt("exitCode", 1);
+ return exitCode;
+ }
+
+ public static void setExitCode(Context context, int exitCode) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ SharedPreferences.Editor edit = prefs.edit();
+ edit.putInt("exitCode", exitCode);
+ edit.commit();
+ }
+
+ public static int getCpuNum(Context context) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ int cpuNum = Integer.parseInt(prefs.getString("cpuNum", "1"));
+ return cpuNum;
+ }
+
+ public static void setCpuNum(Context context, String cpuNum) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ SharedPreferences.Editor edit = prefs.edit();
+ edit.putString("cpuNum", cpuNum);
+ edit.commit();
+ }
+
+ public static String getControlMode(Context context) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ String controlMode = prefs.getString("controlMode", "D");
+ return controlMode;
+ }
+
+ public static void setControlMode(Context context, String controlMode) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ SharedPreferences.Editor edit = prefs.edit();
+ edit.putString("controlMode", controlMode);
+ edit.commit();
+ }
+
+
+ public static void setModeNight(Context context, Boolean nightMode) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ SharedPreferences.Editor edit = prefs.edit();
+ edit.putBoolean("modeNight", nightMode);
+ edit.commit();
+ }
+
+ public static Boolean getModeNight(Context context) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ return prefs.getBoolean("modeNight", false);
+ }
+
+ public static void setCusRam(Activity activity, Boolean cusRam) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
+ SharedPreferences.Editor edit = prefs.edit();
+ edit.putBoolean("customMemory", cusRam);
+ edit.apply();
+ }
+
+ public static boolean getCusRam(Activity activity) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
+ return prefs.getBoolean("customMemory", false);
+ }
+
+ public static boolean isFirstLaunch(Activity activity) {
+ PackageInfo pInfo = null;
+
+ try {
+ pInfo = activity.getPackageManager().getPackageInfo(activity.getClass().getPackage().getName(),
+ PackageManager.GET_META_DATA);
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ }
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
+ boolean firstTime = prefs.getBoolean("firstTime" + pInfo.versionName, true);
+ return firstTime;
+ }
+
+ public static void setFirstLaunch(Activity activity) {
+ PackageInfo pInfo = null;
+
+ try {
+ pInfo = activity.getPackageManager().getPackageInfo(activity.getClass().getPackage().getName(),
+ PackageManager.GET_META_DATA);
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ }
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
+ SharedPreferences.Editor edit = prefs.edit();
+ edit.putBoolean("firstTime" + pInfo.versionName, false);
+ edit.commit();
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (fragment == 0) {
+ finish();
+ super.onBackPressed();
+ } else {
+ MainFragment.mainFragment();
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/vectras/qemu/MainVNCActivity.java b/app/src/main/java/com/vectras/qemu/MainVNCActivity.java
new file mode 100644
index 0000000..5ad8e27
--- /dev/null
+++ b/app/src/main/java/com/vectras/qemu/MainVNCActivity.java
@@ -0,0 +1,1331 @@
+package com.vectras.qemu;
+
+import android.androidVNC.AbstractScaling;
+import android.androidVNC.VncCanvasActivity;
+
+import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.AppCompatDelegate;
+import androidx.appcompat.widget.Toolbar;
+import androidx.core.view.MenuItemCompat;
+import androidx.fragment.app.FragmentTransaction;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.DialogInterface;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.graphics.Point;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.inputmethod.BaseInputConnection;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.RelativeLayout;
+import android.widget.ScrollView;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+import android.widget.TextView;
+
+import com.vectras.vm.Fragment.ControlersOptionsFragment;
+import com.vectras.vm.R;
+import com.vectras.qemu.utils.Machine;
+import com.vectras.qemu.utils.QmpClient;
+import com.vectras.vm.utils.UIUtils;
+
+import java.io.File;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.json.JSONObject;
+
+
+/**
+ * @author Dev
+ */
+public class MainVNCActivity extends VncCanvasActivity {
+
+ public static final int KEYBOARD = 10000;
+ public static final int QUIT = 10001;
+ public static final int HELP = 10002;
+ private static boolean monitorMode = false;
+ private boolean mouseOn = false;
+ private Object lockTime = new Object();
+ private boolean timeQuit = false;
+ private Thread timeListenerThread;
+ private ProgressDialog progDialog;
+ private static boolean firstConnection;
+ String[] functionsArray = {"F1", "F2", "F3", "F4",
+ "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12"};
+
+ public boolean ctrlClicked = false;
+ public boolean altClicked = false;
+ private ImageButton qmpBtn;
+
+ @Override
+ public void onCreate(Bundle b) {
+
+ if (MainSettingsManager.getFullscreen(this))
+ getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
+ WindowManager.LayoutParams.FLAG_FULLSCREEN);
+
+ super.onCreate(b);
+
+ this.vncCanvas.setFocusableInTouchMode(true);
+
+ setDefaulViewMode();
+
+// setUIModeMobile();
+
+ Toolbar mainToolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(mainToolbar);
+
+ View decorView = getWindow().getDecorView();
+ int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+ decorView.setSystemUiVisibility(uiOptions);
+
+ onFitToScreen();
+
+ ImageButton shutdownBtn = findViewById(R.id.shutdownBtn);
+ ImageButton settingBtn = findViewById(R.id.btnSettings);
+ ImageButton keyboardBtn = findViewById(R.id.kbdBtn);
+ ImageButton controllersBtn = findViewById(R.id.btnMode);
+ ImageButton upBtn = findViewById(R.id.upBtn);
+ ImageButton leftBtn = findViewById(R.id.leftBtn);
+ ImageButton downBtn = findViewById(R.id.downBtn);
+ ImageButton rightBtn = findViewById(R.id.rightBtn);
+ ImageButton enterBtn = findViewById(R.id.enterBtn);
+ ImageButton escBtn = findViewById(R.id.escBtn);
+ ImageButton ctrlBtn = findViewById(R.id.ctrlBtn);
+ ImageButton altBtn = findViewById(R.id.altBtn);
+ ImageButton delBtn = findViewById(R.id.delBtn);
+ Button ctrlAltDelBtn = findViewById(R.id.ctrlAltDelBtn);
+ qmpBtn = findViewById(R.id.btnQmp);
+ shutdownBtn.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Machine.stopVM(activity);
+ }
+ });
+ keyboardBtn.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ toggleKeyboardFlag = UIUtils.onKeyboard(activity, toggleKeyboardFlag, vncCanvas);
+ }
+ }, 200);
+ }
+ });
+ controllersBtn.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
+ // Create and show the dialog.
+ ControlersOptionsFragment newFragment = new ControlersOptionsFragment();
+ newFragment.show(ft, "Controllers");
+ }
+ });
+ settingBtn.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final Dialog alertDialog = new Dialog(activity, R.style.MainDialogTheme);
+ alertDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+ alertDialog.getWindow().setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT));
+ alertDialog.setContentView(R.layout.dialog_setting);
+ alertDialog.show();
+ }
+ });
+ upBtn.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ sendKey(KeyEvent.KEYCODE_DPAD_UP, false);
+ return true;
+ } else if (event.getAction() == MotionEvent.ACTION_UP) {
+ sendKey(KeyEvent.KEYCODE_DPAD_UP, true);
+ return true;
+ }
+ return false;
+ }
+ });
+ leftBtn.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ sendKey(KeyEvent.KEYCODE_DPAD_LEFT, false);
+ return true;
+ } else if (event.getAction() == MotionEvent.ACTION_UP) {
+ sendKey(KeyEvent.KEYCODE_DPAD_LEFT, true);
+ return true;
+ }
+ return false;
+ }
+ });
+ downBtn.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ sendKey(KeyEvent.KEYCODE_DPAD_DOWN, false);
+ return true;
+ } else if (event.getAction() == MotionEvent.ACTION_UP) {
+ sendKey(KeyEvent.KEYCODE_DPAD_DOWN, true);
+ return true;
+ }
+ return false;
+ }
+ });
+ rightBtn.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ sendKey(KeyEvent.KEYCODE_DPAD_RIGHT, false);
+ return true;
+ } else if (event.getAction() == MotionEvent.ACTION_UP) {
+ sendKey(KeyEvent.KEYCODE_DPAD_RIGHT, true);
+ return true;
+ }
+ return false;
+ }
+ });
+ escBtn.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ keyDownUp(KeyEvent.KEYCODE_ESCAPE);
+ }
+ });
+ enterBtn.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ keyDownUp(KeyEvent.KEYCODE_ENTER);
+ }
+ });
+ ctrlBtn.setOnClickListener(new View.OnClickListener() {
+ @SuppressLint("UseCompatLoadingForDrawables")
+ @Override
+ public void onClick(View v) {
+ if (!ctrlClicked) {
+ sendKey(KeyEvent.KEYCODE_CTRL_LEFT, false);
+ ctrlBtn.setBackground(getResources().getDrawable(R.drawable.controls_button2));
+ ctrlClicked = true;
+ } else {
+ sendKey(KeyEvent.KEYCODE_CTRL_LEFT, true);
+ ctrlBtn.setBackground(getResources().getDrawable(R.drawable.controls_button1));
+ ctrlClicked = false;
+ }
+ }
+ });
+ altBtn.setOnClickListener(new View.OnClickListener() {
+ @SuppressLint("UseCompatLoadingForDrawables")
+ @Override
+ public void onClick(View v) {
+ if (!altClicked) {
+ sendKey(KeyEvent.KEYCODE_ALT_LEFT, false);
+ altBtn.setBackground(getResources().getDrawable(R.drawable.controls_button2));
+ altClicked = true;
+ } else {
+ sendKey(KeyEvent.KEYCODE_ALT_LEFT, true);
+ altBtn.setBackground(getResources().getDrawable(R.drawable.controls_button1));
+ altClicked = false;
+ }
+ }
+ });
+ delBtn.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ keyDownUp(KeyEvent.KEYCODE_DEL);
+ }
+ });
+ ctrlAltDelBtn.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ sendCtrlAtlDelKey();
+ }
+ });
+ qmpBtn.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (monitorMode) {
+ onVNC();
+ qmpBtn.setImageDrawable(getResources().getDrawable(R.drawable.round_terminal_24));
+ } else {
+ onMonitor();
+ qmpBtn.setImageDrawable(getResources().getDrawable(R.drawable.round_computer_24));
+ }
+ }
+ });
+ if (monitorMode) {
+ qmpBtn.setImageDrawable(getResources().getDrawable(R.drawable.round_terminal_24));
+ } else {
+ qmpBtn.setImageDrawable(getResources().getDrawable(R.drawable.round_computer_24));
+ }
+
+ ArrayAdapter adapter = new ArrayAdapter<>(this,
+ R.layout.container_function, functionsArray);
+
+ ListView listView = findViewById(R.id.functions);
+ listView.setAdapter(adapter);
+ listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ if (position == 0) {
+ keyDownUp(KeyEvent.KEYCODE_F1);
+ } else if (position == 1) {
+ keyDownUp(KeyEvent.KEYCODE_F2);
+ } else if (position == 2) {
+ keyDownUp(KeyEvent.KEYCODE_F3);
+ } else if (position == 3) {
+ keyDownUp(KeyEvent.KEYCODE_F4);
+ } else if (position == 4) {
+ keyDownUp(KeyEvent.KEYCODE_F5);
+ } else if (position == 5) {
+ keyDownUp(KeyEvent.KEYCODE_F6);
+ } else if (position == 6) {
+ keyDownUp(KeyEvent.KEYCODE_F7);
+ } else if (position == 7) {
+ keyDownUp(KeyEvent.KEYCODE_F8);
+ } else if (position == 8) {
+ keyDownUp(KeyEvent.KEYCODE_F9);
+ } else if (position == 9) {
+ keyDownUp(KeyEvent.KEYCODE_F10);
+ } else if (position == 10) {
+ keyDownUp(KeyEvent.KEYCODE_F11);
+ } else if (position == 11) {
+ keyDownUp(KeyEvent.KEYCODE_F12);
+ }
+ }
+ });
+ }
+
+ private void keyDownUp(int keyEventCode) {
+ dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyEventCode));
+ dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyEventCode));
+ }
+
+ private void sendKey(int keyEventCode, boolean up) {
+ if (up)
+ dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyEventCode));
+ else dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyEventCode));
+ }
+
+ public void sendCtrlAtlDelKey() {
+ dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_CTRL_LEFT));
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ALT_LEFT));
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_CTRL_LEFT));
+ dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ALT_LEFT));
+ dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));
+ }
+
+ private void setDefaulViewMode() {
+
+
+ // Fit to Screen
+ AbstractScaling.getById(R.id.itemFitToScreen).setScaleTypeForActivity(this);
+ showPanningState();
+
+// screenMode = VNCScreenMode.FitToScreen;
+ setLayout(getResources().getConfiguration());
+
+ UIUtils.setOrientation(this);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ setLayout(newConfig);
+ }
+
+ public enum VNCScreenMode {
+ Normal,
+ FitToScreen,
+ Fullscreen //fullscreen not implemented yet
+ }
+
+ public static VNCScreenMode screenMode = VNCScreenMode.FitToScreen;
+
+ private void setLayout(Configuration newConfig) {
+
+ boolean isLanscape =
+ (newConfig != null && newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE)
+ || UIUtils.isLandscapeOrientation(this);
+
+ View vnc_canvas_layout = (View) this.findViewById(R.id.vnc_canvas_layout);
+ RelativeLayout.LayoutParams vnc_canvas_layout_params = null;
+ RelativeLayout.LayoutParams vnc_params = null;
+ //normal 1-1
+ if (screenMode == VNCScreenMode.Normal) {
+ if (isLanscape) {
+ vnc_params = new RelativeLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ );
+// vnc_params.addRule(RelativeLayout.CENTER_IN_PARENT);
+ vnc_params.addRule(RelativeLayout.CENTER_HORIZONTAL);
+ vnc_params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
+
+ vnc_canvas_layout_params = new RelativeLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ );
+// vnc_canvas_layout_params.addRule(RelativeLayout.CENTER_IN_PARENT);
+ vnc_canvas_layout_params.addRule(RelativeLayout.CENTER_HORIZONTAL);
+ vnc_canvas_layout_params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
+
+ } else {
+ vnc_params = new RelativeLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ );
+ vnc_params.addRule(RelativeLayout.CENTER_HORIZONTAL);
+ vnc_params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
+
+ vnc_canvas_layout_params = new RelativeLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ );
+ vnc_canvas_layout_params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
+ vnc_canvas_layout_params.addRule(RelativeLayout.CENTER_HORIZONTAL);
+ }
+ } else {
+ //fittoscreen
+ if (isLanscape) {
+ vnc_params = new RelativeLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT
+ );
+ vnc_params.addRule(RelativeLayout.CENTER_IN_PARENT);
+ vnc_canvas_layout_params = new RelativeLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT
+ );
+ vnc_canvas_layout_params.addRule(RelativeLayout.CENTER_IN_PARENT);
+ } else {
+ final Display display = getWindow().getWindowManager().getDefaultDisplay();
+ Point size = new Point();
+ display.getSize(size);
+
+ int h = ViewGroup.LayoutParams.WRAP_CONTENT;
+ if (vncCanvas != null && vncCanvas.rfb != null
+ && vncCanvas.rfb.framebufferWidth != 0
+ && vncCanvas.rfb.framebufferHeight != 0) {
+ h = size.x * vncCanvas.rfb.framebufferHeight / vncCanvas.rfb.framebufferWidth;
+ }
+ vnc_params = new RelativeLayout.LayoutParams(
+ size.x,
+ h
+ );
+ vnc_params.addRule(RelativeLayout.CENTER_HORIZONTAL);
+ vnc_params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
+
+ vnc_canvas_layout_params = new RelativeLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ );
+ vnc_canvas_layout_params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
+ vnc_canvas_layout_params.addRule(RelativeLayout.CENTER_HORIZONTAL);
+ }
+ }
+ this.vncCanvas.setLayoutParams(vnc_params);
+ vnc_canvas_layout.setLayoutParams(vnc_canvas_layout_params);
+
+ this.invalidateOptionsMenu();
+ }
+
+ public void stopTimeListener() {
+ Log.v(TAG, "Stopping Listener");
+ synchronized (this.lockTime) {
+ this.timeQuit = true;
+ this.lockTime.notifyAll();
+ }
+ }
+
+ public void onDestroy() {
+ super.onDestroy();
+ this.stopTimeListener();
+
+ }
+
+ public void onPause() {
+ MainService.updateServiceNotification("Vectras VM Running in Background");
+ super.onPause();
+ }
+
+ public void onResume() {
+ MainService.updateServiceNotification("Vectras VM Running");
+ if (monitorMode) {
+ qmpBtn.setImageDrawable(getResources().getDrawable(R.drawable.round_terminal_24));
+ } else {
+ qmpBtn.setImageDrawable(getResources().getDrawable(R.drawable.round_computer_24));
+ }
+ super.onResume();
+ }
+
+ public void checkStatus() {
+ while (timeQuit != true) {
+ MainActivityCommon.VMStatus status = Machine.checkSaveVMStatus(activity);
+ Log.v(TAG, "Status: " + status);
+ if (status == MainActivityCommon.VMStatus.Unknown
+ || status == MainActivityCommon.VMStatus.Completed
+ || status == MainActivityCommon.VMStatus.Failed
+ ) {
+ //Log.v(TAG, "Saving state is done: " + status);
+ stopTimeListener();
+ return;
+ }
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException ex) {
+ Log.w("SaveVM", "Interrupted");
+ }
+ }
+ Log.v("SaveVM", "Save state complete");
+
+ }
+
+ public void startSaveVMListener() {
+ this.stopTimeListener();
+ timeQuit = false;
+ try {
+ Log.v("Listener", "Time Listener Started...");
+ checkStatus();
+ synchronized (lockTime) {
+ while (timeQuit == false) {
+ lockTime.wait();
+ }
+ lockTime.notifyAll();
+ }
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ Log.v("SaveVM", "Time listener thread error: " + ex.getMessage());
+ }
+ Log.v("Listener", "Time listener thread exited...");
+
+ }
+
+ String TAG = "MainVNCActivity";
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ super.onOptionsItemSelected(item);
+ if (item.getItemId() == this.KEYBOARD || item.getItemId() == R.id.itemKeyboard) {
+ new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ toggleKeyboardFlag = UIUtils.onKeyboard(activity, toggleKeyboardFlag, vncCanvas);
+ }
+ }, 200);
+ } else if (item.getItemId() == R.id.itemReset) {
+ Machine.resetVM(activity);
+ } else if (item.getItemId() == R.id.itemShutdown) {
+ UIUtils.hideKeyboard(this, vncCanvas);
+ Machine.stopVM(activity);
+ } else if (item.getItemId() == R.id.itemDrives) {
+
+ } else if (item.getItemId() == R.id.itemMonitor) {
+ if (this.monitorMode) {
+ this.onVNC();
+ } else {
+ this.onMonitor();
+ }
+ } else if (item.getItemId() == R.id.itemSaveState) {
+ this.promptPause(activity);
+ } else if (item.getItemId() == R.id.itemFitToScreen) {
+ return onFitToScreen();
+ } else if (item.getItemId() == R.id.itemFullScreen) {
+ return toggleFullScreen();
+ } else if (item.getItemId() == this.QUIT) {
+ } else if (item.getItemId() == R.id.itemCenterMouse) {
+ onMouseMode();
+ } else if (item.getItemId() == R.id.itemCalibrateMouse) {
+ calibration();
+ } else if (item.getItemId() == R.id.itemHelp) {
+
+ } else if (item.getItemId() == R.id.itemHideToolbar) {
+ this.onHideToolbar();
+ } else if (item.getItemId() == R.id.itemDisplay) {
+ this.onSelectMenuVNCDisplay();
+ } else if (item.getItemId() == R.id.itemViewLog) {
+
+ }
+
+ this.invalidateOptionsMenu();
+
+ return true;
+ }
+
+ private void onMouseMode() {
+
+ String[] items = {"Trackpad Mouse (Phone)",
+ "Bluetooth/USB Mouse (Desktop mode)", //Physical mouse for Chromebook, Android x86 PC, or Bluetooth Mouse
+ };
+ final AlertDialog.Builder mBuilder = new AlertDialog.Builder(this);
+ mBuilder.setTitle("Mouse");
+ mBuilder.setSingleChoiceItems(items, Config.mouseMode.ordinal(), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int i) {
+ switch (i) {
+ case 0:
+ setUIModeMobile(true);
+ break;
+ case 1:
+ promptSetUIModeDesktop(MainVNCActivity.this, false);
+ break;
+ default:
+ break;
+ }
+ dialog.dismiss();
+ }
+ });
+ final AlertDialog alertDialog = mBuilder.create();
+ alertDialog.show();
+
+ }
+
+ public boolean checkVMResolutionFits() {
+ if (vncCanvas.rfb.framebufferWidth < vncCanvas.getWidth()
+ && vncCanvas.rfb.framebufferHeight < vncCanvas.getHeight())
+ return true;
+
+ return false;
+ }
+
+ private void onDisplayMode() {
+
+ String[] items = {
+ "Normal (One-To-One)",
+ "Fit To Screen"
+ //"Full Screen" //Stretched
+ };
+ int currentScaleType = vncCanvas.getScaleType() == ImageView.ScaleType.FIT_CENTER ? 1 : 0;
+
+ final AlertDialog.Builder mBuilder = new AlertDialog.Builder(this);
+ mBuilder.setTitle("Display Mode");
+ mBuilder.setSingleChoiceItems(items, currentScaleType, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int i) {
+ switch (i) {
+ case 0:
+ onNormalScreen();
+ onMouse();
+ break;
+ case 1:
+ if (Config.mouseMode == Config.MouseMode.External) {
+ UIUtils.toastShort(MainVNCActivity.this, "Fit to Screen disabled under Desktop mode");
+ dialog.dismiss();
+ return;
+ }
+ onFitToScreen();
+ onMouse();
+ break;
+ default:
+ break;
+ }
+ dialog.dismiss();
+ }
+ });
+ final AlertDialog alertDialog = mBuilder.create();
+ alertDialog.show();
+
+ }
+
+ private void setUIModeMobile(boolean fitToScreen) {
+
+ try {
+ MotionEvent a = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
+
+ Config.mouseMode = Config.MouseMode.Trackpad;
+ MainSettingsManager.setDesktopMode(this, false);
+ if (fitToScreen)
+ onFitToScreen();
+ else
+ onNormalScreen();
+ onMouse();
+
+ //UIUtils.toastShort(MainVNCActivity.this, "Trackpad Calibrating");
+ invalidateOptionsMenu();
+ } catch (Exception ex) {
+ if (Config.debug)
+ ex.printStackTrace();
+ }
+ }
+
+ private void promptSetUIModeDesktop(final Activity activity, final boolean mouseMethodAlt) {
+
+
+ final AlertDialog alertDialog;
+ alertDialog = new AlertDialog.Builder(activity, R.style.MainDialogTheme).create();
+ alertDialog.setTitle("Desktop mode");
+
+ LinearLayout mLayout = new LinearLayout(this);
+ mLayout.setPadding(20, 20, 20, 20);
+ mLayout.setOrientation(LinearLayout.VERTICAL);
+
+ TextView textView = new TextView(activity);
+ textView.setVisibility(View.VISIBLE);
+
+ String desktopInstructions = this.getString(R.string.desktopInstructions);
+ if (!checkVMResolutionFits()) {
+ String resolutionWarning = "Warning: Machine resolution "
+ + vncCanvas.rfb.framebufferWidth + "x" + vncCanvas.rfb.framebufferHeight +
+ " is too high for Desktop Mode. " +
+ "Scaling will be used and Mouse Alignment will not be accurate. " +
+ "Reduce display resolution for better experience\n\n";
+ desktopInstructions = resolutionWarning + desktopInstructions;
+ }
+ textView.setText(desktopInstructions);
+
+ LinearLayout.LayoutParams textViewParams = new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+ ScrollView scrollView = new ScrollView(this);
+ scrollView.addView(textView);
+ mLayout.addView(scrollView, textViewParams);
+ alertDialog.setView(mLayout);
+
+ alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "OK", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+
+ setUIModeDesktop();
+ alertDialog.dismiss();
+ }
+ });
+ alertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancel", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ alertDialog.dismiss();
+ }
+ });
+ alertDialog.show();
+
+ }
+
+ private void setUIModeDesktop() {
+
+ try {
+ MotionEvent a = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
+ Config.mouseMode = Config.MouseMode.External;
+ MainSettingsManager.setDesktopMode(this, true);
+ if (Config.showToast)
+ UIUtils.toastShort(MainVNCActivity.this, "External Mouse Enabled");
+ onNormalScreen();
+ AbstractScaling.getById(R.id.itemOneToOne).setScaleTypeForActivity(MainVNCActivity.this);
+ showPanningState();
+
+ onMouse();
+ } catch (Exception e) {
+ if (Config.debug)
+ e.printStackTrace();
+ }
+ //vncCanvas.reSize(false);
+ invalidateOptionsMenu();
+ }
+
+ public void setContentView() {
+
+ setContentView(R.layout.activity_vnc);
+
+ }
+
+ private boolean toggleFullScreen() {
+
+ UIUtils.toastShort(this, "VNC Fullscreen not supported");
+
+ return false;
+ }
+
+ private boolean onFitToScreen() {
+
+ try {
+ UIUtils.setOrientation(this);
+ ActionBar bar = this.getSupportActionBar();
+ if (bar != null && !MainSettingsManager.getAlwaysShowMenuToolbar(this)) {
+ bar.hide();
+ }
+
+ inputHandler = getInputHandlerById(R.id.itemInputTouchpad);
+ connection.setInputMode(inputHandler.getName());
+ connection.setFollowMouse(true);
+ mouseOn = true;
+ AbstractScaling.getById(R.id.itemFitToScreen).setScaleTypeForActivity(this);
+ showPanningState();
+ screenMode = VNCScreenMode.FitToScreen;
+ setLayout(null);
+
+ return true;
+ } catch (Exception ex) {
+ if (Config.debug)
+ ex.printStackTrace();
+ }
+ return false;
+ }
+
+ private boolean onNormalScreen() {
+
+ try {
+ //Force only landscape
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
+ ActionBar bar = MainVNCActivity.this.getSupportActionBar();
+ if (bar != null) {
+ bar.hide();
+ }
+
+ inputHandler = getInputHandlerById(R.id.itemInputTouchpad);
+ connection.setInputMode(inputHandler.getName());
+ connection.setFollowMouse(true);
+ mouseOn = true;
+ AbstractScaling.getById(R.id.itemOneToOne).setScaleTypeForActivity(this);
+ showPanningState();
+ screenMode = VNCScreenMode.Normal;
+ setLayout(null);
+
+ return true;
+ } catch (Exception ex) {
+ if (Config.debug)
+ ex.printStackTrace();
+ } finally {
+
+ }
+ return false;
+ }
+
+ private boolean onMouse() {
+
+ // Main: For now we disable other modes
+ if (Config.disableMouseModes)
+ mouseOn = false;
+
+
+ if (mouseOn == false) {
+ inputHandler = getInputHandlerById(R.id.itemInputTouchpad);
+ connection.setInputMode(inputHandler.getName());
+ connection.setFollowMouse(true);
+ mouseOn = true;
+ } else {
+ // XXX: Main
+ // we disable panning for now
+ // input1 = getInputHandlerById(R.id.itemFitToScreen);
+ // input1 = getInputHandlerById(R.id.itemInputTouchPanZoomMouse);
+ // connection.setFollowMouse(false);
+ // mouseOn = false;
+ }
+
+ //Start calibration
+ calibration();
+
+ return true;
+ }
+
+ //XXX: We need to adjust the mouse inside the Guest
+ // This is a known issue with QEMU under VNC mode
+ // this only fixes things temporarily.
+ // There is a workaround to choose USB Tablet for mouse emulation
+ // though it might not work for all Guest OSes
+ public void calibration() {
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ try {
+
+ int origX = vncCanvas.mouseX;
+ int origY = vncCanvas.mouseY;
+ MotionEvent event = null;
+
+ for (int i = 0; i < 4 * 20; i++) {
+ int x = 0 + i * 50;
+ int y = 0 + i * 50;
+ if (i % 4 == 1) {
+ x = vncCanvas.rfb.framebufferWidth;
+ } else if (i % 4 == 2) {
+ y = vncCanvas.rfb.framebufferHeight;
+ } else if (i % 4 == 3) {
+ x = 0;
+ }
+
+ event = MotionEvent.obtain(SystemClock.uptimeMillis(),
+ SystemClock.uptimeMillis(), MotionEvent.ACTION_MOVE,
+ x, y, 0);
+ Thread.sleep(10);
+ vncCanvas.processPointerEvent(event, false, false);
+
+
+ }
+
+ Thread.sleep(50);
+ event = MotionEvent.obtain(SystemClock.uptimeMillis(),
+ SystemClock.uptimeMillis(), MotionEvent.ACTION_MOVE,
+ origX, origY, 0);
+ vncCanvas.processPointerEvent(event, false, false);
+
+ } catch (Exception ex) {
+
+ }
+ }
+ });
+ t.start();
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ menu.clear();
+ return this.setupMenu(menu);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ menu.clear();
+ return this.setupMenu(menu);
+
+ }
+
+ public boolean setupMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.vnccanvasactivitymenu, menu);
+
+ int maxMenuItemsShown = 4;
+ int actionShow = MenuItemCompat.SHOW_AS_ACTION_IF_ROOM;
+ if (UIUtils.isLandscapeOrientation(this)) {
+ maxMenuItemsShown = 6;
+ actionShow = MenuItemCompat.SHOW_AS_ACTION_ALWAYS;
+ }
+
+ if (vncCanvas.scaling != null) {
+ menu.findItem(vncCanvas.scaling.getId()).setChecked(true);
+ }
+
+ if (this.monitorMode) {
+ menu.findItem(R.id.itemMonitor).setTitle("VM Display");
+
+ } else {
+ menu.findItem(R.id.itemMonitor).setTitle("QEMU Monitor");
+
+ }
+
+ //XXX: We don't need these for now
+ menu.removeItem(menu.findItem(R.id.itemEnterText).getItemId());
+ menu.removeItem(menu.findItem(R.id.itemSendKeyAgain).getItemId());
+ menu.removeItem(menu.findItem(R.id.itemSpecialKeys).getItemId());
+ menu.removeItem(menu.findItem(R.id.itemInputMode).getItemId());
+ menu.removeItem(menu.findItem(R.id.itemScaling).getItemId());
+ menu.removeItem(menu.findItem(R.id.itemCtrlAltDel).getItemId());
+ menu.removeItem(menu.findItem(R.id.itemCtrlC).getItemId());
+ menu.removeItem(menu.findItem(R.id.itemColorMode).getItemId());
+ menu.removeItem(menu.findItem(R.id.itemFullScreen).getItemId());
+
+ if (MainSettingsManager.getAlwaysShowMenuToolbar(activity) || Config.mouseMode == Config.MouseMode.External) {
+ menu.removeItem(menu.findItem(R.id.itemHideToolbar).getItemId());
+ maxMenuItemsShown--;
+ }
+
+ // Menu inputMenu = menu.findItem(R.id.itemInputMode).getSubMenu();
+ //
+ // inputModeMenuItems = new MenuItem[inputModeIds.length];
+ // for (int i = 0; i < inputModeIds.length; i++) {
+ // inputModeMenuItems[i] = inputMenu.findItem(inputModeIds[i]);
+ // }
+ // updateInputMenu();
+ // menu.removeItem(menu.findItem(R.id.itemCenterMouse).getItemId());
+
+ // Main: Disable Panning for now
+ // if (this.mouseOn) {
+ // menu.findItem(R.id.itemCenterMouse).setTitle("Pan (Mouse Off)");
+ // menu.findItem(R.id.itemCenterMouse).setIcon(R.drawable.pan);
+ // } else {
+ menu.findItem(R.id.itemCenterMouse).setTitle("Mouse");
+ //
+ // }
+
+
+ for (int i = 0; i < menu.size() && i < maxMenuItemsShown; i++) {
+ MenuItemCompat.setShowAsAction(menu.getItem(i), actionShow);
+ }
+
+ return true;
+
+ }
+
+
+ public static boolean toggleKeyboardFlag = true;
+
+ private void onMonitor() {
+ if (Config.showToast)
+ UIUtils.toastShort(this, "Connecting to QEMU Monitor");
+
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ monitorMode = true;
+ vncCanvas.sendMetaKey1(50, 6);
+
+ }
+ });
+ t.start();
+ }
+
+ private void onVNC() {
+ UIUtils.toastShort(this, "Connecting to VM");
+
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ monitorMode = false;
+ vncCanvas.sendMetaKey1(49, 6);
+ }
+ });
+ t.start();
+
+
+ }
+
+ // FIXME: We need this to able to catch complex characters strings like
+ // grave and send it as text
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (event.getAction() == KeyEvent.ACTION_MULTIPLE && event.getKeyCode() == KeyEvent.KEYCODE_UNKNOWN) {
+ vncCanvas.sendText(event.getCharacters().toString());
+ return true;
+ } else
+ return super.dispatchKeyEvent(event);
+
+ }
+
+ private void resumeVM() {
+ if (MainActivityCommon.vmexecutor == null) {
+ return;
+ }
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ if (MainActivityCommon.vmexecutor.paused == 1) {
+ try {
+ Thread.sleep(4000);
+ } catch (InterruptedException ex) {
+ Logger.getLogger(MainVNCActivity.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ if (vncCanvas == null)
+ return;
+
+ MainActivityCommon.vmexecutor.paused = 0;
+ String command = QmpClient.cont();
+ String msg = QmpClient.sendCommand(command);
+ new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ setUIModeMobile(screenMode == VNCScreenMode.FitToScreen);
+ }
+ }, 500);
+
+ }
+ }
+ });
+ t.start();
+
+ }
+
+ private void onPauseVM() {
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ // Delete any previous state file
+ if (MainActivityCommon.vmexecutor.save_state_name != null) {
+ File file = new File(MainActivityCommon.vmexecutor.save_state_name);
+ if (file.exists()) {
+ file.delete();
+ }
+ }
+
+ UIUtils.toastShort(getApplicationContext(), "Please wait while saving VM State");
+
+ String uri = "fd:" + MainActivityCommon.vmexecutor.get_fd(MainActivityCommon.vmexecutor.save_state_name);
+ String command = QmpClient.stop();
+ String msg = QmpClient.sendCommand(command);
+// if (msg != null)
+// Log.i(TAG, msg);
+ command = QmpClient.migrate(false, false, uri);
+ msg = QmpClient.sendCommand(command);
+ if (msg != null) {
+// Log.i(TAG, msg);
+ processMigrationResponse(msg);
+ }
+
+ new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ VMListener a = new VMListener();
+ a.execute();
+ }
+ }, 0);
+ }
+ });
+ t.start();
+
+ }
+
+ private void processMigrationResponse(String response) {
+ String errorStr = null;
+
+ if (response.contains("error")) {
+ try {
+ JSONObject object = new JSONObject(response);
+ errorStr = object.getString("error");
+ } catch (Exception ex) {
+ if (Config.debug)
+ ex.printStackTrace();
+ }
+ }
+ if (errorStr != null && errorStr.contains("desc")) {
+ String descStr = null;
+
+ try {
+ JSONObject descObj = new JSONObject(errorStr);
+ descStr = descObj.getString("desc");
+ } catch (Exception ex) {
+ if (Config.debug)
+ ex.printStackTrace();
+ }
+ final String descStr1 = descStr;
+
+ new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ Machine.pausedErrorVM(activity, descStr1);
+ }
+ }, 100);
+
+ }
+
+ }
+
+
+ private class VMListener extends AsyncTask {
+
+ @Override
+ protected Void doInBackground(Void... arg0) {
+ startSaveVMListener();
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void test) {
+ // if (progDialog.isShowing()) {
+ // progDialog.dismiss();
+ // }
+
+ }
+ }
+
+ private void fullScreen() {
+ AbstractScaling.getById(R.id.itemFitToScreen).setScaleTypeForActivity(this);
+ showPanningState();
+ }
+
+ public void promptPause(final Activity activity) {
+ final AlertDialog alertDialog;
+ alertDialog = new AlertDialog.Builder(activity, R.style.MainDialogTheme).create();
+ alertDialog.setTitle("Pause VM");
+ TextView stateView = new TextView(activity);
+ stateView.setText("This make take a while depending on the RAM size used");
+ stateView.setPadding(20, 20, 20, 20);
+ alertDialog.setView(stateView);
+
+ alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "Pause", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ onPauseVM();
+ return;
+ }
+ });
+ alertDialog.show();
+
+ }
+
+ public void onBackPressed() {
+
+ // super.onBackPressed();
+ if (!MainSettingsManager.getAlwaysShowMenuToolbar(activity)) {
+ ActionBar bar = this.getSupportActionBar();
+ if (bar != null) {
+ if (bar.isShowing() && Config.mouseMode == Config.MouseMode.Trackpad) {
+ bar.hide();
+ }/* else
+ bar.show();*/
+ }
+ } else
+ super.onBackPressed();
+
+ }
+
+ public void onHideToolbar() {
+ ActionBar bar = this.getSupportActionBar();
+ if (bar != null) {
+ bar.hide();
+ }
+ }
+
+ @Override
+ public void onConnected() {
+ this.resumeVM();
+ if (!firstConnection)
+ UIUtils.showHints(this);
+ firstConnection = true;
+
+ new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
+ @Override
+ public void run() {
+
+ if (Config.mouseMode == Config.MouseMode.External)
+ setUIModeDesktop();
+ else
+ setUIModeMobile(screenMode == VNCScreenMode.FitToScreen);
+ }
+ }, 1000);
+
+ }
+
+ public void onSelectMenuVNCDisplay() {
+
+ final AlertDialog alertDialog;
+ alertDialog = new AlertDialog.Builder(activity, R.style.MainDialogTheme).create();
+ alertDialog.setTitle("Display");
+
+ LinearLayout.LayoutParams volParams = new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+
+ LinearLayout t = createVNCDisplayPanel();
+ t.setLayoutParams(volParams);
+
+ ScrollView s = new ScrollView(activity);
+ s.addView(t);
+ alertDialog.setView(s);
+ alertDialog.setButton(Dialog.BUTTON_POSITIVE, "OK", new DialogInterface.OnClickListener() {
+
+ public void onClick(DialogInterface dialog, int which) {
+ alertDialog.cancel();
+ }
+ });
+ alertDialog.show();
+
+ }
+
+
+ public LinearLayout createVNCDisplayPanel() {
+ LinearLayout layout = new LinearLayout(this);
+ layout.setOrientation(LinearLayout.VERTICAL);
+ layout.setPadding(20, 20, 20, 20);
+
+ LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+ int currRate = getCurrentVNCRefreshRate();
+
+ LinearLayout buttonsLayout = new LinearLayout(this);
+ buttonsLayout.setOrientation(LinearLayout.HORIZONTAL);
+ buttonsLayout.setGravity(Gravity.CENTER_HORIZONTAL);
+ Button displayMode = new Button(this);
+
+ displayMode.setText("Display Mode");
+ displayMode.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View view) {
+ onDisplayMode();
+ }
+ });
+ buttonsLayout.addView(displayMode);
+
+
+ Button colors = new Button(this);
+ colors.setText("Color Mode");
+ colors.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View view) {
+ selectColorModel();
+
+ }
+ });
+ buttonsLayout.addView(colors);
+
+ layout.addView(buttonsLayout);
+
+ final TextView value = new TextView(this);
+ value.setText("Display Refresh Rate: " + currRate + " Hz");
+ layout.addView(value);
+ value.setLayoutParams(params);
+
+ SeekBar rate = new SeekBar(this);
+ rate.setMax(Config.MAX_DISPLAY_REFRESH_RATE);
+
+ rate.setProgress(currRate);
+ rate.setLayoutParams(params);
+
+ ((SeekBar) rate).setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
+
+ public void onProgressChanged(SeekBar s, int progress, boolean touch) {
+ value.setText("Refresh Rate: " + (progress + 1) + " Hz");
+ }
+
+ public void onStartTrackingTouch(SeekBar arg0) {
+
+ }
+
+ public void onStopTrackingTouch(SeekBar arg0) {
+ int progress = arg0.getProgress() + 1;
+ int refreshMs = 1000 / progress;
+ Log.v(TAG, "Changing display refresh rate (ms): " + refreshMs);
+ MainActivityCommon.vmexecutor.setvncrefreshrate(refreshMs);
+
+ }
+ });
+
+
+ layout.addView(rate);
+
+ return layout;
+
+ }
+
+ public int getCurrentVNCRefreshRate() {
+ return 1000 / MainActivityCommon.vmexecutor.getvncrefreshrate();
+ }
+
+}
diff --git a/app/src/main/java/com/vectras/qemu/SettingsFragment.java b/app/src/main/java/com/vectras/qemu/SettingsFragment.java
new file mode 100644
index 0000000..13504fe
--- /dev/null
+++ b/app/src/main/java/com/vectras/qemu/SettingsFragment.java
@@ -0,0 +1,100 @@
+package com.vectras.qemu;
+
+import static android.content.Context.MODE_PRIVATE;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.os.Handler;
+
+import androidx.annotation.Nullable;
+import androidx.preference.EditTextPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceManager;
+import androidx.preference.SwitchPreferenceCompat;
+
+import com.vectras.qemu.utils.Machine;
+import com.vectras.vm.R;
+import com.vectras.vm.SplashActivity;
+
+public class SettingsFragment extends PreferenceFragmentCompat {
+
+ private Handler mHandler;
+ public SharedPreferences mPref;
+
+ @Override
+ public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) {
+ setPreferencesFromResource(R.xml.settings, rootKey);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mHandler = new Handler();
+ SharedPreferences.OnSharedPreferenceChangeListener listener;
+ listener = new SharedPreferences.OnSharedPreferenceChangeListener() {
+ public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+ switch (key) {
+
+ case "modeNight":
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ Intent startActivity = new Intent(getContext(), SplashActivity.class);
+ int pendingIntentId = 123456;
+ PendingIntent pendingIntent = PendingIntent.getActivity(getContext(), pendingIntentId, startActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+
+ AlarmManager mgr = (AlarmManager) MainSettingsManager.activity.getSystemService(Context.ALARM_SERVICE);
+ mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 500, pendingIntent);
+
+ System.exit(0);
+ }
+ }, 300);
+
+ getActivity().finish();
+ break;
+ case "customMemory":
+ if (prefs.getBoolean("customMemory", false))
+ findPreference("memory").setEnabled(true);
+ else
+ findPreference("memory").setEnabled(false);
+ break;
+ case "MTTCG":
+ if (prefs.getBoolean("MTTCG", false)) {
+ findPreference("cpuNum").setEnabled(false);
+ MainSettingsManager.setCpuCores(getContext(), 1);
+ } else {
+ findPreference("cpuNum").setEnabled(true);
+ }
+ break;
+ }
+ }
+ };
+
+ mPref = getPreferenceManager().getDefaultSharedPreferences(getContext());
+ if (mPref != null) {
+ mPref.registerOnSharedPreferenceChangeListener(listener);
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ if (mPref.getBoolean("customMemory", false))
+ findPreference("memory").setEnabled(true);
+ else
+ findPreference("memory").setEnabled(false);
+
+ if (mPref.getBoolean("MTTCG", false)) {
+ findPreference("cpuNum").setEnabled(false);
+ } else {
+ findPreference("cpuNum").setEnabled(true);
+ }
+ }
+}
diff --git a/app/src/main/java/com/vectras/qemu/jni/StartVM.java b/app/src/main/java/com/vectras/qemu/jni/StartVM.java
new file mode 100644
index 0000000..af63609
--- /dev/null
+++ b/app/src/main/java/com/vectras/qemu/jni/StartVM.java
@@ -0,0 +1,868 @@
+package com.vectras.qemu.jni;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import com.vectras.qemu.Config;
+import com.vectras.qemu.MainActivityCommon;
+import com.vectras.qemu.MainSDLActivity;
+import com.vectras.qemu.MainService;
+import com.vectras.qemu.MainSettingsManager;
+import com.vectras.qemu.utils.FileUtils;
+import com.vectras.qemu.utils.Machine;
+import com.vectras.qemu.utils.QmpClient;
+import com.vectras.qemu.utils.RamInfo;
+import com.vectras.vm.logger.VectrasStatus;
+import com.vectras.vm.utils.UIUtils;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class StartVM {
+
+ private static final String TAG = "StartVM";
+ private static Context context;
+
+ String[] params = null;
+
+ //native lib
+ private String libqemu = null;
+
+ //qmp server
+ public int enableqmp;
+ private String qmp_server;
+ private int qmp_port;
+
+ //state
+ public int paused;
+ public String snapshot_name = null;
+ public String save_state_name = null;
+ private String save_dir;
+ public int current_fd = 0;
+
+ public String base_dir;
+ public String dns_addr;
+ public String append = "";
+ public boolean busy = false;
+ public String name;
+
+ //ui
+ public int enablespice = 0;
+ public String keyboard_layout = Config.defaultKeyboardLayout;
+
+ public String mouse = null;
+ public int enablevnc;
+ public int vnc_allow_external = 0;
+ public int qmp_allow_external = 0;
+ public String vnc_passwd = "vectras";
+
+ // cpu/board settings
+ private String cpu;
+ private String arch = "x86";
+ private String machine_type;
+ private int memory = 128;
+ private int cpuNum = 1;
+ public int enablekvm;
+ public int enable_mttcg;
+
+ // disks
+ public String hda_img_path;
+ private String hdb_img_path;
+ private String hdc_img_path;
+ private String hdd_img_path;
+ public String shared_folder_path;
+ public int shared_folder_readonly = 1;
+ private String hd_cache = "default";
+
+ //removable devices
+ public String cd_iso_path;
+ public String fda_img_path;
+ public String fdb_img_path;
+ public String sd_img_path;
+
+ //boot options
+ private String bootdevice = null;
+ private String kernel;
+ private String initrd;
+
+ //graphics
+ private String vga_type = "std";
+
+ //audio
+ public String sound_card;
+
+ // net
+ private String net_cfg = "None";
+ private String nic_card = null;
+ private String hostfwd = null;
+ private String guestfwd = null;
+
+ //advanced
+ private int disableacpi = 0;
+ private int disablehpet = 0;
+ private int disabletsc = 0;
+ public String extra_params;
+
+ /**
+ * @throws Exception
+ */
+ public StartVM(Context context) throws Exception {
+
+ name = Config.machinename;
+ base_dir = Config.getBasefileDir();
+ save_dir = Config.getMachineDir() + name;
+ save_state_name = save_dir + "/" + Config.state_filename;
+ hda_img_path = Config.hda_path;
+ extra_params = Config.extra_params;
+ shared_folder_path = Config.sharedFolder;
+ //extra_params = Config.extra_params;
+ this.context = context;
+ this.libqemu = FileUtils.getNativeLibDir(context) + "/libqemu-system-x86_64.so";
+ this.arch = "x86_64";
+ this.cpuNum = MainSettingsManager.getCpuNum(MainActivityCommon.activity);
+ if (MainSettingsManager.getMTTCG(MainActivityCommon.activity))
+ this.enable_mttcg = 1;
+ else
+ this.enable_mttcg = 0;
+ this.vnc_allow_external = 0;
+
+ }
+
+ public static void onVMResolutionChanged(int width, int height) {
+
+ if (MainSDLActivity.mIsSurfaceReady)
+ MainSDLActivity.onVMResolutionChanged(width, height);
+ }
+
+ public void print(String[] params) {
+ VectrasStatus.logInfo("Params:");
+ Log.d(TAG, "Params:");
+ for (int i = 0; i < params.length; i++) {
+ VectrasStatus.logInfo(i + ": " + params[i]);
+ Log.d(TAG, i + ": " + params[i]);
+ }
+
+ }
+
+ public String startvm() {
+
+ String res = null;
+ try {
+ prepareParams();
+ } catch (Exception ex) {
+ UIUtils.toastLong(context, ex.getMessage());
+ return res;
+ }
+
+ //set the exit code
+ MainSettingsManager.setExitCode(context, 2);
+
+ try {
+ res = start(Config.storagedir, this.base_dir, this.libqemu, Config.SDLHintScale, params, this.paused, this.save_state_name);
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ Log.e(TAG, "Vectras Exception: " + ex.toString());
+ }
+ return res;
+ }
+
+ public void prepareParams() throws Exception {
+
+ params = null;
+ ArrayList paramsList = new ArrayList();
+
+ paramsList.add(libqemu);
+
+ addUIOptions(paramsList);
+
+ addCpuBoardOptions(paramsList);
+
+ addDrives(paramsList);
+
+ addRemovableDrives(paramsList);
+
+ addBootOptions(paramsList);
+
+ addGraphicsOptions(paramsList);
+
+ addAudioOptions(paramsList);
+
+ addNetworkOptions(paramsList);
+
+ addAdvancedOptions(paramsList);
+
+ addGenericOptions(paramsList);
+
+ addStateOptions(paramsList);
+
+ params = (String[]) paramsList.toArray(new String[paramsList.size()]);
+
+ print(params);
+
+ }
+
+ private void addStateOptions(ArrayList paramsList) {
+ if (paused == 1 && this.save_state_name != null && !save_state_name.equals("")) {
+ int fd_tmp = FileUtils.get_fd(context, save_state_name);
+ if (fd_tmp < 0) {
+ Log.e(TAG, "Error while getting fd for: " + save_state_name);
+ } else {
+ //Log.i(TAG, "Got new fd "+fd_tmp + " for: " +save_state_name);
+ paramsList.add("-incoming");
+ paramsList.add("fd:" + fd_tmp);
+ }
+ }
+ }
+
+ private void addUIOptions(ArrayList paramsList) {
+ if (enablevnc != 0) {
+ Log.v(TAG, "Enable VNC server");
+ paramsList.add("-vnc");
+
+ if (vnc_allow_external != 0) {
+ //TODO: Allow connections from External
+ // Use with x509 auth and TLS for encryption
+ paramsList.add(":1");
+ } else {
+ // Allow connections only from localhost using localsocket without a password
+ //paramsList.add(Config.defaultVNCHost+":" + Config.defaultVNCPort);
+ String qmpParams = "unix:";
+ qmpParams += Config.getLocalVNCSocketPath();
+ paramsList.add(qmpParams);
+ }
+ //Allow monitor console only for VNC,
+ // SDL for android doesn't support more
+ // than 1 window
+ paramsList.add("-monitor");
+ paramsList.add("vc");
+
+ } else if (enablespice != 0) {
+ //Not working right now
+ Log.v(TAG, "Enable SPICE server");
+ paramsList.add("-spice");
+ String spiceParams = "port=5902";
+
+ if (vnc_allow_external != 0 && vnc_passwd != null) {
+ spiceParams += ",password=";
+ spiceParams += vnc_passwd;
+ } else
+ spiceParams += ",addr=127.0.0.1"; // Allow only connections from localhost without password
+
+ spiceParams += ",disable-ticketing";
+ //argv.add("-chardev");
+ //argv.add("spicevm");
+ } else {
+ //SDL needs explicit keyboard layout
+ Log.v(TAG, "Disabling VNC server, using SDL instead");
+ if (keyboard_layout == null) {
+ paramsList.add("-k");
+ paramsList.add("en-us");
+ }
+
+ //XXX: monitor, serial, and parallel display crashes cause SDL doesn't support more than 1 window
+ paramsList.add("-monitor");
+ paramsList.add("none");
+
+ paramsList.add("-serial");
+ paramsList.add("none");
+
+ paramsList.add("-parallel");
+ paramsList.add("none");
+ }
+
+ if (keyboard_layout != null) {
+ paramsList.add("-k");
+ paramsList.add(keyboard_layout);
+ }
+
+ if (mouse != null && !mouse.equals("ps2")) {
+ paramsList.add("-usb");
+ paramsList.add("-device");
+ paramsList.add(mouse);
+ }
+
+ }
+
+ private void addAdvancedOptions(ArrayList paramsList) {
+
+ if (disableacpi != 0) {
+ paramsList.add("-no-acpi"); //disable ACPI
+ }
+ if (disablehpet != 0) {
+ paramsList.add("-no-hpet"); // disable HPET
+ }
+
+ //TODO:Extra options
+ if (extra_params != null && !extra_params.trim().equals("")) {
+ String[] paramsTmp = extra_params.split(" ");
+ paramsList.addAll(Arrays.asList(paramsTmp));
+ }
+
+ }
+
+ private void addAudioOptions(ArrayList paramsList) {
+
+ if (sound_card != null && !sound_card.equals("None")) {
+ paramsList.add("-soundhw");
+ paramsList.add(sound_card);
+ }
+
+ }
+
+ private void addGenericOptions(ArrayList paramsList) {
+
+ paramsList.add("-L");
+ paramsList.add(base_dir);
+
+ //XXX: Snapshots not working currently, use migrate/incoming instead
+ if (snapshot_name != null && !snapshot_name.equals("")) {
+ paramsList.add("-loadvm");
+ paramsList.add(snapshot_name);
+ }
+
+ if (enableqmp != 0) {
+
+ paramsList.add("-qmp");
+
+ if (qmp_allow_external != 0) {
+ String qmpParams = "tcp:";
+ qmpParams += (":" + this.qmp_port);
+ qmpParams += ",server,nowait";
+ paramsList.add(qmpParams);
+ } else {
+ //Specify a unix local domain as localhost to limit to local connections only
+ String qmpParams = "unix:";
+ qmpParams += Config.getLocalQMPSocketPath();
+ qmpParams += ",server,nowait";
+ paramsList.add(qmpParams);
+
+ }
+
+
+ }
+
+ //Enable Tracing log
+ // argv.add("-D");
+ // argv.add("/sdcard/vectras/log.txt");
+ // argv.add("--trace");
+ // argv.add("events=/sdcard/vectras/tmp/events");
+ // argv.add("--trace");
+ // argv.add("file=/sdcard/vectras/tmp/trace");
+
+// paramsList.add("-tb-size");
+// paramsList.add("32M"); //Don't increase it crashes
+
+ paramsList.add("-overcommit");
+ paramsList.add("mem-lock=off");
+
+ paramsList.add("-rtc");
+ paramsList.add("base=localtime");
+
+ paramsList.add("-nodefaults");
+
+
+ //XXX: Usb redir not working under User mode
+ //Redirect ports (SSH)
+ // argv.add("-redir");
+ // argv.add("5555::22");
+
+ }
+
+ private void addCpuBoardOptions(ArrayList paramsList) {
+
+ //XXX: SMP is not working correctly for some guest OSes
+ //so we enable multi core only under KVM
+ // anyway regular emulation is not gaining any benefit unless mttcg is enabled but that
+ // doesn't work for x86 guests yet
+ if (this.cpuNum > 1 &&
+ (enablekvm == 1 || enable_mttcg == 1 || !Config.enableSMPOnlyOnKVM)) {
+ paramsList.add("-smp");
+ paramsList.add(this.cpuNum + "");
+ }
+
+ if (machine_type != null && !machine_type.equals("Default")) {
+ paramsList.add("-M");
+ paramsList.add(machine_type);
+ }
+
+ //FIXME: something is wrong with quoting that doesn't let sparc qemu find the cpu def
+ // for now we remove the cpu drop downlist items for sparc
+ if (this.cpu != null && this.cpu.contains(" "))
+ cpu = "'" + cpu + "'"; // XXX: needed for sparc cpu names
+
+ //XXX: we disable tsc feature for x86 since some guests are kernel panicking
+ // if the cpu has not specified by user we use the internal qemu32/64
+ if (disabletsc == 1 && (arch.equals("x86") || arch.equals("x86_64"))) {
+ if (cpu == null || cpu.equals("Default")) {
+ if (arch.equals("x86"))
+ cpu = "qemu32";
+ else if (arch.equals("x86_64"))
+ cpu = "qemu64";
+ }
+ cpu += ",-tsc";
+ }
+
+ if (this.cpu != null && !cpu.equals("Default")) {
+ paramsList.add("-cpu");
+ paramsList.add(cpu);
+
+ }
+
+ paramsList.add("-m");
+ paramsList.add(RamInfo.vectrasMemory() + "");
+
+
+ if (enablekvm != 0) {
+ paramsList.add("-enable-kvm");
+ } else if (this.enable_mttcg != 0 && Machine.isHost64Bit()) {
+ //XXX: we should only do this for 64bit hosts
+ paramsList.add("-accel");
+ String tcgParams = "tcg";
+ if (cpuNum > 1)
+ tcgParams += ",thread=multi";
+ paramsList.add(tcgParams);
+ //#endif
+ }
+
+ }
+
+ private void addNetworkOptions(ArrayList paramsList) throws Exception {
+
+ if (this.net_cfg != null) {
+ paramsList.add("-net");
+ if (net_cfg.equals("user")) {
+ String netParams = net_cfg;
+ if (hostfwd != null) {
+
+ //hostfwd=[tcp|udp]:[hostaddr]:hostport-[guestaddr]:guestport{,hostfwd=...}
+ // example forward ssh from guest port 2222 to guest port 22:
+ // hostfwd=tcp::2222-:22
+ if (hostfwd.startsWith("hostfwd")) {
+ throw new Exception("Invalid format for Host Forward, should be: tcp:hostport1:guestport1,udp:hostport2:questport2,...");
+ }
+ String[] hostfwdparams = hostfwd.split(",");
+ for (int i = 0; i < hostfwdparams.length; i++) {
+ netParams += ",";
+ String[] hostfwdparam = hostfwdparams[i].split(":");
+ netParams += ("hostfwd=" + hostfwdparam[0] + "::" + hostfwdparam[1] + "-:" + hostfwdparam[2]);
+ }
+ }
+ if (guestfwd != null) {
+ netParams += ",";
+ netParams += guestfwd;
+ }
+ paramsList.add(netParams);
+ } else if (net_cfg.equals("tap")) {
+ paramsList.add("tap,vlan=0,ifname=tap0,script=no");
+ } else if (net_cfg.equals("none")) {
+ paramsList.add("none");
+ } else {
+ //Unknown interface
+ paramsList.add("none");
+ }
+ }
+
+ if (nic_card != null) {
+ paramsList.add("-net");
+ String nicParams = "nic";
+ if (net_cfg.equals("tap"))
+ nicParams += ",vlan=0";
+ if (!nic_card.equals("Default"))
+ nicParams += (",model=" + nic_card);
+ paramsList.add(nicParams);
+ }
+ }
+
+ private void addGraphicsOptions(ArrayList paramsList) {
+ if (vga_type != null) {
+ if (vga_type.equals("Default")) {
+ //do nothing
+ } else if (vga_type.equals("virtio-gpu-pci")) {
+ paramsList.add("-device");
+ paramsList.add(vga_type);
+ } else if (vga_type.equals("nographic")) {
+ paramsList.add("-nographic");
+ } else {
+ paramsList.add("-vga");
+ paramsList.add(vga_type);
+ }
+ }
+
+
+ }
+
+ private void addBootOptions(ArrayList paramsList) {
+ if (this.bootdevice != null) {
+ paramsList.add("-boot");
+ paramsList.add(bootdevice);
+ }
+
+ if (this.kernel != null && !this.kernel.equals("")) {
+ paramsList.add("-kernel");
+ paramsList.add(this.kernel);
+ }
+
+ if (initrd != null && !initrd.equals("")) {
+ paramsList.add("-initrd");
+ paramsList.add(initrd);
+ }
+
+ if (append != null && !append.equals("")) {
+ paramsList.add("-append");
+ paramsList.add(append);
+ }
+ }
+
+ public void addDrives(ArrayList paramsList) {
+ if (hda_img_path != null) {
+ paramsList.add("-drive"); //empty
+ String param = "index=0";
+ if (Config.enable_hd_if) {
+ param += ",if=";
+ param += Config.hd_if_type;
+ }
+ param += ",media=disk";
+ if (!hda_img_path.equals("")) {
+ param += ",file=" + hda_img_path;
+ }
+ paramsList.add(param);
+ }
+
+ if (hdb_img_path != null) {
+ paramsList.add("-drive"); //empty
+ String param = "index=1";
+ if (Config.enable_hd_if) {
+ param += ",if=";
+ param += Config.hd_if_type;
+ }
+ param += ",media=disk";
+ if (!hdb_img_path.equals("")) {
+ param += ",file=" + hdb_img_path;
+ }
+ paramsList.add(param);
+ }
+
+ if (hdc_img_path != null) {
+ paramsList.add("-drive"); //empty
+ String param = "index=2";
+ if (Config.enable_hd_if) {
+ param += ",if=";
+ param += Config.hd_if_type;
+ }
+ param += ",media=disk";
+ if (!hdc_img_path.equals("")) {
+ param += ",file=" + hdc_img_path;
+ }
+ paramsList.add(param);
+ }
+
+ if (hdd_img_path != null) {
+ paramsList.add("-drive"); //empty
+ String param = "index=3";
+ if (Config.enable_hd_if) {
+ param += ",if=";
+ param += Config.hd_if_type;
+ }
+ param += ",media=disk";
+ if (!hdd_img_path.equals("")) {
+ param += ",file=" + hdd_img_path;
+ }
+ paramsList.add(param);
+ } else if (shared_folder_path != null) {
+ //XXX; We use hdd to mount any virtual fat drives
+ paramsList.add("-drive"); //empty
+ String driveParams = "index=3";
+ driveParams += ",media=disk";
+ if (Config.enable_hd_if) {
+ driveParams += ",if=";
+ driveParams += Config.hd_if_type;
+ }
+ driveParams += ",format=raw";
+ driveParams += ",file=fat:";
+ driveParams += "rw:"; //Always Read/Write
+ driveParams += shared_folder_path;
+ paramsList.add(driveParams);
+ }
+
+ }
+
+ public void addRemovableDrives(ArrayList paramsList) {
+
+ if (cd_iso_path != null) {
+ paramsList.add("-drive"); //empty
+ String param = "index=2";
+ if (Config.enable_hd_if) {
+ param += ",if=";
+ param += Config.hd_if_type;
+ }
+ param += ",media=cdrom";
+ if (!cd_iso_path.equals("")) {
+ param += ",file=" + cd_iso_path;
+ }
+ paramsList.add(param);
+ }
+
+ if (Config.enableEmulatedFloppy && fda_img_path != null) {
+ paramsList.add("-drive"); //empty
+ String param = "index=0,if=floppy";
+ if (!fda_img_path.equals("")) {
+ param += ",file=" + fda_img_path;
+ }
+ paramsList.add(param);
+ }
+
+ if (Config.enableEmulatedFloppy && fdb_img_path != null) {
+ paramsList.add("-drive"); //empty
+ String param = "index=1,if=floppy";
+ if (!fdb_img_path.equals("")) {
+ param += ",file=" + fdb_img_path;
+ }
+ paramsList.add(param);
+ }
+
+ if (Config.enableEmulatedSDCard && sd_img_path != null) {
+ paramsList.add("-device");
+ paramsList.add("sd-card,drive=sd0,bus=sd-bus");
+ paramsList.add("-drive");
+ String param = "if=none,id=sd0";
+ if (!sd_img_path.equals("")) {
+ param += ",file=" + sd_img_path;
+ }
+ paramsList.add(param);
+ }
+
+ }
+
+ //JNI Methods
+ public native String start(String storage_dir, String base_dir, String lib_path, int sdl_scale_hint, Object[] params, int paused, String save_state_name);
+
+ public native String stop(int restart);
+
+ public native void setsdlrefreshrate(int value);
+
+ public native void setvncrefreshrate(int value);
+
+ public native int getsdlrefreshrate();
+
+ public native int getvncrefreshrate();
+
+ private native int onmouse(int button, int action, int relative, float x, float y);
+
+ private native int setrelativemousemode(int relativemousemode);
+
+ protected void vncchangepassword(String vnc_passwd) {
+ String res = QmpClient.sendCommand(QmpClient.changevncpasswd(vnc_passwd));
+ String desc = null;
+ if (res != null && !res.equals("")) {
+ try {
+ JSONObject resObj = new JSONObject(res);
+ if (resObj != null && !resObj.equals("") && res.contains("error")) {
+ String resInfo = resObj.getString("error");
+ if (resInfo != null && !resInfo.equals("")) {
+ JSONObject resInfoObj = new JSONObject(resInfo);
+ desc = resInfoObj.getString("desc");
+ UIUtils.toastLong(context, "Could not set VNC Password: " + desc);
+ Log.e(TAG, desc);
+ }
+ }
+
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ protected String changedev(String dev, String dev_value) {
+ QmpClient.sendCommand(QmpClient.changedev(dev, dev_value));
+ String display_dev_value = FileUtils.getFullPathFromDocumentFilePath(dev_value);
+ return "Changed device: " + dev + " to " + display_dev_value;
+ }
+
+ protected String ejectdev(String dev) {
+ QmpClient.sendCommand(QmpClient.ejectdev(dev));
+ return "Ejected device: " + dev;
+ }
+
+ public String startvm(Context context, int ui) {
+ MainService.executor = this;
+ Intent i = new Intent(Config.ACTION_START, null, context, MainService.class);
+ Bundle b = new Bundle();
+ // b.putString("machine_type", this.machine_type);
+ b.putInt("ui", ui);
+ i.putExtras(b);
+ context.startService(i);
+ Log.v(TAG, "start VM service");
+ return "startVMService";
+
+ }
+
+ public void stopvm(final int restart) {
+
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ doStopVM(restart);
+ }
+ }).start();
+ }
+
+ public void doStopVM(final int restart) {
+
+ if (restart == 0) {
+ MainService.stopService();
+
+ //XXX: Wait till service goes down
+ try {
+ Thread.sleep(2000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ if (restart != 0) {
+ QmpClient.sendCommand(QmpClient.reset());
+ } else {
+ //XXX: Qmp command only halts the VM but doesn't exit
+ // so we use force close
+// QmpClient.sendCommand(QmpClient.powerDown());
+ stop(restart);
+ }
+
+ }
+
+ public String savevm(String statename) {
+ // Set to delete previous snapshots after vm resumed
+ Log.v(TAG, "Save Snapshot");
+ this.snapshot_name = statename;
+
+ String res = null;
+ //TODO:
+ //res = QmpClient.sendCommand(QmpClient.saveSnapshot());
+ return res;
+ }
+
+ public String resumevm() {
+ // Set to delete previous snapshots after vm resumed
+ Log.v(TAG, "Resume the VM");
+ String res = startvm();
+ Log.d(TAG, res);
+ return res;
+ }
+
+ public void change_vnc_password() {
+ Thread thread = new Thread(new Runnable() {
+ public void run() {
+ vncchangepassword(vnc_passwd);
+ }
+ });
+ thread.start();
+ }
+
+ public String get_state() {
+
+ return null;
+ }
+
+ public void change_dev(final String dev, final String image_path) {
+
+ Thread thread = new Thread(new Runnable() {
+ public void run() {
+ String image_path_conv = FileUtils.convertDocumentFilePath(image_path);
+ if (image_path_conv == null || image_path_conv.trim().equals("")) {
+ StartVM.this.busy = true;
+ String res = StartVM.this.ejectdev(dev);
+ Log.d(TAG, res);
+ StartVM.this.busy = false;
+ } else if (FileUtils.fileValid(context, image_path_conv)) {
+ StartVM.this.busy = true;
+ String res = StartVM.this.changedev(dev, image_path_conv);
+ Log.d(TAG, res);
+ StartVM.this.busy = false;
+ } else {
+ Log.d(TAG, "File does not exist");
+ }
+ }
+ });
+ thread.setPriority(Thread.MIN_PRIORITY);
+ thread.start();
+
+
+ }
+
+ public int get_fd(String path) {
+ int fd = FileUtils.get_fd(context, path);
+ return fd;
+
+ }
+
+ public int close_fd(int fd) {
+ int res = FileUtils.close_fd(fd);
+ return res;
+
+ }
+
+ public void prepPaths() {
+ File destDir = new File(save_dir);
+ if (!destDir.exists()) {
+ destDir.mkdirs();
+ }
+
+ // Protect the paths from qemu thinking they contain a protocol in the string
+
+ this.hda_img_path = FileUtils.convertDocumentFilePath(this.hda_img_path);
+ if (this.hda_img_path != null && hda_img_path.equals("")) {
+ hda_img_path = null;
+ }
+ this.hdb_img_path = FileUtils.convertDocumentFilePath(this.hdb_img_path);
+ if (this.hdb_img_path != null && hdb_img_path.equals("")) {
+ hdb_img_path = null;
+ }
+ this.hdc_img_path = FileUtils.convertDocumentFilePath(this.hdc_img_path);
+ if (this.hdc_img_path != null && hdc_img_path.equals("")) {
+ hdc_img_path = null;
+ }
+ this.hdd_img_path = FileUtils.convertDocumentFilePath(this.hdd_img_path);
+ if (this.hdd_img_path != null && hdd_img_path.equals("")) {
+ hdd_img_path = null;
+ }
+
+ // Removable disks
+ this.cd_iso_path = FileUtils.convertDocumentFilePath(this.cd_iso_path);
+ this.fda_img_path = FileUtils.convertDocumentFilePath(this.fda_img_path);
+
+ this.fdb_img_path = FileUtils.convertDocumentFilePath(this.fdb_img_path);
+ this.sd_img_path = FileUtils.convertDocumentFilePath(this.sd_img_path);
+
+ this.kernel = FileUtils.convertDocumentFilePath(this.kernel);
+ this.initrd = FileUtils.convertDocumentFilePath(this.initrd);
+ }
+
+ public int setRelativeMouseMode(int relative) {
+ return setrelativemousemode(relative);
+ }
+
+ public int onVectrasMouse(int button, int action, int relative, float x, float y) {
+ //XXX: Make sure that mouse motion is not triggering crashes in SDL while resizing
+ if (!MainSDLActivity.mIsSurfaceReady || MainSDLActivity.isResizing) {
+// Log.w(TAG, "onVectrasMouse: Ignoring mouse event surface not ready");
+ return -1;
+ }
+
+ //XXX: Check boundaries, perhaps not necessary since SDL is also doing the same thing
+ if (relative == 1
+ || (x >= 0 && x <= MainSDLActivity.vm_width && y >= 0 && y <= MainSDLActivity.vm_height)
+ || (action == MotionEvent.ACTION_SCROLL)) {
+// Log.d(TAG, "onVectrasMouse: B: " + button + ", A: " + action + ", R: " + relative + ", X: " + x + ", Y: " + y);
+ return onmouse(button, action, relative, x, y);
+ }
+ return -1;
+ }
+}
+
diff --git a/app/src/main/java/com/vectras/qemu/utils/FileInstaller.java b/app/src/main/java/com/vectras/qemu/utils/FileInstaller.java
new file mode 100644
index 0000000..fbb2a0d
--- /dev/null
+++ b/app/src/main/java/com/vectras/qemu/utils/FileInstaller.java
@@ -0,0 +1,271 @@
+/*
+ Copyright (C) Max Kastanas 2012
+
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+package com.vectras.qemu.utils;
+
+import android.app.Activity;
+import android.content.res.AssetManager;
+import android.net.Uri;
+import androidx.documentfile.provider.DocumentFile;
+import android.util.Log;
+
+import com.vectras.qemu.Config;
+import com.vectras.vm.utils.UIUtils;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author dev
+ */
+public class FileInstaller {
+
+ public static void installFiles(Activity activity, boolean force) {
+
+ Log.v("Installer", "Installing files...");
+ File tmpDir = new File(Config.getBasefileDir());
+ if (!tmpDir.exists()) {
+ tmpDir.mkdirs();
+ }
+
+ File tmpDir1 = new File(Config.getMachineDir());
+ if (!tmpDir1.exists()) {
+ tmpDir1.mkdirs();
+ }
+
+
+ //Install base dir
+ File dir = new File(Config.getBasefileDir());
+ if (dir.exists() && dir.isDirectory()) {
+ //don't create again
+ } else if (dir.exists() && !dir.isDirectory()) {
+ Log.v("Installer", "Could not create Dir, file found: " + Config.getBasefileDir());
+ return;
+ } else if (!dir.exists()) {
+ dir.mkdir();
+ }
+
+ String destDir = Config.getBasefileDir();
+
+ //Get each file in assets under ./roms/ and install in SDCARD
+ AssetManager am = activity.getResources().getAssets();
+ String[] files = null;
+ try {
+ files = am.list("roms");
+ } catch (IOException ex) {
+ Logger.getLogger(FileInstaller.class.getName()).log(Level.SEVERE, null, ex);
+ Log.v("Installer", "Could not install files: " + ex.getMessage());
+ return;
+ }
+
+ for (int i = 0; i < files.length; i++) {
+ //Log.v("Installer", "File: " + files[i]);
+ String[] subfiles = null;
+ try {
+ subfiles = am.list("roms/" + files[i]);
+ } catch (IOException ex) {
+ Logger.getLogger(FileInstaller.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ if (subfiles != null && subfiles.length > 0) {
+ //Install base dir
+ File dir1 = new File(Config.getBasefileDir() + files[i]);
+ if (dir1.exists() && dir1.isDirectory()) {
+ //don't create again
+ } else if (dir1.exists() && !dir1.isDirectory()) {
+ Log.v("Installer", "Could not create Dir, file found: " + Config.getBasefileDir() + files[i]);
+ return;
+ } else if (!dir1.exists()) {
+ dir1.mkdir();
+ }
+ for (int k = 0; k < subfiles.length; k++) {
+
+ File file = new File(destDir, files[i] + "/" + subfiles[k]);
+ if(!file.exists() || force) {
+ Log.v("Installer", "Installing file: " + file.getPath());
+ installAssetFile(activity, files[i] + "/" + subfiles[k], destDir, "roms", null);
+ }
+ }
+ } else {
+ File file = new File(destDir, files[i]);
+ if(!file.exists() || force) {
+ Log.v("Installer", "Installing file: " + file.getPath());
+ installAssetFile(activity, files[i], Config.getBasefileDir(), "roms", null);
+ }
+ }
+ }
+// InputStream is = am.open(srcFile);
+
+ }
+
+ public static boolean installAssetFile(Activity activity, String srcFile,
+ String destDir, String assetsDir, String destFile) {
+ try {
+ AssetManager am = activity.getResources().getAssets(); // get the local asset manager
+ InputStream is = am.open(assetsDir + "/" + srcFile); // open the input stream for reading
+ File destDirF = new File(destDir);
+ if (!destDirF.exists()) {
+ boolean res = destDirF.mkdirs();
+ if(!res){
+ UIUtils.toastShort(activity, "Could not create directory for image");
+ }
+ }
+
+ if(destFile==null)
+ destFile=srcFile;
+ OutputStream os = new FileOutputStream(destDir + "/" + destFile);
+ byte[] buf = new byte[8092];
+ int n;
+ while ((n = is.read(buf)) > 0) {
+ os.write(buf, 0, n);
+ }
+ os.close();
+ is.close();
+ return true;
+ } catch (Exception ex) {
+ Log.e("Installer", "failed to install file: " + destFile + ", Error:" + ex.getMessage());
+ return false;
+ }
+ }
+
+ public static Uri installImageTemplateToSDCard(Activity activity, String srcFile,
+ Uri destDir, String assetsDir, String destFile) {
+
+ DocumentFile destFileF = null;
+ OutputStream os = null;
+ InputStream is = null;
+ Uri uri = null;
+
+ try {
+
+ DocumentFile dir = DocumentFile.fromTreeUri(activity, destDir);
+ AssetManager am = activity.getResources().getAssets(); // get the local asset manager
+ is = am.open(assetsDir + "/" + srcFile); // open the input stream for reading
+
+ if(destFile==null)
+ destFile=srcFile;
+
+ //Create the file if doesn't exist
+ destFileF = dir.findFile(destFile);
+ if(destFileF == null) {
+ destFileF = dir.createFile("application/octet-stream", destFile);
+ }
+ else {
+ UIUtils.toastShort(activity, "File exists, choose another filename");
+ return null;
+ }
+
+ //Write to the dest
+ os = activity.getContentResolver().openOutputStream(destFileF.getUri());
+ //OutputStream os = new FileOutputStream(destDir + "/" + destFile);
+ byte[] buf = new byte[8092];
+ int n;
+ while ((n = is.read(buf)) > 0) {
+ os.write(buf, 0, n);
+ }
+
+ //success
+ uri = destFileF.getUri();
+
+ } catch (Exception ex) {
+ Log.e("Installer", "failed to install file: " + destFile + ", Error:" + ex.getMessage());
+ } finally {
+ if(os!=null) {
+ try {
+ os.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ if(is!=null) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ }
+ return uri;
+ }
+
+
+ public static String installImageTemplateToExternalStorage(Activity activity, String srcFile,
+ String destDir, String assetsDir, String destFile) {
+
+ File file = new File(destDir, destFile);
+ String filePath = null;
+ OutputStream os = null;
+ InputStream is = null;
+ try {
+
+ AssetManager am = activity.getResources().getAssets(); // get the local asset manager
+ is = am.open(assetsDir + "/" + srcFile); // open the input stream for reading
+
+ if(destFile==null)
+ destFile=srcFile;
+
+ //Create the file if doesn't exist
+ if(!file.exists()){
+ file.createNewFile();
+ }
+ else {
+ UIUtils.toastShort(activity, "File exists, choose another filename");
+ return null;
+ }
+
+ //Write to the dest
+ os = new FileOutputStream(file);
+
+ //OutputStream os = new FileOutputStream(destDir + "/" + destFile);
+ byte[] buf = new byte[8092];
+ int n;
+ while ((n = is.read(buf)) > 0) {
+ os.write(buf, 0, n);
+ }
+
+ //success
+ filePath = file.getAbsolutePath();
+
+ } catch (Exception ex) {
+ Log.e("Installer", "failed to install file: " + destFile + ", Error:" + ex.getMessage());
+ } finally {
+ if(os!=null) {
+ try {
+ os.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ if(is!=null) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ }
+ return filePath;
+ }
+}
diff --git a/app/src/main/java/com/vectras/qemu/utils/FileUtils.java b/app/src/main/java/com/vectras/qemu/utils/FileUtils.java
new file mode 100644
index 0000000..ccf86d4
--- /dev/null
+++ b/app/src/main/java/com/vectras/qemu/utils/FileUtils.java
@@ -0,0 +1,647 @@
+/*
+Copyright (C) Max Kastanas 2012
+
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+package com.vectras.qemu.utils;
+
+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.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import androidx.documentfile.provider.DocumentFile;
+import android.text.Spannable;
+import android.util.Log;
+import android.webkit.MimeTypeMap;
+
+import com.vectras.vm.R;
+import com.vectras.qemu.Config;
+import com.vectras.vm.utils.UIUtils;
+
+/**
+ * @author dev
+ */
+public class FileUtils {
+ private final static String TAG = "FileUtils";
+ public static HashMap fds = new HashMap();
+
+ public static String getNativeLibDir(Context context) {
+ return context.getApplicationInfo().nativeLibraryDir;
+ }
+
+ public static String getFullPathFromDocumentFilePath(String filePath) {
+
+ filePath = filePath.replaceAll("%3A", "^3A");
+ int index = filePath.lastIndexOf("^3A");
+ if (index > 0)
+ filePath = filePath.substring(index + 3);
+ if (!filePath.startsWith("/"))
+ filePath = "/" + filePath;
+
+// filePath = filePath.replaceAll("%2F", "/");
+// filePath = filePath.replaceAll("\\^2F", "/");
+
+ //remove any spaces encoded by the ASF
+ try {
+ filePath = URLDecoder.decode(filePath, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ e.printStackTrace();
+ }
+
+ return filePath;
+ }
+
+ public static String getFilenameFromPath(String filePath) {
+ filePath = filePath.replaceAll("%2F", "/");
+ filePath = filePath.replaceAll("%3A", "/");
+ filePath = filePath.replaceAll("\\^2F", "/");
+ filePath = filePath.replaceAll("\\^3A", "/");
+
+
+ int index = filePath.lastIndexOf("/");
+ if (index > 0)
+ return filePath.substring(index + 1);
+ return filePath;
+ }
+
+ public static String unconvertDocumentFilePath(String filePath) {
+ if (filePath != null && filePath.startsWith("/content//")) {
+ filePath = filePath.replace("/content//", "content://");
+ filePath = filePath.replaceAll("\\^\\^\\^", "%");
+ }
+ return filePath;
+ }
+
+ public static String convertDocumentFilePath(String filePath) {
+ if (filePath != null && filePath.startsWith("content://")) {
+ filePath = filePath.replace("content://", "/content//");
+ filePath = filePath.replaceAll("%", "\\^\\^\\^");;
+
+ }
+ return filePath;
+ }
+
+ public static void saveFileContents(String filePath, String contents) {
+ // TODO: we assume that the contents are of small size so we keep in an array
+ byteArrayToFile(contents.getBytes(), new File(filePath));
+ }
+
+ 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 InputStream getStreamFromFilePath(Context context, String importFilePath) throws FileNotFoundException {
+ InputStream stream = null;
+ if (importFilePath.startsWith("content://")) {
+ Uri uri = Uri.parse(importFilePath);
+ String mode = "rw";
+ ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, mode);
+ return new FileInputStream(pfd.getFileDescriptor());
+ } else {
+ return new FileInputStream(importFilePath);
+ }
+ }
+
+ public static String getFileContents(String filePath) {
+
+ File file = new File(filePath);
+ if(!file.exists())
+ return "";
+ StringBuilder builder = new StringBuilder("");
+ try {
+ FileInputStream stream = new FileInputStream(file);
+ byte[] buff = new byte[32768];
+ int bytesRead = 0;
+ while ((bytesRead = stream.read(buff, 0, buff.length)) > 0) {
+ builder.append(new String(buff, "UTF-8"));
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ String contents = builder.toString();
+ return contents;
+ }
+
+ public static void viewVectrasLog(final Activity activity) {
+
+ String contents = FileUtils.getFileContents(Config.logFilePath);
+
+ if (contents.length() > 50 * 1024)
+ contents = contents.substring(0, 25 * 1024)
+ + "\n.....\n" +
+ contents.substring(contents.length() - 25 * 1024);
+
+ final String finalContents = contents;
+ final Spannable contentsFormatted = UIUtils.formatAndroidLog(contents);
+
+ activity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+
+ if (Config.viewLogInternally) {
+ UIUtils.UIAlertLog(activity, "Vectras Log", contentsFormatted);
+ } else {
+ try {
+ Intent intent = new Intent(Intent.ACTION_EDIT);
+ File file = new File(Config.logFilePath);
+ Uri uri = Uri.fromFile(file);
+ intent.setDataAndType(uri, "text/plain");
+ activity.startActivity(intent);
+ } catch (Exception ex) {
+// UIUtils.toastShort(activity, "Could not find a Text Viewer on your device");
+ UIUtils.UIAlertLog(activity, "Vectras Log", contentsFormatted);
+ }
+ }
+
+
+ }
+ });
+
+ }
+
+ 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;
+ }
+
+ //TODO: we should pass the modes from the backend and translate them
+ // instead of blindly using "rw". ie ISOs should be read only.
+ public static int get_fd(final Context context, String path) {
+ synchronized (fds) {
+ int fd = 0;
+ if (path == null)
+ return 0;
+
+// Log.d(TAG, "Opening Filepath: " + path);
+ if (path.startsWith("/content//") || path.startsWith("content://")) {
+ String npath = unconvertDocumentFilePath(path);
+
+//Is this needed?
+// FileInfo info = getExistingFd(npath);
+// if (info!=null) {
+// ParcelFileDescriptor pfd = info.pfd;
+// fd = pfd.getFd();
+// Log.d(TAG, "Retrieved hashed documentfile: " + npath + ", FD: " + fd);
+// return fd;
+// }
+
+
+// Log.d(TAG, "Opening unconverted: " + npath);
+ try {
+ Uri uri = Uri.parse(npath);
+ String mode = "rw";
+ if (path.toLowerCase().endsWith(".iso"))
+ mode = "r";
+ ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, mode);
+ fd = pfd.getFd();
+// Log.d(TAG, "Opening DocumentFile: " + npath + ", FD: " + fd);
+ fds.put(fd, new FileInfo(path, npath, pfd));
+
+ } catch (Exception e) {
+ Log.e(TAG, "Could not open DocumentFile: " + npath + ", FD: " + fd);
+ if(Config.debug)
+ e.printStackTrace();
+ }
+ } else {
+ //Is this needed?
+// FileInfo info = getExistingFd(path);
+// if (info!=null) {
+// ParcelFileDescriptor pfd = info.pfd;
+// fd = pfd.getFd();
+// Log.d(TAG, "Retrieved hashed file: " + path + ", FD: " + fd);
+// return fd;
+// }
+
+ try {
+ int mode = ParcelFileDescriptor.MODE_READ_WRITE;
+ if (path.toLowerCase().endsWith(".iso"))
+ mode = ParcelFileDescriptor.MODE_READ_ONLY;
+
+ File file = new File(path);
+ if (!file.exists())
+ file.createNewFile();
+ ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, mode);
+ fd = pfd.getFd();
+ fds.put(fd, new FileInfo(path, path, pfd));
+ Log.d(TAG, "Opening File: " + path + ", FD: " + fd);
+ } catch (Exception e) {
+ Log.e(TAG, "Could not open File: " + path + ", FD: " + fd);
+ if(Config.debug)
+ e.printStackTrace();
+ }
+
+ }
+ return fd;
+ }
+ }
+
+ private static FileInfo getExistingFd(String npath) {
+ Set> fileInfoSet = fds.entrySet();
+ Iterator> iter = fileInfoSet.iterator();
+ while (iter.hasNext()) {
+ Map.Entry entry = iter.next();
+ FileInfo fileInfo = entry.getValue();
+ if (fileInfo.npath.equals(npath)) {
+ return fileInfo;
+ }
+ }
+ return null;
+ }
+
+ public static void close_fds() {
+ synchronized (fds) {
+ Integer[] fds = FileUtils.fds.keySet().toArray(new Integer[FileUtils.fds.keySet().size()]);
+ for (int i = 0; i < fds.length; i++) {
+ FileUtils.close_fd(fds[i]);
+ }
+ }
+ }
+
+ public static int close_fd(int fd) {
+ if(!Config.closeFileDescriptors) {
+ return 0;
+ }
+ synchronized (fds) {
+// Log.d(TAG, "Closing FD: " + fd);
+ if (FileUtils.fds.containsKey(fd)) {
+
+ FileInfo info = FileUtils.fds.get(fd);
+
+
+ try {
+
+ ParcelFileDescriptor pfd = info.pfd;
+ try {
+ pfd.getFileDescriptor().sync();
+ } catch (IOException e) {
+ if(Config.debug) {
+ Log.w(TAG, "Syncing DocumentFile: " + info.path + ": " + fd + " : " + e);
+ e.printStackTrace();
+ }
+ }
+
+// Log.d(TAG, "Closing DocumentFile: " + info.npath + ", FD: " + fd);
+ pfd.close();
+ FileUtils.fds.remove(fd);
+ return 0; // success for Native side
+ } catch (IOException e) {
+ Log.e(TAG, "Error Closing DocumentFile: " + info.path + ": " + fd + " : " + e);
+ if(Config.debug)
+ e.printStackTrace();
+ }
+
+
+ } else {
+
+ ParcelFileDescriptor pfd = null;
+ String path = "unknown";
+ try {
+
+ //xxx: check the hash
+ FileInfo info = FileUtils.fds.get(fd);
+ if(info!=null) {
+ pfd = info.pfd;
+ path = info.path;
+// Log.d(TAG, "Closing hashe File FD: " + fd + ": " + info.path);
+ }
+
+ //xxx: else get a new parcel
+ if(pfd == null)
+ pfd = ParcelFileDescriptor.fromFd(fd);
+
+// Log.d(TAG, "Closing File FD: " + fd);
+ try {
+ pfd.getFileDescriptor().sync();
+ } catch (IOException e) {
+ if(Config.debug) {
+ Log.e(TAG, "Error Syncing File: " + path + ": " + fd + " : " + e);
+ e.printStackTrace();
+ }
+ }
+
+ pfd.close();
+ return 0;
+ } catch (Exception e) {
+ Log.e(TAG, "Error Closing File FD: " + path + ": " + fd + " : " + e);
+ if(Config.debug)
+ e.printStackTrace();
+ }
+ }
+ return -1;
+ }
+ }
+
+ public static void startLogging() {
+
+ if (Config.logFilePath == null) {
+ Log.e(TAG, "Log file is not setup");
+ return;
+ }
+
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+
+ FileOutputStream os = null;
+ File logFile = null;
+ try {
+ logFile = new File(Config.logFilePath);
+ if (logFile.exists()) {
+ logFile.delete();
+ }
+ logFile.createNewFile();
+ Runtime.getRuntime().exec("logcat -c");
+ Process process = Runtime.getRuntime().exec("logcat v main");
+ os = new FileOutputStream(logFile);
+ BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+
+ StringBuilder log = new StringBuilder("");
+ String line = "";
+ while ((line = bufferedReader.readLine()) != null) {
+ log.setLength(0);
+ log.append(line).append("\n");
+ os.write(log.toString().getBytes("UTF-8"));
+ os.flush();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ try {
+ if (os != null) {
+ os.flush();
+ os.close();
+ }
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ }
+
+ }
+ });
+ t.setName("VectrasLogger");
+ t.start();
+ }
+
+ public static 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(activity.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 String getExtensionFromFilename(String fileName) {
+ if (fileName == null)
+ return "";
+
+ int index = fileName.lastIndexOf(".");
+ if (index >= 0) {
+ return fileName.substring(index + 1);
+ } else
+ return "";
+ }
+
+ public static int getIconForFile(String file) {
+// file = file.toLowerCase();
+// String ext = FileUtils.getExtensionFromFilename(file).toLowerCase();
+//
+// if(ext.equals("img") || ext.equals("qcow")
+// || ext.equals("qcow2") || ext.equals("vmdk") || ext.equals("vdi") || ext.equals("cow")
+// || ext.equals("dmg") || ext.equals("bochs") || ext.equals("vpc")
+// || ext.equals("vhd") || ext.equals("fs"))
+// return R.drawable.harddisk;
+// else if(ext.equals("iso"))
+// return R.drawable.cd;
+// else if(ext.equals("ima"))
+// return R.drawable.floppy;
+// else if(ext.equals("csv"))
+// return R.drawable.importvms;
+// else if(file.contains("kernel") || file.contains("vmlinuz") || file.contains("initrd"))
+// return R.drawable.sysfile;
+// else
+// return R.drawable.close;
+ return R.mipmap.ic_launcher;
+//
+ }
+
+
+
+
+ public static Uri saveLogFileSDCard(Activity activity,
+ Uri destDir) {
+
+ DocumentFile destFileF = null;
+ OutputStream os = null;
+ Uri uri = null;
+
+ try {
+ String logFileContents = getFileContents(Config.logFilePath);
+ DocumentFile dir = DocumentFile.fromTreeUri(activity, destDir);
+
+ //Create the file if doesn't exist
+ destFileF = dir.findFile(Config.destLogFilename);
+ if(destFileF == null) {
+ destFileF = dir.createFile(MimeTypeMap.getSingleton().getMimeTypeFromExtension("txt"), Config.destLogFilename);
+ }
+
+ //Write to the dest
+ os = activity.getContentResolver().openOutputStream(destFileF.getUri());
+ os.write(logFileContents.getBytes());
+
+ //success
+ uri = destFileF.getUri();
+
+ } catch (Exception ex) {
+ UIUtils.toastShort(activity, "Failed to save log file: " + ex.getMessage());
+ } finally {
+ if(os!=null) {
+ try {
+ os.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ }
+ return uri;
+ }
+
+ public static String saveLogFileLegacy(Activity activity,
+ String destLogFilePath) {
+
+ String filePath = null;
+ File destFileF = new File(destLogFilePath, Config.destLogFilename);
+
+ try {
+ String logFileContents = getFileContents(Config.logFilePath);
+ FileUtils.saveFileContents(destFileF.getAbsolutePath(), logFileContents);
+
+ //success
+ filePath = destFileF.getAbsolutePath();
+
+ } catch (Exception ex) {
+ UIUtils.toastShort(activity, "Failed to save log file: " + destFileF.getAbsolutePath() + ", Error:" + ex.getMessage());
+ } finally {
+ }
+ return filePath;
+ }
+
+
+ public static String getFileUriFromIntent(Activity activity, Intent data, boolean write) {
+ if(data == null)
+ return null;
+
+ Uri uri = data.getData();
+ DocumentFile pickedFile = DocumentFile.fromSingleUri(activity, uri);
+ String file = uri.toString();
+ if (!file.contains("com.android.externalstorage.documents")) {
+ UIUtils.showFileNotSupported(activity);
+ return null;
+ }
+ activity.grantUriPermission(activity.getPackageName(), uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ if(write)
+ activity.grantUriPermission(activity.getPackageName(), uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ activity.grantUriPermission(activity.getPackageName(), uri, Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
+
+ int takeFlags = data.getFlags() & Intent.FLAG_GRANT_READ_URI_PERMISSION;
+ if(write)
+ takeFlags = takeFlags | Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
+
+ activity.getContentResolver().takePersistableUriPermission(uri, takeFlags);
+ return file;
+ }
+
+ public static String getDirPathFromIntent(Activity activity, Intent data) {
+ if(data == null)
+ return null;
+ Bundle b = data.getExtras();
+ String file = b.getString("currDir");
+ return file;
+ }
+
+
+ public static String getFilePathFromIntent(Activity activity, Intent data) {
+ if(data == null)
+ return null;
+ Bundle b = data.getExtras();
+ String file = b.getString("file");
+ return file;
+ }
+
+ public static class FileInfo {
+ public String path;
+ public String npath;
+ public ParcelFileDescriptor pfd;
+
+ public FileInfo(String path, String npath, ParcelFileDescriptor pfd) {
+ this.npath = npath;
+ this.path = path;
+ this.pfd = pfd;
+ }
+ }
+
+
+
+ public static void saveLogToFile(final Activity activity, final String logFileDestDir) {
+
+
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+
+ String displayName = null;
+ if (logFileDestDir.startsWith("content://")) {
+ Uri exportDirUri = Uri.parse(logFileDestDir);
+ Uri fileCreatedUri = FileUtils.saveLogFileSDCard(activity,
+ exportDirUri);
+ displayName = FileUtils.getFullPathFromDocumentFilePath(fileCreatedUri.toString());
+ } else {
+ String filePath = FileUtils.saveLogFileLegacy(activity, logFileDestDir);
+ displayName = filePath;
+ }
+
+ if(displayName!=null){
+
+ UIUtils.toastShort(activity, "Logfile saved");
+ }
+ }
+ });
+ t.start();
+ }
+
+
+}
diff --git a/app/src/main/java/com/vectras/qemu/utils/Machine.java b/app/src/main/java/com/vectras/qemu/utils/Machine.java
new file mode 100644
index 0000000..b9079d3
--- /dev/null
+++ b/app/src/main/java/com/vectras/qemu/utils/Machine.java
@@ -0,0 +1,222 @@
+package com.vectras.qemu.utils;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import com.vectras.qemu.Config;
+import com.vectras.qemu.MainActivityCommon;
+import com.vectras.vm.R;
+import com.vectras.vm.utils.UIUtils;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class Machine {
+
+ public static final String EMPTY = "empty";
+ public static String TAG = "Machine";
+
+ public static void promptPausedVM(final Activity activity) {
+
+ new AlertDialog.Builder(activity, R.style.MainDialogTheme).setCancelable(false).setTitle("Paused").setMessage("VM is now Paused tap OK to exit")
+ .setPositiveButton("OK", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+
+ Log.i(TAG, "VM Paused, Shutting Down");
+ if (activity.getParent() != null) {
+ activity.getParent().finish();
+ } else {
+ activity.finish();
+ }
+
+ if (MainActivityCommon.vmexecutor != null) {
+ MainActivityCommon.vmexecutor.stopvm(0);
+ }
+ }
+ }).show();
+ }
+
+
+ public static void onRestartVM(final Context context) {
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ if (MainActivityCommon.vmexecutor != null) {
+ Log.v(TAG, "Restarting the VM...");
+ MainActivityCommon.vmexecutor.stopvm(1);
+
+ MainActivityCommon.vmStarted = true;
+ if(Config.showToast)
+ UIUtils.toastShort(context, "VM Reset");
+
+ } else {
+ if(Config.showToast)
+ UIUtils.toastShort(context, "VM Not Running");
+ }
+ }
+ });
+ t.start();
+ }
+
+ public static void pausedErrorVM(Activity activity, String errStr) {
+
+ errStr = errStr != null ? errStr : "Could not pause VM. View log for details";
+
+ new AlertDialog.Builder(activity, R.style.MainDialogTheme).setTitle("Error").setMessage(errStr)
+ .setPositiveButton("OK", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ String command = QmpClient.cont();
+ String msg = QmpClient.sendCommand(command);
+ }
+ });
+ t.start();
+ }
+ }).show();
+ }
+
+ public static void stopVM(final Activity activity) {
+
+ new AlertDialog.Builder(activity, R.style.MainDialogTheme).setTitle("Shutdown VM")
+ .setMessage("To avoid any corrupt data make sure you "
+ + "have already shutdown the Operating system from within the VM. Continue?")
+ .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ if (activity.getParent() != null) {
+ activity.getParent().finish();
+ } else {
+ activity.finish();
+ }
+
+ if (MainActivityCommon.vmexecutor != null) {
+ MainActivityCommon.vmexecutor.stopvm(0);
+ }
+ }
+ }).setNegativeButton("No", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ }
+ }).show();
+ }
+
+ public static MainActivityCommon.VMStatus checkSaveVMStatus(final Activity activity) {
+ String pause_state = "";
+ if (MainActivityCommon.vmexecutor != null) {
+
+ String command = QmpClient.query_migrate();
+ String res = QmpClient.sendCommand(command);
+
+ if (res != null && !res.equals("")) {
+ //Log.d(TAG, "Migrate status: " + res);
+ try {
+ JSONObject resObj = new JSONObject(res);
+ String resInfo = resObj.getString("return");
+ JSONObject resInfoObj = new JSONObject(resInfo);
+ pause_state = resInfoObj.getString("status");
+ } catch (JSONException e) {
+ if (Config.debug)
+ Log.e(TAG,e.getMessage());
+ //e.printStackTrace();
+ }
+ if (pause_state != null && pause_state.toUpperCase().equals("FAILED")) {
+ Log.e(TAG, "Error: " + res);
+ }
+ }
+ }
+
+ if (pause_state.toUpperCase().equals("ACTIVE")) {
+ return MainActivityCommon.VMStatus.Saving;
+ } else if (pause_state.toUpperCase().equals("COMPLETED")) {
+ MainActivityCommon.vmexecutor.paused = 1;
+
+ new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ promptPausedVM(activity);
+ }
+ }, 1000);
+ return MainActivityCommon.VMStatus.Completed;
+
+ } else if (pause_state.toUpperCase().equals("FAILED")) {
+ new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ pausedErrorVM(activity, null);
+ }
+ }, 100);
+ return MainActivityCommon.VMStatus.Failed;
+ }
+ return MainActivityCommon.VMStatus.Unknown;
+ }
+
+ public static boolean isHostX86_64() {
+ if(Build.SUPPORTED_64_BIT_ABIS != null)
+ {
+ for(int i=0; i< Build.SUPPORTED_64_BIT_ABIS.length ; i++)
+ if(Build.SUPPORTED_64_BIT_ABIS[i].equals("x86_64"))
+ return true;
+ }
+ return false;
+ }
+
+ public static boolean isHostX86() {
+ if(Build.SUPPORTED_32_BIT_ABIS != null)
+ {
+ for(int i=0; i< Build.SUPPORTED_32_BIT_ABIS.length ; i++)
+ if(Build.SUPPORTED_32_BIT_ABIS[i].equals("x86"))
+ return true;
+ }
+ return false;
+ }
+
+ public static boolean isHostArm() {
+ if(Build.SUPPORTED_32_BIT_ABIS != null)
+ {
+ for(int i=0; i< Build.SUPPORTED_32_BIT_ABIS.length ; i++)
+ if(Build.SUPPORTED_32_BIT_ABIS[i].equals("armeabi-v7a"))
+ return true;
+ }
+ return false;
+ }
+
+ public static boolean isHostArmv8() {
+ if(Build.SUPPORTED_64_BIT_ABIS != null)
+ {
+ for(int i=0; i< Build.SUPPORTED_64_BIT_ABIS.length ; i++)
+ if(Build.SUPPORTED_64_BIT_ABIS[i].equals("arm64-v8a"))
+ return true;
+ }
+ return false;
+ }
+
+ public static boolean isHost64Bit() {
+ return Build.SUPPORTED_64_BIT_ABIS!=null && Build.SUPPORTED_64_BIT_ABIS.length > 0 ;
+ }
+
+
+ public static void resetVM(final Activity activity) {
+
+ new AlertDialog.Builder(activity, R.style.MainDialogTheme).setTitle("Reset VM")
+ .setMessage("To avoid any corrupt data make sure you "
+ + "have already shutdown the Operating system from within the VM. Continue?")
+ .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ new Thread(new Runnable() {
+ public void run() {
+ Log.v(TAG, "VM is reset");
+ Machine.onRestartVM(activity);
+ }
+ }).start();
+
+ }
+ }).setNegativeButton("No", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ }
+ }).show();
+ }
+}
diff --git a/app/src/main/java/com/vectras/qemu/utils/QmpClient.java b/app/src/main/java/com/vectras/qemu/utils/QmpClient.java
new file mode 100644
index 0000000..da37113
--- /dev/null
+++ b/app/src/main/java/com/vectras/qemu/utils/QmpClient.java
@@ -0,0 +1,277 @@
+package com.vectras.qemu.utils;
+
+import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
+import android.util.Log;
+
+import com.vectras.qemu.Config;
+
+import org.json.JSONObject;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.net.Socket;
+
+public class QmpClient {
+
+ private static final String TAG = "QmpClient";
+ private static String requestCommandMode = "{ \"execute\": \"qmp_capabilities\" }";
+ public static boolean allow_external = false;
+
+ public synchronized static String sendCommand(String command) {
+ String response = null;
+ int trial=0;
+ Socket pingSocket = null;
+ LocalSocket localSocket = null;
+ PrintWriter out = null;
+ BufferedReader in = null;
+
+ try {
+ if(allow_external) {
+ pingSocket = new Socket(Config.QMPServer, Config.QMPPort);
+ pingSocket.setSoTimeout(5000);
+ out = new PrintWriter(pingSocket.getOutputStream(), true);
+ in = new BufferedReader(new InputStreamReader(pingSocket.getInputStream()));
+ } else {
+ localSocket = new LocalSocket();
+ String localQMPSocketPath = Config.getLocalQMPSocketPath();
+ LocalSocketAddress localSocketAddr = new LocalSocketAddress(localQMPSocketPath, LocalSocketAddress.Namespace.FILESYSTEM);
+ localSocket.connect(localSocketAddr);
+ localSocket.setSoTimeout(5000);
+ out = new PrintWriter(localSocket.getOutputStream(), true);
+ in = new BufferedReader(new InputStreamReader(localSocket.getInputStream()));
+ }
+
+
+ sendRequest(out, QmpClient.requestCommandMode);
+ while(true){
+ response = getResponse(in);
+ if(response == null || response.equals("") || trial <10)
+ break;
+
+ Thread.sleep(1000);
+ trial++;
+ }
+
+ sendRequest(out, command);
+ trial=0;
+ while((response = getResponse(in)).equals("") && trial < 10){
+ Thread.sleep(1000);
+ trial++;
+ }
+ } catch (java.net.ConnectException e) {
+ Log.w(TAG, "Could not connect to QMP: " + e);
+ if(Config.debugQmp)
+ e.printStackTrace();
+ } catch(Exception e) {
+ // TODO Auto-generated catch block
+ Log.e(TAG, "Error while connecting to QMP: " + e);
+ if(Config.debugQmp)
+ e.printStackTrace();
+ } finally {
+ if (out != null)
+ out.close();
+ try {
+ if (in != null)
+ in.close();
+ if (pingSocket != null)
+ pingSocket.close();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ }
+
+ return response;
+ }
+
+ private static void sendRequest(PrintWriter out, String request) {
+
+ if(Config.debugQmp)
+ Log.i(TAG, "QMP request" + request);
+ out.println(request);
+ }
+
+ private static String getResponse(BufferedReader in) throws Exception {
+
+ String line;
+ StringBuilder stringBuilder = new StringBuilder("");
+
+ try {
+ do {
+ line = in.readLine();
+ if (line != null) {
+ if(Config.debugQmp)
+ Log.i(TAG, "QMP response: " + line);
+ JSONObject object = new JSONObject(line);
+ String returnStr = null;
+ String errStr = null;
+
+ try {
+ if(line.contains("return"))
+ returnStr = object.getString("return");
+ } catch (Exception ex) {
+ if(Config.debugQmp)
+ ex.printStackTrace();
+ }
+
+ if (returnStr != null) {
+ stringBuilder.append(line);
+ stringBuilder.append("\n");
+ break;
+ }
+
+ try {
+ if(line.contains("error"))
+ errStr = object.getString("error");
+ } catch (Exception ex) {
+ if(Config.debugQmp)
+ ex.printStackTrace();
+ }
+
+ stringBuilder.append(line);
+ stringBuilder.append("\n");
+
+ if (errStr != null) {
+ break;
+ }
+
+
+ } else
+ break;
+ } while (true);
+ } catch (Exception ex) {
+ Log.e(TAG, "Could not get Response: " + ex.getMessage());
+ if(Config.debugQmp)
+ ex.printStackTrace();
+ }
+ return stringBuilder.toString();
+ }
+
+ private static String getQueryMigrateResponse(BufferedReader in) throws Exception {
+
+ String line;
+ StringBuilder stringBuilder = new StringBuilder("");
+
+ try {
+ do {
+ line = in.readLine();
+ if (line != null) {
+ if(Config.debugQmp)
+ Log.i(TAG, "QMP query-migrate response: " + line);
+ JSONObject object = new JSONObject(line);
+ String returnStr = null;
+ String errStr = null;
+
+ try {
+ returnStr = object.getString("return");
+ } catch (Exception ex) {
+
+ }
+
+ if (returnStr != null) {
+ break;
+ }
+
+ try {
+ errStr = object.getString("error");
+ } catch (Exception ex) {
+
+ }
+
+ stringBuilder.append(line);
+ stringBuilder.append("\n");
+
+ if (errStr != null) {
+ break;
+ }
+
+
+ } else
+ break;
+ } while (true);
+ } catch (Exception ex) {
+
+ }
+ return stringBuilder.toString();
+ }
+
+ public static String migrate(boolean block, boolean inc, String uri) {
+
+ // XXX: Detach should not be used via QMP according to docs
+ // return "{\"execute\":\"migrate\",\"arguments\":{\"detach\":" + detach
+ // + ",\"blk\":" + block + ",\"inc\":" + inc
+ // + ",\"uri\":\"" + uri + "\"},\"id\":\"limbo\"}";
+
+ // its better not to use block (full disk copy) cause its slow (though
+ // safer)
+ // see qmp-commands.hx for more info
+ return "{\"execute\":\"migrate\",\"arguments\":{\"blk\":" + block + ",\"inc\":" + inc + ",\"uri\":\"" + uri
+ + "\"},\"id\":\"limbo\"}";
+
+ }
+
+ public static String changevncpasswd(String passwd) {
+
+ return "{\"execute\": \"change\", \"arguments\": { \"device\": \"vnc\", \"target\": \"password\", \"arg\": \"" + passwd +"\" } }";
+
+ }
+
+ public static String ejectdev(String dev) {
+
+ return "{ \"execute\": \"eject\", \"arguments\": { \"device\": \""+ dev +"\" } }";
+
+ }
+
+ public static String changedev(String dev, String value) {
+
+ return "{ \"execute\": \"change\", \"arguments\": { \"device\": \""+dev+"\", \"target\": \"" + value + "\" } }";
+
+ }
+
+
+
+ public static String query_migrate() {
+ return "{ \"execute\": \"query-migrate\" }";
+
+ }
+
+ public static String save_snapshot(String snapshot_name) {
+ return "{\"execute\": \"snapshot-create\", \"arguments\": {\"name\": \""+ snapshot_name+"\"} }";
+
+ }
+
+ public static String query_snapshot() {
+ return "{ \"execute\": \"query-snapshot-status\" }";
+
+ }
+
+ public static String stop() {
+ return "{ \"execute\": \"stop\" }";
+
+ }
+
+ public static String cont() {
+ return "{ \"execute\": \"cont\" }";
+
+ }
+
+ public static String powerDown() {
+ return "{ \"execute\": \"system_powerdown\" }";
+
+ }
+
+ public static String reset() {
+ return "{ \"execute\": \"system_reset\" }";
+
+ }
+
+ public static String getState() {
+ return "{ \"execute\": \"query-status\" }";
+
+ }
+}
diff --git a/app/src/main/java/com/vectras/qemu/utils/RamInfo.java b/app/src/main/java/com/vectras/qemu/utils/RamInfo.java
new file mode 100644
index 0000000..ae29aed
--- /dev/null
+++ b/app/src/main/java/com/vectras/qemu/utils/RamInfo.java
@@ -0,0 +1,38 @@
+package com.vectras.qemu.utils;
+
+import static android.content.Context.ACTIVITY_SERVICE;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+
+import com.vectras.qemu.MainSettingsManager;
+
+public class RamInfo {
+ public static Activity activity;
+
+ public static int safeLongToInt(long l) {
+ if (l < Integer.MIN_VALUE || l > Integer.MAX_VALUE) {
+ throw new IllegalArgumentException(l + " cannot be cast to int without changing its value.");
+ }
+ return (int) l;
+ }
+
+ public static int vectrasMemory() {
+ ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
+ ActivityManager activityManager = (ActivityManager) activity.getSystemService(ACTIVITY_SERVICE);
+ activityManager.getMemoryInfo(mi);
+ long freeMem = mi.availMem / 1048576L;
+ long totalMem = mi.totalMem / 1048576L;
+ long usedMem = totalMem - freeMem;
+ int freeRamInt = safeLongToInt(freeMem);
+ int totalRamInt = safeLongToInt(totalMem);
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
+ if (prefs.getBoolean("customMemory", false)) {
+ return Integer.parseInt(prefs.getString("memory", String.valueOf(256)));
+ } else {
+ return freeRamInt - 100;
+ }
+ }
+}
diff --git a/app/src/main/java/com/vectras/vm/AboutActivity.java b/app/src/main/java/com/vectras/vm/AboutActivity.java
new file mode 100644
index 0000000..6169bb5
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/AboutActivity.java
@@ -0,0 +1,235 @@
+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.google.android.gms.ads.LoadAdError;
+import com.google.android.gms.ads.MobileAds;
+import com.google.android.gms.ads.initialization.InitializationStatus;
+import com.google.android.gms.ads.initialization.OnInitializationCompleteListener;
+import com.google.android.gms.ads.interstitial.InterstitialAd;
+import com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback;
+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, btn_instagram, btn_facebook;
+ String appInfo;
+
+ public String TAG = "AboutActivity";
+ private InterstitialAd mInterstitialAd;
+ @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("About");
+ //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_instagram = (Button) findViewById(R.id.btn_instagram);
+ btn_facebook = (Button) findViewById(R.id.btn_facebook);
+ 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_instagram.setOnClickListener(this);
+ btn_facebook.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();
+ }
+
+ }
+ });
+
+ MobileAds.initialize(this, new OnInitializationCompleteListener() {
+ @Override
+ public void onInitializationComplete(InitializationStatus initializationStatus) {}
+ });
+ InterstitialAd.load(this,"ca-app-pub-3568137780412047/4892595373", adRequest,
+ new InterstitialAdLoadCallback() {
+ @Override
+ public void onAdLoaded(@NonNull InterstitialAd interstitialAd) {
+ // The mInterstitialAd reference will be null until
+ // an ad is loaded.
+ mInterstitialAd = interstitialAd;
+ Log.i(TAG, "onAdLoaded");
+ }
+
+ @Override
+ public void onAdFailedToLoad(@NonNull LoadAdError loadAdError) {
+ // Handle the error
+ Log.d(TAG, loadAdError.toString());
+ mInterstitialAd = null;
+ }
+ });
+ if (mInterstitialAd != null) {
+ mInterstitialAd.show(AboutActivity.this);
+ } else {
+ Log.d("TAG", "The interstitial ad wasn't ready yet.");
+ }
+ }
+
+ @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 IG = R.id.btn_instagram;
+ public static final int FB = R.id.btn_facebook;
+ 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 == IG) {
+ String ig = "https://www.instagram.com/vectrasvm";
+ Intent i = new Intent(Intent.ACTION_VIEW);
+ i.setData(Uri.parse(ig));
+ startActivity(i);
+ } else if (id == FB) {
+ String fb = "https://www.facebook.com/profile.php?id=61555122773211";
+ Intent f = new Intent(Intent.ACTION_VIEW);
+ f.setData(Uri.parse(fb));
+ startActivity(f);
+ } 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();
+ }
+ }
+}
diff --git a/app/src/main/java/com/vectras/vm/AppConfig.java b/app/src/main/java/com/vectras/vm/AppConfig.java
new file mode 100644
index 0000000..7a71305
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/AppConfig.java
@@ -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/";
+
+}
diff --git a/app/src/main/java/com/vectras/vm/Blog/AdapterBlog.java b/app/src/main/java/com/vectras/vm/Blog/AdapterBlog.java
new file mode 100644
index 0000000..8444ad2
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/Blog/AdapterBlog.java
@@ -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 {
+
+ private Context context;
+ private LayoutInflater inflater;
+ List data = Collections.emptyList();
+ DataBlog current;
+ int currentPos = 0;
+
+ // create constructor to innitilize context and data sent from MainActivity
+ public AdapterBlog(Context context, List data) {
+ this.context = context;
+ inflater = LayoutInflater.from(context);
+ this.data = data;
+ }
+
+ // Inflate the layout when viewholder created
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View view = inflater.inflate(R.layout.container_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);
+ }
+
+ }
+
+}
diff --git a/app/src/main/java/com/vectras/vm/Blog/DataBlog.java b/app/src/main/java/com/vectras/vm/Blog/DataBlog.java
new file mode 100644
index 0000000..f3e17e0
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/Blog/DataBlog.java
@@ -0,0 +1,9 @@
+package com.vectras.vm.Blog;
+
+public class DataBlog {
+
+ public String postThumb;
+ public String postTitle;
+ public String postContent;
+ public String postDate;
+}
diff --git a/app/src/main/java/com/vectras/vm/Fragment/ControlersOptionsFragment.java b/app/src/main/java/com/vectras/vm/Fragment/ControlersOptionsFragment.java
new file mode 100644
index 0000000..9f51809
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/Fragment/ControlersOptionsFragment.java
@@ -0,0 +1,50 @@
+package com.vectras.vm.Fragment;
+
+import android.app.Dialog;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Bundle;
+
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.Fragment;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+
+import com.vectras.qemu.MainSettingsManager;
+import com.vectras.vm.R;
+public class ControlersOptionsFragment extends DialogFragment {
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Dialog alertDialog = new Dialog(getActivity(), R.style.MainDialogTheme);
+ alertDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+ alertDialog.getWindow().setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT));
+ alertDialog.setContentView(R.layout.fragment_controlers_options);
+ alertDialog.findViewById(R.id.gamepadBtn).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ MainSettingsManager.setControlMode(getActivity(), "G");
+ alertDialog.cancel();
+ }
+ });
+ alertDialog.findViewById(R.id.desktopBtn).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ MainSettingsManager.setControlMode(getActivity(), "D");
+ alertDialog.cancel();
+ }
+ });
+ alertDialog.findViewById(R.id.hideBtn).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ MainSettingsManager.setControlMode(getActivity(), "H");
+ alertDialog.cancel();
+ }
+ });
+ alertDialog.show();
+ return alertDialog;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/vectras/vm/Fragment/DialogSettingsFragment.java b/app/src/main/java/com/vectras/vm/Fragment/DialogSettingsFragment.java
new file mode 100644
index 0000000..2eac5a0
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/Fragment/DialogSettingsFragment.java
@@ -0,0 +1,46 @@
+package com.vectras.vm.Fragment;
+
+import android.os.Bundle;
+
+import androidx.fragment.app.Fragment;
+
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.google.android.material.navigation.NavigationView;
+import com.vectras.vm.R;
+
+public class DialogSettingsFragment extends Fragment {
+
+ public View view;
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ // Inflate the layout for this fragment
+ view = inflater.inflate(R.layout.content_settings_dialog, container, false);
+
+ NavigationView navView = view.findViewById(R.id.navView);
+ View fragment = view.findViewById(R.id.fragment);
+ navView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
+
+ // This method will trigger on item Click of navigation menu
+ @Override
+ public boolean onNavigationItemSelected(MenuItem menuItem) {
+ //Check to see which item was being clicked and perform appropriate action
+ int id = menuItem.getItemId();
+ if (id == R.id.uiInterface) {
+
+ }
+ return false;
+ }
+ });
+ return view;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/vectras/vm/Fragment/HomeFragment.java b/app/src/main/java/com/vectras/vm/Fragment/HomeFragment.java
new file mode 100644
index 0000000..332fc79
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/Fragment/HomeFragment.java
@@ -0,0 +1,163 @@
+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.GridLayoutManager;
+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 mRVMainRoms;
+ public static LinearLayout romsLayout;
+ public static AdapterMainRoms mMainAdapter;
+ public MainActivity activity;
+ public static JSONArray jArray;
+ public static List 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);
+
+ SwipeRefreshLayout refreshRoms = view.findViewById(R.id.refreshRoms);
+
+ refreshRoms.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
+ @Override
+ public void onRefresh() {
+ 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();
+
+ 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[E] "+logLine+"");
+ VectrasStatus.logError("[W] "+logLine2+"");
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ });
+ }
+ };
+ _timer.scheduleAtFixedRate(t, (int) (0), (int) (100));
+ } catch (IOException e) {
+ Toast.makeText(activity, "There was an error: " + Log.getStackTraceString(e), Toast.LENGTH_LONG).show();
+ e.printStackTrace();
+ }
+
+ return view;
+ }
+
+}
diff --git a/app/src/main/java/com/vectras/vm/Fragment/UiSettingsFragment.java b/app/src/main/java/com/vectras/vm/Fragment/UiSettingsFragment.java
new file mode 100644
index 0000000..23894f4
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/Fragment/UiSettingsFragment.java
@@ -0,0 +1,27 @@
+package com.vectras.vm.Fragment;
+
+import android.os.Bundle;
+
+import androidx.fragment.app.Fragment;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.vectras.vm.R;
+
+public class UiSettingsFragment extends Fragment {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ // Inflate the layout for this fragment
+ return inflater.inflate(R.layout.fragment_ui_settings, container, false);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/vectras/vm/ImagePrvActivity.java b/app/src/main/java/com/vectras/vm/ImagePrvActivity.java
new file mode 100644
index 0000000..e7e9b97
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/ImagePrvActivity.java
@@ -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);
+ }
+}
diff --git a/app/src/main/java/com/vectras/vm/MainActivity.java b/app/src/main/java/com/vectras/vm/MainActivity.java
new file mode 100644
index 0000000..e1d6f44
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/MainActivity.java
@@ -0,0 +1,593 @@
+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.util.Log;
+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.Button;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+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.bumptech.glide.Glide;
+import com.google.android.gms.ads.LoadAdError;
+import com.google.android.gms.ads.MobileAds;
+import com.google.android.gms.ads.initialization.InitializationStatus;
+import com.google.android.gms.ads.initialization.OnInitializationCompleteListener;
+import com.google.android.gms.ads.interstitial.InterstitialAd;
+import com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback;
+import com.google.android.material.bottomappbar.BottomAppBar;
+import com.google.android.material.button.MaterialButton;
+import com.google.android.material.color.DynamicColors;
+import com.google.android.material.color.utilities.DynamicColor;
+import com.google.firebase.auth.FirebaseAuth;
+import com.google.firebase.auth.FirebaseUser;
+import com.google.firebase.database.FirebaseDatabase;
+import com.vectras.qemu.Config;
+import com.vectras.qemu.MainActivityCommon;
+import com.vectras.qemu.MainService;
+import com.vectras.qemu.MainSettingsManager;
+import com.vectras.qemu.jni.StartVM;
+import com.vectras.qemu.utils.FileUtils;
+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.Objects;
+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;
+ private Timer _timer = new Timer();
+ private TimerTask t;
+ public ViewPager viewPager;
+ MenuItem prevMenuItem;
+ int pager_number = 2;
+ private InterstitialAd mInterstitialAd;
+ private AdRequest adRequest;
+ public static AppBarLayout appbar;
+
+ 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();
+ }
+
+ // This is easier: traverse the interfaces and get the local IPs
+ public static String getLocalIpAddress() {
+ try {
+ for (Enumeration en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {
+ NetworkInterface intf = en.nextElement();
+ for (Enumeration 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") + "
update size: " + obj.getString("size")))
+ .setCancelable(obj.getBoolean("cancellable"))
+ .setNegativeButton("Update", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ try {
+ startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(obj.getString("url"))));
+ } catch (JSONException e) {
+
+ }
+ }
+ }).show();
+
+ }
+ } else if (result.contains("Error on getting data") && showDialog) {
+ errorUpdateDialog(result);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }).start(showDialog);
+ }
+
+ private void errorUpdateDialog(String error) {
+ VectrasStatus.logInfo(String.format(error));
+ }
+
+ private MenuItem vectrasInfo;
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.home_toolbar_menu, menu);
+ vectrasInfo = menu.findItem(R.id.vectrasInfo);
+ return true;
+
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+
+ // Menu items
+ int id = item.getItemId();
+ if (id == R.id.vectrasInfo) {
+ appbar = findViewById(R.id.appbar);
+ if (appbar.getTop() < 0)
+ appbar.setExpanded(true);
+ else
+ appbar.setExpanded(false);
+
+ } else if (id == R.id.installRoms) {
+ startActivity(new Intent(MainActivity.activity, RomsManagerActivity.class));
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void initNavigationMenu() {
+ BottomNavigationView bottomNavigationView = (BottomNavigationView) findViewById(R.id.bNav);
+
+ bottomNavigationView
+ .setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
+ @Override
+ public boolean onNavigationItemSelected(@NonNull MenuItem item) {
+ int id = item.getItemId();
+ if (id == R.id.menu_home) {
+ viewPager.setCurrentItem(0);
+ } else if (id == R.id.menu_logger) {
+ viewPager.setCurrentItem(1);
+ }
+ return false;
+ }
+ });
+ }
+
+ public class MyAdapter extends FragmentPagerAdapter {
+
+ MyAdapter(FragmentManager fm) {
+ super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
+ }
+
+ @Override
+ public Fragment getItem(int position) {
+
+ switch (position) {
+ case 0:
+ return new HomeFragment();
+ case 1:
+ return new LoggerFragment();
+ }
+ return null;
+ }
+
+ @Override
+ public int getCount() {
+ return pager_number;
+ }
+ }
+
+ public static LinearLayout extVncLayout;
+ public MaterialButton stopBtn;
+ FirebaseAuth mAuth;
+ FirebaseUser mCurrentUser;
+
+ // Setting up the UI
+ public void setupWidgets() {
+ extVncLayout = findViewById(R.id.extVnc);
+ stopBtn = findViewById(R.id.stopBtn);
+ stopBtn.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ MainActivityCommon.onStopButton(true);
+ }
+ });
+ viewPager = findViewById(R.id.viewPager);
+ viewPager.setAdapter(new MyAdapter(getSupportFragmentManager()));
+ viewPager.setOffscreenPageLimit(pager_number);
+ final BottomNavigationView bottomNavigationView = (BottomNavigationView) findViewById(R.id.bNav);
+ 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) {
+
+ }
+ });
+
+ appbar = findViewById(R.id.appbar);
+ appbar.setExpanded(false);
+ mainToolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(mainToolbar);
+ mainDrawer = (DrawerLayout) findViewById(R.id.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);
+ mAuth = FirebaseAuth.getInstance();
+ mCurrentUser = mAuth.getCurrentUser();
+ assert mCurrentUser != null;
+ String name = mCurrentUser.getDisplayName();
+ String email = mCurrentUser.getEmail();
+ Uri picture = mCurrentUser.getPhotoUrl();
+ TextView navUsername = (TextView) headerView.findViewById(R.id.usernameTxt);
+ navUsername.setText(name);
+ TextView navEmail = (TextView) headerView.findViewById(R.id.emailTxt);
+ navEmail.setText(email);
+ TextView viewProfile = (TextView) headerView.findViewById(R.id.viewProfile);
+
+ ImageView ivProfile = (ImageView) headerView.findViewById(R.id.profilePic2);
+
+ viewProfile.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View view) {
+
+ startActivity(new Intent(activity, ProfileActivity.class));
+
+ }
+ });
+
+ //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_view_logs) {
+ FileUtils.viewVectrasLog(activity);
+ } 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);
+ String vectrasMemory = String.valueOf(RamInfo.vectrasMemory());
+ 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 (" + vectrasMemory + " used)");
+ 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));
+
+ AdView mAdView = findViewById(R.id.adView);
+ adRequest = new AdRequest.Builder().build();
+ mAdView.loadAd(adRequest);
+
+ MobileAds.initialize(this, new OnInitializationCompleteListener() {
+ @Override
+ public void onInitializationComplete(InitializationStatus initializationStatus) {
+ }
+ });
+ 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.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();
+
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ InterstitialAd.load(this, "ca-app-pub-3568137780412047/7745973511", adRequest,
+ new InterstitialAdLoadCallback() {
+ @Override
+ public void onAdLoaded(@NonNull InterstitialAd interstitialAd) {
+ // The mInterstitialAd reference will be null until
+ // an ad is loaded.
+ mInterstitialAd = interstitialAd;
+ Log.i(TAG, "onAdLoaded");
+ }
+
+ @Override
+ public void onAdFailedToLoad(@NonNull LoadAdError loadAdError) {
+ // Handle the error
+ Log.d(TAG, loadAdError.toString());
+ mInterstitialAd = null;
+ }
+ });
+ if (mInterstitialAd != null) {
+ mInterstitialAd.show(MainActivity.this);
+ } else {
+ Log.d("TAG", "The interstitial ad wasn't ready yet.");
+ }
+ }
+
+ public void onPause() {
+ super.onPause();
+ MainActivityCommon.stopTimeListener();
+ }
+
+ public boolean loaded = false;
+
+ public void onResume() {
+ super.onResume();
+ assert mCurrentUser != null;
+ String name = mCurrentUser.getDisplayName();
+ String email = mCurrentUser.getEmail();
+ Uri picture = mCurrentUser.getPhotoUrl();
+ NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
+ View headerView = navigationView.getHeaderView(0);
+ TextView navUsername = (TextView) headerView.findViewById(R.id.usernameTxt);
+ navUsername.setText(name);
+ TextView navEmail = (TextView) headerView.findViewById(R.id.emailTxt);
+ navEmail.setText(email);
+ ImageView ivProfile = (ImageView) headerView.findViewById(R.id.profilePic2);
+ if (MainService.isRunning && Objects.equals(Config.ui, "VNC")) {
+ MainActivityCommon.startvnc();
+ }
+ MainActivityCommon.execTimer();
+
+ InterstitialAd.load(this, "ca-app-pub-3568137780412047/7745973511", adRequest,
+ new InterstitialAdLoadCallback() {
+ @Override
+ public void onAdLoaded(@NonNull InterstitialAd interstitialAd) {
+ // The mInterstitialAd reference will be null until
+ // an ad is loaded.
+ mInterstitialAd = interstitialAd;
+ Log.i(TAG, "onAdLoaded");
+ }
+
+ @Override
+ public void onAdFailedToLoad(@NonNull LoadAdError loadAdError) {
+ // Handle the error
+ Log.d(TAG, loadAdError.toString());
+ mInterstitialAd = null;
+ }
+ });
+ if (mInterstitialAd != null && !loaded && !MainService.isRunning) {
+ mInterstitialAd.show(MainActivity.this);
+ loaded = true;
+ } else {
+ Log.d("TAG", "The interstitial ad wasn't ready yet.");
+ }
+ }
+
+
+}
diff --git a/app/src/main/java/com/vectras/vm/MainRoms/AdapterMainRoms.java b/app/src/main/java/com/vectras/vm/MainRoms/AdapterMainRoms.java
new file mode 100644
index 0000000..c3e68fc
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/MainRoms/AdapterMainRoms.java
@@ -0,0 +1,190 @@
+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.GridLayoutManager;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.vectras.qemu.Config;
+import com.vectras.qemu.MainActivityCommon;
+import com.vectras.qemu.MainService;
+import com.vectras.qemu.MainSettingsManager;
+import com.vectras.qemu.jni.StartVM;
+import com.vectras.qemu.utils.Machine;
+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.Objects;
+import java.util.Timer;
+import java.util.TimerTask;
+
+public class AdapterMainRoms extends RecyclerView.Adapter {
+
+ private Context context;
+ private LayoutInflater inflater;
+ public List data = Collections.emptyList();
+ int currentPos = 0;
+ private int mSelectedItem = -1;
+
+ // create constructor to innitilize context and data sent from MainActivity
+ public AdapterMainRoms(Context context, List data) {
+ this.context = context;
+ inflater = LayoutInflater.from(context);
+ this.data = data;
+ }
+
+ // Inflate the layout when viewholder created
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View view = inflater.inflate(R.layout.container_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) {
+ MainActivityCommon.setupNativeLibs();
+ Config.hda_path = current.itemPath;
+ Config.extra_params = current.itemExtra;
+ 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();
+
+ }
+ });
+ myHolder.cdRoms.setOnLongClickListener(new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ showDialog(current.itemName, current.itemPath, current.itemIcon);
+ return false;
+ }
+ });
+ }
+
+ private void showDialog(String title, String path, String pathIcon) {
+
+ 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) {
+ AlertDialog ad;
+ ad = new AlertDialog.Builder(MainActivity.activity, R.style.MainDialogTheme).create();
+ ad.setTitle("Remove " + title);
+ ad.setMessage("Are you sure?");
+ ad.setButton(Dialog.BUTTON_NEGATIVE, "REMOVE " + title, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ File file = new File(path);
+ file.delete();
+ File fileIcon = new File(pathIcon);
+ fileIcon.delete();
+
+ HomeFragment.mMainAdapter = new AdapterMainRoms(MainActivity.activity, HomeFragment.data);
+ HomeFragment.data.remove(currentPos);
+ HomeFragment.mRVMainRoms.setAdapter(HomeFragment.mMainAdapter);
+ HomeFragment.mRVMainRoms.setLayoutManager(new GridLayoutManager(MainActivity.activity, 2));
+ 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, title + " are removed successfully!");
+ return;
+ }
+ });
+ ad.setButton(Dialog.BUTTON_POSITIVE, "CANCEL", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ return;
+ }
+ });
+ ad.show();
+ dialog.dismiss();
+ }
+ });
+
+ 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);
+ }
+
+ }
+
+}
diff --git a/app/src/main/java/com/vectras/vm/MainRoms/DataMainRoms.java b/app/src/main/java/com/vectras/vm/MainRoms/DataMainRoms.java
new file mode 100644
index 0000000..75506cd
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/MainRoms/DataMainRoms.java
@@ -0,0 +1,7 @@
+package com.vectras.vm.MainRoms;
+public class DataMainRoms {
+ public String itemIcon;
+ public String itemName;
+ public String itemPath;
+ public String itemExtra;
+}
diff --git a/app/src/main/java/com/vectras/vm/PostActivity.java b/app/src/main/java/com/vectras/vm/PostActivity.java
new file mode 100644
index 0000000..fe68b3e
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/PostActivity.java
@@ -0,0 +1,150 @@
+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.google.android.gms.ads.AdRequest;
+import com.google.android.gms.ads.AdView;
+import com.google.android.gms.ads.LoadAdError;
+import com.google.android.gms.ads.MobileAds;
+import com.google.android.gms.ads.initialization.InitializationStatus;
+import com.google.android.gms.ads.initialization.OnInitializationCompleteListener;
+import com.google.android.gms.ads.interstitial.InterstitialAd;
+import com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback;
+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;
+
+ private InterstitialAd mInterstitialAd;
+ private String TAG = "PostActivity";
+
+ /**
+ * 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.toolbar);
+ setSupportActionBar(tb);
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setDisplayShowHomeEnabled(true);
+
+ AdView mAdView = findViewById(R.id.adView);
+ AdRequest adRequest = new AdRequest.Builder().build();
+ mAdView.loadAd(adRequest);
+ MobileAds.initialize(this, new OnInitializationCompleteListener() {
+ @Override
+ public void onInitializationComplete(InitializationStatus initializationStatus) {
+ }
+ });
+ InterstitialAd.load(this, "ca-app-pub-3568137780412047/7745973511", adRequest,
+ new InterstitialAdLoadCallback() {
+ @Override
+ public void onAdLoaded(@NonNull InterstitialAd interstitialAd) {
+ // The mInterstitialAd reference will be null until
+ // an ad is loaded.
+ mInterstitialAd = interstitialAd;
+ Log.i(TAG, "onAdLoaded");
+ }
+
+ @Override
+ public void onAdFailedToLoad(@NonNull LoadAdError loadAdError) {
+ // Handle the error
+ Log.d(TAG, loadAdError.toString());
+ mInterstitialAd = null;
+ }
+ });
+ postContent.setTextIsSelectable(true);
+
+ Glide.with(this).load(thumb).into(postThumb);
+ new Thread(new Runnable() {
+
+ public void run() {
+
+ BufferedReader reader = null;
+ final StringBuilder builder = new StringBuilder();
+
+ try {
+ // Create a URL for the desired page
+ URL url = new URL(content); //My text file location
+ //First open the connection
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ conn.setConnectTimeout(60000); // timing out in a minute
+
+ BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
+
+ //t=(TextView)findViewById(R.id.TextView1); // ideally do this in onCreate()
+ String str;
+ while ((str = in.readLine()) != null) {
+ builder.append(str);
+ }
+ in.close();
+ } catch (Exception e) {
+ postContent.setText("no internet connection");
+ UIUtils.toastLong(PostActivity.this, "check your internet connection");
+ Log.d("VECTRAS", e.toString());
+ }
+
+ //since we are in background thread, to post results we have to go back to ui thread. do the following for that
+
+ PostActivity.this.runOnUiThread(new Runnable() {
+ public void run() {
+ contentStr = builder.toString(); // My TextFile has 3 lines
+ postContent.setText(Html.fromHtml(contentStr));
+ }
+ });
+
+ }
+ }).start();
+ postDate.setText(date);
+ postTitle.setText(title);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(@NonNull MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ finish();
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public void onBackPressed() {
+ super.onBackPressed();
+ finish();
+ }
+
+}
diff --git a/app/src/main/java/com/vectras/vm/ProfileActivity.java b/app/src/main/java/com/vectras/vm/ProfileActivity.java
new file mode 100644
index 0000000..e28f63a
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/ProfileActivity.java
@@ -0,0 +1,195 @@
+package com.vectras.vm;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import com.bumptech.glide.Glide;
+import com.google.android.gms.tasks.Continuation;
+import com.google.android.gms.tasks.OnCompleteListener;
+import com.google.android.gms.tasks.OnSuccessListener;
+import com.google.android.gms.tasks.Task;
+import com.google.android.material.button.MaterialButton;
+import com.google.android.material.snackbar.Snackbar;
+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.google.firebase.database.DatabaseReference;
+import com.google.firebase.database.FirebaseDatabase;
+import com.google.firebase.storage.FirebaseStorage;
+import com.google.firebase.storage.StorageReference;
+import com.google.firebase.storage.UploadTask;
+import com.vectras.qemu.MainService;
+import com.vectras.vm.ui.login.SignupActivity;
+
+import java.io.ByteArrayOutputStream;
+
+public class ProfileActivity extends AppCompatActivity {
+
+ private ProfileActivity activity;
+ public TextInputEditText profileUsername;
+ public MaterialButton saveBtn;
+ FirebaseAuth mAuth;
+ FirebaseUser mCurrentUser;
+ private DatabaseReference newUser;
+ public Uri profileUri;
+
+ public ImageView profilePic;
+
+ public ProgressBar loadingPb;
+
+ private Uri imgUri = null;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_profile);
+ activity = this;
+ Toolbar toolbar = findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setDisplayShowHomeEnabled(true);
+ mAuth = FirebaseAuth.getInstance();
+ mCurrentUser = mAuth.getCurrentUser();
+ newUser = FirebaseDatabase.getInstance().getReference().child(mCurrentUser.getUid());
+ newUser.child("email").setValue(mCurrentUser.getEmail());
+ String name = mCurrentUser.getDisplayName();
+ String email = mCurrentUser.getEmail();
+ Uri picture = mCurrentUser.getPhotoUrl();
+ profilePic = findViewById(R.id.profilePic);
+ profileUsername = findViewById(R.id.profileName);
+ saveBtn = findViewById(R.id.saveBtn);
+ loadingPb = findViewById(R.id.loadingPb);
+ //Glide.with(activity).load(picture).error(R.drawable.person_24).into(profilePic);
+ profileUsername.setText(name);
+ if (profileUsername.getText().toString().equals(mCurrentUser.getDisplayName())) {
+ saveBtn.setEnabled(false);
+ } else {
+ saveBtn.setEnabled(true);
+ }
+ 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 (profileUsername.getText().toString().trim().length() > 0) {
+ if (profileUsername.getText().toString().equals(mCurrentUser.getDisplayName())) {
+ saveBtn.setEnabled(false);
+ } else {
+ saveBtn.setEnabled(true);
+ }
+ } else {
+ saveBtn.setEnabled(false);
+ profileUsername.setError("username can't be empty!");
+ }
+ }
+ };
+ profilePic.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent i = new Intent(Intent.ACTION_PICK);
+ i.setType("image/*");
+ startActivityForResult(i, 1009);
+ }
+ });
+ profileUsername.addTextChangedListener(afterTextChangedListener);
+ saveBtn.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View view) {
+ loadingPb.setVisibility(View.VISIBLE);
+
+ UserProfileChangeRequest profileUpdates = new UserProfileChangeRequest.Builder()
+ .setDisplayName(String.valueOf(profileUsername.getText()))
+ .setPhotoUri(imgUri)
+ .build();
+
+ mCurrentUser.updateProfile(profileUpdates)
+ .addOnCompleteListener(new OnCompleteListener() {
+ @Override
+ public void onComplete(@NonNull Task task) {
+ if (task.isSuccessful()) {
+ mCurrentUser.sendEmailVerification()
+ .addOnCompleteListener(new OnCompleteListener() {
+ @Override
+ public void onComplete(@NonNull Task task) {
+ saveBtn.setEnabled(false);
+ loadingPb.setVisibility(View.GONE);
+ View rootView = findViewById(R.id.main_layout);
+ Snackbar.make(rootView, "Updated Successfully!", 3000).show();
+ }
+ });
+
+ }
+ }
+ });
+
+ }
+ });
+
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (requestCode == 1009 && resultCode == RESULT_OK) {
+ imgUri = data.getData();
+ Glide.with(activity).load(imgUri).into(profilePic);
+ saveBtn.setEnabled(true);
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(@NonNull MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ finish();
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ public void onBackPressed() {
+ finish();
+ super.onBackPressed();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (profileUsername.getText().toString().equals(mCurrentUser.getDisplayName())) {
+ saveBtn.setEnabled(false);
+ } else {
+ saveBtn.setEnabled(true);
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ if (profileUsername.getText().toString().equals(mCurrentUser.getDisplayName())) {
+ saveBtn.setEnabled(false);
+ } else {
+ saveBtn.setEnabled(true);
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/vectras/vm/Roms/AdapterRoms.java b/app/src/main/java/com/vectras/vm/Roms/AdapterRoms.java
new file mode 100644
index 0000000..854ae2d
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/Roms/AdapterRoms.java
@@ -0,0 +1,128 @@
+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.LinearLayoutManager;
+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.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+import androidx.appcompat.app.AlertDialog;
+
+import android.content.DialogInterface;
+import android.app.Dialog;
+
+public class AdapterRoms extends RecyclerView.Adapter {
+
+ private Context context;
+ private LayoutInflater inflater;
+ static List data = Collections.emptyList();
+ static List filteredData = Collections.emptyList();
+ DataRoms current;
+ int currentPos = 0;
+ private int mSelectedItem = -1;
+
+ // create constructor to innitilize context and data sent from MainActivity
+ public AdapterRoms(Context context, List data) {
+ this.context = context;
+ inflater = LayoutInflater.from(context);
+ AdapterRoms.data = data;
+ AdapterRoms.filteredData = 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);
+ }
+
+ }
+
+}
diff --git a/app/src/main/java/com/vectras/vm/Roms/DataRoms.java b/app/src/main/java/com/vectras/vm/Roms/DataRoms.java
new file mode 100644
index 0000000..e36c36e
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/Roms/DataRoms.java
@@ -0,0 +1,14 @@
+package com.vectras.vm.Roms;
+
+public class DataRoms {
+
+ public String itemIcon;
+ public String itemName;
+ public String itemArch;
+ public String itemKernel;
+ public Boolean itemAvail;
+ public String itemSize;
+ public String itemUrl;
+ public String itemPath;
+ public String itemExtra;
+}
diff --git a/app/src/main/java/com/vectras/vm/RomsManagerActivity.java b/app/src/main/java/com/vectras/vm/RomsManagerActivity.java
new file mode 100644
index 0000000..044fa39
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/RomsManagerActivity.java
@@ -0,0 +1,646 @@
+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.appcompat.widget.Toolbar;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.google.android.material.button.MaterialButtonToggleGroup;
+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;
+ public static RecyclerView mRVRoms;
+ public static AdapterRoms mAdapter;
+ public static String Data;
+ public static List 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;
+
+ public MaterialButtonToggleGroup filterToggle;
+ public MaterialButton windowsToggle;
+ public MaterialButton linuxToggle;
+ public MaterialButton appleToggle;
+ public MaterialButton androidToggle;
+ public MaterialButton otherToggle;
+
+ /**
+ * Called when the activity is first created.
+ */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+
+ super.onCreate(savedInstanceState);
+ activity = this;
+ setContentView(R.layout.activity_roms_manager);
+ filterToggle = findViewById(R.id.filterToggle);
+ windowsToggle = findViewById(R.id.windowsToggle);
+ linuxToggle = findViewById(R.id.linuxToggle);
+ appleToggle = findViewById(R.id.appleToggle);
+ androidToggle = findViewById(R.id.androidToggle);
+ otherToggle = findViewById(R.id.otherToggle);
+ mRVRoms = findViewById(R.id.romsRv);
+ filterToggle.addOnButtonCheckedListener(new MaterialButtonToggleGroup.OnButtonCheckedListener() {
+ @Override
+ public void onButtonChecked(MaterialButtonToggleGroup group, int checkedId, boolean isChecked) {
+ if (checkedId == R.id.windowsToggle) {
+ if (isChecked)
+ filter = "windows";
+ else
+ filter = null;
+ } else if (checkedId == R.id.linuxToggle) {
+ if (isChecked)
+ filter = "linux";
+ else
+ filter = null;
+ } else if (checkedId == R.id.appleToggle) {
+ if (isChecked)
+ filter = "apple";
+ else
+ filter = null;
+ } else if (checkedId == R.id.androidToggle) {
+ if (isChecked)
+ filter = "android";
+ else
+ filter = null;
+ } else if (checkedId == R.id.otherToggle) {
+ if (isChecked)
+ filter = "other";
+ else
+ filter = null;
+ }
+ new RomsManagerActivity.AsyncLogin().execute();
+ }
+ });
+
+ Toolbar toolbar = findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setDisplayShowHomeEnabled(true);
+
+ 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);
+ UIUtils.toastLong(activity, "no internet connection "+e.toString());
+ }
+
+ //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();
+ }
+
+ public static String filter = null;
+
+ public static class AsyncLogin extends AsyncTask {
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+
+ //this method will be running on UI thread
+
+ }
+
+ @Override
+ protected String doInBackground(String... params) {
+ HttpsURLConnection con = null;
+ try {
+ URL u = new URL(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
+ 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.itemKernel = json_data.getString("rom_kernel");
+ romsData.itemExtra = json_data.getString("rom_extra");
+ if (filter != null) {
+ if (romsData.itemKernel.toLowerCase().contains(filter.toLowerCase())) {
+ data.add(romsData);
+ }
+ } else {
+ data.add(romsData);
+ }
+ }
+
+ // Setup and Handover data to recyclerview
+
+ } catch (JSONException e) {
+ UIUtils.toastLong(activity, e.toString());
+ }
+ mRVRoms = (RecyclerView) activity.findViewById(R.id.romsRv);
+ mAdapter = new AdapterRoms(activity, data);
+ mRVRoms.setAdapter(mAdapter);
+ mRVRoms.setLayoutManager(new LinearLayoutManager(activity, LinearLayoutManager.VERTICAL, false));
+
+ }
+
+ }
+
+ @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 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 {
+
+ @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();
+ }
+
+}
diff --git a/app/src/main/java/com/vectras/vm/SplashActivity.java b/app/src/main/java/com/vectras/vm/SplashActivity.java
new file mode 100644
index 0000000..dfa9f54
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/SplashActivity.java
@@ -0,0 +1,85 @@
+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.MainSettingsManager;
+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;
+
+ MainSettingsManager.setOrientationSetting(activity, 1);
+ }
+
+ public static final String CREDENTIAL_SHARED_PREF = "settings_prefs";
+
+ @Override
+ public void run() {
+ SharedPreferences prefs = getSharedPreferences(CREDENTIAL_SHARED_PREF, Context.MODE_PRIVATE);
+
+ boolean isAccessed = prefs.getBoolean("isFirstLaunch", false);
+ if (!isAccessed) {
+ startActivity(new Intent(this, LoginActivity.class));
+ } else {
+ if (FirebaseAuth.getInstance().getCurrentUser() != null)
+ startActivity(new Intent(this, MainActivity.class));
+ else
+ startActivity(new Intent(this, LoginActivity.class));
+ }
+ finish();
+ }
+}
diff --git a/app/src/main/java/com/vectras/vm/Store/AdapterStore.java b/app/src/main/java/com/vectras/vm/Store/AdapterStore.java
new file mode 100644
index 0000000..a75707d
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/Store/AdapterStore.java
@@ -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 {
+
+ private Context context;
+ private LayoutInflater inflater;
+ List data = Collections.emptyList();
+ DataStore current;
+ int currentPos = 0;
+
+ // create constructor to innitilize context and data sent from MainActivity
+ public AdapterStore(Context context, List data) {
+ this.context = context;
+ inflater = LayoutInflater.from(context);
+ this.data = data;
+ }
+
+ // Inflate the layout when viewholder created
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View view = inflater.inflate(R.layout.container_store, parent, false);
+ MyHolder holder = new MyHolder(view);
+ return holder;
+ }
+
+ // Bind data
+ @Override
+ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+
+ // Get current position of item in recyclerview to bind data and assign values from list
+ MyHolder myHolder = (MyHolder) holder;
+ final DataStore current = data.get(position);
+ myHolder.textName.setText(current.itemName);
+ myHolder.textSize.setText("Size: " + current.itemSize);
+ Glide.with(StoreActivity.activity).load(current.itemIcon).into(myHolder.ivIcon);
+ Animation animation;
+ animation = AnimationUtils.loadAnimation(MainActivity.activity, android.R.anim.slide_in_left);
+ animation.setDuration(300);
+
+ myHolder.cdItem.startAnimation(animation);
+ animation = null;
+ myHolder.cdItem.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View view) {
+ StoreItemActivity.name = current.itemName;
+ StoreItemActivity.icon = current.itemIcon;
+ StoreItemActivity.size = current.itemSize;
+ StoreItemActivity.desc = current.itemData;
+ StoreItemActivity.link = current.itemLink;
+ StoreItemActivity.prvMain = current.itemPreviewMain;
+ StoreItemActivity.prv1 = current.itemPreview1;
+ StoreItemActivity.prv2 = current.itemPreview2;
+ StoreActivity.activity.startActivity(new Intent(StoreActivity.activity, StoreItemActivity.class));
+ }
+ });
+ }
+
+ // return total item from List
+ @Override
+ public int getItemCount() {
+ return data.size();
+ }
+
+ class MyHolder extends RecyclerView.ViewHolder {
+
+ CardView cdItem;
+ TextView textName;
+ ImageView ivIcon;
+ TextView textSize;
+
+ // create constructor to get widget reference
+ public MyHolder(View itemView) {
+ super(itemView);
+ cdItem = (CardView) itemView.findViewById(R.id.cdItem);
+ textName = (TextView) itemView.findViewById(R.id.textName);
+ ivIcon = (ImageView) itemView.findViewById(R.id.ivIcon);
+ textSize = (TextView) itemView.findViewById(R.id.textSize);
+ }
+
+ }
+
+}
diff --git a/app/src/main/java/com/vectras/vm/Store/DataStore.java b/app/src/main/java/com/vectras/vm/Store/DataStore.java
new file mode 100644
index 0000000..3e623f2
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/Store/DataStore.java
@@ -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;
+}
diff --git a/app/src/main/java/com/vectras/vm/StoreActivity.java b/app/src/main/java/com/vectras/vm/StoreActivity.java
new file mode 100644
index 0000000..02cd874
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/StoreActivity.java
@@ -0,0 +1,204 @@
+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.google.android.gms.ads.AdRequest;
+import com.google.android.gms.ads.AdView;
+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;
+
+ AdView mAdView = findViewById(R.id.adView);
+ AdRequest adRequest = new AdRequest.Builder().build();
+ mAdView.loadAd(adRequest);
+ noConnectionLayout = findViewById(R.id.noConnectionLayout);
+ mRVStore = findViewById(R.id.storeRv);
+
+ if (checkConnection(activity)) {
+ new StoreActivity.AsyncLogin().execute();
+ noConnectionLayout.setVisibility(View.GONE);
+ //mRVBlog.setVisibility(View.VISIBLE);
+ } else {
+ noConnectionLayout.setVisibility(View.VISIBLE);
+ //mRVBlog.setVisibility(View.GONE);
+ }
+
+ pullToRefresh = findViewById(R.id.refreshLayout);
+ pullToRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
+ @Override
+ public void onRefresh() {
+ if (checkConnection(activity)) {
+ new StoreActivity.AsyncLogin().execute();
+ } else {
+ noConnectionLayout.setVisibility(View.VISIBLE);
+ pullToRefresh.setRefreshing(false);
+ }
+ }
+ });
+ }
+ public boolean checkConnection(Context context) {
+ final ConnectivityManager connMgr = (ConnectivityManager) context
+ .getSystemService(Context.CONNECTIVITY_SERVICE);
+
+ if (connMgr != null) {
+ NetworkInfo activeNetworkInfo = connMgr.getActiveNetworkInfo();
+
+ if (activeNetworkInfo != null) { // connected to the internet
+ // connected to the mobile provider's data plan
+ if (activeNetworkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
+ // connected to wifi
+ return true;
+ } else
+ return activeNetworkInfo.getType() == ConnectivityManager.TYPE_MOBILE;
+ }
+ }
+ return false;
+ }
+
+ private class AsyncLogin extends AsyncTask {
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+
+ //this method will be running on UI thread
+
+ }
+
+ @Override
+ protected String doInBackground(String... params) {
+ HttpsURLConnection con = null;
+ try {
+ URL u = new URL(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 data = new ArrayList<>();
+
+ try {
+
+ JSONArray jArray = new JSONArray(Data);
+
+ // Extract data from json and store into ArrayList as class objects
+ for (int i = 0; i < jArray.length(); i++) {
+ JSONObject json_data = jArray.getJSONObject(i);
+ DataStore storeData = new DataStore();
+ storeData.itemName = json_data.getString("item_name");
+ storeData.itemIcon = json_data.getString("item_icon");
+ storeData.itemData = json_data.getString("item_data");
+ storeData.itemSize = json_data.getString("item_size");
+ storeData.itemLink = json_data.getString("item_link");
+ storeData.itemPreviewMain = json_data.getString("item_preview_main");
+ storeData.itemPreview1 = json_data.getString("item_preview_1");
+ storeData.itemPreview2 = json_data.getString("item_preview_2");
+ data.add(storeData);
+ }
+
+ // Setup and Handover data to recyclerview
+
+ } catch (JSONException e) {
+ Toast.makeText(activity, e.toString(), Toast.LENGTH_LONG).show();
+ }
+ mRVStore = (RecyclerView) findViewById(R.id.storeRv);
+ mAdapter = new AdapterStore(activity, data);
+ mRVStore.setAdapter(mAdapter);
+ mRVStore.setLayoutManager(new LinearLayoutManager(activity));
+
+ }
+
+ }
+ @Override
+ public boolean onOptionsItemSelected(@NonNull MenuItem item) {
+ if(item.getItemId()== android.R.id.home){
+ finish();
+ }
+ return super.onOptionsItemSelected(item);
+ }
+}
diff --git a/app/src/main/java/com/vectras/vm/StoreItemActivity.java b/app/src/main/java/com/vectras/vm/StoreItemActivity.java
new file mode 100644
index 0000000..215ecc4
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/StoreItemActivity.java
@@ -0,0 +1,304 @@
+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.google.android.gms.ads.AdRequest;
+import com.google.android.gms.ads.AdView;
+import com.google.android.gms.ads.LoadAdError;
+import com.google.android.gms.ads.MobileAds;
+import com.google.android.gms.ads.initialization.InitializationStatus;
+import com.google.android.gms.ads.initialization.OnInitializationCompleteListener;
+import com.google.android.gms.ads.interstitial.InterstitialAd;
+import com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback;
+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;
+
+ private InterstitialAd mInterstitialAd;
+
+ @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);
+
+ AdView mAdView = findViewById(R.id.adView);
+ AdRequest adRequest = new AdRequest.Builder().build();
+ mAdView.loadAd(adRequest);
+
+ MobileAds.initialize(this, new OnInitializationCompleteListener() {
+ @Override
+ public void onInitializationComplete(InitializationStatus initializationStatus) {}
+ });
+ InterstitialAd.load(this,"ca-app-pub-3568137780412047/4892595373", adRequest,
+ new InterstitialAdLoadCallback() {
+ @Override
+ public void onAdLoaded(@NonNull InterstitialAd interstitialAd) {
+ // The mInterstitialAd reference will be null until
+ // an ad is loaded.
+ mInterstitialAd = interstitialAd;
+ Log.i(TAG, "onAdLoaded");
+ }
+
+ @Override
+ public void onAdFailedToLoad(@NonNull LoadAdError loadAdError) {
+ // Handle the error
+ Log.d(TAG, loadAdError.toString());
+ mInterstitialAd = null;
+ }
+ });
+ if (mInterstitialAd != null) {
+ mInterstitialAd.show(StoreItemActivity.this);
+ } else {
+ Log.d("TAG", "The interstitial ad wasn't ready yet.");
+ }
+ dBtn.setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View view) {
+
+ InterstitialAd.load(activity,"ca-app-pub-3568137780412047/7937545204", adRequest,
+ new InterstitialAdLoadCallback() {
+ @Override
+ public void onAdLoaded(@NonNull InterstitialAd interstitialAd) {
+ // The mInterstitialAd reference will be null until
+ // an ad is loaded.
+ mInterstitialAd = interstitialAd;
+ Log.i(TAG, "onAdLoaded");
+ }
+
+ @Override
+ public void onAdFailedToLoad(@NonNull LoadAdError loadAdError) {
+ // Handle the error
+ Log.d(TAG, loadAdError.toString());
+ mInterstitialAd = null;
+ }
+ });
+ startDownload();
+ }
+ });
+ itemName.setText(name);
+ itemSize.setText(size);
+
+ Glide.with(this).load(icon).into(itemIcon);
+ Glide.with(this).load(prvMain).into(itemPrvMain);
+ Glide.with(this).load(prv1).into(itemPrv1);
+ Glide.with(this).load(prv2).into(itemPrv2);
+ new Thread(new Runnable() {
+
+ public void run() {
+
+ BufferedReader reader = null;
+ final StringBuilder builder = new StringBuilder();
+
+ try {
+ // Create a URL for the desired page
+ URL url = new URL(desc); //My text file location
+ //First open the connection
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ conn.setConnectTimeout(60000); // timing out in a minute
+
+ BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
+
+ //t=(TextView)findViewById(R.id.TextView1); // ideally do this in onCreate()
+ String str;
+ while ((str = in.readLine()) != null) {
+ builder.append(str);
+ }
+ in.close();
+ } catch (Exception e) {
+ itemDesc.setText("no internet connection");
+ UIUtils.toastLong(StoreItemActivity.this, "check your internet connection");
+ Log.d("VECTRAS", e.toString());
+ }
+
+ //since we are in background thread, to post results we have to go back to ui thread. do the following for that
+
+ StoreItemActivity.this.runOnUiThread(new Runnable() {
+ public void run() {
+ descStr = builder.toString(); // My TextFile has 3 lines
+ itemDesc.setText(Html.fromHtml(descStr));
+ }
+ });
+
+ }
+ }).start();
+ itemPrvMain.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ ImagePrvActivity.linkIv = prvMain;
+ startActivity(new Intent(activity, ImagePrvActivity.class));
+ }
+ });
+ itemPrv1.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ ImagePrvActivity.linkIv = prv1;
+ startActivity(new Intent(activity, ImagePrvActivity.class));
+ }
+ });
+ itemPrv2.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ ImagePrvActivity.linkIv = prv2;
+ startActivity(new Intent(activity, ImagePrvActivity.class));
+ }
+ });
+ }
+
+ public static final int DIALOG_DOWNLOAD_PROGRESS = 0;
+ private ProgressDialog mProgressDialog;
+
+ private void startDownload() {
+ String url = link;
+ new DownloadFileAsync().execute(url);
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ switch (id) {
+ case DIALOG_DOWNLOAD_PROGRESS:
+ mProgressDialog = new ProgressDialog(this, R.style.MainDialogTheme);
+ mProgressDialog.setMessage("Downloading file..");
+ mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
+ mProgressDialog.setCancelable(false);
+ mProgressDialog.show();
+ return mProgressDialog;
+ default:
+ return null;
+ }
+ }
+
+ class DownloadFileAsync extends AsyncTask {
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ showDialog(DIALOG_DOWNLOAD_PROGRESS);
+ }
+
+ @Override
+ protected String doInBackground(String... aurl) {
+ int count;
+
+ try {
+ URL url = new URL(aurl[0]);
+ URLConnection conexion = url.openConnection();
+ conexion.connect();
+
+ int lenghtOfFile = conexion.getContentLength();
+ Log.d(TAG, "Lenght of file: " + lenghtOfFile);
+ String fileName = URLUtil.guessFileName(link,null,null);
+ InputStream input = new BufferedInputStream(url.openStream());
+ OutputStream output = new FileOutputStream(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);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/vectras/vm/VectrasApp.java b/app/src/main/java/com/vectras/vm/VectrasApp.java
new file mode 100644
index 0000000..8ce9a98
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/VectrasApp.java
@@ -0,0 +1,379 @@
+package com.vectras.vm;
+
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
+
+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 androidx.appcompat.app.AppCompatDelegate;
+
+import com.google.android.material.color.DynamicColors;
+import com.vectras.qemu.MainSettingsManager;
+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());
+ private static Context app;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ CrashHandler.getInstance().registerGlobal(this);
+ CrashHandler.getInstance().registerPart(this);
+ try {
+ Class.forName("android.os.AsyncTask");
+ } catch (Throwable ignore) {
+ // ignored
+ }
+ app = getApplicationContext();
+ setModeNight(this);
+ DynamicColors.applyToActivitiesIfAvailable(this);
+ }
+
+ private void setModeNight(Context context) {
+ if (MainSettingsManager.getModeNight(context)) {
+ AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
+ setTheme(R.style.AppTheme);
+ } else {
+ AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
+ setTheme(R.style.AppTheme);
+ }
+
+ }
+
+ public static Context getApp() {
+ return app;
+ }
+
+ public static void write(InputStream input, OutputStream output) throws IOException {
+ byte[] buf = new byte[1024 * 8];
+ int len;
+ while ((len = input.read(buf)) != -1) {
+ output.write(buf, 0, len);
+ }
+ }
+
+ public static void write(File file, byte[] data) throws IOException {
+ File parent = file.getParentFile();
+ if (parent != null && !parent.exists())
+ parent.mkdirs();
+
+ ByteArrayInputStream input = new ByteArrayInputStream(data);
+ FileOutputStream output = new FileOutputStream(file);
+ try {
+ write(input, output);
+ } finally {
+ closeIO(input, output);
+ }
+ }
+
+ public static String toString(InputStream input) throws IOException {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ write(input, output);
+ try {
+ return output.toString("UTF-8");
+ } finally {
+ closeIO(input, output);
+ }
+ }
+
+ public static void closeIO(Closeable... closeables) {
+ for (Closeable closeable : closeables) {
+ try {
+ if (closeable != null)
+ closeable.close();
+ } catch (IOException ignored) {
+ }
+ }
+ }
+
+ public static class CrashHandler {
+
+ public static final UncaughtExceptionHandler DEFAULT_UNCAUGHT_EXCEPTION_HANDLER = Thread
+ .getDefaultUncaughtExceptionHandler();
+
+ private static CrashHandler sInstance;
+
+ private PartCrashHandler mPartCrashHandler;
+
+ public static CrashHandler getInstance() {
+ if (sInstance == null) {
+ sInstance = new CrashHandler();
+ }
+ return sInstance;
+ }
+
+ public void registerGlobal(Context context) {
+ registerGlobal(context, null);
+ }
+
+ public void registerGlobal(Context context, String crashDir) {
+ Thread.setDefaultUncaughtExceptionHandler(
+ new UncaughtExceptionHandlerImpl(context.getApplicationContext(), crashDir));
+ }
+
+ public void unregister() {
+ Thread.setDefaultUncaughtExceptionHandler(DEFAULT_UNCAUGHT_EXCEPTION_HANDLER);
+ }
+
+ public void registerPart(Context context) {
+ unregisterPart(context);
+ mPartCrashHandler = new PartCrashHandler(context.getApplicationContext());
+ MAIN_HANDLER.postAtFrontOfQueue(mPartCrashHandler);
+ }
+
+ public void unregisterPart(Context context) {
+ if (mPartCrashHandler != null) {
+ mPartCrashHandler.isRunning.set(false);
+ mPartCrashHandler = null;
+ }
+ }
+
+ private static class PartCrashHandler implements Runnable {
+
+ private final Context mContext;
+
+ public AtomicBoolean isRunning = new AtomicBoolean(true);
+
+ public PartCrashHandler(Context context) {
+ this.mContext = context;
+ }
+
+ @Override
+ public void run() {
+ while (isRunning.get()) {
+ try {
+ Looper.loop();
+ } catch (final Throwable e) {
+ e.printStackTrace();
+ if (isRunning.get()) {
+ MAIN_HANDLER.post(new Runnable() {
+
+ @Override
+ public void run() {
+ VectrasStatus.logError("[E] "+e.getMessage()+"");
+ }
+ });
+ } else {
+ if (e instanceof RuntimeException) {
+ throw (RuntimeException) e;
+ } else {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private static class UncaughtExceptionHandlerImpl implements UncaughtExceptionHandler {
+
+ private static DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy_MM_dd-HH_mm_ss");
+
+ private final Context mContext;
+
+ private final File mCrashDir;
+
+ public UncaughtExceptionHandlerImpl(Context context, String crashDir) {
+ this.mContext = context;
+ this.mCrashDir = TextUtils.isEmpty(crashDir) ? new File(mContext.getExternalCacheDir(), "crash")
+ : new File(crashDir);
+ }
+
+ @Override
+ public void uncaughtException(Thread thread, Throwable throwable) {
+ try {
+
+ String log = buildLog(throwable);
+ writeLog(log);
+
+ try {
+ Intent intent = new Intent(mContext, CrashActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(Intent.EXTRA_TEXT, log);
+ mContext.startActivity(intent);
+ } catch (Throwable e) {
+ e.printStackTrace();
+ writeLog(e.toString());
+ }
+
+ throwable.printStackTrace();
+ android.os.Process.killProcess(android.os.Process.myPid());
+ System.exit(0);
+
+ } catch (Throwable e) {
+ if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null)
+ DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(thread, throwable);
+ }
+ }
+
+ private String buildLog(Throwable throwable) {
+ String time = DATE_FORMAT.format(new Date());
+
+ String versionName = "unknown";
+ long versionCode = 0;
+ try {
+ PackageInfo packageInfo = mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0);
+ versionName = packageInfo.versionName;
+ versionCode = Build.VERSION.SDK_INT >= 28 ? packageInfo.getLongVersionCode()
+ : packageInfo.versionCode;
+ } catch (Throwable ignored) {
+ }
+
+ LinkedHashMap head = new LinkedHashMap();
+ head.put("Time Of Crash", time);
+ head.put("Device", String.format("%s, %s", Build.MANUFACTURER, Build.MODEL));
+ head.put("Android Version", String.format("%s (%d)", Build.VERSION.RELEASE, Build.VERSION.SDK_INT));
+ head.put("App Version", String.format("%s (%d)", versionName, versionCode));
+ head.put("Kernel", getKernel());
+ head.put("Support Abis",
+ Build.VERSION.SDK_INT >= 21 && Build.SUPPORTED_ABIS != null
+ ? Arrays.toString(Build.SUPPORTED_ABIS)
+ : "unknown");
+ head.put("Fingerprint", Build.FINGERPRINT);
+
+ StringBuilder builder = new StringBuilder();
+
+ for (String key : head.keySet()) {
+ if (builder.length() != 0)
+ builder.append("\n");
+ builder.append(key);
+ builder.append(" : ");
+ builder.append(head.get(key));
+ }
+
+ builder.append("\n\n");
+ builder.append(Log.getStackTraceString(throwable));
+
+ return builder.toString();
+ }
+
+ private void writeLog(String log) {
+ String time = DATE_FORMAT.format(new Date());
+ File file = new File(mCrashDir, "crash_" + time + ".txt");
+ try {
+ write(file, log.getBytes("UTF-8"));
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ }
+
+ private static String getKernel() {
+ try {
+ return VectrasApp.toString(new FileInputStream("/proc/version")).trim();
+ } catch (Throwable e) {
+ return e.getMessage();
+ }
+ }
+ }
+ }
+
+ public static final class CrashActivity extends Activity {
+
+ private String mLog;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setTheme(android.R.style.Theme_DeviceDefault);
+ setTitle("App Crash");
+
+ mLog = getIntent().getStringExtra(Intent.EXTRA_TEXT);
+
+ ScrollView contentView = new ScrollView(this);
+ contentView.setFillViewport(true);
+
+ HorizontalScrollView horizontalScrollView = new HorizontalScrollView(this);
+
+ TextView textView = new TextView(this);
+ int padding = dp2px(16);
+ textView.setPadding(padding, padding, padding, padding);
+ textView.setText(mLog);
+ textView.setTextIsSelectable(true);
+ textView.setTypeface(Typeface.DEFAULT);
+ textView.setLinksClickable(true);
+
+ horizontalScrollView.addView(textView);
+ contentView.addView(horizontalScrollView, ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT);
+
+ setContentView(contentView);
+ }
+
+ private void restart() {
+ Intent intent = getPackageManager().getLaunchIntentForPackage(getPackageName());
+ if (intent != null) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+ }
+ finish();
+ android.os.Process.killProcess(android.os.Process.myPid());
+ System.exit(0);
+ }
+
+ private static int dp2px(float dpValue) {
+ final float scale = Resources.getSystem().getDisplayMetrics().density;
+ return (int) (dpValue * scale + 0.5f);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ menu.add(0, android.R.id.copy, 0, android.R.string.copy).setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.copy:
+ ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
+ cm.setPrimaryClip(ClipData.newPlainText(getPackageName(), mLog));
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public void onBackPressed() {
+ restart();
+ }
+ }
+}
diff --git a/app/src/main/java/com/vectras/vm/WidgetProvider.java b/app/src/main/java/com/vectras/vm/WidgetProvider.java
new file mode 100644
index 0000000..4fe9895
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/WidgetProvider.java
@@ -0,0 +1,54 @@
+package com.vectras.vm;
+
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.widget.ListView;
+import android.widget.RemoteViews;
+import android.widget.Toast;
+
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.vectras.vm.MainRoms.AdapterMainRoms;
+import com.vectras.vm.MainRoms.DataMainRoms;
+import com.vectras.vm.utils.FileUtils;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+public class WidgetProvider extends AppWidgetProvider {
+
+ public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
+ // Perform this loop procedure for each widget that belongs to this
+ // provider.
+ for (int i=0; i < appWidgetIds.length; i++) {
+ int appWidgetId = appWidgetIds[i];
+ // Create an Intent to launch ExampleActivity
+ Intent intent = new Intent(context, MainActivity.class);
+ PendingIntent pendingIntent = PendingIntent.getActivity(
+ /* context = */ context,
+ /* requestCode = */ 0,
+ /* intent = */ intent,
+ /* flags = */ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
+ );
+
+ // Get the layout for the widget and attach an onClick listener to
+ // the button.
+ RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widgetlayout);
+ views.setOnClickPendingIntent(R.id.button, pendingIntent);
+ views.setRemoteAdapter(R.id.mRVMainRoms, intent);
+ // Tell the AppWidgetManager to perform an update on the current app
+ // widget.
+ appWidgetManager.updateAppWidget(appWidgetId, views);
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/vectras/vm/adapter/LogsAdapter.java b/app/src/main/java/com/vectras/vm/adapter/LogsAdapter.java
new file mode 100644
index 0000000..95d1607
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/adapter/LogsAdapter.java
@@ -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
+ implements VectrasStatus.LogListener ,Handler.Callback,
+ View.OnTouchListener
+{
+ private static final int MESSAGE_NEWLOG = 0;
+
+ private static final int MESSAGE_CLEARLOG = 1;
+
+ private static final int MESSAGE_NEWTS = 2;
+ private static final int MESSAGE_NEWLOGLEVEL = 3;
+
+ public static final int TIME_FORMAT_NONE = 0;
+ public static final int TIME_FORMAT_SHORT = 1;
+ public static final int TIME_FORMAT_ISO = 2;
+ private static final int MAX_STORED_LOG_ENTRIES = 1000;
+
+ private Vector allEntries = new Vector<>();
+
+ private Vector currentLevelEntries = new Vector();
+
+ private Handler mHandler;
+ private Context mContext;
+ private OnItemClickListener itemClickListener;
+ private LinearLayoutManager mLinearLayoutManager;
+
+ private Vector observers = new Vector<>();
+
+ private int mTimeFormat = -100;
+ private int mLogLevel = 3;
+ private boolean mLockAutoScroll = false;
+
+
+ /**
+ * Interfaces
+ */
+
+ public interface OnItemClickListener
+ {
+ void onItemClick(View view, int position, String logText);
+ void onItemLongClick(View view, int position, String logText);
+ }
+
+ public class logViewHolder extends RecyclerView.ViewHolder
+ {
+ TextView textLog;
+
+ logViewHolder(View itemView)
+ {
+ super(itemView);
+
+ this.textLog = itemView.findViewById(R.id.textLog);
+ }
+ }
+
+
+ public LogsAdapter(LinearLayoutManager layoutManager,
+ Context context)
+ {
+ this.mContext = context;
+ this.mLinearLayoutManager = layoutManager;
+
+ setLogLevel(VectrasStatus.LogLevel.DEBUG.getInt());
+
+ initLogBuffer();
+ if (mHandler == null)
+ {
+ mHandler = new Handler(this);
+ }
+
+ VectrasStatus.addLogListener(this);
+ }
+
+ public void setOnItemClickListener(OnItemClickListener listener) {
+ this.itemClickListener = listener;
+ }
+
+ private void initLogBuffer()
+ {
+ allEntries.clear();
+ Collections.addAll(allEntries, VectrasStatus.getlogbuffer());
+ initCurrentMessages();
+ }
+
+ private void initCurrentMessages()
+ {
+ currentLevelEntries.clear();
+ for (LogItem li : allEntries)
+ {
+ if (li.getLogLevel().getInt() <= mLogLevel)
+ currentLevelEntries.add(li);
+ }
+ }
+
+ @Override
+ public logViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
+ {
+ Context context = parent.getContext();
+ LayoutInflater inflater = LayoutInflater.from(context);
+
+ View logView = inflater.inflate(R.layout.list_item_log,
+ parent, false);
+ logView.setOnTouchListener(this);
+
+ return new logViewHolder(logView);
+ }
+
+ @Override
+ public void onBindViewHolder(final logViewHolder viewHolder,
+ final int position)
+ {
+ final String text;
+
+ try
+ {
+ LogItem logItem = currentLevelEntries.get(position);
+ String msg = logItem.getString(mContext);
+ String time = getTime(logItem, mTimeFormat);
+ text = (!time.isEmpty() ? String.format("[%s] ", time) : "") + msg;
+ viewHolder.textLog.setText(Html.fromHtml(text));
+ }
+ catch (Exception e)
+ {
+ VectrasStatus.logException(e);
+ return;
+ }
+
+ viewHolder.textLog.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v)
+ {
+ if (itemClickListener != null)
+ itemClickListener.onItemClick(v, position, text);
+ }
+ });
+
+ viewHolder.textLog.setOnLongClickListener(new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v)
+ {
+ if (itemClickListener != null)
+ itemClickListener.onItemLongClick(v, position, text);
+ return true;
+ }
+ });
+ }
+
+ @Override
+ public void registerAdapterDataObserver(RecyclerView.AdapterDataObserver observer)
+ {
+ super.registerAdapterDataObserver(observer);
+ observers.add(observer);
+ }
+
+ @Override
+ public void unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver observer)
+ {
+ super.unregisterAdapterDataObserver(observer);
+ observers.remove(observer);
+ }
+
+ @Override
+ public int getItemCount()
+ {
+ return currentLevelEntries.size();
+ }
+
+ @Override
+ public long getItemId(int position)
+ {
+ return ((Object) currentLevelEntries.get(position)).hashCode();
+ }
+
+ public boolean isEmpty()
+ {
+ return currentLevelEntries.isEmpty();
+ }
+
+ @Override
+ public void onAttachedToRecyclerView(
+ RecyclerView recyclerView)
+ {
+ super.onAttachedToRecyclerView(recyclerView);
+ }
+
+ @Override
+ public boolean onTouch(View p1, MotionEvent event)
+ {
+ // aqui deveria pausar autoscroll
+ /*int action = event.getAction();
+
+ if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_MOVE){
+ mLockAutoScroll = true;
+
+ return true;
+ }
+
+ mLockAutoScroll = false;*/
+
+ return false;
+ }
+
+ private String getTime(LogItem le, int time)
+ {
+ if (time != TIME_FORMAT_NONE)
+ {
+ Date d = new Date(le.getLogtime());
+ java.text.DateFormat timeformat;
+ if (time == TIME_FORMAT_SHORT)
+ timeformat = new SimpleDateFormat("HH:mm a");
+ else
+ timeformat = DateFormat.getTimeFormat(mContext);
+
+ return timeformat.format(d);
+
+ }
+ else
+ {
+ return "";
+ }
+ }
+
+
+ /**
+ * Handler implementação
+ */
+
+ @Override
+ public boolean handleMessage(Message msg)
+ {
+ // We have been called
+ if (msg.what == MESSAGE_NEWLOG)
+ {
+ LogItem logMessage = msg.getData().getParcelable("logmessage");
+ if (addLogMessage(logMessage))
+ {
+
+ for (AdapterDataObserver observer : observers)
+ {
+ observer.onChanged();
+ }
+
+ if (!mLockAutoScroll)
+ scrollToLastPosition();
+ }
+ }
+ else if (msg.what == MESSAGE_CLEARLOG)
+ {
+ for (AdapterDataObserver observer : observers)
+ {
+ observer.onChanged();
+ }
+ initLogBuffer();
+ }
+ else if (msg.what == MESSAGE_NEWTS)
+ {
+ for (AdapterDataObserver observer : observers)
+ {
+ observer.onChanged();
+ }
+ }
+ else if (msg.what == MESSAGE_NEWLOGLEVEL)
+ {
+ initCurrentMessages();
+
+ for (AdapterDataObserver observer : observers)
+ {
+ observer.onChanged();
+ }
+
+ }
+
+ return true;
+ }
+
+
+ /**
+ * @param logmessage
+ * @return True if the current entries have changed
+ */
+ private boolean addLogMessage(LogItem logmessage)
+ {
+ allEntries.add(logmessage);
+
+ if (allEntries.size() > MAX_STORED_LOG_ENTRIES)
+ {
+ Vector oldAllEntries = allEntries;
+ allEntries = new Vector(allEntries.size());
+ for (int i = 50; i < oldAllEntries.size(); i++)
+ {
+ allEntries.add(oldAllEntries.elementAt(i));
+ }
+ initCurrentMessages();
+ return true;
+ }
+ else
+ {
+ if (logmessage.getLogLevel().getInt() <= mLogLevel)
+ {
+ currentLevelEntries.add(logmessage);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ }
+
+ public LogItem getItem(int position)
+ {
+ return currentLevelEntries.get(position);
+ }
+
+ public void clearLog()
+ {
+ // Actually is probably called from GUI Thread as result of the user
+ // pressing a button. But better safe than sorry
+ VectrasStatus.clearLog();
+ }
+
+ public void scrollToLastPosition()
+ {
+ // scroll para ultima mensagem
+ mLinearLayoutManager.scrollToPosition(
+ mLinearLayoutManager.getItemCount() - 1);
+ }
+
+ public void setLogLevel(int level) {
+ mLogLevel = level;
+ }
+
+
+ /**
+ * LogListener
+ */
+
+ @Override
+ public void newLog(LogItem logMessage)
+ {
+ Message msg = Message.obtain();
+
+ assert (msg != null);
+ msg.what = MESSAGE_NEWLOG;
+
+ Bundle bundle = new Bundle();
+ bundle.putParcelable("logmessage", logMessage);
+
+ msg.setData(bundle);
+ mHandler.sendMessage(msg);
+ }
+
+ @Override
+ public void onClear()
+ {
+ mHandler.sendEmptyMessage(MESSAGE_CLEARLOG);
+ }
+
+}
diff --git a/app/src/main/java/com/vectras/vm/data/LoginDataSource.java b/app/src/main/java/com/vectras/vm/data/LoginDataSource.java
new file mode 100644
index 0000000..11d98c2
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/data/LoginDataSource.java
@@ -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 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
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/vectras/vm/data/LoginRepository.java b/app/src/main/java/com/vectras/vm/data/LoginRepository.java
new file mode 100644
index 0000000..fffde4e
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/data/LoginRepository.java
@@ -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 login(String username, String password) {
+ // handle login
+ Result result = dataSource.login(username, password);
+ if (result instanceof Result.Success) {
+ setLoggedInUser(((Result.Success) result).getData());
+ }
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/vectras/vm/data/Result.java b/app/src/main/java/com/vectras/vm/data/Result.java
new file mode 100644
index 0000000..2585d54
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/data/Result.java
@@ -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 {
+ // 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 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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/vectras/vm/data/model/LoggedInUser.java b/app/src/main/java/com/vectras/vm/data/model/LoggedInUser.java
new file mode 100644
index 0000000..0b647a5
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/data/model/LoggedInUser.java
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/vectras/vm/logger/LogItem.java b/app/src/main/java/com/vectras/vm/logger/LogItem.java
new file mode 100644
index 0000000..a51b312
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/logger/LogItem.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+
+package com.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 CREATOR
+ = new Creator() {
+ public LogItem createFromParcel(Parcel in) {
+ return new LogItem(in);
+ }
+
+ public LogItem[] newArray(int size) {
+ return new LogItem[size];
+ }
+ };
+
+ public VectrasStatus.LogLevel getLogLevel() {
+ return mLevel;
+ }
+
+ public long getLogtime() {
+ return logtime;
+ }
+
+ public String getMessage() {
+ return mMessage;
+ }
+
+ public String getString(Context c) {
+ try {
+ if (mMessage != null) {
+ return mMessage;
+ } else {
+ if (c != null) {
+ if (mResourceId == R.string.app_name)
+ return getAppInfoString(c);
+ else if (mArgs == null)
+ return c.getString(mResourceId);
+ else
+ return c.getString(mResourceId, mArgs);
+ } else {
+ String str = String.format(Locale.ENGLISH, "Log (no context) resid %d", mResourceId);
+ if (mArgs != null)
+ str += join("|", mArgs);
+
+ return str;
+ }
+ }
+ } catch (UnknownFormatConversionException e) {
+ if (c != null)
+ throw new UnknownFormatConversionException(e.getLocalizedMessage() + getString(null));
+ else
+ throw e;
+ } catch (java.util.FormatFlagsConversionMismatchException e) {
+ if (c != null)
+ throw new FormatFlagsConversionMismatchException(e.getLocalizedMessage() + getString(null), e.getConversion());
+ else
+ throw e;
+ }
+ }
+
+ //private String listb = "";
+
+ // The lint is wrong here
+ @SuppressLint("StringFormatMatches")
+ private String getAppInfoString(Context c) {
+ c.getPackageManager();
+ String apksign = "error getting package signature";
+
+ String version = "error getting version";
+ try {
+ @SuppressLint("PackageManagerGetSignatures")
+ Signature raw = c.getPackageManager().getPackageInfo(c.getPackageName(), PackageManager.GET_SIGNATURES).signatures[0];
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ X509Certificate cert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(raw.toByteArray()));
+ MessageDigest md = MessageDigest.getInstance("SHA-1");
+ byte[] der = cert.getEncoded();
+ md.update(der);
+ byte[] digest = md.digest();
+ if (!Arrays.equals(digest, VectrasStatus.oficialkey) && !Arrays.equals(digest, VectrasStatus.oficialdebugkey))
+ apksign = "";
+ PackageInfo packageinfo = c.getPackageManager().getPackageInfo(c.getPackageName(), 0);
+ version = String.format("%s Projeto %d", packageinfo.versionName, packageinfo.versionCode);
+
+ } catch (PackageManager.NameNotFoundException | CertificateException |
+ NoSuchAlgorithmException ignored) {
+ }
+
+ /* Object[] argsext = Arrays.copyOf(mArgs, mArgs.length);
+ argsext[argsext.length - 1] = apksign;
+ argsext[argsext.length - 2] = version;*/
+
+ return c.getString(R.string.app_name, version, apksign);
+
+ }
+
+ // TextUtils.join will cause not macked exeception in tests ....
+ public static String join(CharSequence delimiter, Object[] tokens) {
+ StringBuilder sb = new StringBuilder();
+ boolean firstTime = true;
+ for (Object token : tokens) {
+ if (firstTime) {
+ firstTime = false;
+ } else {
+ sb.append(delimiter);
+ }
+ sb.append(token);
+ }
+ return sb.toString();
+ }
+
+ public int getVerbosityLevel() {
+ if (mVerbosityLevel == -1) {
+ // Hack:
+ // For message not from OpenVPN, report the status level as log level
+ return mLevel.getInt();
+ }
+ return mVerbosityLevel;
+ }
+}
diff --git a/app/src/main/java/com/vectras/vm/logger/VMStatus.java b/app/src/main/java/com/vectras/vm/logger/VMStatus.java
new file mode 100644
index 0000000..c67811a
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/logger/VMStatus.java
@@ -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 CREATOR = new Creator() {
+ @Override
+ public VMStatus createFromParcel(Parcel in) {
+ return VMStatus.values()[in.readInt()];
+ }
+
+ @Override
+ public VMStatus[] newArray(int size) {
+ return new VMStatus[size];
+ }
+ };
+}
+
diff --git a/app/src/main/java/com/vectras/vm/logger/VectrasStatus.java b/app/src/main/java/com/vectras/vm/logger/VectrasStatus.java
new file mode 100644
index 0000000..b7e5aa2
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/logger/VectrasStatus.java
@@ -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 logbuffer;
+
+ private static Vector logListener;
+ private static Vector stateListener;
+
+ private static VMStatus mLastLevel = VMStatus.V_STOPVM;
+
+ private static String mLaststatemsg = "";
+ private static String mLaststate = "NOPROCESS";
+ private static int mLastStateresid = R.string.noproccesses;
+ private static Intent mLastIntent = null;
+
+
+ static final int MAXLOGENTRIES = 1000;
+
+ public static boolean isVMActive() {
+ return mLastLevel != VMStatus.V_STOPVM && !(mLastLevel == VMStatus.V_STOPVM);
+ }
+
+ public static String getLastState() {
+ return mLaststate;
+ }
+
+ public static String getLastCleanLogMessage(Context c) {
+ String message = mLaststatemsg;
+ switch (mLastLevel) {
+ case V_STARTVM:
+ String[] parts = mLaststatemsg.split(",");
+ if (parts.length >= 7)
+ message = String.format(Locale.US, "%s %s", parts[1], parts[6]);
+ break;
+ }
+
+ while (message.endsWith(","))
+ message = message.substring(0, message.length() - 1);
+
+ String status = mLaststate;
+ if (status.equals("NOPROCESS"))
+ return message;
+
+ String prefix = c.getString(mLastStateresid);
+ if (mLastStateresid == R.string.unknownstate)
+ message = status + message;
+ if (message.length() > 0)
+ prefix += ": ";
+
+ return prefix + message;
+
+ }
+
+
+ public static enum LogLevel {
+
+ INFO(2),
+ ERROR(-2),
+ WARNING(1),
+ VERBOSE(3),
+ DEBUG(4);
+
+ protected int mValue;
+
+ LogLevel(int value) {
+ mValue = value;
+ }
+
+ public int getInt() {
+ return mValue;
+ }
+
+ public static LogLevel getEnumByValue(int value) {
+ switch (value) {
+ case 2:
+ return INFO;
+ case -2:
+ return ERROR;
+ case 1:
+ return WARNING;
+ case 3:
+ return VERBOSE;
+ case 4:
+ return DEBUG;
+
+ default:
+ return null;
+ }
+ }
+ }
+
+ // keytool -printcert -jarfile de.blinkt.openvpn_85.apk
+ // tudo ok, certificado da Playstore
+ static final byte[] oficialkey = {93, -72, 88, 103, -128, 115, -1, -47, 120, 113, 98, -56, 12, -56, 52, -62, 95, -2, -114, 95};
+ // já atualizado, slipk certificado
+ static final byte[] oficialdebugkey = {-41, 73, 58, 102, -81, -27, -120, 45, -56, -3, 53, -49, 119, -97, -20, -80, 65, 68, -72, -22};
+
+ static {
+ logbuffer = new LinkedList<>();
+ logListener = new Vector<>();
+ stateListener = new Vector<>();
+
+ logInformation();
+ }
+
+
+ public synchronized static void clearLog() {
+ logbuffer.clear();
+ logInformation();
+ logInfo("LOGS CLEARED!");
+
+ for (LogListener li : logListener) {
+ li.onClear();
+ }
+ }
+
+ public synchronized static LogItem[] getlogbuffer() {
+
+ // The stoned way of java to return an array from a vector
+ // brought to you by eclipse auto complete
+ return logbuffer.toArray(new LogItem[logbuffer.size()]);
+ }
+
+ private static void logInformation() {
+ logInfo(R.string.app_name);
+ logInfo(R.string.app_version);
+ logInfo("MOBILE MODEL: " + Build.MODEL);
+ logInfo("ANDROID VERSION: " + Build.VERSION.SDK_INT);
+ }
+
+
+ /**
+ * Listeners
+ */
+
+ public interface LogListener {
+ void newLog(LogItem logItem);
+ void onClear();
+ }
+
+ public interface StateListener {
+ void updateState(String state, String logMessage, int localizedResId, VMStatus level, Intent intent);
+ }
+
+ public synchronized static void addLogListener(LogListener ll) {
+ if (!logListener.contains(ll)) {
+ logListener.add(ll);
+ }
+ }
+
+ public synchronized static void removeLogListener(LogListener ll) {
+ if (logListener.contains(ll)) {
+ logListener.remove(ll);
+ }
+ }
+
+ public synchronized static void addStateListener(StateListener sl) {
+ if (!stateListener.contains(sl)) {
+ stateListener.add(sl);
+ if (mLaststate != null)
+ sl.updateState(mLaststate, mLaststatemsg, mLastStateresid, mLastLevel, mLastIntent);
+ }
+ }
+
+ public synchronized static void removeStateListener(StateListener sl) {
+ if (stateListener.contains(sl)) {
+ stateListener.remove(sl);
+ }
+ }
+
+
+ /**
+ * State
+ */
+
+ public static final String
+ V_STARTVM = "STARTING VM",
+ V_STOPVM = "STOPPING VM";
+
+ public static int getLocalizedState(String state) {
+ switch (state) {
+ case V_STARTVM:
+ return R.string.startvm;
+ case V_STOPVM:
+ return R.string.stopvm;
+ }
+ return R.string.unknownstate;
+ }
+
+ private static VMStatus getLevel(String state) {
+ String[] noreplyet = {V_STARTVM, V_STOPVM};
+ String[] reply = {V_STARTVM, V_STOPVM};
+ String[] startedvm = {V_STARTVM};
+ String[] stoppedvm = {V_STOPVM};
+
+ for (String x : noreplyet)
+ if (state.equals(x))
+ return VMStatus.V_STARTVM;
+
+ for (String x : reply)
+ if (state.equals(x))
+ return VMStatus.V_STOPVM;
+
+ for (String x : startedvm)
+ if (state.equals(x))
+ return VMStatus.V_STARTVM;
+
+ for (String x : stoppedvm)
+ if (state.equals(x))
+ return VMStatus.V_STOPVM;
+
+ return VMStatus.UNKNOWN_LEVEL;
+ }
+
+ public static void updateStateString(String state, String msg) {
+ int rid = getLocalizedState(state);
+ VMStatus level = getLevel(state);
+ updateStateString(state, msg, rid, level);
+ }
+
+ public synchronized static void updateStateString(String state, String msg, int resid, VMStatus level)
+ {
+ updateStateString(state, msg, resid, level, null);
+ }
+
+ public synchronized static void updateStateString(String state, String msg, int resid, VMStatus level, Intent intent) {
+ // Workound for OpenVPN doing AUTH and wait and being startedvm
+ // Simply ignore these state
+ /*if (mLastLevel == VMStatus.LEVEL_CONNECTED &&
+ (state.equals(SSH_AUTHENTICATING))) {
+ newLogItem(new LogItem((LogLevel.DEBUG), String.format("Ignoring SocksHttp Status in CONNECTED state (%s->%s): %s", state, level.toString(), msg)));
+ return;
+ }*/
+
+ mLaststate = state;
+ mLaststatemsg = msg;
+ mLastStateresid = resid;
+ mLastLevel = level;
+ mLastIntent = intent;
+
+
+ for (StateListener sl : stateListener) {
+ sl.updateState(state, msg, resid, level, intent);
+ }
+
+ //newLogItem(new LogItem((LogLevel.DEBUG), String.format("SocksHttp Novo Status (%s->%s): %s",state,level.toString(),msg)));
+ }
+
+
+ /**
+ * NewLog
+ */
+
+ static void newLogItem(LogItem logItem) {
+ newLogItem(logItem, false);
+ }
+
+ synchronized static void newLogItem(LogItem logItem, boolean cachedLine) {
+ if (cachedLine) {
+ logbuffer.addFirst(logItem);
+ } else {
+ logbuffer.addLast(logItem);
+ }
+
+ if (logbuffer.size() > MAXLOGENTRIES + MAXLOGENTRIES / 2) {
+ while (logbuffer.size() > MAXLOGENTRIES)
+ logbuffer.removeFirst();
+ }
+
+ for (LogListener ll : logListener) {
+ ll.newLog(logItem);
+ }
+ }
+
+
+ /**
+ * Logger static methods
+ */
+
+ public static void logException(String context, String e) {
+ logException(LogLevel.ERROR, context, e);
+ }
+
+ public static void logException(LogLevel ll, String context, String e) {
+
+ LogItem li;
+
+ if (context != null)
+ li = new LogItem(ll, String.format("%s: %s", context, e));
+ else
+ li = new LogItem(ll, String.format("Error: %s", e));
+
+ newLogItem(li);
+ }
+
+ public static void logException(Exception e) {
+ logException(LogLevel.ERROR, null, e.getMessage());
+ }
+
+ public static void logInfo(String message) {
+ newLogItem(new LogItem(LogLevel.INFO, message));
+ }
+
+ public static void logDebug(String message) {
+ newLogItem(new LogItem(LogLevel.DEBUG, message));
+ }
+
+ public static void logInfo(int resourceId, Object... args) {
+ newLogItem(new LogItem(LogLevel.INFO, resourceId, args));
+ }
+
+ public static void logDebug(int resourceId, Object... args) {
+ newLogItem(new LogItem(LogLevel.DEBUG, resourceId, args));
+ }
+
+ public static void logError(String msg) {
+ newLogItem(new LogItem(LogLevel.ERROR, msg));
+ }
+
+ public static void logWarning(int resourceId, Object... args) {
+ newLogItem(new LogItem(LogLevel.WARNING, resourceId, args));
+ }
+
+ public static void logWarning(String msg) {
+ newLogItem(new LogItem(LogLevel.WARNING, msg));
+ }
+
+ public static void logError(int resourceId) {
+ newLogItem(new LogItem(LogLevel.ERROR, resourceId));
+ }
+
+ public static void logError(int resourceId, Object... args) {
+ newLogItem(new LogItem(LogLevel.ERROR, resourceId, args));
+ }
+
+}
diff --git a/app/src/main/java/com/vectras/vm/ui/login/GoogleSignInActivity.java b/app/src/main/java/com/vectras/vm/ui/login/GoogleSignInActivity.java
new file mode 100644
index 0000000..2a45c76
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/ui/login/GoogleSignInActivity.java
@@ -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 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() {
+ @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() {
+ @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() {
+ @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();
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/vectras/vm/ui/login/LoginActivity.java b/app/src/main/java/com/vectras/vm/ui/login/LoginActivity.java
new file mode 100644
index 0000000..7aa3bd7
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/ui/login/LoginActivity.java
@@ -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) 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() {
+ @Override
+ public void onComplete(@NonNull Task 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);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/vectras/vm/ui/login/SignupActivity.java b/app/src/main/java/com/vectras/vm/ui/login/SignupActivity.java
new file mode 100644
index 0000000..6fe6122
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/ui/login/SignupActivity.java
@@ -0,0 +1,140 @@
+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);
+ }
+ }
+ };
+ fullNameEditText.addTextChangedListener(afterTextChangedListener);
+ 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() {
+ @Override
+ public void onComplete(@NonNull Task task) {
+ if (task.isSuccessful()) {
+ user.sendEmailVerification()
+ .addOnCompleteListener(new OnCompleteListener() {
+ @Override
+ public void onComplete(@NonNull Task task) {
+ startActivity(new Intent(SignupActivity.this, RomsManagerActivity.class));
+ }
+ });
+
+ }
+ }
+ });
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/vectras/vm/utils/AppUpdater.java b/app/src/main/java/com/vectras/vm/utils/AppUpdater.java
new file mode 100644
index 0000000..c4a56f2
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/utils/AppUpdater.java
@@ -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 {
+
+ 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);
+ }
+ }
+}
diff --git a/app/src/main/java/com/vectras/vm/utils/FileUtils.java b/app/src/main/java/com/vectras/vm/utils/FileUtils.java
new file mode 100644
index 0000000..f1a7ad7
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/utils/FileUtils.java
@@ -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 fds = new HashMap();
+
+ public static int get_fd(final Context context, String path) {
+ int fd = 0;
+ if (path == null)
+ return 0;
+
+ if (path.startsWith("/content") || path.startsWith("content://")) {
+ path = path.replaceFirst("/content", "content:");
+
+ try {
+ ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(Uri.parse(path), "rw");
+ fd = pfd.getFd();
+ fds.put(fd, pfd);
+ } catch (final FileNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ new Handler(Looper.getMainLooper()).post(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(context, "Error: " + e, Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+ } else {
+ try {
+ File file = new File(path);
+ if (!file.exists())
+ file.createNewFile();
+ ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_WRITE_ONLY);
+ fd = pfd.getFd();
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ }
+ return fd;
+ }
+
+ public static int close_fd(int fd) {
+
+ if (FileUtils.fds.containsKey(fd)) {
+ ParcelFileDescriptor pfd = FileUtils.fds.get(fd);
+ try {
+ pfd.close();
+ FileUtils.fds.remove(fd);
+ return 0; // success for Native side
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ }
+ return -1;
+ }
+
+ public static void writeToFile(String data, File file, Context context) {
+ try {
+ FileOutputStream fileOutStream = new FileOutputStream(file);
+ OutputStreamWriter outputWriter = new OutputStreamWriter(fileOutStream);
+ outputWriter.write(data);
+ outputWriter.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static String readFromFile(Context context, File file) {
+ String contents = null;
+ try {
+ int length = (int) file.length();
+
+ byte[] bytes = new byte[length];
+
+ FileInputStream in = new FileInputStream(file);
+ try {
+ in.read(bytes);
+ } finally {
+ in.close();
+ }
+
+ contents = new String(bytes);
+ } catch (Exception e) {
+ UIUtils.toastLong(context, e.toString());
+ return "error";
+ }
+ return contents;
+ }
+
+ public static boolean moveFile(String oldfilename, String newFolderPath, String newFilename) {
+ File folder = new File(newFolderPath);
+ if (!folder.exists())
+ folder.mkdirs();
+
+ File oldfile = new File(oldfilename);
+ File newFile = new File(newFolderPath, newFilename);
+
+ if (!newFile.exists())
+ try {
+ newFile.createNewFile();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return oldfile.renameTo(newFile);
+ }
+
+}
diff --git a/app/src/main/java/com/vectras/vm/utils/UIUtils.java b/app/src/main/java/com/vectras/vm/utils/UIUtils.java
new file mode 100644
index 0000000..c0710db
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/utils/UIUtils.java
@@ -0,0 +1,378 @@
+package com.vectras.vm.utils;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.text.InputType;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.style.ForegroundColorSpan;
+import android.util.Log;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+import android.webkit.WebView;
+import android.widget.ScrollView;
+import android.widget.TextView;
+import android.widget.Toast;
+import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+
+import com.vectras.qemu.Config;
+import com.vectras.qemu.MainActivityCommon;
+import com.vectras.qemu.MainSettingsManager;
+import com.vectras.qemu.utils.FileUtils;
+import com.vectras.vm.R;
+import com.vectras.vm.AppConfig;
+import com.vectras.vm.logger.VectrasStatus;
+
+import java.io.IOException;
+import java.util.Scanner;
+
+public class UIUtils {
+
+ private static final String TAG = "UIUtils";
+
+ public static Spannable formatAndroidLog(String contents) {
+
+ Scanner scanner = null;
+ Spannable formattedString = new SpannableString(contents);
+ if(contents.length()==0)
+ return formattedString;
+
+ try {
+ scanner = new Scanner(contents);
+ int counter = 0;
+ ForegroundColorSpan colorSpan = null;
+ while (scanner.hasNextLine()) {
+ String line = scanner.nextLine();
+ //FIXME: some devices don't have standard format for the log
+ if (line.startsWith("E/") || line.contains(" E ")) {
+ colorSpan = new ForegroundColorSpan(Color.rgb(255, 22, 22));
+ } else if (line.startsWith("W/") || line.contains(" W ")) {
+ colorSpan = new ForegroundColorSpan(Color.rgb(22, 44, 255));
+ } else {
+ colorSpan = null;
+ }
+ if (colorSpan!= null) {
+ formattedString.setSpan(colorSpan, counter, counter + line.length(),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ counter += line.length()+1;
+ }
+
+ }catch (Exception ex) {
+ Log.e(TAG, "Could not format vectras log: " + ex.getMessage());
+ } finally {
+ if(scanner!=null) {
+ try {
+ scanner.close();
+ } catch (Exception ex) {
+ if(Config.debug)
+ ex.printStackTrace();
+ }
+ }
+
+ }
+ return formattedString;
+ }
+ public static void toastLong(final Context activity, final String errStr) {
+ new Handler(Looper.getMainLooper()).post(new Runnable() {
+ @Override
+ public void run() {
+
+ Toast toast = Toast.makeText(activity, errStr, Toast.LENGTH_LONG);
+ toast.show();
+ VectrasStatus.logInfo("[I] "+errStr+"");
+
+ }
+ });
+
+ }
+
+ public static void showFileNotSupported(Activity context){
+ UIAlert(context, "Error", "File path is not supported. Make sure you choose a file/directory from your internal storage or external sd card. Root and Download Directories are not supported.");
+ }
+
+
+ public static boolean onKeyboard(Activity activity, boolean toggle, View view) {
+ // Prevent crashes from activating mouse when machine is paused
+ if (MainActivityCommon.vmexecutor.paused == 1)
+ return !toggle;
+
+ InputMethodManager inputMgr = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
+
+ //XXX: we need to get the focused view to make this always work
+ //inputMgr.toggleSoftInput(0, 0);
+
+
+// View view = activity.getCurrentFocus();
+ if (toggle || !Config.enableToggleKeyboard){
+ if(view!=null) {
+ view.requestFocus();
+ inputMgr.showSoftInput(view, InputMethodManager.SHOW_FORCED);
+ }
+ } else {
+ if (view != null) {
+ inputMgr.hideSoftInputFromWindow(view.getWindowToken(), 0);
+ }
+ }
+
+ return !toggle;
+ }
+
+ public static void hideKeyboard(Activity activity, View view) {
+ InputMethodManager inputMgr = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
+ if (view != null) {
+ inputMgr.hideSoftInputFromWindow(view.getWindowToken(), 0);
+ }
+ }
+
+ public static void toastShortTop(final Activity activity, final String errStr) {
+ UIUtils.toast(activity, errStr, Gravity.TOP | Gravity.CENTER, Toast.LENGTH_SHORT);
+ }
+
+ public static void toast(final Context context, final String errStr, final int gravity, final int length) {
+ new Handler(Looper.getMainLooper()).post(new Runnable() {
+ @Override
+ public void run() {
+ if(context instanceof Activity && ((Activity) context).isFinishing()) {
+ return ;
+ }
+ Toast toast = Toast.makeText(context, errStr, length);
+ toast.setGravity(gravity, 0, 0);
+ toast.show();
+
+ }
+ });
+
+ }
+
+ public static void toastShort(final Context context, final String errStr) {
+ toast(context, errStr, Gravity.CENTER | Gravity.CENTER, Toast.LENGTH_SHORT);
+
+ }
+
+ public static void setOrientation(Activity activity) {
+ int orientation = MainSettingsManager.getOrientationSetting(activity);
+ switch (orientation) {
+ case 0:
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+ break;
+ case 1:
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+ break;
+ case 2:
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
+ break;
+ case 3:
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+ break;
+ case 4:
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT);
+ break;
+ }
+ }
+
+
+ public static void onChangeLog(Activity activity) {
+ PackageInfo pInfo = null;
+
+ try {
+ pInfo = activity.getPackageManager().getPackageInfo(activity.getClass().getPackage().getName(),
+ PackageManager.GET_META_DATA);
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ }
+ com.vectras.qemu.utils.FileUtils fileutils = new com.vectras.qemu.utils.FileUtils();
+ try {
+ UIUtils.UIAlert(activity,"CHANGELOG", fileutils.LoadFile(activity, "CHANGELOG", false),
+ 0, false, "OK", null, null, null, null, null);
+ } catch (IOException e) {
+
+ e.printStackTrace();
+ }
+ }
+
+
+
+ public static void showHints(Activity activity) {
+
+
+ UIUtils.toastShortTop(activity, "Press Volume Down for Right Click");
+
+
+ }
+
+ public static boolean isLandscapeOrientation(Activity activity)
+ {
+ Display display = activity.getWindowManager().getDefaultDisplay();
+ Point screenSize = new Point();
+ display.getSize(screenSize);
+ if(screenSize.x < screenSize.y)
+ return false;
+ return true;
+ }
+
+ private static void openURL(Activity activity, String url) {
+ try {
+ Intent fileIntent = new Intent(Intent.ACTION_VIEW);
+ fileIntent.setData(Uri.parse(url));
+ activity.startActivity(fileIntent);
+ }catch (Exception ex) {
+ UIUtils.toastShort(activity, "Could not open url");
+ }
+ }
+
+ public static void UIAlert(Activity activity, String title, String body) {
+
+ AlertDialog alertDialog;
+ alertDialog = new AlertDialog.Builder(activity, R.style.MainDialogTheme).create();
+ alertDialog.setTitle(title);
+ TextView textView = new TextView(activity);
+ textView.setPadding(20,20,20,20);
+ textView.setText(body);
+ ScrollView view = new ScrollView(activity);
+ view.addView(textView);
+ alertDialog.setView(view);
+ alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "OK", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ return;
+ }
+ });
+ alertDialog.show();
+ }
+
+ public static void UIAlert(Activity activity, String title, String body, int textSize, boolean cancelable,
+ String button1title, DialogInterface.OnClickListener button1Listener,
+ String button2title, DialogInterface.OnClickListener button2Listener,
+ String button3title, DialogInterface.OnClickListener button3Listener
+ ) {
+
+ AlertDialog alertDialog;
+ alertDialog = new AlertDialog.Builder(activity, R.style.MainDialogTheme).create();
+ alertDialog.setTitle(title);
+ alertDialog.setCanceledOnTouchOutside(cancelable);
+ TextView textView = new TextView(activity);
+ textView.setPadding(20,20,20,20);
+ textView.setText(body);
+ if(textSize>0)
+ textView.setTextSize(textSize);
+ textView.setBackgroundColor(Color.WHITE);
+ textView.setTextColor(Color.BLACK);
+ ScrollView view = new ScrollView(activity);
+ view.addView(textView);
+ alertDialog.setView(view);
+ if(button1title!=null)
+ alertDialog.setButton(AlertDialog.BUTTON_POSITIVE, button1title, button1Listener);
+ if(button2title!=null)
+ alertDialog.setButton(AlertDialog.BUTTON_NEGATIVE, button2title, button2Listener);
+ if(button3title!=null)
+ alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, button3title, button3Listener);
+ alertDialog.show();
+ }
+
+ public static void UIAlertLog(final Activity activity, String title, Spannable body) {
+
+ AlertDialog alertDialog;
+ alertDialog = new AlertDialog.Builder(activity, R.style.MainDialogTheme).create();
+ alertDialog.setTitle(title);
+ TextView textView = new TextView(activity);
+ textView.setPadding(20,20,20,20);
+ textView.setText(body);
+ textView.setBackgroundColor(Color.BLACK);
+ textView.setTextSize(12);
+ textView.setInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
+ textView.setSingleLine(false);
+ ScrollView view = new ScrollView(activity);
+ view.addView(textView);
+ alertDialog.setView(view);
+ alertDialog.setCanceledOnTouchOutside(false);
+ alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "OK", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ return;
+ }
+ });
+ alertDialog.setButton(DialogInterface.BUTTON_NEUTRAL, "Copy To", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ ClipboardManager clipboardManager = (ClipboardManager)
+ activity.getSystemService(Context.CLIPBOARD_SERVICE);
+ ClipData clipData = ClipData.newPlainText("nonsense_data",
+ body);
+ clipboardManager.setPrimaryClip(clipData);
+ UIUtils.toastShort(activity, "Copied to clipboard successfully!");
+ return;
+ }
+ });
+ alertDialog.show();
+ }
+
+ public static void UIAlertHtml(String title, String body, Activity activity) {
+
+ AlertDialog alertDialog;
+ alertDialog = new AlertDialog.Builder(activity, R.style.MainDialogTheme).create();
+ alertDialog.setTitle(title);
+
+ try {
+ WebView webview = new WebView(activity);
+ webview.loadData(body, "text/html", "UTF-8");
+ alertDialog.setView(webview);
+ } catch (Exception ex) {
+ TextView textView = new TextView(activity);
+ textView.setText(body);
+ alertDialog.setView(textView);
+ }
+
+ alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "OK", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ return;
+ }
+ });
+ alertDialog.show();
+ }
+
+ public static void promptShowLog(final Activity activity) {
+
+ final AlertDialog alertDialog;
+ alertDialog = new AlertDialog.Builder(activity, R.style.MainDialogTheme).create();
+ alertDialog.setTitle("Show log");
+ TextView stateView = new TextView(activity);
+ stateView.setText("Something happened during last run, do you want to see the log?");
+ stateView.setPadding(20, 20, 20, 20);
+ alertDialog.setView(stateView);
+
+ // alertDialog.setMessage(body);
+ alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "Yes",
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+
+ FileUtils.viewVectrasLog(activity);
+ }
+ });
+ alertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancel",
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+
+
+ }
+ });
+ alertDialog.show();
+
+ }
+
+
+}
diff --git a/app/src/main/java/com/vectras/vm/widgets/JoystickView.java b/app/src/main/java/com/vectras/vm/widgets/JoystickView.java
new file mode 100644
index 0000000..4c3e164
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/widgets/JoystickView.java
@@ -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;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/vectras/vm/widgets/RadioGroupPlus.java b/app/src/main/java/com/vectras/vm/widgets/RadioGroupPlus.java
new file mode 100644
index 0000000..5b326b4
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/widgets/RadioGroupPlus.java
@@ -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);
+ }
+
+ /**
+ *
Sets the selection to the radio button whose identifier is passed in
+ * parameter. Using -1 as the selection identifier clears the selection;
+ * such an operation is equivalent to invoking {@link #clearCheck()}.
+ *
+ * @param id the unique id of the radio button to select in this group
+ * @see #getCheckedRadioButtonId()
+ * @see #clearCheck()
+ */
+ public void check(@IdRes int id) {
+ // don't even bother
+ if (id != -1 && (id == mCheckedId)) {
+ return;
+ }
+
+ if (mCheckedId != -1) {
+ setCheckedStateForView(mCheckedId, false);
+ }
+
+ if (id != -1) {
+ setCheckedStateForView(id, true);
+ }
+
+ setCheckedId(id);
+ }
+
+ private void setCheckedId(@IdRes int id) {
+ mCheckedId = id;
+ if (mOnCheckedChangeListener != null) {
+ mOnCheckedChangeListener.onCheckedChanged(this, mCheckedId);
+ }
+ }
+
+ private void setCheckedStateForView(int viewId, boolean checked) {
+ View checkedView = findViewById(viewId);
+ if (checkedView != null && checkedView instanceof RadioButton) {
+ ((RadioButton) checkedView).setChecked(checked);
+ }
+ }
+
+ /**
+ *
Returns the identifier of the selected radio button in this group.
+ * Upon empty selection, the returned value is -1.
+ *
+ * @return the unique id of the selected radio button in this group
+ * @attr ref android.R.styleable#RadioGroup_checkedButton
+ * @see #check(int)
+ * @see #clearCheck()
+ */
+ @IdRes
+ public int getCheckedRadioButtonId() {
+ return mCheckedId;
+ }
+
+ /**
+ *
Clears the selection. When the selection is cleared, no radio button
+ * in this group is selected and {@link #getCheckedRadioButtonId()} returns
+ * null.
Fixes the child's width to
+ * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and the child's
+ * height to {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}
+ * when not specified in the XML file.
+ *
+ * @param a the styled attributes set
+ * @param widthAttr the width attribute to fetch
+ * @param heightAttr the height attribute to fetch
+ */
+ @Override
+ protected void setBaseAttributes(TypedArray a,
+ int widthAttr, int heightAttr) {
+
+ if (a.hasValue(widthAttr)) {
+ width = a.getLayoutDimension(widthAttr, "layout_width");
+ } else {
+ width = WRAP_CONTENT;
+ }
+
+ if (a.hasValue(heightAttr)) {
+ height = a.getLayoutDimension(heightAttr, "layout_height");
+ } else {
+ height = WRAP_CONTENT;
+ }
+ }
+ }
+
+ /**
+ *
Interface definition for a callback to be invoked when the checked
+ * radio button changed in this group.
Called when the checked radio button has changed. When the
+ * selection is cleared, checkedId is -1.
+ *
+ * @param group the group in which the checked radio button has changed
+ * @param checkedId the unique identifier of the newly checked radio button
+ */
+ public void onCheckedChanged(RadioGroupPlus group, @IdRes int checkedId);
+ }
+
+ private class CheckedStateTracker implements CompoundButton.OnCheckedChangeListener {
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ // prevents from infinite recursion
+ if (mProtectFromCheckedChange) {
+ return;
+ }
+
+ mProtectFromCheckedChange = true;
+ if (mCheckedId != -1) {
+ setCheckedStateForView(mCheckedId, false);
+ }
+ mProtectFromCheckedChange = false;
+
+ int id = buttonView.getId();
+ setCheckedId(id);
+ }
+ }
+
+ /**
+ *
A pass-through listener acts upon the events and dispatches them
+ * to another listener. This allows the table layout to set its own internal
+ * hierarchy change listener without preventing the user to setup his.
+ */
+ private class PassThroughHierarchyChangeListener implements
+ ViewGroup.OnHierarchyChangeListener {
+ private ViewGroup.OnHierarchyChangeListener mOnHierarchyChangeListener;
+
+ public void traverseTree(View view) {
+ if (view instanceof RadioButton) {
+ int id = view.getId();
+ // generates an id if it's missing
+ if (id == View.NO_ID) {
+ id = View.generateViewId();
+ view.setId(id);
+ }
+ ((RadioButton) view).setOnCheckedChangeListener(
+ mChildOnCheckedChangeListener);
+ }
+ if (!(view instanceof ViewGroup)) {
+ return;
+ }
+ ViewGroup viewGroup = (ViewGroup) view;
+ if (viewGroup.getChildCount() == 0) {
+ return;
+ }
+ for (int i = 0; i < viewGroup.getChildCount(); i++) {
+ traverseTree(viewGroup.getChildAt(i));
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void onChildViewAdded(View parent, View child) {
+ traverseTree(child);
+ if (parent == RadioGroupPlus.this && child instanceof RadioButton) {
+ int id = child.getId();
+ // generates an id if it's missing
+ if (id == View.NO_ID) {
+ id = View.generateViewId();
+ child.setId(id);
+ }
+ ((RadioButton) child).setOnCheckedChangeListener(
+ mChildOnCheckedChangeListener);
+ }
+
+ if (mOnHierarchyChangeListener != null) {
+ mOnHierarchyChangeListener.onChildViewAdded(parent, child);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void onChildViewRemoved(View parent, View child) {
+ if (parent == RadioGroupPlus.this && child instanceof RadioButton) {
+ ((RadioButton) child).setOnCheckedChangeListener(null);
+ }
+
+ if (mOnHierarchyChangeListener != null) {
+ mOnHierarchyChangeListener.onChildViewRemoved(parent, child);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/libsdl/app/SDL.java b/app/src/main/java/org/libsdl/app/SDL.java
new file mode 100644
index 0000000..cfe4830
--- /dev/null
+++ b/app/src/main/java/org/libsdl/app/SDL.java
@@ -0,0 +1,37 @@
+package org.libsdl.app;
+
+import android.content.Context;
+
+/**
+ SDL library initialization
+*/
+public class SDL {
+
+ // This function should be called first and sets up the native code
+ // so it can call into the Java classes
+ public static void setupJNI() {
+ SDLActivity.nativeSetupJNI();
+ SDLAudioManager.nativeSetupJNI();
+ SDLControllerManager.nativeSetupJNI();
+ }
+
+ // This function should be called each time the activity is started
+ public static void initialize() {
+ setContext(null);
+
+ SDLActivity.initialize();
+ SDLAudioManager.initialize();
+ SDLControllerManager.initialize();
+ }
+
+ // This function stores the current activity (SDL or not)
+ public static void setContext(Context context) {
+ mContext = context;
+ }
+
+ public static Context getContext() {
+ return mContext;
+ }
+
+ protected static Context mContext;
+}
diff --git a/app/src/main/java/org/libsdl/app/SDLActivity.java b/app/src/main/java/org/libsdl/app/SDLActivity.java
new file mode 100644
index 0000000..c12258d
--- /dev/null
+++ b/app/src/main/java/org/libsdl/app/SDLActivity.java
@@ -0,0 +1,1721 @@
+package org.libsdl.app;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.lang.reflect.Method;
+
+import android.app.*;
+import android.content.*;
+import android.content.res.Configuration;
+import android.text.InputType;
+import android.view.*;
+import android.view.inputmethod.BaseInputConnection;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.RelativeLayout;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.os.*;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.SparseArray;
+import android.graphics.*;
+import android.graphics.drawable.Drawable;
+import android.hardware.*;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ApplicationInfo;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+/**
+ SDL Activity
+*/
+public class SDLActivity extends AppCompatActivity {
+ private static final String TAG = "SDL";
+
+ public static boolean mIsResumedCalled, mIsSurfaceReady, mHasFocus;
+
+ // Handle the state of the native layer
+ public enum NativeState {
+ INIT, RESUMED, PAUSED
+ }
+
+ public static NativeState mNextNativeState;
+ public static NativeState mCurrentNativeState;
+
+ public static boolean mExitCalledFromJava;
+
+ /** If shared libraries (e.g. SDL or the native application) could not be loaded. */
+ public static boolean mBrokenLibraries;
+
+ // If we want to separate mouse and touch events.
+ // This is only toggled in native code when a hint is set!
+ public static boolean mSeparateMouseAndTouch;
+
+ // Main components
+ protected static SDLActivity mSingleton;
+ protected static SDLSurface mSurface;
+ protected static View mTextEdit;
+ protected static boolean mScreenKeyboardShown;
+ protected static ViewGroup mLayout;
+ protected static SDLClipboardHandler mClipboardHandler;
+
+
+ // This is what SDL runs in. It invokes SDL_main(), eventually
+ protected static Thread mSDLThread;
+
+ /**
+ * This method returns the name of the shared object with the application entry point
+ * It can be overridden by derived classes.
+ */
+ protected String getMainSharedObject() {
+ String library;
+ String[] libraries = SDLActivity.mSingleton.getLibraries();
+ if (libraries.length > 0) {
+ library = "lib" + libraries[libraries.length - 1] + ".so";
+ } else {
+ library = "libmain.so";
+ }
+ return library;
+ }
+
+ /**
+ * This method returns the name of the application entry point
+ * It can be overridden by derived classes.
+ */
+ protected String getMainFunction() {
+ return "SDL_main";
+ }
+
+ /**
+ * This method is called by SDL before loading the native shared libraries.
+ * It can be overridden to provide names of shared libraries to be loaded.
+ * The default implementation returns the defaults. It never returns null.
+ * An array returned by a new implementation must at least contain "SDL2".
+ * Also keep in mind that the order the libraries are loaded may matter.
+ * @return names of shared libraries to be loaded (e.g. "SDL2", "main").
+ */
+ protected String[] getLibraries() {
+ return new String[] {
+ "SDL2",
+ // "SDL2_image",
+ // "SDL2_mixer",
+ // "SDL2_net",
+ // "SDL2_ttf",
+ "main"
+ };
+ }
+
+ // Load the .so
+ public void loadLibraries() {
+ for (String lib : getLibraries()) {
+ System.loadLibrary(lib);
+ }
+ }
+
+ /**
+ * This method is called by SDL before starting the native application thread.
+ * It can be overridden to provide the arguments after the application name.
+ * The default implementation returns an empty array. It never returns null.
+ * @return arguments for the native application.
+ */
+ protected String[] getArguments() {
+ return new String[0];
+ }
+
+ public static void initialize() {
+ // The static nature of the singleton and Android quirkyness force us to initialize everything here
+ // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values
+ mSingleton = null;
+ mSurface = null;
+ mTextEdit = null;
+ mLayout = null;
+ mClipboardHandler = null;
+ mSDLThread = null;
+ mExitCalledFromJava = false;
+ mBrokenLibraries = false;
+ mIsResumedCalled = false;
+ mIsSurfaceReady = false;
+ mHasFocus = true;
+ mNextNativeState = NativeState.INIT;
+ mCurrentNativeState = NativeState.INIT;
+ }
+
+ // Setup
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ Log.v(TAG, "Device: " + android.os.Build.DEVICE);
+ Log.v(TAG, "Model: " + android.os.Build.MODEL);
+ Log.v(TAG, "onCreate()");
+ super.onCreate(savedInstanceState);
+
+ // Load shared libraries
+ String errorMsgBrokenLib = "";
+ try {
+ loadLibraries();
+ } catch(UnsatisfiedLinkError e) {
+ System.err.println(e.getMessage());
+ mBrokenLibraries = true;
+ errorMsgBrokenLib = e.getMessage();
+ } catch(Exception e) {
+ System.err.println(e.getMessage());
+ mBrokenLibraries = true;
+ errorMsgBrokenLib = e.getMessage();
+ }
+
+ if (mBrokenLibraries)
+ {
+ mSingleton = this;
+ AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this);
+ dlgAlert.setMessage("An error occurred while trying to start the application. Please try again and/or reinstall."
+ + System.getProperty("line.separator")
+ + System.getProperty("line.separator")
+ + "Error: " + errorMsgBrokenLib);
+ dlgAlert.setTitle("SDL Error");
+ dlgAlert.setPositiveButton("Exit",
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog,int id) {
+ // if this button is clicked, close current activity
+ SDLActivity.mSingleton.finish();
+ }
+ });
+ dlgAlert.setCancelable(false);
+ dlgAlert.create().show();
+
+ return;
+ }
+
+ // Set up JNI
+ SDL.setupJNI();
+
+ // Initialize state
+ SDL.initialize();
+
+ // So we can call stuff from static callbacks
+ mSingleton = this;
+ SDL.setContext(this);
+
+ if (Build.VERSION.SDK_INT >= 11) {
+ mClipboardHandler = new SDLClipboardHandler_API11();
+ } else {
+ /* Before API 11, no clipboard notification (eg no SDL_CLIPBOARDUPDATE) */
+ mClipboardHandler = new SDLClipboardHandler_Old();
+ }
+
+ //Vectras: override
+ // Set up the surface
+ //mSurface = new SDLSurface(getApplication());
+
+ //Vectras: override
+ //mLayout = new RelativeLayout(this);
+ //mLayout.addView(mSurface);
+
+ //setContentView(mLayout);
+
+ setWindowStyle(false);
+
+ // Get filename from "Open with" of another application
+ Intent intent = getIntent();
+ if (intent != null && intent.getData() != null) {
+ String filename = intent.getData().getPath();
+ if (filename != null) {
+ Log.v(TAG, "Got filename: " + filename);
+ SDLActivity.onNativeDropFile(filename);
+ }
+ }
+ }
+
+ // Events
+ @Override
+ protected void onPause() {
+ Log.v(TAG, "onPause()");
+ super.onPause();
+ mNextNativeState = NativeState.PAUSED;
+ mIsResumedCalled = false;
+
+ if (SDLActivity.mBrokenLibraries) {
+ return;
+ }
+
+ SDLActivity.handleNativeState();
+ }
+
+ @Override
+ protected void onResume() {
+ Log.v(TAG, "onResume()");
+ super.onResume();
+ mNextNativeState = NativeState.RESUMED;
+ mIsResumedCalled = true;
+
+ if (SDLActivity.mBrokenLibraries) {
+ return;
+ }
+
+ SDLActivity.handleNativeState();
+ }
+
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+ Log.v(TAG, "onWindowFocusChanged(): " + hasFocus);
+
+ if (SDLActivity.mBrokenLibraries) {
+ return;
+ }
+
+ SDLActivity.mHasFocus = hasFocus;
+ if (hasFocus) {
+ mNextNativeState = NativeState.RESUMED;
+ } else {
+ mNextNativeState = NativeState.PAUSED;
+ }
+
+ SDLActivity.handleNativeState();
+ }
+
+ @Override
+ public void onLowMemory() {
+ Log.v(TAG, "onLowMemory()");
+ super.onLowMemory();
+
+ if (SDLActivity.mBrokenLibraries) {
+ return;
+ }
+
+ SDLActivity.nativeLowMemory();
+ }
+
+ @Override
+ protected void onDestroy() {
+ Log.v(TAG, "onDestroy()");
+
+ if (SDLActivity.mBrokenLibraries) {
+ super.onDestroy();
+ // Reset everything in case the user re opens the app
+ SDLActivity.initialize();
+ return;
+ }
+
+ mNextNativeState = NativeState.PAUSED;
+ SDLActivity.handleNativeState();
+
+ // Send a quit message to the application
+ SDLActivity.mExitCalledFromJava = true;
+ SDLActivity.nativeQuit();
+
+ // Now wait for the SDL thread to quit
+ if (SDLActivity.mSDLThread != null) {
+ try {
+ SDLActivity.mSDLThread.join();
+ } catch(Exception e) {
+ Log.v(TAG, "Problem stopping thread: " + e);
+ }
+ SDLActivity.mSDLThread = null;
+
+ //Log.v(TAG, "Finished waiting for SDL thread");
+ }
+
+ super.onDestroy();
+
+ // Reset everything in case the user re opens the app
+ SDLActivity.initialize();
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+
+ if (SDLActivity.mBrokenLibraries) {
+ return false;
+ }
+
+ int keyCode = event.getKeyCode();
+ // Ignore certain special keys so they're handled by Android
+ if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ||
+ keyCode == KeyEvent.KEYCODE_VOLUME_UP ||
+ keyCode == KeyEvent.KEYCODE_CAMERA ||
+ keyCode == KeyEvent.KEYCODE_ZOOM_IN || /* API 11 */
+ keyCode == KeyEvent.KEYCODE_ZOOM_OUT /* API 11 */
+ ) {
+ return false;
+ }
+ return super.dispatchKeyEvent(event);
+ }
+
+ /* Transition to next state */
+ public static void handleNativeState() {
+
+ if (mNextNativeState == mCurrentNativeState) {
+ // Already in same state, discard.
+ return;
+ }
+
+ // Try a transition to init state
+ if (mNextNativeState == NativeState.INIT) {
+
+ mCurrentNativeState = mNextNativeState;
+ return;
+ }
+
+ // Try a transition to paused state
+ if (mNextNativeState == NativeState.PAUSED) {
+ nativePause();
+ if (mSurface != null)
+ mSurface.handlePause();
+ mCurrentNativeState = mNextNativeState;
+ return;
+ }
+
+ // Try a transition to resumed state
+ if (mNextNativeState == NativeState.RESUMED) {
+ if (mIsSurfaceReady && mHasFocus && mIsResumedCalled) {
+ if (mSDLThread == null) {
+ // This is the entry point to the C app.
+ // Start up the C app thread and enable sensor input for the first time
+ // FIXME: Why aren't we enabling sensor input at start?
+
+ mSDLThread = new Thread(new SDLMain(), "SDLThread");
+ mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, true);
+ mSDLThread.start();
+ }
+
+ nativeResume();
+ mSurface.handleResume();
+ mCurrentNativeState = mNextNativeState;
+ }
+ }
+ }
+
+ /* The native thread has finished */
+ public static void handleNativeExit() {
+ SDLActivity.mSDLThread = null;
+ mSingleton.finish();
+ }
+
+
+ // Messages from the SDLMain thread
+ static final int COMMAND_CHANGE_TITLE = 1;
+ static final int COMMAND_CHANGE_WINDOW_STYLE = 2;
+ static final int COMMAND_TEXTEDIT_HIDE = 3;
+ static final int COMMAND_SET_KEEP_SCREEN_ON = 5;
+
+ protected static final int COMMAND_USER = 0x8000;
+
+ /**
+ * This method is called by SDL if SDL did not handle a message itself.
+ * This happens if a received message contains an unsupported command.
+ * Method can be overwritten to handle Messages in a different class.
+ * @param command the command of the message.
+ * @param param the parameter of the message. May be null.
+ * @return if the message was handled in overridden method.
+ */
+ protected boolean onUnhandledMessage(int command, Object param) {
+ return false;
+ }
+
+ /**
+ * A Handler class for Messages from native SDL applications.
+ * It uses current Activities as target (e.g. for the title).
+ * static to prevent implicit references to enclosing object.
+ */
+ protected static class SDLCommandHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ Context context = SDL.getContext();
+ if (context == null) {
+ Log.e(TAG, "error handling message, getContext() returned null");
+ return;
+ }
+ switch (msg.arg1) {
+ case COMMAND_CHANGE_TITLE:
+ if (context instanceof Activity) {
+ ((Activity) context).setTitle((String)msg.obj);
+ } else {
+ Log.e(TAG, "error handling message, getContext() returned no Activity");
+ }
+ break;
+ case COMMAND_CHANGE_WINDOW_STYLE:
+ if (Build.VERSION.SDK_INT < 19) {
+ // This version of Android doesn't support the immersive fullscreen mode
+ break;
+ }
+/* This needs more testing, per bug 4096 - Enabling fullscreen on Android causes the app to toggle fullscreen mode continuously in a loop
+ ***
+ if (context instanceof Activity) {
+ Window window = ((Activity) context).getWindow();
+ if (window != null) {
+ if ((msg.obj instanceof Integer) && (((Integer) msg.obj).intValue() != 0)) {
+ int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
+ View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
+ View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_FULLSCREEN |
+ View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+ window.getDecorView().setSystemUiVisibility(flags);
+ window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ } else {
+ int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
+ window.getDecorView().setSystemUiVisibility(flags);
+ window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ }
+ }
+ } else {
+ Log.e(TAG, "error handling message, getContext() returned no Activity");
+ }
+***/
+ break;
+ case COMMAND_TEXTEDIT_HIDE:
+ if (mTextEdit != null) {
+ // Note: On some devices setting view to GONE creates a flicker in landscape.
+ // Setting the View's sizes to 0 is similar to GONE but without the flicker.
+ // The sizes will be set to useful values when the keyboard is shown again.
+ mTextEdit.setLayoutParams(new RelativeLayout.LayoutParams(0, 0));
+
+ InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0);
+
+ mScreenKeyboardShown = false;
+ }
+ break;
+ case COMMAND_SET_KEEP_SCREEN_ON:
+ {
+ if (context instanceof Activity) {
+ Window window = ((Activity) context).getWindow();
+ if (window != null) {
+ if ((msg.obj instanceof Integer) && (((Integer) msg.obj).intValue() != 0)) {
+ window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ } else {
+ window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+ }
+ }
+ break;
+ }
+ default:
+ if ((context instanceof SDLActivity) && !((SDLActivity) context).onUnhandledMessage(msg.arg1, msg.obj)) {
+ Log.e(TAG, "error handling message, command is " + msg.arg1);
+ }
+ }
+ }
+ }
+
+ // Handler for the messages
+ Handler commandHandler = new SDLCommandHandler();
+
+ // Send a message from the SDLMain thread
+ boolean sendCommand(int command, Object data) {
+ Message msg = commandHandler.obtainMessage();
+ msg.arg1 = command;
+ msg.obj = data;
+ return commandHandler.sendMessage(msg);
+ }
+
+ // C functions we call
+ public static native int nativeSetupJNI();
+ public static native int nativeRunMain(String library, String function, Object arguments);
+ public static native void nativeLowMemory();
+ public static native void nativeQuit();
+ public static native void nativePause();
+ public static native void nativeResume();
+ public static native void onNativeDropFile(String filename);
+ public static native void onNativeResize(int x, int y, int format, float rate);
+ public static native void onNativeKeyDown(int keycode);
+ public static native void onNativeKeyUp(int keycode);
+ public static native void onNativeKeyboardFocusLost();
+ private static native void onNativeMouse(int button, int action, float x, float y);
+ private static native void onNativeTouch(int touchDevId, int pointerFingerId,
+ int action, float x,
+ float y, float p);
+ public static native void onNativeAccel(float x, float y, float z);
+ public static native void onNativeClipboardChanged();
+ public static native void onNativeSurfaceChanged();
+ public static native void onNativeSurfaceDestroyed();
+ public static native String nativeGetHint(String name);
+ public static native void nativeSetenv(String name, String value);
+
+ public static void onSDLNativeMouse(int button, int action, float x, float y){
+ Log.d(TAG, "onSDLNativeMouse: B: " + button + ", A: " + action + ", X: " + x + ", Y: " + y);
+ onNativeMouse(button, action, x, y);
+ }
+ public static void onSDLNativeTouch(int touchDevId, int pointerFingerId, int action, float x, float y, float p){
+ Log.d(TAG, "onSDLNativeTouch: F: " + pointerFingerId + ", A: " + action + ", X: " + x + ", Y: " + y);
+ onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
+
+ }
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static boolean setActivityTitle(String title) {
+ // Called from SDLMain() thread and can't directly affect the view
+ return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title);
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static void setWindowStyle(boolean fullscreen) {
+ // Called from SDLMain() thread and can't directly affect the view
+ mSingleton.sendCommand(COMMAND_CHANGE_WINDOW_STYLE, fullscreen ? 1 : 0);
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ * This is a static method for JNI convenience, it calls a non-static method
+ * so that is can be overridden
+ */
+ public static void setOrientation(int w, int h, boolean resizable, String hint)
+ {
+ if (mSingleton != null) {
+ mSingleton.setOrientationBis(w, h, resizable, hint);
+ }
+ }
+
+ /**
+ * This can be overridden
+ */
+ public void setOrientationBis(int w, int h, boolean resizable, String hint)
+ {
+ int orientation = -1;
+
+ if (hint.contains("LandscapeRight") && hint.contains("LandscapeLeft")) {
+ orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
+ } else if (hint.contains("LandscapeRight")) {
+ orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+ } else if (hint.contains("LandscapeLeft")) {
+ orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
+ } else if (hint.contains("Portrait") && hint.contains("PortraitUpsideDown")) {
+ orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;
+ } else if (hint.contains("Portrait")) {
+ orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+ } else if (hint.contains("PortraitUpsideDown")) {
+ orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
+ }
+
+ /* no valid hint */
+ if (orientation == -1) {
+ if (resizable) {
+ /* no fixed orientation */
+ } else {
+ if (w > h) {
+ orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
+ } else {
+ orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;
+ }
+ }
+ }
+
+ Log.v("SDL", "setOrientation() orientation=" + orientation + " width=" + w +" height="+ h +" resizable=" + resizable + " hint=" + hint);
+ if (orientation != -1) {
+ mSingleton.setRequestedOrientation(orientation);
+ }
+ }
+
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static boolean isScreenKeyboardShown()
+ {
+ if (mTextEdit == null) {
+ return false;
+ }
+
+ if (!mScreenKeyboardShown) {
+ return false;
+ }
+
+ InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ return imm.isAcceptingText();
+
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static boolean sendMessage(int command, int param) {
+ if (mSingleton == null) {
+ return false;
+ }
+ return mSingleton.sendCommand(command, Integer.valueOf(param));
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static Context getContext() {
+ return SDL.getContext();
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static boolean isAndroidTV() {
+ UiModeManager uiModeManager = (UiModeManager) getContext().getSystemService(UI_MODE_SERVICE);
+ return (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION);
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static DisplayMetrics getDisplayDPI() {
+ return getContext().getResources().getDisplayMetrics();
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static boolean getManifestEnvironmentVariables() {
+ try {
+ ApplicationInfo applicationInfo = getContext().getPackageManager().getApplicationInfo(getContext().getPackageName(), PackageManager.GET_META_DATA);
+ Bundle bundle = applicationInfo.metaData;
+ if (bundle == null) {
+ return false;
+ }
+ String prefix = "SDL_ENV.";
+ final int trimLength = prefix.length();
+ for (String key : bundle.keySet()) {
+ if (key.startsWith(prefix)) {
+ String name = key.substring(trimLength);
+ String value = bundle.get(key).toString();
+ nativeSetenv(name, value);
+ }
+ }
+ /* environment variables set! */
+ return true;
+ } catch (Exception e) {
+ Log.v("SDL", "exception " + e.toString());
+ }
+ return false;
+ }
+
+ static class ShowTextInputTask implements Runnable {
+ /*
+ * This is used to regulate the pan&scan method to have some offset from
+ * the bottom edge of the input region and the top edge of an input
+ * method (soft keyboard)
+ */
+ static final int HEIGHT_PADDING = 15;
+
+ public int x, y, w, h;
+
+ public ShowTextInputTask(int x, int y, int w, int h) {
+ this.x = x;
+ this.y = y;
+ this.w = w;
+ this.h = h;
+ }
+
+ @Override
+ public void run() {
+ RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(w, h + HEIGHT_PADDING);
+ params.leftMargin = x;
+ params.topMargin = y;
+
+ if (mTextEdit == null) {
+ mTextEdit = new DummyEdit(SDL.getContext());
+
+ mLayout.addView(mTextEdit, params);
+ } else {
+ mTextEdit.setLayoutParams(params);
+ }
+
+ mTextEdit.setVisibility(View.VISIBLE);
+ mTextEdit.requestFocus();
+
+ InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.showSoftInput(mTextEdit, 0);
+
+ mScreenKeyboardShown = true;
+ }
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static boolean showTextInput(int x, int y, int w, int h) {
+ // Transfer the task to the main thread as a Runnable
+ return mSingleton.commandHandler.post(new ShowTextInputTask(x, y, w, h));
+ }
+
+ public static boolean isTextInputEvent(KeyEvent event) {
+
+ // Key pressed with Ctrl should be sent as SDL_KEYDOWN/SDL_KEYUP and not SDL_TEXTINPUT
+ if (Build.VERSION.SDK_INT >= 11) {
+ if (event.isCtrlPressed()) {
+ return false;
+ }
+ }
+
+ return event.isPrintingKey() || event.getKeyCode() == KeyEvent.KEYCODE_SPACE;
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static Surface getNativeSurface() {
+ if (SDLActivity.mSurface == null) {
+ return null;
+ }
+ return SDLActivity.mSurface.getNativeSurface();
+ }
+
+ // Input
+
+ /**
+ * This method is called by SDL using JNI.
+ * @return an array which may be empty but is never null.
+ */
+ public static int[] inputGetInputDeviceIds(int sources) {
+ int[] ids = InputDevice.getDeviceIds();
+ int[] filtered = new int[ids.length];
+ int used = 0;
+ for (int i = 0; i < ids.length; ++i) {
+ InputDevice device = InputDevice.getDevice(ids[i]);
+ if ((device != null) && ((device.getSources() & sources) != 0)) {
+ filtered[used++] = device.getId();
+ }
+ }
+ return Arrays.copyOf(filtered, used);
+ }
+
+ // APK expansion files support
+
+ /** com.android.vending.expansion.zipfile.ZipResourceFile object or null. */
+ private static Object expansionFile;
+
+ /** com.android.vending.expansion.zipfile.ZipResourceFile's getInputStream() or null. */
+ private static Method expansionFileMethod;
+
+ /**
+ * This method is called by SDL using JNI.
+ * @return an InputStream on success or null if no expansion file was used.
+ * @throws IOException on errors. Message is set for the SDL error message.
+ */
+ public static InputStream openAPKExpansionInputStream(String fileName) throws IOException {
+ // Get a ZipResourceFile representing a merger of both the main and patch files
+ if (expansionFile == null) {
+ String mainHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_MAIN_FILE_VERSION");
+ if (mainHint == null) {
+ return null; // no expansion use if no main version was set
+ }
+ String patchHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_PATCH_FILE_VERSION");
+ if (patchHint == null) {
+ return null; // no expansion use if no patch version was set
+ }
+
+ Integer mainVersion;
+ Integer patchVersion;
+ try {
+ mainVersion = Integer.valueOf(mainHint);
+ patchVersion = Integer.valueOf(patchHint);
+ } catch (NumberFormatException ex) {
+ ex.printStackTrace();
+ throw new IOException("No valid file versions set for APK expansion files", ex);
+ }
+
+ try {
+ // To avoid direct dependency on Google APK expansion library that is
+ // not a part of Android SDK we access it using reflection
+ expansionFile = Class.forName("com.android.vending.expansion.zipfile.APKExpansionSupport")
+ .getMethod("getAPKExpansionZipFile", Context.class, int.class, int.class)
+ .invoke(null, SDL.getContext(), mainVersion, patchVersion);
+
+ expansionFileMethod = expansionFile.getClass()
+ .getMethod("getInputStream", String.class);
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ expansionFile = null;
+ expansionFileMethod = null;
+ throw new IOException("Could not access APK expansion support library", ex);
+ }
+ }
+
+ // Get an input stream for a known file inside the expansion file ZIPs
+ InputStream fileStream;
+ try {
+ fileStream = (InputStream)expansionFileMethod.invoke(expansionFile, fileName);
+ } catch (Exception ex) {
+ // calling "getInputStream" failed
+ ex.printStackTrace();
+ throw new IOException("Could not open stream from APK expansion file", ex);
+ }
+
+ if (fileStream == null) {
+ // calling "getInputStream" was successful but null was returned
+ throw new IOException("Could not find path in APK expansion file");
+ }
+
+ return fileStream;
+ }
+
+ // Messagebox
+
+ /** Result of current messagebox. Also used for blocking the calling thread. */
+ protected final int[] messageboxSelection = new int[1];
+
+ /** Id of current dialog. */
+ protected int dialogs = 0;
+
+ /**
+ * This method is called by SDL using JNI.
+ * Shows the messagebox from UI thread and block calling thread.
+ * buttonFlags, buttonIds and buttonTexts must have same length.
+ * @param buttonFlags array containing flags for every button.
+ * @param buttonIds array containing id for every button.
+ * @param buttonTexts array containing text for every button.
+ * @param colors null for default or array of length 5 containing colors.
+ * @return button id or -1.
+ */
+ public int messageboxShowMessageBox(
+ final int flags,
+ final String title,
+ final String message,
+ final int[] buttonFlags,
+ final int[] buttonIds,
+ final String[] buttonTexts,
+ final int[] colors) {
+
+ messageboxSelection[0] = -1;
+
+ // sanity checks
+
+ if ((buttonFlags.length != buttonIds.length) && (buttonIds.length != buttonTexts.length)) {
+ return -1; // implementation broken
+ }
+
+ // collect arguments for Dialog
+
+ final Bundle args = new Bundle();
+ args.putInt("flags", flags);
+ args.putString("title", title);
+ args.putString("message", message);
+ args.putIntArray("buttonFlags", buttonFlags);
+ args.putIntArray("buttonIds", buttonIds);
+ args.putStringArray("buttonTexts", buttonTexts);
+ args.putIntArray("colors", colors);
+
+ // trigger Dialog creation on UI thread
+
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ showDialog(dialogs++, args);
+ }
+ });
+
+ // block the calling thread
+
+ synchronized (messageboxSelection) {
+ try {
+ messageboxSelection.wait();
+ } catch (InterruptedException ex) {
+ ex.printStackTrace();
+ return -1;
+ }
+ }
+
+ // return selected value
+
+ return messageboxSelection[0];
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int ignore, Bundle args) {
+
+ // TODO set values from "flags" to messagebox dialog
+
+ // get colors
+
+ int[] colors = args.getIntArray("colors");
+ int backgroundColor;
+ int textColor;
+ int buttonBorderColor;
+ int buttonBackgroundColor;
+ int buttonSelectedColor;
+ if (colors != null) {
+ int i = -1;
+ backgroundColor = colors[++i];
+ textColor = colors[++i];
+ buttonBorderColor = colors[++i];
+ buttonBackgroundColor = colors[++i];
+ buttonSelectedColor = colors[++i];
+ } else {
+ backgroundColor = Color.TRANSPARENT;
+ textColor = Color.TRANSPARENT;
+ buttonBorderColor = Color.TRANSPARENT;
+ buttonBackgroundColor = Color.TRANSPARENT;
+ buttonSelectedColor = Color.TRANSPARENT;
+ }
+
+ // create dialog with title and a listener to wake up calling thread
+
+ final Dialog dialog = new Dialog(this);
+ dialog.setTitle(args.getString("title"));
+ dialog.setCancelable(false);
+ dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
+ @Override
+ public void onDismiss(DialogInterface unused) {
+ synchronized (messageboxSelection) {
+ messageboxSelection.notify();
+ }
+ }
+ });
+
+ // create text
+
+ TextView message = new TextView(this);
+ message.setGravity(Gravity.CENTER);
+ message.setText(args.getString("message"));
+ if (textColor != Color.TRANSPARENT) {
+ message.setTextColor(textColor);
+ }
+
+ // create buttons
+
+ int[] buttonFlags = args.getIntArray("buttonFlags");
+ int[] buttonIds = args.getIntArray("buttonIds");
+ String[] buttonTexts = args.getStringArray("buttonTexts");
+
+ final SparseArray