diff --git a/AndroidManifest.xml b/AndroidManifest.xml
new file mode 100644
index 0000000..edf2d8f
--- /dev/null
+++ b/AndroidManifest.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gen/se/bitcraze/crazyfliecontrol/BuildConfig.java b/gen/se/bitcraze/crazyfliecontrol/BuildConfig.java
new file mode 100644
index 0000000..f087bd2
--- /dev/null
+++ b/gen/se/bitcraze/crazyfliecontrol/BuildConfig.java
@@ -0,0 +1,6 @@
+/** Automatically generated file. DO NOT MODIFY */
+package se.bitcraze.crazyfliecontrol;
+
+public final class BuildConfig {
+ public final static boolean DEBUG = true;
+}
\ No newline at end of file
diff --git a/gen/se/bitcraze/crazyfliecontrol/R.java b/gen/se/bitcraze/crazyfliecontrol/R.java
new file mode 100644
index 0000000..ef2d481
--- /dev/null
+++ b/gen/se/bitcraze/crazyfliecontrol/R.java
@@ -0,0 +1,40 @@
+/* AUTO-GENERATED FILE. DO NOT MODIFY.
+ *
+ * This class was automatically generated by the
+ * aapt tool from the resource data it found. It
+ * should not be modified by hand.
+ */
+
+package se.bitcraze.crazyfliecontrol;
+
+public final class R {
+ public static final class attr {
+ }
+ public static final class drawable {
+ public static final int ic_action_search=0x7f020000;
+ public static final int ic_launcher=0x7f020001;
+ }
+ public static final class id {
+ public static final int joysticks=0x7f070000;
+ public static final int menu_connect=0x7f070001;
+ public static final int menu_disconnect=0x7f070002;
+ }
+ public static final class layout {
+ public static final int activity_main=0x7f030000;
+ }
+ public static final class menu {
+ public static final int activity_main=0x7f060000;
+ }
+ public static final class string {
+ public static final int app_name=0x7f040000;
+ public static final int connect=0x7f040004;
+ public static final int copter_channel=0x7f040005;
+ public static final int hello_world=0x7f040001;
+ public static final int menu_connect=0x7f040006;
+ public static final int menu_disconnect=0x7f040002;
+ public static final int title_activity_main=0x7f040003;
+ }
+ public static final class style {
+ public static final int AppTheme=0x7f050000;
+ }
+}
diff --git a/ic_launcher-web.png b/ic_launcher-web.png
new file mode 100644
index 0000000..518339f
Binary files /dev/null and b/ic_launcher-web.png differ
diff --git a/libs/android-support-v4.jar b/libs/android-support-v4.jar
new file mode 100644
index 0000000..feaf44f
Binary files /dev/null and b/libs/android-support-v4.jar differ
diff --git a/res/drawable-hdpi/ic_action_search.png b/res/drawable-hdpi/ic_action_search.png
new file mode 100644
index 0000000..67de12d
Binary files /dev/null and b/res/drawable-hdpi/ic_action_search.png differ
diff --git a/res/drawable-hdpi/ic_launcher.png b/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..fe13414
Binary files /dev/null and b/res/drawable-hdpi/ic_launcher.png differ
diff --git a/res/drawable-ldpi/ic_launcher.png b/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..9f554ca
Binary files /dev/null and b/res/drawable-ldpi/ic_launcher.png differ
diff --git a/res/drawable-mdpi/ic_action_search.png b/res/drawable-mdpi/ic_action_search.png
new file mode 100644
index 0000000..134d549
Binary files /dev/null and b/res/drawable-mdpi/ic_action_search.png differ
diff --git a/res/drawable-mdpi/ic_launcher.png b/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..58f379f
Binary files /dev/null and b/res/drawable-mdpi/ic_launcher.png differ
diff --git a/res/drawable-xhdpi/ic_action_search.png b/res/drawable-xhdpi/ic_action_search.png
new file mode 100644
index 0000000..d699c6b
Binary files /dev/null and b/res/drawable-xhdpi/ic_action_search.png differ
diff --git a/res/drawable-xhdpi/ic_launcher.png b/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..220c12a
Binary files /dev/null and b/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/res/layout/activity_main.xml b/res/layout/activity_main.xml
new file mode 100644
index 0000000..122c562
--- /dev/null
+++ b/res/layout/activity_main.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
diff --git a/res/menu/activity_main.xml b/res/menu/activity_main.xml
new file mode 100644
index 0000000..53062f5
--- /dev/null
+++ b/res/menu/activity_main.xml
@@ -0,0 +1,7 @@
+
diff --git a/res/values-v11/styles.xml b/res/values-v11/styles.xml
new file mode 100644
index 0000000..d408cbc
--- /dev/null
+++ b/res/values-v11/styles.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/res/values-v14/styles.xml b/res/values-v14/styles.xml
new file mode 100644
index 0000000..1c089a7
--- /dev/null
+++ b/res/values-v14/styles.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
new file mode 100644
index 0000000..8299710
--- /dev/null
+++ b/res/values/strings.xml
@@ -0,0 +1,11 @@
+
+
+ CrazyflieControl
+ Hello world!
+ Disconnect
+ Crazyflie Control
+ Connect
+ Copter channel:
+ Connect
+
+
\ No newline at end of file
diff --git a/res/values/styles.xml b/res/values/styles.xml
new file mode 100644
index 0000000..4dba0d0
--- /dev/null
+++ b/res/values/styles.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/com/MobileAnarchy/Android/Widgets/Joystick/DualJoystickView.java b/src/com/MobileAnarchy/Android/Widgets/Joystick/DualJoystickView.java
new file mode 100644
index 0000000..bb419d4
--- /dev/null
+++ b/src/com/MobileAnarchy/Android/Widgets/Joystick/DualJoystickView.java
@@ -0,0 +1,147 @@
+package com.MobileAnarchy.Android.Widgets.Joystick;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+public class DualJoystickView extends LinearLayout {
+ @SuppressWarnings("unused")
+ private static final String TAG = DualJoystickView.class.getSimpleName();
+
+ private final boolean D = false;
+ private Paint dbgPaint1;
+
+ private JoystickView stickL;
+ private JoystickView stickR;
+
+ private View pad;
+
+ public DualJoystickView(Context context) {
+ super(context);
+ stickL = new JoystickView(context);
+ stickR = new JoystickView(context);
+ initDualJoystickView();
+ }
+
+ public DualJoystickView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ stickL = new JoystickView(context, attrs);
+ stickR = new JoystickView(context, attrs);
+ initDualJoystickView();
+ }
+
+ private void initDualJoystickView() {
+ setOrientation(LinearLayout.HORIZONTAL);
+
+ if ( D ) {
+ dbgPaint1 = new Paint(Paint.ANTI_ALIAS_FLAG);
+ dbgPaint1.setColor(Color.CYAN);
+ dbgPaint1.setStrokeWidth(1);
+ dbgPaint1.setStyle(Paint.Style.STROKE);
+ }
+
+ pad = new View(getContext());
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ removeView(stickL);
+ removeView(stickR);
+
+ float padW = getMeasuredWidth()-(getMeasuredHeight()*2);
+ int joyWidth = (int) ((getMeasuredWidth()-padW)/2);
+ LayoutParams joyLParams = new LayoutParams(joyWidth,getMeasuredHeight());
+
+ stickL.setLayoutParams(joyLParams);
+ stickR.setLayoutParams(joyLParams);
+
+ stickL.TAG = "L";
+ stickR.TAG = "R";
+ stickL.setPointerId(JoystickView.INVALID_POINTER_ID);
+ stickR.setPointerId(JoystickView.INVALID_POINTER_ID);
+
+ addView(stickL);
+
+ ViewGroup.LayoutParams padLParams = new ViewGroup.LayoutParams((int) padW,getMeasuredHeight());
+ removeView(pad);
+ pad.setLayoutParams(padLParams);
+ addView(pad);
+
+ addView(stickR);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+ stickR.setTouchOffset(stickR.getLeft(), stickR.getTop());
+ }
+
+ public void setAutoReturnToCenter(boolean left, boolean right) {
+ stickL.setAutoReturnToCenter(left);
+ stickR.setAutoReturnToCenter(right);
+ }
+
+ public void setOnJostickMovedListener(JoystickMovedListener left, JoystickMovedListener right) {
+ stickL.setOnJostickMovedListener(left);
+ stickR.setOnJostickMovedListener(right);
+ }
+
+ public void setOnJostickClickedListener(JoystickClickedListener left, JoystickClickedListener right) {
+ stickL.setOnJostickClickedListener(left);
+ stickR.setOnJostickClickedListener(right);
+ }
+
+ public void setYAxisInverted(boolean leftYAxisInverted, boolean rightYAxisInverted) {
+ stickL.setYAxisInverted(leftYAxisInverted);
+ stickL.setYAxisInverted(rightYAxisInverted);
+ }
+
+ public void setMovementConstraint(int movementConstraint) {
+ stickL.setMovementConstraint(movementConstraint);
+ stickR.setMovementConstraint(movementConstraint);
+ }
+
+ public void setMovementRange(float movementRangeLeft, float movementRangeRight) {
+ stickL.setMovementRange(movementRangeLeft);
+ stickR.setMovementRange(movementRangeRight);
+ }
+
+ public void setMoveResolution(float leftMoveResolution, float rightMoveResolution) {
+ stickL.setMoveResolution(leftMoveResolution);
+ stickR.setMoveResolution(rightMoveResolution);
+ }
+
+ public void setUserCoordinateSystem(int leftCoordinateSystem, int rightCoordinateSystem) {
+ stickL.setUserCoordinateSystem(leftCoordinateSystem);
+ stickR.setUserCoordinateSystem(rightCoordinateSystem);
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
+ if (D) {
+ canvas.drawRect(1, 1, getMeasuredWidth()-1, getMeasuredHeight()-1, dbgPaint1);
+ }
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ boolean l = stickL.dispatchTouchEvent(ev);
+ boolean r = stickR.dispatchTouchEvent(ev);
+ return l || r;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ boolean l = stickL.onTouchEvent(ev);
+ boolean r = stickR.onTouchEvent(ev);
+ return l || r;
+ }
+}
diff --git a/src/com/MobileAnarchy/Android/Widgets/Joystick/JoystickClickedListener.java b/src/com/MobileAnarchy/Android/Widgets/Joystick/JoystickClickedListener.java
new file mode 100644
index 0000000..1288289
--- /dev/null
+++ b/src/com/MobileAnarchy/Android/Widgets/Joystick/JoystickClickedListener.java
@@ -0,0 +1,6 @@
+package com.MobileAnarchy.Android.Widgets.Joystick;
+
+public interface JoystickClickedListener {
+ public void OnClicked();
+ public void OnReleased();
+}
diff --git a/src/com/MobileAnarchy/Android/Widgets/Joystick/JoystickMovedListener.java b/src/com/MobileAnarchy/Android/Widgets/Joystick/JoystickMovedListener.java
new file mode 100644
index 0000000..346f2ef
--- /dev/null
+++ b/src/com/MobileAnarchy/Android/Widgets/Joystick/JoystickMovedListener.java
@@ -0,0 +1,7 @@
+package com.MobileAnarchy.Android.Widgets.Joystick;
+
+public interface JoystickMovedListener {
+ public void OnMoved(int pan, int tilt);
+ public void OnReleased();
+ public void OnReturnedToCenter();
+}
diff --git a/src/com/MobileAnarchy/Android/Widgets/Joystick/JoystickView.java b/src/com/MobileAnarchy/Android/Widgets/Joystick/JoystickView.java
new file mode 100644
index 0000000..3411b8a
--- /dev/null
+++ b/src/com/MobileAnarchy/Android/Widgets/Joystick/JoystickView.java
@@ -0,0 +1,520 @@
+package com.MobileAnarchy.Android.Widgets.Joystick;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.util.FloatMath;
+import android.util.Log;
+import android.view.HapticFeedbackConstants;
+import android.view.MotionEvent;
+import android.view.View;
+
+public class JoystickView extends View {
+ public static final int INVALID_POINTER_ID = -1;
+
+ // =========================================
+ // Private Members
+ // =========================================
+ private final boolean D = false;
+ String TAG = "JoystickView";
+
+ private Paint dbgPaint1;
+ private Paint dbgPaint2;
+
+ private Paint bgPaint;
+ private Paint handlePaint;
+
+ private int innerPadding;
+ private int bgRadius;
+ private int handleRadius;
+ private int movementRadius;
+ private int handleInnerBoundaries;
+
+ private JoystickMovedListener moveListener;
+ private JoystickClickedListener clickListener;
+
+ //# of pixels movement required between reporting to the listener
+ private float moveResolution;
+
+ private boolean yAxisInverted;
+ private boolean autoReturnToCenter;
+
+ //Max range of movement in user coordinate system
+ public final static int CONSTRAIN_BOX = 0;
+ public final static int CONSTRAIN_CIRCLE = 1;
+ private int movementConstraint;
+ private float movementRange;
+
+ public final static int COORDINATE_CARTESIAN = 0; //Regular cartesian coordinates
+ public final static int COORDINATE_DIFFERENTIAL = 1; //Uses polar rotation of 45 degrees to calc differential drive paramaters
+ private int userCoordinateSystem;
+
+ //Records touch pressure for click handling
+ private float touchPressure;
+ private boolean clicked;
+ private float clickThreshold;
+
+ //Last touch point in view coordinates
+ private int pointerId = INVALID_POINTER_ID;
+ private float touchX, touchY;
+
+ //Last reported position in view coordinates (allows different reporting sensitivities)
+ private float reportX, reportY;
+
+ //Handle center in view coordinates
+ private float handleX, handleY;
+
+ //Center of the view in view coordinates
+ private int cX, cY;
+
+ //Size of the view in view coordinates
+ private int dimX; //, dimY;
+
+ //Cartesian coordinates of last touch point - joystick center is (0,0)
+ private int cartX, cartY;
+
+ //Polar coordinates of the touch point from joystick center
+ private double radial;
+ private double angle;
+
+ //User coordinates of last touch point
+ private int userX, userY;
+
+ //Offset co-ordinates (used when touch events are received from parent's coordinate origin)
+ private int offsetX;
+ private int offsetY;
+
+ // =========================================
+ // Constructors
+ // =========================================
+
+ public JoystickView(Context context) {
+ super(context);
+ initJoystickView();
+ }
+
+ public JoystickView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initJoystickView();
+ }
+
+ public JoystickView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ initJoystickView();
+ }
+
+ // =========================================
+ // Initialization
+ // =========================================
+
+ private void initJoystickView() {
+ setFocusable(true);
+
+ dbgPaint1 = new Paint(Paint.ANTI_ALIAS_FLAG);
+ dbgPaint1.setColor(Color.RED);
+ dbgPaint1.setStrokeWidth(1);
+ dbgPaint1.setStyle(Paint.Style.STROKE);
+
+ dbgPaint2 = new Paint(Paint.ANTI_ALIAS_FLAG);
+ dbgPaint2.setColor(Color.GREEN);
+ dbgPaint2.setStrokeWidth(1);
+ dbgPaint2.setStyle(Paint.Style.STROKE);
+
+ bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ bgPaint.setColor(Color.GRAY);
+ bgPaint.setStrokeWidth(1);
+ bgPaint.setStyle(Paint.Style.FILL_AND_STROKE);
+
+ handlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ handlePaint.setColor(Color.DKGRAY);
+ handlePaint.setStrokeWidth(1);
+ handlePaint.setStyle(Paint.Style.FILL_AND_STROKE);
+
+ innerPadding = 10;
+
+ setMovementRange(10);
+ setMoveResolution(1.0f);
+ setClickThreshold(0.4f);
+ setYAxisInverted(true);
+ setUserCoordinateSystem(COORDINATE_CARTESIAN);
+ setAutoReturnToCenter(true);
+ }
+
+ public void setAutoReturnToCenter(boolean autoReturnToCenter) {
+ this.autoReturnToCenter = autoReturnToCenter;
+ }
+
+ public boolean isAutoReturnToCenter() {
+ return autoReturnToCenter;
+ }
+
+ public void setUserCoordinateSystem(int userCoordinateSystem) {
+ if (userCoordinateSystem < COORDINATE_CARTESIAN || movementConstraint > COORDINATE_DIFFERENTIAL)
+ Log.e(TAG, "invalid value for userCoordinateSystem");
+ else
+ this.userCoordinateSystem = userCoordinateSystem;
+ }
+
+ public int getUserCoordinateSystem() {
+ return userCoordinateSystem;
+ }
+
+ public void setMovementConstraint(int movementConstraint) {
+ if (movementConstraint < CONSTRAIN_BOX || movementConstraint > CONSTRAIN_CIRCLE)
+ Log.e(TAG, "invalid value for movementConstraint");
+ else
+ this.movementConstraint = movementConstraint;
+ }
+
+ public int getMovementConstraint() {
+ return movementConstraint;
+ }
+
+ public boolean isYAxisInverted() {
+ return yAxisInverted;
+ }
+
+ public void setYAxisInverted(boolean yAxisInverted) {
+ this.yAxisInverted = yAxisInverted;
+ }
+
+ /**
+ * Set the pressure sensitivity for registering a click
+ * @param clickThreshold threshold 0...1.0f inclusive. 0 will cause clicks to never be reported, 1.0 is a very hard click
+ */
+ public void setClickThreshold(float clickThreshold) {
+ if (clickThreshold < 0 || clickThreshold > 1.0f)
+ Log.e(TAG, "clickThreshold must range from 0...1.0f inclusive");
+ else
+ this.clickThreshold = clickThreshold;
+ }
+
+ public float getClickThreshold() {
+ return clickThreshold;
+ }
+
+ public void setMovementRange(float movementRange) {
+ this.movementRange = movementRange;
+ }
+
+ public float getMovementRange() {
+ return movementRange;
+ }
+
+ public void setMoveResolution(float moveResolution) {
+ this.moveResolution = moveResolution;
+ }
+
+ public float getMoveResolution() {
+ return moveResolution;
+ }
+
+ // =========================================
+ // Public Methods
+ // =========================================
+
+ public void setOnJostickMovedListener(JoystickMovedListener listener) {
+ this.moveListener = listener;
+ }
+
+ public void setOnJostickClickedListener(JoystickClickedListener listener) {
+ this.clickListener = listener;
+ }
+
+ // =========================================
+ // Drawing Functionality
+ // =========================================
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // Here we make sure that we have a perfect circle
+ int measuredWidth = measure(widthMeasureSpec);
+ int measuredHeight = measure(heightMeasureSpec);
+ setMeasuredDimension(measuredWidth, measuredHeight);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+
+ int d = Math.min(getMeasuredWidth(), getMeasuredHeight());
+
+ dimX = d;
+ /*dimY = d;*/
+
+ cX = d / 2;
+ cY = d / 2;
+
+ bgRadius = dimX/2 - innerPadding;
+ handleRadius = (int)(d * 0.25);
+ handleInnerBoundaries = handleRadius;
+ movementRadius = Math.min(cX, cY) - handleInnerBoundaries;
+ }
+
+ private int measure(int measureSpec) {
+ int result = 0;
+ // Decode the measurement specifications.
+ int specMode = MeasureSpec.getMode(measureSpec);
+ int specSize = MeasureSpec.getSize(measureSpec);
+ if (specMode == MeasureSpec.UNSPECIFIED) {
+ // Return a default size of 200 if no bounds are specified.
+ result = 200;
+ } else {
+ // As you want to fill the available space
+ // always return the full available bounds.
+ result = specSize;
+ }
+ return result;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ canvas.save();
+ // Draw the background
+ canvas.drawCircle(cX, cY, bgRadius, bgPaint);
+
+ // Draw the handle
+ handleX = touchX + cX;
+ handleY = touchY + cY;
+ canvas.drawCircle(handleX, handleY, handleRadius, handlePaint);
+
+ if (D) {
+ canvas.drawRect(1, 1, getMeasuredWidth()-1, getMeasuredHeight()-1, dbgPaint1);
+
+ canvas.drawCircle(handleX, handleY, 3, dbgPaint1);
+
+ if ( movementConstraint == CONSTRAIN_CIRCLE ) {
+ canvas.drawCircle(cX, cY, this.movementRadius, dbgPaint1);
+ }
+ else {
+ canvas.drawRect(cX-movementRadius, cY-movementRadius, cX+movementRadius, cY+movementRadius, dbgPaint1);
+ }
+
+ //Origin to touch point
+ canvas.drawLine(cX, cY, handleX, handleY, dbgPaint2);
+
+ int baseY = (int) (touchY < 0 ? cY + handleRadius : cY - handleRadius);
+ canvas.drawText(String.format("%s (%.0f,%.0f)", TAG, touchX, touchY), handleX-20, baseY-7, dbgPaint2);
+ canvas.drawText("("+ String.format("%.0f, %.1f", radial, angle * 57.2957795) + (char) 0x00B0 + ")", handleX-20, baseY+15, dbgPaint2);
+ }
+
+// Log.d(TAG, String.format("touch(%f,%f)", touchX, touchY));
+// Log.d(TAG, String.format("onDraw(%.1f,%.1f)\n\n", handleX, handleY));
+ canvas.restore();
+ }
+
+ // Constrain touch within a box
+ private void constrainBox() {
+ touchX = Math.max(Math.min(touchX, movementRadius), -movementRadius);
+ touchY = Math.max(Math.min(touchY, movementRadius), -movementRadius);
+ }
+
+ // Constrain touch within a circle
+ private void constrainCircle() {
+ float diffX = touchX;
+ float diffY = touchY;
+ double radial = FloatMath.sqrt((diffX*diffX) + (diffY*diffY));
+ if ( radial > movementRadius ) {
+ touchX = (int)((diffX / radial) * movementRadius);
+ touchY = (int)((diffY / radial) * movementRadius);
+ }
+ }
+
+ public void setPointerId(int id) {
+ this.pointerId = id;
+ }
+
+ public int getPointerId() {
+ return pointerId;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ final int action = ev.getAction();
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_MOVE: {
+ return processMoveEvent(ev);
+ }
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP: {
+ if ( pointerId != INVALID_POINTER_ID ) {
+// Log.d(TAG, "ACTION_UP");
+ returnHandleToCenter();
+ setPointerId(INVALID_POINTER_ID);
+ }
+ break;
+ }
+ case MotionEvent.ACTION_POINTER_UP: {
+ if ( pointerId != INVALID_POINTER_ID ) {
+ final int pointerIndex = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ final int pointerId = ev.getPointerId(pointerIndex);
+ if ( pointerId == this.pointerId ) {
+// Log.d(TAG, "ACTION_POINTER_UP: " + pointerId);
+ returnHandleToCenter();
+ setPointerId(INVALID_POINTER_ID);
+ return true;
+ }
+ }
+ break;
+ }
+ case MotionEvent.ACTION_DOWN: {
+ if ( pointerId == INVALID_POINTER_ID ) {
+ int x = (int) ev.getX();
+ if ( x >= offsetX && x < offsetX + dimX ) {
+ setPointerId(ev.getPointerId(0));
+// Log.d(TAG, "ACTION_DOWN: " + getPointerId());
+ return true;
+ }
+ }
+ break;
+ }
+ case MotionEvent.ACTION_POINTER_DOWN: {
+ if ( pointerId == INVALID_POINTER_ID ) {
+ final int pointerIndex = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ final int pointerId = ev.getPointerId(pointerIndex);
+ int x = (int) ev.getX(pointerId);
+ if ( x >= offsetX && x < offsetX + dimX ) {
+// Log.d(TAG, "ACTION_POINTER_DOWN: " + pointerId);
+ setPointerId(pointerId);
+ return true;
+ }
+ }
+ break;
+ }
+ }
+ return false;
+ }
+
+ private boolean processMoveEvent(MotionEvent ev) {
+ if ( pointerId != INVALID_POINTER_ID ) {
+ final int pointerIndex = ev.findPointerIndex(pointerId);
+
+ // Translate touch position to center of view
+ float x = ev.getX(pointerIndex);
+ touchX = x - cX - offsetX;
+ float y = ev.getY(pointerIndex);
+ touchY = y - cY - offsetY;
+
+// Log.d(TAG, String.format("ACTION_MOVE: (%03.0f, %03.0f) => (%03.0f, %03.0f)", x, y, touchX, touchY));
+
+ reportOnMoved();
+ invalidate();
+
+ touchPressure = ev.getPressure(pointerIndex);
+ reportOnPressure();
+
+ return true;
+ }
+ return false;
+ }
+
+ private void reportOnMoved() {
+ if ( movementConstraint == CONSTRAIN_CIRCLE )
+ constrainCircle();
+ else
+ constrainBox();
+
+ calcUserCoordinates();
+
+ if (moveListener != null) {
+ boolean rx = Math.abs(touchX - reportX) >= moveResolution;
+ boolean ry = Math.abs(touchY - reportY) >= moveResolution;
+ if (rx || ry) {
+ this.reportX = touchX;
+ this.reportY = touchY;
+
+// Log.d(TAG, String.format("moveListener.OnMoved(%d,%d)", (int)userX, (int)userY));
+ moveListener.OnMoved(userX, userY);
+ }
+ }
+ }
+
+ private void calcUserCoordinates() {
+ //First convert to cartesian coordinates
+ cartX = (int)(touchX / movementRadius * movementRange);
+ cartY = (int)(touchY / movementRadius * movementRange);
+
+ radial = Math.sqrt((cartX*cartX) + (cartY*cartY));
+ angle = Math.atan2(cartY, cartX);
+
+ //Invert Y axis if requested
+ if ( !yAxisInverted )
+ cartY *= -1;
+
+ if ( userCoordinateSystem == COORDINATE_CARTESIAN ) {
+ userX = cartX;
+ userY = cartY;
+ }
+ else if ( userCoordinateSystem == COORDINATE_DIFFERENTIAL ) {
+ userX = cartY + cartX / 4;
+ userY = cartY - cartX / 4;
+
+ if ( userX < -movementRange )
+ userX = (int)-movementRange;
+ if ( userX > movementRange )
+ userX = (int)movementRange;
+
+ if ( userY < -movementRange )
+ userY = (int)-movementRange;
+ if ( userY > movementRange )
+ userY = (int)movementRange;
+ }
+
+ }
+
+ //Simple pressure click
+ private void reportOnPressure() {
+// Log.d(TAG, String.format("touchPressure=%.2f", this.touchPressure));
+ if ( clickListener != null ) {
+ if ( clicked && touchPressure < clickThreshold ) {
+ clickListener.OnReleased();
+ this.clicked = false;
+// Log.d(TAG, "reset click");
+ invalidate();
+ }
+ else if ( !clicked && touchPressure >= clickThreshold ) {
+ clicked = true;
+ clickListener.OnClicked();
+// Log.d(TAG, "click");
+ invalidate();
+ performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+ }
+ }
+ }
+
+ private void returnHandleToCenter() {
+ if ( autoReturnToCenter ) {
+ final int numberOfFrames = 5;
+ final double intervalsX = (0 - touchX) / numberOfFrames;
+ final double intervalsY = (0 - touchY) / numberOfFrames;
+
+ for (int i = 0; i < numberOfFrames; i++) {
+ final int j = i;
+ postDelayed(new Runnable() {
+ public void run() {
+ touchX += intervalsX;
+ touchY += intervalsY;
+
+ reportOnMoved();
+ invalidate();
+
+ if (moveListener != null && j == numberOfFrames - 1) {
+ moveListener.OnReturnedToCenter();
+ }
+ }
+ }, i * 40);
+ }
+
+ if (moveListener != null) {
+ moveListener.OnReleased();
+ }
+ }
+ }
+
+ public void setTouchOffset(int x, int y) {
+ offsetX = x;
+ offsetY = y;
+ }
+}
diff --git a/src/se/bitcraze/crazyfliecontrol/MainActivity.java b/src/se/bitcraze/crazyfliecontrol/MainActivity.java
new file mode 100644
index 0000000..9c8c32f
--- /dev/null
+++ b/src/se/bitcraze/crazyfliecontrol/MainActivity.java
@@ -0,0 +1,27 @@
+package se.bitcraze.crazyfliecontrol;
+
+import com.MobileAnarchy.Android.Widgets.Joystick.DualJoystickView;
+import android.os.Bundle;
+import android.app.Activity;
+import android.content.pm.ActivityInfo;
+import android.view.Menu;
+
+public class MainActivity extends Activity {
+
+ DualJoystickView joysticks;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+
+ this.joysticks = (DualJoystickView) findViewById(R.id.joysticks);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.activity_main, menu);
+ return true;
+ }
+}