The {@link TextView} class also
+ * takes care of its own scrolling, so does not require a TwoDScrollView, but
+ * using the two together is possible to achieve the effect of a text view
+ * within a larger container.
+ */
+public class ScrollView2D extends FrameLayout {
+
+ // interface to receive notifications when the view is scrolled
+ public interface ScrollView2DListener {
+ abstract void onScrollChanged(ScrollView2D scrollView, int x, int y, int oldx, int oldy);
+ }
+
+ private ScrollView2DListener scrollView2DListener = null;
+
+ static final int ANIMATED_SCROLL_GAP = 250;
+ static final float MAX_SCROLL_FACTOR = 0.5f;
+
+ private long mLastScroll;
+
+ private final Rect mTempRect = new Rect();
+ private Scroller mScroller;
+
+ private boolean scrollEnabled = true;
+
+ /**
+ * Flag to indicate that we are moving focus ourselves. This is so the
+ * code that watches for focus changes initiated outside this TwoDScrollView
+ * knows that it does not have to do anything.
+ */
+ private boolean mTwoDScrollViewMovedFocus;
+
+ /**
+ * Position of the last motion event.
+ */
+ private float mLastMotionY;
+ private float mLastMotionX;
+
+ /**
+ * True when the layout has changed but the traversal has not come through yet.
+ * Ideally the view hierarchy would keep track of this for us.
+ */
+ private boolean mIsLayoutDirty = true;
+
+ /**
+ * The child to give focus to in the event that a child has requested focus while the
+ * layout is dirty. This prevents the scroll from being wrong if the child has not been
+ * laid out before requesting focus.
+ */
+ private View mChildToScrollTo = null;
+
+ /**
+ * True if the user is currently dragging this TwoDScrollView around. This is
+ * not the same as 'is being flinged', which can be checked by
+ * mScroller.isFinished() (flinging begins when the user lifts his finger).
+ */
+ private boolean mIsBeingDragged = false;
+
+ /**
+ * Determines speed during touch scrolling
+ */
+ private VelocityTracker mVelocityTracker;
+
+ /**
+ * Whether arrow scrolling is animated.
+ */
+ private int mTouchSlop;
+ private int mMinimumVelocity;
+ private int mMaximumVelocity;
+
+ public ScrollView2D(Context context) {
+ super(context);
+ initTwoDScrollView();
+ }
+
+ public ScrollView2D(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initTwoDScrollView();
+ }
+
+ public ScrollView2D(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ initTwoDScrollView();
+ }
+
+ @Override
+ protected float getTopFadingEdgeStrength() {
+ if (getChildCount() == 0) {
+ return 0.0f;
+ }
+ final int length = getVerticalFadingEdgeLength();
+ if (getScrollY() < length) {
+ return getScrollY() / (float) length;
+ }
+ return 1.0f;
+ }
+
+ @Override
+ protected float getBottomFadingEdgeStrength() {
+ if (getChildCount() == 0) {
+ return 0.0f;
+ }
+ final int length = getVerticalFadingEdgeLength();
+ final int bottomEdge = getHeight() - getPaddingBottom();
+ final int span = getChildAt(0).getBottom() - getScrollY() - bottomEdge;
+ if (span < length) {
+ return span / (float) length;
+ }
+ return 1.0f;
+ }
+
+ @Override
+ protected float getLeftFadingEdgeStrength() {
+ if (getChildCount() == 0) {
+ return 0.0f;
+ }
+ final int length = getHorizontalFadingEdgeLength();
+ if (getScrollX() < length) {
+ return getScrollX() / (float) length;
+ }
+ return 1.0f;
+ }
+
+ @Override
+ protected float getRightFadingEdgeStrength() {
+ if (getChildCount() == 0) {
+ return 0.0f;
+ }
+ final int length = getHorizontalFadingEdgeLength();
+ final int rightEdge = getWidth() - getPaddingRight();
+ final int span = getChildAt(0).getRight() - getScrollX() - rightEdge;
+ if (span < length) {
+ return span / (float) length;
+ }
+ return 1.0f;
+ }
+
+ /**
+ * Disable/Enable scrolling
+ */
+ public void setScrollEnabled(boolean enable)
+ {
+ scrollEnabled = enable;
+ }
+
+ /**
+ * @return The maximum amount this scroll view will scroll in response to
+ * an arrow event.
+ */
+ public int getMaxScrollAmountVertical() {
+ return (int) (MAX_SCROLL_FACTOR * getHeight());
+ }
+ public int getMaxScrollAmountHorizontal() {
+ return (int) (MAX_SCROLL_FACTOR * getWidth());
+ }
+
+ private void initTwoDScrollView() {
+ mScroller = new Scroller(getContext());
+ setFocusable(true);
+ setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
+ setWillNotDraw(false);
+ final ViewConfiguration configuration = ViewConfiguration.get(getContext());
+ mTouchSlop = configuration.getScaledTouchSlop();
+ mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
+ mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
+ }
+
+ @Override
+ public void addView(View child) {
+ if (getChildCount() > 0) {
+ throw new IllegalStateException("TwoDScrollView can host only one direct child");
+ }
+ super.addView(child);
+ }
+
+ @Override
+ public void addView(View child, int index) {
+ if (getChildCount() > 0) {
+ throw new IllegalStateException("TwoDScrollView can host only one direct child");
+ }
+ super.addView(child, index);
+ }
+
+ @Override
+ public void addView(View child, ViewGroup.LayoutParams params) {
+ if (getChildCount() > 0) {
+ throw new IllegalStateException("TwoDScrollView can host only one direct child");
+ }
+ super.addView(child, params);
+ }
+
+ @Override
+ public void addView(View child, int index, ViewGroup.LayoutParams params) {
+ if (getChildCount() > 0) {
+ throw new IllegalStateException("TwoDScrollView can host only one direct child");
+ }
+ super.addView(child, index, params);
+ }
+
+ /**
+ * @return Returns true this TwoDScrollView can be scrolled
+ */
+ private boolean canScroll() {
+ if(!scrollEnabled)
+ return false;
+ View child = getChildAt(0);
+ if (child != null) {
+ int childHeight = child.getHeight();
+ int childWidth = child.getWidth();
+ return (getHeight() < childHeight + getPaddingTop() + getPaddingBottom()) ||
+ (getWidth() < childWidth + getPaddingLeft() + getPaddingRight());
+ }
+ return false;
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ // Let the focused view and/or our descendants get the key first
+ boolean handled = super.dispatchKeyEvent(event);
+ if (handled) {
+ return true;
+ }
+ return executeKeyEvent(event);
+ }
+
+ /**
+ * You can call this function yourself to have the scroll view perform
+ * scrolling from a key event, just as if the event had been dispatched to
+ * it by the view hierarchy.
+ *
+ * @param event The key event to execute.
+ * @return Return true if the event was handled, else false.
+ */
+ public boolean executeKeyEvent(KeyEvent event) {
+ mTempRect.setEmpty();
+ if (!canScroll()) {
+ if (isFocused()) {
+ View currentFocused = findFocus();
+ if (currentFocused == this) currentFocused = null;
+ View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, View.FOCUS_DOWN);
+ return nextFocused != null && nextFocused != this && nextFocused.requestFocus(View.FOCUS_DOWN);
+ }
+ return false;
+ }
+ boolean handled = false;
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ switch (event.getKeyCode()) {
+ case KeyEvent.KEYCODE_DPAD_UP:
+ if (!event.isAltPressed()) {
+ handled = arrowScroll(View.FOCUS_UP, false);
+ } else {
+ handled = fullScroll(View.FOCUS_UP, false);
+ }
+ break;
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ if (!event.isAltPressed()) {
+ handled = arrowScroll(View.FOCUS_DOWN, false);
+ } else {
+ handled = fullScroll(View.FOCUS_DOWN, false);
+ }
+ break;
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ if (!event.isAltPressed()) {
+ handled = arrowScroll(View.FOCUS_LEFT, true);
+ } else {
+ handled = fullScroll(View.FOCUS_LEFT, true);
+ }
+ break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ if (!event.isAltPressed()) {
+ handled = arrowScroll(View.FOCUS_RIGHT, true);
+ } else {
+ handled = fullScroll(View.FOCUS_RIGHT, true);
+ }
+ break;
+ }
+ }
+ return handled;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ /*
+ * This method JUST determines whether we want to intercept the motion.
+ * If we return true, onMotionEvent will be called and we do the actual
+ * scrolling there.
+ *
+ * Shortcut the most recurring case: the user is in the dragging
+ * state and he is moving his finger. We want to intercept this
+ * motion.
+ */
+ final int action = ev.getAction();
+ if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
+ return true;
+ }
+ if (!canScroll()) {
+ mIsBeingDragged = false;
+ return false;
+ }
+ final float y = ev.getY();
+ final float x = ev.getX();
+ switch (action) {
+ case MotionEvent.ACTION_MOVE:
+ /*
+ * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
+ * whether the user has moved far enough from his original down touch.
+ */
+ /*
+ * Locally do absolute value. mLastMotionY is set to the y value
+ * of the down event.
+ */
+ final int yDiff = (int) Math.abs(y - mLastMotionY);
+ final int xDiff = (int) Math.abs(x - mLastMotionX);
+ if (yDiff > mTouchSlop || xDiff > mTouchSlop) {
+ mIsBeingDragged = true;
+ }
+ break;
+
+ case MotionEvent.ACTION_DOWN:
+ /* Remember location of down touch */
+ mLastMotionY = y;
+ mLastMotionX = x;
+
+ /*
+ * If being flinged and user touches the screen, initiate drag;
+ * otherwise don't. mScroller.isFinished should be false when
+ * being flinged.
+ */
+ mIsBeingDragged = !mScroller.isFinished();
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ /* Release the drag */
+ mIsBeingDragged = false;
+ break;
+ }
+
+ /*
+ * The only time we want to intercept motion events is if we are in the
+ * drag mode.
+ */
+ return mIsBeingDragged;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+
+ if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
+ // Don't handle edge touches immediately -- they may actually belong to one of our
+ // descendants.
+ return false;
+ }
+
+ if (!canScroll()) {
+ return false;
+ }
+
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(ev);
+
+ final int action = ev.getAction();
+ final float y = ev.getY();
+ final float x = ev.getX();
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ /*
+ * If being flinged and user touches, stop the fling. isFinished
+ * will be false if being flinged.
+ */
+ if (!mScroller.isFinished()) {
+ mScroller.abortAnimation();
+ }
+
+ // Remember where the motion event started
+ mLastMotionY = y;
+ mLastMotionX = x;
+ break;
+ case MotionEvent.ACTION_MOVE:
+ // Scroll to follow the motion event
+ int deltaX = (int) (mLastMotionX - x);
+ int deltaY = (int) (mLastMotionY - y);
+ mLastMotionX = x;
+ mLastMotionY = y;
+
+ if (deltaX < 0) {
+ if (getScrollX() < 0) {
+ deltaX = 0;
+ }
+ } else if (deltaX > 0) {
+ final int rightEdge = getWidth() - getPaddingRight();
+ final int availableToScroll = getChildAt(0).getRight() - getScrollX() - rightEdge;
+ if (availableToScroll > 0) {
+ deltaX = Math.min(availableToScroll, deltaX);
+ } else {
+ deltaX = 0;
+ }
+ }
+ if (deltaY < 0) {
+ if (getScrollY() < 0) {
+ deltaY = 0;
+ }
+ } else if (deltaY > 0) {
+ final int bottomEdge = getHeight() - getPaddingBottom();
+ final int availableToScroll = getChildAt(0).getBottom() - getScrollY() - bottomEdge;
+ if (availableToScroll > 0) {
+ deltaY = Math.min(availableToScroll, deltaY);
+ } else {
+ deltaY = 0;
+ }
+ }
+ if (deltaY != 0 || deltaX != 0)
+ scrollBy(deltaX, deltaY);
+ break;
+ case MotionEvent.ACTION_UP:
+ final VelocityTracker velocityTracker = mVelocityTracker;
+ velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+ int initialXVelocity = (int) velocityTracker.getXVelocity();
+ int initialYVelocity = (int) velocityTracker.getYVelocity();
+ if ((Math.abs(initialXVelocity) + Math.abs(initialYVelocity) > mMinimumVelocity) && getChildCount() > 0) {
+ fling(-initialXVelocity, -initialYVelocity);
+ }
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Finds the next focusable component that fits in this View's bounds
+ * (excluding fading edges) pretending that this View's top is located at
+ * the parameter top.
+ *
+ * @param topFocus look for a candidate is the one at the top of the bounds
+ * if topFocus is true, or at the bottom of the bounds if topFocus is
+ * false
+ * @param top the top offset of the bounds in which a focusable must be
+ * found (the fading edge is assumed to start at this position)
+ * @param preferredFocusable the View that has highest priority and will be
+ * returned if it is within my bounds (null is valid)
+ * @return the next focusable component in the bounds or null if none can be
+ * found
+ */
+ private View findFocusableViewInMyBounds(final boolean topFocus, final int top, final boolean leftFocus, final int left, View preferredFocusable) {
+ /*
+ * The fading edge's transparent side should be considered for focus
+ * since it's mostly visible, so we divide the actual fading edge length
+ * by 2.
+ */
+ final int verticalFadingEdgeLength = getVerticalFadingEdgeLength() / 2;
+ final int topWithoutFadingEdge = top + verticalFadingEdgeLength;
+ final int bottomWithoutFadingEdge = top + getHeight() - verticalFadingEdgeLength;
+ final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength() / 2;
+ final int leftWithoutFadingEdge = left + horizontalFadingEdgeLength;
+ final int rightWithoutFadingEdge = left + getWidth() - horizontalFadingEdgeLength;
+
+ if ((preferredFocusable != null)
+ && (preferredFocusable.getTop() < bottomWithoutFadingEdge)
+ && (preferredFocusable.getBottom() > topWithoutFadingEdge)
+ && (preferredFocusable.getLeft() < rightWithoutFadingEdge)
+ && (preferredFocusable.getRight() > leftWithoutFadingEdge)) {
+ return preferredFocusable;
+ }
+ return findFocusableViewInBounds(topFocus, topWithoutFadingEdge, bottomWithoutFadingEdge, leftFocus, leftWithoutFadingEdge, rightWithoutFadingEdge);
+ }
+
+ /**
+ * Finds the next focusable component that fits in the specified bounds.
+ *
+ *
+ * @param topFocus look for a candidate is the one at the top of the bounds
+ * if topFocus is true, or at the bottom of the bounds if topFocus is
+ * false
+ * @param top the top offset of the bounds in which a focusable must be
+ * found
+ * @param bottom the bottom offset of the bounds in which a focusable must
+ * be found
+ * @return the next focusable component in the bounds or null if none can
+ * be found
+ */
+ private View findFocusableViewInBounds(boolean topFocus, int top, int bottom, boolean leftFocus, int left, int right) {
+ List focusables = getFocusables(View.FOCUS_FORWARD);
+ View focusCandidate = null;
+
+ /*
+ * A fully contained focusable is one where its top is below the bound's
+ * top, and its bottom is above the bound's bottom. A partially
+ * contained focusable is one where some part of it is within the
+ * bounds, but it also has some part that is not within bounds. A fully contained
+ * focusable is preferred to a partially contained focusable.
+ */
+ boolean foundFullyContainedFocusable = false;
+
+ int count = focusables.size();
+ for (int i = 0; i < count; i++) {
+ View view = focusables.get(i);
+ int viewTop = view.getTop();
+ int viewBottom = view.getBottom();
+ int viewLeft = view.getLeft();
+ int viewRight = view.getRight();
+
+ if (top < viewBottom && viewTop < bottom && left < viewRight && viewLeft < right) {
+ /*
+ * the focusable is in the target area, it is a candidate for
+ * focusing
+ */
+ final boolean viewIsFullyContained = (top < viewTop) && (viewBottom < bottom) && (left < viewLeft) && (viewRight < right);
+ if (focusCandidate == null) {
+ /* No candidate, take this one */
+ focusCandidate = view;
+ foundFullyContainedFocusable = viewIsFullyContained;
+ } else {
+ final boolean viewIsCloserToVerticalBoundary =
+ (topFocus && viewTop < focusCandidate.getTop()) ||
+ (!topFocus && viewBottom > focusCandidate.getBottom());
+ final boolean viewIsCloserToHorizontalBoundary =
+ (leftFocus && viewLeft < focusCandidate.getLeft()) ||
+ (!leftFocus && viewRight > focusCandidate.getRight());
+ if (foundFullyContainedFocusable) {
+ if (viewIsFullyContained && viewIsCloserToVerticalBoundary && viewIsCloserToHorizontalBoundary) {
+ /*
+ * We're dealing with only fully contained views, so
+ * it has to be closer to the boundary to beat our
+ * candidate
+ */
+ focusCandidate = view;
+ }
+ } else {
+ if (viewIsFullyContained) {
+ /* Any fully contained view beats a partially contained view */
+ focusCandidate = view;
+ foundFullyContainedFocusable = true;
+ } else if (viewIsCloserToVerticalBoundary && viewIsCloserToHorizontalBoundary) {
+ /*
+ * Partially contained view beats another partially
+ * contained view if it's closer
+ */
+ focusCandidate = view;
+ }
+ }
+ }
+ }
+ }
+ return focusCandidate;
+ }
+
+ /**
+ * Handles scrolling in response to a "home/end" shortcut press. This
+ * method will scroll the view to the top or bottom and give the focus
+ * to the topmost/bottommost component in the new visible area. If no
+ * component is a good candidate for focus, this scrollview reclaims the
+ * focus.
+ *
+ * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
+ * to go the top of the view or
+ * {@link android.view.View#FOCUS_DOWN} to go the bottom
+ * @return true if the key event is consumed by this method, false otherwise
+ */
+ public boolean fullScroll(int direction, boolean horizontal) {
+ if (!horizontal) {
+ boolean down = direction == View.FOCUS_DOWN;
+ int height = getHeight();
+ mTempRect.top = 0;
+ mTempRect.bottom = height;
+ if (down) {
+ int count = getChildCount();
+ if (count > 0) {
+ View view = getChildAt(count - 1);
+ mTempRect.bottom = view.getBottom();
+ mTempRect.top = mTempRect.bottom - height;
+ }
+ }
+ return scrollAndFocus(direction, mTempRect.top, mTempRect.bottom, 0, 0, 0);
+ } else {
+ boolean right = direction == View.FOCUS_DOWN;
+ int width = getWidth();
+ mTempRect.left = 0;
+ mTempRect.right = width;
+ if (right) {
+ int count = getChildCount();
+ if (count > 0) {
+ View view = getChildAt(count - 1);
+ mTempRect.right = view.getBottom();
+ mTempRect.left = mTempRect.right - width;
+ }
+ }
+ return scrollAndFocus(0, 0, 0, direction, mTempRect.top, mTempRect.bottom);
+ }
+ }
+
+ /**
+ * Scrolls the view to make the area defined by top
and
+ * bottom
visible. This method attempts to give the focus
+ * to a component visible in this area. If no component can be focused in
+ * the new visible area, the focus is reclaimed by this scrollview.
+ *
+ * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
+ * to go upward
+ * {@link android.view.View#FOCUS_DOWN} to downward
+ * @param top the top offset of the new area to be made visible
+ * @param bottom the bottom offset of the new area to be made visible
+ * @return true if the key event is consumed by this method, false otherwise
+ */
+ private boolean scrollAndFocus(int directionY, int top, int bottom, int directionX, int left, int right) {
+ boolean handled = true;
+ int height = getHeight();
+ int containerTop = getScrollY();
+ int containerBottom = containerTop + height;
+ boolean up = directionY == View.FOCUS_UP;
+ int width = getWidth();
+ int containerLeft = getScrollX();
+ int containerRight = containerLeft + width;
+ boolean leftwards = directionX == View.FOCUS_UP;
+ View newFocused = findFocusableViewInBounds(up, top, bottom, leftwards, left, right);
+ if (newFocused == null) {
+ newFocused = this;
+ }
+ if ((top >= containerTop && bottom <= containerBottom) || (left >= containerLeft && right <= containerRight)) {
+ handled = false;
+ } else {
+ int deltaY = up ? (top - containerTop) : (bottom - containerBottom);
+ int deltaX = leftwards ? (left - containerLeft) : (right - containerRight);
+ doScroll(deltaX, deltaY);
+ }
+ if (newFocused != findFocus() && newFocused.requestFocus(directionY)) {
+ mTwoDScrollViewMovedFocus = true;
+ mTwoDScrollViewMovedFocus = false;
+ }
+ return handled;
+ }
+
+ /**
+ * Handle scrolling in response to an up or down arrow click.
+ *
+ * @param direction The direction corresponding to the arrow key that was
+ * pressed
+ * @return True if we consumed the event, false otherwise
+ */
+ public boolean arrowScroll(int direction, boolean horizontal) {
+ View currentFocused = findFocus();
+ if (currentFocused == this) currentFocused = null;
+ View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction);
+ final int maxJump = horizontal ? getMaxScrollAmountHorizontal() : getMaxScrollAmountVertical();
+
+ if (!horizontal) {
+ if (nextFocused != null) {
+ nextFocused.getDrawingRect(mTempRect);
+ offsetDescendantRectToMyCoords(nextFocused, mTempRect);
+ int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
+ doScroll(0, scrollDelta);
+ nextFocused.requestFocus(direction);
+ } else {
+ // no new focus
+ int scrollDelta = maxJump;
+ if (direction == View.FOCUS_UP && getScrollY() < scrollDelta) {
+ scrollDelta = getScrollY();
+ } else if (direction == View.FOCUS_DOWN) {
+ if (getChildCount() > 0) {
+ int daBottom = getChildAt(0).getBottom();
+ int screenBottom = getScrollY() + getHeight();
+ if (daBottom - screenBottom < maxJump) {
+ scrollDelta = daBottom - screenBottom;
+ }
+ }
+ }
+ if (scrollDelta == 0) {
+ return false;
+ }
+ doScroll(0, direction == View.FOCUS_DOWN ? scrollDelta : -scrollDelta);
+ }
+ } else {
+ if (nextFocused != null) {
+ nextFocused.getDrawingRect(mTempRect);
+ offsetDescendantRectToMyCoords(nextFocused, mTempRect);
+ int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
+ doScroll(scrollDelta, 0);
+ nextFocused.requestFocus(direction);
+ } else {
+ // no new focus
+ int scrollDelta = maxJump;
+ if (direction == View.FOCUS_UP && getScrollY() < scrollDelta) {
+ scrollDelta = getScrollY();
+ } else if (direction == View.FOCUS_DOWN) {
+ if (getChildCount() > 0) {
+ int daBottom = getChildAt(0).getBottom();
+ int screenBottom = getScrollY() + getHeight();
+ if (daBottom - screenBottom < maxJump) {
+ scrollDelta = daBottom - screenBottom;
+ }
+ }
+ }
+ if (scrollDelta == 0) {
+ return false;
+ }
+ doScroll(direction == View.FOCUS_DOWN ? scrollDelta : -scrollDelta, 0);
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Smooth scroll by a Y delta
+ *
+ * @param delta the number of pixels to scroll by on the Y axis
+ */
+ private void doScroll(int deltaX, int deltaY) {
+ if (deltaX != 0 || deltaY != 0) {
+ smoothScrollBy(deltaX, deltaY);
+ }
+ }
+
+ /**
+ * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
+ *
+ * @param dx the number of pixels to scroll by on the X axis
+ * @param dy the number of pixels to scroll by on the Y axis
+ */
+ public final void smoothScrollBy(int dx, int dy) {
+ long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
+ if (duration > ANIMATED_SCROLL_GAP) {
+ mScroller.startScroll(getScrollX(), getScrollY(), dx, dy);
+ awakenScrollBars(mScroller.getDuration());
+ invalidate();
+ } else {
+ if (!mScroller.isFinished()) {
+ mScroller.abortAnimation();
+ }
+ scrollBy(dx, dy);
+ }
+ mLastScroll = AnimationUtils.currentAnimationTimeMillis();
+ }
+
+ /**
+ * Like {@link #scrollTo}, but scroll smoothly instead of immediately.
+ *
+ * @param x the position where to scroll on the X axis
+ * @param y the position where to scroll on the Y axis
+ */
+ public final void smoothScrollTo(int x, int y) {
+ smoothScrollBy(x - getScrollX(), y - getScrollY());
+ }
+
+ /**
+ * The scroll range of a scroll view is the overall height of all of its
+ * children.
+ */
+ @Override
+ protected int computeVerticalScrollRange() {
+ int count = getChildCount();
+ return count == 0 ? getHeight() : (getChildAt(0)).getBottom();
+ }
+ @Override
+ protected int computeHorizontalScrollRange() {
+ int count = getChildCount();
+ return count == 0 ? getWidth() : (getChildAt(0)).getRight();
+ }
+
+ @Override
+ protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
+ ViewGroup.LayoutParams lp = child.getLayoutParams();
+ int childWidthMeasureSpec;
+ int childHeightMeasureSpec;
+
+ childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, getPaddingLeft() + getPaddingRight(), lp.width);
+ childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+
+ child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+ }
+
+ @Override
+ protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
+ final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+ final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.leftMargin + lp.rightMargin, MeasureSpec.UNSPECIFIED);
+ final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.topMargin + lp.bottomMargin, MeasureSpec.UNSPECIFIED);
+
+ child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+ }
+
+ @Override
+ public void computeScroll() {
+ if (mScroller.computeScrollOffset()) {
+ // This is called at drawing time by ViewGroup. We don't want to
+ // re-show the scrollbars at this point, which scrollTo will do,
+ // so we replicate most of scrollTo here.
+ //
+ // It's a little odd to call onScrollChanged from inside the drawing.
+ //
+ // It is, except when you remember that computeScroll() is used to
+ // animate scrolling. So unless we want to defer the onScrollChanged()
+ // until the end of the animated scrolling, we don't really have a
+ // choice here.
+ //
+ // I agree. The alternative, which I think would be worse, is to post
+ // something and tell the subclasses later. This is bad because there
+ // will be a window where mScrollX/Y is different from what the app
+ // thinks it is.
+ //
+ int oldX = getScrollX();
+ int oldY = getScrollY();
+ int x = mScroller.getCurrX();
+ int y = mScroller.getCurrY();
+ if (getChildCount() > 0) {
+ View child = getChildAt(0);
+ scrollTo(clamp(x, getWidth() - getPaddingRight() - getPaddingLeft(), child.getWidth()),
+ clamp(y, getHeight() - getPaddingBottom() - getPaddingTop(), child.getHeight()));
+ } else {
+ scrollTo(x, y);
+ }
+ if (oldX != getScrollX() || oldY != getScrollY()) {
+ onScrollChanged(getScrollX(), getScrollY(), oldX, oldY);
+ }
+
+ // Keep on drawing until the animation has finished.
+ postInvalidate();
+ }
+ }
+
+ /**
+ * Scrolls the view to the given child.
+ *
+ * @param child the View to scroll to
+ */
+ private void scrollToChild(View child) {
+ child.getDrawingRect(mTempRect);
+ /* Offset from child's local coordinates to TwoDScrollView coordinates */
+ offsetDescendantRectToMyCoords(child, mTempRect);
+ int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
+ if (scrollDelta != 0) {
+ scrollBy(0, scrollDelta);
+ }
+ }
+
+ /**
+ * If rect is off screen, scroll just enough to get it (or at least the
+ * first screen size chunk of it) on screen.
+ *
+ * @param rect The rectangle.
+ * @param immediate True to scroll immediately without animation
+ * @return true if scrolling was performed
+ */
+ private boolean scrollToChildRect(Rect rect, boolean immediate) {
+ final int delta = computeScrollDeltaToGetChildRectOnScreen(rect);
+ final boolean scroll = delta != 0;
+ if (scroll) {
+ if (immediate) {
+ scrollBy(0, delta);
+ } else {
+ smoothScrollBy(0, delta);
+ }
+ }
+ return scroll;
+ }
+
+ /**
+ * Compute the amount to scroll in the Y direction in order to get
+ * a rectangle completely on the screen (or, if taller than the screen,
+ * at least the first screen size chunk of it).
+ *
+ * @param rect The rect.
+ * @return The scroll delta.
+ */
+ protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) {
+ if (getChildCount() == 0) return 0;
+ int height = getHeight();
+ int screenTop = getScrollY();
+ int screenBottom = screenTop + height;
+ int fadingEdge = getVerticalFadingEdgeLength();
+ // leave room for top fading edge as long as rect isn't at very top
+ if (rect.top > 0) {
+ screenTop += fadingEdge;
+ }
+
+ // leave room for bottom fading edge as long as rect isn't at very bottom
+ if (rect.bottom < getChildAt(0).getHeight()) {
+ screenBottom -= fadingEdge;
+ }
+ int scrollYDelta = 0;
+ if (rect.bottom > screenBottom && rect.top > screenTop) {
+ // need to move down to get it in view: move down just enough so
+ // that the entire rectangle is in view (or at least the first
+ // screen size chunk).
+ if (rect.height() > height) {
+ // just enough to get screen size chunk on
+ scrollYDelta += (rect.top - screenTop);
+ } else {
+ // get entire rect at bottom of screen
+ scrollYDelta += (rect.bottom - screenBottom);
+ }
+
+ // make sure we aren't scrolling beyond the end of our content
+ int bottom = getChildAt(0).getBottom();
+ int distanceToBottom = bottom - screenBottom;
+ scrollYDelta = Math.min(scrollYDelta, distanceToBottom);
+
+ } else if (rect.top < screenTop && rect.bottom < screenBottom) {
+ // need to move up to get it in view: move up just enough so that
+ // entire rectangle is in view (or at least the first screen
+ // size chunk of it).
+
+ if (rect.height() > height) {
+ // screen size chunk
+ scrollYDelta -= (screenBottom - rect.bottom);
+ } else {
+ // entire rect at top
+ scrollYDelta -= (screenTop - rect.top);
+ }
+
+ // make sure we aren't scrolling any further than the top our content
+ scrollYDelta = Math.max(scrollYDelta, -getScrollY());
+ }
+ return scrollYDelta;
+ }
+
+ @Override
+ public void requestChildFocus(View child, View focused) {
+ if (!mTwoDScrollViewMovedFocus) {
+ if (!mIsLayoutDirty) {
+ scrollToChild(focused);
+ } else {
+ // The child may not be laid out yet, we can't compute the scroll yet
+ mChildToScrollTo = focused;
+ }
+ }
+ super.requestChildFocus(child, focused);
+ }
+
+ /**
+ * When looking for focus in children of a scroll view, need to be a little
+ * more careful not to give focus to something that is scrolled off screen.
+ *
+ * This is more expensive than the default {@link android.view.ViewGroup}
+ * implementation, otherwise this behavior might have been made the default.
+ */
+ @Override
+ protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+ // convert from forward / backward notation to up / down / left / right
+ // (ugh).
+ if (direction == View.FOCUS_FORWARD) {
+ direction = View.FOCUS_DOWN;
+ } else if (direction == View.FOCUS_BACKWARD) {
+ direction = View.FOCUS_UP;
+ }
+
+ final View nextFocus = previouslyFocusedRect == null ?
+ FocusFinder.getInstance().findNextFocus(this, null, direction) :
+ FocusFinder.getInstance().findNextFocusFromRect(this,
+ previouslyFocusedRect, direction);
+
+ if (nextFocus == null) {
+ return false;
+ }
+
+ return nextFocus.requestFocus(direction, previouslyFocusedRect);
+ }
+
+ @Override
+ public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
+ // offset into coordinate space of this scroll view
+ rectangle.offset(child.getLeft() - child.getScrollX(), child.getTop() - child.getScrollY());
+ return scrollToChildRect(rectangle, immediate);
+ }
+
+ @Override
+ public void requestLayout() {
+ mIsLayoutDirty = true;
+ super.requestLayout();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+ mIsLayoutDirty = false;
+ // Give a child focus if it needs it
+ if (mChildToScrollTo != null && isViewDescendantOf(mChildToScrollTo, this)) {
+ scrollToChild(mChildToScrollTo);
+ }
+ mChildToScrollTo = null;
+
+ // Calling this with the present values causes it to re-clam them
+ scrollTo(getScrollX(), getScrollY());
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+
+ View currentFocused = findFocus();
+ if (null == currentFocused || this == currentFocused)
+ return;
+
+ // If the currently-focused view was visible on the screen when the
+ // screen was at the old height, then scroll the screen to make that
+ // view visible with the new screen height.
+ currentFocused.getDrawingRect(mTempRect);
+ offsetDescendantRectToMyCoords(currentFocused, mTempRect);
+ int scrollDeltaX = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
+ int scrollDeltaY = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
+ doScroll(scrollDeltaX, scrollDeltaY);
+ }
+
+ /**
+ * Return true if child is an descendant of parent, (or equal to the parent).
+ */
+ private boolean isViewDescendantOf(View child, View parent) {
+ if (child == parent) {
+ return true;
+ }
+
+ final ViewParent theParent = child.getParent();
+ return (theParent instanceof ViewGroup) && isViewDescendantOf((View) theParent, parent);
+ }
+
+ /**
+ * Fling the scroll view
+ *
+ * @param velocityY The initial velocity in the Y direction. Positive
+ * numbers mean that the finger/curor is moving down the screen,
+ * which means we want to scroll towards the top.
+ */
+ public void fling(int velocityX, int velocityY) {
+ if (getChildCount() > 0) {
+ int height = getHeight() - getPaddingBottom() - getPaddingTop();
+ int bottom = getChildAt(0).getHeight();
+ int width = getWidth() - getPaddingRight() - getPaddingLeft();
+ int right = getChildAt(0).getWidth();
+
+ mScroller.fling(getScrollX(), getScrollY(), velocityX, velocityY, 0, right - width, 0, bottom - height);
+
+ final boolean movingDown = velocityY > 0;
+ final boolean movingRight = velocityX > 0;
+
+ View newFocused = findFocusableViewInMyBounds(movingRight, mScroller.getFinalX(), movingDown, mScroller.getFinalY(), findFocus());
+ if (newFocused == null) {
+ newFocused = this;
+ }
+
+ if (newFocused != findFocus() && newFocused.requestFocus(movingDown ? View.FOCUS_DOWN : View.FOCUS_UP)) {
+ mTwoDScrollViewMovedFocus = true;
+ mTwoDScrollViewMovedFocus = false;
+ }
+
+ awakenScrollBars(mScroller.getDuration());
+ invalidate();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * This version also clamps the scrolling to the bounds of our child.
+ */
+ public void scrollTo(int x, int y) {
+ // we rely on the fact the View.scrollBy calls scrollTo.
+ if (getChildCount() > 0) {
+ View child = getChildAt(0);
+ x = clamp(x, getWidth() - getPaddingRight() - getPaddingLeft(), child.getWidth());
+ y = clamp(y, getHeight() - getPaddingBottom() - getPaddingTop(), child.getHeight());
+ if (x != getScrollX() || y != getScrollY()) {
+ super.scrollTo(x, y);
+ }
+ }
+ }
+
+ private int clamp(int n, int my, int child) {
+ if (my >= child || n < 0) {
+ /* my >= child is this case:
+ * |--------------- me ---------------|
+ * |------ child ------|
+ * or
+ * |--------------- me ---------------|
+ * |------ child ------|
+ * or
+ * |--------------- me ---------------|
+ * |------ child ------|
+ *
+ * n < 0 is this case:
+ * |------ me ------|
+ * |-------- child --------|
+ * |-- mScrollX --|
+ */
+ return 0;
+ }
+ if ((my+n) > child) {
+ /* this case:
+ * |------ me ------|
+ * |------ child ------|
+ * |-- mScrollX --|
+ */
+ return child-my;
+ }
+ return n;
+ }
+
+ public void setScrollViewListener(ScrollView2DListener scrollViewListener) {
+ this.scrollView2DListener = scrollViewListener;
+ }
+
+ @Override
+ protected void onScrollChanged(int x, int y, int oldx, int oldy) {
+ super.onScrollChanged(x, y, oldx, oldy);
+ if(scrollView2DListener != null) {
+ scrollView2DListener.onScrollChanged(this, x, y, oldx, oldy);
+ }
+ }
+
+}
diff --git a/client/Android/src/com/freerdp/afreerdp/presentation/SessionActivity.java b/client/Android/src/com/freerdp/afreerdp/presentation/SessionActivity.java
new file mode 100644
index 000000000..eef2c3d65
--- /dev/null
+++ b/client/Android/src/com/freerdp/afreerdp/presentation/SessionActivity.java
@@ -0,0 +1,1137 @@
+/*
+ Android Session Activity
+
+ Copyright 2013 Thinstuff Technologies GmbH, Author: Martin Fleisz
+
+ This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
+ If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+package com.freerdp.afreerdp.presentation;
+
+import java.util.Iterator;
+import java.util.List;
+
+import com.freerdp.afreerdp.R;
+import com.freerdp.afreerdp.application.GlobalApp;
+import com.freerdp.afreerdp.application.GlobalSettings;
+import com.freerdp.afreerdp.application.SessionState;
+import com.freerdp.afreerdp.domain.BookmarkBase;
+import com.freerdp.afreerdp.domain.ConnectionReference;
+import com.freerdp.afreerdp.domain.ManualBookmark;
+import com.freerdp.afreerdp.services.LibFreeRDP;
+import com.freerdp.afreerdp.utils.KeyboardMapper;
+import com.freerdp.afreerdp.utils.Mouse;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.app.AlertDialog;
+import android.app.ProgressDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.ScaleGestureDetector;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.Toast;
+import android.widget.ZoomControls;
+import android.inputmethodservice.Keyboard;
+import android.inputmethodservice.KeyboardView;
+
+
+public class SessionActivity extends Activity
+ implements LibFreeRDP.UIEventListener, KeyboardView.OnKeyboardActionListener, ScrollView2D.ScrollView2DListener,
+ KeyboardMapper.KeyProcessingListener, SessionView.SessionViewListener, TouchPointerView.TouchPointerListener
+{
+ private class UIHandler extends Handler {
+
+ public static final int REFRESH_SESSIONVIEW = 1;
+ public static final int DISPLAY_TOAST = 2;
+ public static final int HIDE_ZOOMCONTROLS = 3;
+ public static final int SEND_MOVE_EVENT = 4;
+ public static final int SHOW_DIALOG = 5;
+ public static final int GRAPHICS_CHANGED = 6;
+ public static final int SCROLLING_REQUESTED = 7;
+
+ UIHandler() {
+ super();
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch(msg.what)
+ {
+ case GRAPHICS_CHANGED:
+ {
+ sessionView.onSurfaceChange(session);
+ scrollView.requestLayout();
+ break;
+ }
+ case REFRESH_SESSIONVIEW:
+ {
+ sessionView.invalidateRegion();
+ break;
+ }
+ case DISPLAY_TOAST:
+ {
+ Toast errorToast = Toast.makeText(getApplicationContext(), msg.obj.toString(), Toast.LENGTH_LONG);
+ errorToast.show();
+ break;
+ }
+ case HIDE_ZOOMCONTROLS:
+ {
+ zoomControls.hide();
+ break;
+ }
+ case SEND_MOVE_EVENT:
+ {
+ LibFreeRDP.sendCursorEvent(session.getInstance(), msg.arg1, msg.arg2, Mouse.getMoveEvent());
+ break;
+ }
+ case SHOW_DIALOG:
+ {
+ // create and show the dialog
+ ((Dialog)msg.obj).show();
+ break;
+ }
+ case SCROLLING_REQUESTED:
+ {
+ int scrollX = 0;
+ int scrollY = 0;
+ float[] pointerPos = touchPointerView.getPointerPosition();
+
+ if (pointerPos[0] > (screen_width - touchPointerView.getPointerWidth()))
+ scrollX = SCROLLING_DISTANCE;
+ else if (pointerPos[0] < 0)
+ scrollX = -SCROLLING_DISTANCE;
+
+ if (pointerPos[1] > (screen_height - touchPointerView.getPointerHeight()))
+ scrollY = SCROLLING_DISTANCE;
+ else if (pointerPos[1] < 0)
+ scrollY = -SCROLLING_DISTANCE;
+
+ scrollView.scrollBy(scrollX, scrollY);
+
+ // see if we reached the min/max scroll positions
+ if (scrollView.getScrollX() == 0 || scrollView.getScrollX() == (sessionView.getWidth() - scrollView.getWidth()))
+ scrollX = 0;
+ if (scrollView.getScrollY() == 0 || scrollView.getScrollY() == (sessionView.getHeight() - scrollView.getHeight()))
+ scrollY = 0;
+
+ if (scrollX != 0 || scrollY != 0)
+ uiHandler.sendEmptyMessageDelayed(SCROLLING_REQUESTED, SCROLLING_TIMEOUT);
+ else
+ Log.v(TAG, "Stopping auto-scroll");
+ break;
+ }
+ }
+ }
+ }
+
+ private class PinchZoomListener extends ScaleGestureDetector.SimpleOnScaleGestureListener
+ {
+ private float scaleFactor = 1.0f;
+
+ @Override
+ public boolean onScaleBegin(ScaleGestureDetector detector) {
+ scrollView.setScrollEnabled(false);
+ return true;
+ }
+
+ @Override
+ public boolean onScale(ScaleGestureDetector detector) {
+
+ // calc scale factor
+ scaleFactor *= detector.getScaleFactor();
+ scaleFactor = Math.max(SessionView.MIN_SCALE_FACTOR, Math.min(scaleFactor, SessionView.MAX_SCALE_FACTOR));
+ sessionView.setZoom(scaleFactor);
+
+ if(!sessionView.isAtMinZoom() && !sessionView.isAtMaxZoom())
+ {
+ // transform scroll origin to the new zoom space
+ float transOriginX = scrollView.getScrollX() * detector.getScaleFactor();
+ float transOriginY = scrollView.getScrollY() * detector.getScaleFactor();
+
+ // transform center point to the zoomed space
+ float transCenterX = (scrollView.getScrollX() + detector.getFocusX()) * detector.getScaleFactor();
+ float transCenterY = (scrollView.getScrollY() + detector.getFocusY()) * detector.getScaleFactor();
+
+ // scroll by the difference between the distance of the transformed center/origin point and their old distance (focusX/Y)
+ scrollView.scrollBy((int)((transCenterX - transOriginX) - detector.getFocusX()), (int)((transCenterY - transOriginY) - detector.getFocusY()));
+ }
+
+ return true;
+ }
+
+ @Override
+ public void onScaleEnd(ScaleGestureDetector de)
+ {
+ scrollView.setScrollEnabled(true);
+ }
+ }
+
+ private class LibFreeRDPBroadcastReceiver extends BroadcastReceiver
+ {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // still got a valid session?
+ if (session == null)
+ return;
+
+ // is this event for the current session?
+ if (session.getInstance() != intent.getExtras().getInt(GlobalApp.EVENT_PARAM, -1))
+ return;
+
+ switch(intent.getExtras().getInt(GlobalApp.EVENT_TYPE, -1))
+ {
+ case GlobalApp.FREERDP_EVENT_CONNECTION_SUCCESS:
+ OnConnectionSuccess(context);
+ break;
+
+ case GlobalApp.FREERDP_EVENT_CONNECTION_FAILURE:
+ OnConnectionFailure(context);
+ break;
+ case GlobalApp.FREERDP_EVENT_DISCONNECTED:
+ OnDisconnected(context);
+ break;
+ }
+ }
+
+ private void OnConnectionSuccess(Context context)
+ {
+ Log.v(TAG, "OnConnectionSuccess");
+
+ // bind session
+ bindSession();
+
+ if(progressDialog != null)
+ {
+ progressDialog.dismiss();
+ progressDialog = null;
+ }
+
+ // add hostname to history if quick connect was used
+ Bundle bundle = getIntent().getExtras();
+ if(bundle != null && bundle.containsKey(PARAM_CONNECTION_REFERENCE))
+ {
+ if(ConnectionReference.isHostnameReference(bundle.getString(PARAM_CONNECTION_REFERENCE)))
+ {
+ assert session.getBookmark().getType() == BookmarkBase.TYPE_MANUAL;
+ String item = session.getBookmark().get().getHostname();
+ if(!GlobalApp.getQuickConnectHistoryGateway().historyItemExists(item))
+ GlobalApp.getQuickConnectHistoryGateway().addHistoryItem(item);
+ }
+ }
+ }
+
+ private void OnConnectionFailure(Context context)
+ {
+ Log.v(TAG, "OnConnectionFailure");
+
+ if(progressDialog != null)
+ {
+ progressDialog.dismiss();
+ progressDialog = null;
+ }
+
+ // post error message on UI thread
+ if (!connectCancelledByUser)
+ uiHandler.sendMessage(Message.obtain(null, UIHandler.DISPLAY_TOAST, getResources().getText(R.string.error_connection_failure)));
+
+ session = null;
+ closeSessionActivity(RESULT_CANCELED);
+ }
+
+ private void OnDisconnected(Context context)
+ {
+ Log.v(TAG, "OnDisconnected");
+
+ if(progressDialog != null)
+ {
+ progressDialog.dismiss();
+ progressDialog = null;
+ }
+
+ session.setUIEventListener(null);
+ session = null;
+ closeSessionActivity(RESULT_OK);
+ }
+ }
+
+ public static final String PARAM_CONNECTION_REFERENCE = "conRef";
+ public static final String PARAM_INSTANCE = "instance";
+
+ private static final float ZOOMING_STEP = 0.5f;
+ private static final int ZOOMCONTROLS_AUTOHIDE_TIMEOUT = 4000;
+
+ // timeout between subsequent scrolling requests when the touch-pointer is at the edge of the session view
+ private static final int SCROLLING_TIMEOUT = 50;
+ private static final int SCROLLING_DISTANCE = 20;
+
+ private Bitmap bitmap;
+ private SessionState session;
+ private SessionView sessionView;
+ private TouchPointerView touchPointerView;
+ private ProgressDialog progressDialog;
+ private KeyboardView keyboardView;
+ private KeyboardView modifiersKeyboardView;
+ private ZoomControls zoomControls;
+ private KeyboardMapper keyboardMapper;
+
+ private Keyboard specialkeysKeyboard;
+ private Keyboard numpadKeyboard;
+ private Keyboard cursorKeyboard;
+ private Keyboard modifiersKeyboard;
+
+ private AlertDialog dlgVerifyCertificate;
+ private AlertDialog dlgUserCredentials;
+ private View userCredView;
+
+ private UIHandler uiHandler;
+
+ private int screen_width;
+ private int screen_height;
+
+ private boolean autoScrollTouchPointer = GlobalSettings.getAutoScrollTouchPointer();
+ private boolean connectCancelledByUser = false;
+ private boolean sessionRunning = false;
+ private boolean toggleMouseButtons = false;
+
+ private LibFreeRDPBroadcastReceiver libFreeRDPBroadcastReceiver;
+
+ private static final String TAG = "FreeRDP.SessionActivity";
+
+ private ScrollView2D scrollView;
+
+ // keyboard visibility flags
+ private boolean sysKeyboardVisible = false;
+ private boolean extKeyboardVisible = false;
+
+ // variables for delayed move event sending
+ private static final int MAX_DISCARDED_MOVE_EVENTS = 3;
+ private static final int SEND_MOVE_EVENT_TIMEOUT = 150;
+ private int discardedMoveEvents = 0;
+
+ private void createDialogs()
+ {
+ // build verify certificate dialog
+ dlgVerifyCertificate = new AlertDialog.Builder(this)
+ .setTitle(R.string.dlg_title_verify_certificate)
+ .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ callbackDialogResult = true;
+ synchronized(dialog)
+ {
+ dialog.notify();
+ }
+ }
+ })
+ .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ callbackDialogResult = false;
+ connectCancelledByUser = true;
+ synchronized(dialog)
+ {
+ dialog.notify();
+ }
+ }
+ })
+ .setCancelable(false)
+ .create();
+
+ // build the dialog
+ userCredView = getLayoutInflater().inflate(R.layout.credentials, null, true);
+ dlgUserCredentials = new AlertDialog.Builder(this)
+ .setView(userCredView)
+ .setTitle(R.string.dlg_title_credentials)
+ .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ callbackDialogResult = true;
+ synchronized(dialog)
+ {
+ dialog.notify();
+ }
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ callbackDialogResult = false;
+ connectCancelledByUser = true;
+ synchronized(dialog)
+ {
+ dialog.notify();
+ }
+ }
+ })
+ .setCancelable(false)
+ .create();
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+
+ // show status bar or make fullscreen?
+ if(GlobalSettings.getHideStatusBar())
+ getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ this.setContentView(R.layout.session);
+
+ Log.v(TAG, "Session.onCreate");
+
+ // ATTENTION: We use the onGlobalLayout notification to start our session.
+ // This is because only then we can know the exact size of our session when using fit screen
+ // accounting for any status bars etc. that Android might throws on us. A bit weird looking
+ // but this is the only way ...
+ final View activityRootView = findViewById(R.id.session_root_view);
+ activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener()
+ {
+ @Override
+ public void onGlobalLayout() {
+ screen_width = activityRootView.getWidth();
+ screen_height = activityRootView.getHeight();
+
+ // start session
+ if(!sessionRunning && getIntent() != null)
+ {
+ processIntent(getIntent());
+ sessionRunning = true;
+ }
+ }
+ });
+
+ sessionView = (SessionView) findViewById(R.id.sessionView);
+ sessionView.setScaleGestureDetector(new ScaleGestureDetector(this, new PinchZoomListener()));
+ sessionView.setSessionViewListener(this);
+ sessionView.requestFocus();
+
+ touchPointerView = (TouchPointerView) findViewById(R.id.touchPointerView);
+ touchPointerView.setTouchPointerListener(this);
+
+ keyboardMapper = new KeyboardMapper();
+ keyboardMapper.init(this);
+
+ modifiersKeyboard = new Keyboard(getApplicationContext(), R.xml.modifiers_keyboard);
+ specialkeysKeyboard = new Keyboard(getApplicationContext(), R.xml.specialkeys_keyboard);
+ numpadKeyboard = new Keyboard(getApplicationContext(), R.xml.numpad_keyboard);
+ cursorKeyboard = new Keyboard(getApplicationContext(), R.xml.cursor_keyboard);
+
+ // hide keyboard below the sessionView
+ keyboardView = (KeyboardView)findViewById(R.id.extended_keyboard);
+ keyboardView.setKeyboard(specialkeysKeyboard);
+ keyboardView.setOnKeyboardActionListener(this);
+
+ modifiersKeyboardView = (KeyboardView) findViewById(R.id.extended_keyboard_header);
+ modifiersKeyboardView.setKeyboard(modifiersKeyboard);
+ modifiersKeyboardView.setOnKeyboardActionListener(this);
+
+ scrollView = (ScrollView2D) findViewById(R.id.sessionScrollView);
+ scrollView.setScrollViewListener(this);
+ uiHandler = new UIHandler();
+ libFreeRDPBroadcastReceiver = new LibFreeRDPBroadcastReceiver();
+
+ zoomControls = (ZoomControls) findViewById(R.id.zoomControls);
+ zoomControls.hide();
+ zoomControls.setOnZoomInClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ resetZoomControlsAutoHideTimeout();
+ zoomControls.setIsZoomInEnabled(sessionView.zoomIn(ZOOMING_STEP));
+ zoomControls.setIsZoomOutEnabled(true);
+ }
+ });
+ zoomControls.setOnZoomOutClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ resetZoomControlsAutoHideTimeout();
+ zoomControls.setIsZoomOutEnabled(sessionView.zoomOut(ZOOMING_STEP));
+ zoomControls.setIsZoomInEnabled(true);
+ }
+ });
+
+ toggleMouseButtons = false;
+
+ createDialogs();
+
+ // register freerdp events broadcast receiver
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(GlobalApp.ACTION_EVENT_FREERDP);
+ registerReceiver(libFreeRDPBroadcastReceiver, filter);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ Log.v(TAG, "Session.onStart");
+ }
+
+ @Override
+ protected void onRestart() {
+ super.onRestart();
+ Log.v(TAG, "Session.onRestart");
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ Log.v(TAG, "Session.onResume");
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ Log.v(TAG, "Session.onPause");
+
+ // hide any visible keyboards
+ showKeyboard(false, false);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ Log.v(TAG, "Session.onStop");
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ Log.v(TAG, "Session.onDestroy");
+
+ // unregister freerdp events broadcast receiver
+ unregisterReceiver(libFreeRDPBroadcastReceiver);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+
+ // reload keyboard resources (changed from landscape)
+ modifiersKeyboard = new Keyboard(getApplicationContext(), R.xml.modifiers_keyboard);
+ specialkeysKeyboard = new Keyboard(getApplicationContext(), R.xml.specialkeys_keyboard);
+ numpadKeyboard = new Keyboard(getApplicationContext(), R.xml.numpad_keyboard);
+ cursorKeyboard = new Keyboard(getApplicationContext(), R.xml.cursor_keyboard);
+
+ // apply loaded keyboards
+ keyboardView.setKeyboard(specialkeysKeyboard);
+ modifiersKeyboardView.setKeyboard(modifiersKeyboard);
+ }
+
+ private void processIntent(Intent intent)
+ {
+ // get either session instance or create one from a bookmark
+ Bundle bundle = intent.getExtras();
+ if(bundle.containsKey(PARAM_INSTANCE))
+ {
+ int inst = bundle.getInt(PARAM_INSTANCE);
+ session = GlobalApp.getSession(inst);
+ bitmap = session.getSurface().getBitmap();
+ bindSession();
+ }
+ else if(bundle.containsKey(PARAM_CONNECTION_REFERENCE))
+ {
+ BookmarkBase bookmark = null;
+ String refStr = bundle.getString(PARAM_CONNECTION_REFERENCE);
+ if(ConnectionReference.isHostnameReference(refStr))
+ {
+ bookmark = new ManualBookmark();
+ bookmark.get().setHostname(ConnectionReference.getHostname(refStr));
+ }
+ else if(ConnectionReference.isBookmarkReference(refStr))
+ {
+ if(ConnectionReference.isManualBookmarkReference(refStr))
+ bookmark = GlobalApp.getManualBookmarkGateway().findById(ConnectionReference.getManualBookmarkId(refStr));
+ else
+ assert false;
+ }
+
+ if(bookmark != null)
+ connect(bookmark);
+ else
+ closeSessionActivity(RESULT_CANCELED);
+ }
+ else
+ {
+ // no session found - exit
+ closeSessionActivity(RESULT_CANCELED);
+ }
+ }
+
+ private void connect(BookmarkBase bookmark)
+ {
+ session = GlobalApp.createSession(bookmark);
+ session.setUIEventListener(this);
+
+ // set writeable data directory
+ LibFreeRDP.setDataDirectory(session.getInstance(), getFilesDir().toString());
+
+ BookmarkBase.ScreenSettings screenSettings = session.getBookmark().getActiveScreenSettings();
+ Log.v(TAG, "Screen Resolution: " + screenSettings.getResolutionString());
+ if (screenSettings.isAutomatic())
+ {
+ if((getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE)
+ {
+ // large screen device i.e. tablet: simply use screen info
+ screenSettings.setHeight(screen_height);
+ screenSettings.setWidth(screen_width);
+ }
+ else
+ {
+ // small screen device i.e. phone:
+ // Automatic uses the largest side length of the screen and makes a 16:10 resolution setting out of it
+ int screenMax = (screen_width > screen_height) ? screen_width : screen_height;
+ screenSettings.setHeight(screenMax);
+ screenSettings.setWidth((int)((float)screenMax * 1.6f));
+ }
+ }
+
+ progressDialog = new ProgressDialog(this);
+ progressDialog.setTitle(bookmark.getLabel());
+ progressDialog.setMessage(getResources().getText(R.string.dlg_msg_connecting));
+ progressDialog.setButton(ProgressDialog.BUTTON_NEGATIVE, "Cancel", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ connectCancelledByUser = true;
+ LibFreeRDP.cancelConnection(session.getInstance());
+ }
+ });
+ progressDialog.setCancelable(false);
+ progressDialog.show();
+
+ Thread thread = new Thread(new Runnable() {
+ public void run() {
+ session.connect();
+ }
+ });
+ thread.start();
+ }
+
+ // binds the current session to the activity by wiring it up with the sessionView and updating all internal objects accordingly
+ private void bindSession() {
+ Log.v("SessionActivity", "bindSession called");
+ session.setUIEventListener(this);
+ sessionView.onSurfaceChange(session);
+ scrollView.requestLayout();
+ keyboardMapper.reset(this);
+ }
+
+ // displays either the system or the extended keyboard or non of them
+ private void showKeyboard(boolean showSystemKeyboard, boolean showExtendedKeyboard) {
+ // no matter what we are doing ... hide the zoom controls
+ // TODO: this is not working correctly as hiding the keyboard issues a onScrollChange notification showing the control again ...
+ uiHandler.removeMessages(UIHandler.HIDE_ZOOMCONTROLS);
+ if(zoomControls.getVisibility() == View.VISIBLE)
+ zoomControls.hide();
+
+ InputMethodManager mgr;
+ if(showSystemKeyboard)
+ {
+ // hide extended keyboard
+ keyboardView.setVisibility(View.GONE);
+
+ // show system keyboard
+ mgr = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+ if(!mgr.isActive(sessionView))
+ Log.e(TAG, "Failed to show system keyboard: SessionView is not the active view!");
+ mgr.showSoftInput(sessionView, 0);
+
+ // show modifiers keyboard
+ modifiersKeyboardView.setVisibility(View.VISIBLE);
+ }
+ else if(showExtendedKeyboard)
+ {
+ // hide system keyboard
+ mgr = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+ mgr.hideSoftInputFromWindow(sessionView.getWindowToken(), 0);
+
+ // show extended keyboard
+ keyboardView.setKeyboard(specialkeysKeyboard);
+ keyboardView.setVisibility(View.VISIBLE);
+ modifiersKeyboardView.setVisibility(View.VISIBLE);
+ }
+ else
+ {
+ // hide both
+ mgr = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+ mgr.hideSoftInputFromWindow(sessionView.getWindowToken(), 0);
+ keyboardView.setVisibility(View.GONE);
+ modifiersKeyboardView.setVisibility(View.GONE);
+
+ // clear any active key modifiers)
+ keyboardMapper.clearlAllModifiers();
+ }
+
+ sysKeyboardVisible = showSystemKeyboard;
+ extKeyboardVisible = showExtendedKeyboard;
+ }
+
+ private void closeSessionActivity(int resultCode) {
+ // Go back to home activity (and send intent data back to home)
+ setResult(resultCode, getIntent());
+ finish();
+ }
+
+ // update the state of our modifier keys
+ private void updateModifierKeyStates() {
+ // check if any key is in the keycodes list
+
+ List keys = modifiersKeyboard.getKeys();
+ for(Iterator it = keys.iterator(); it.hasNext(); )
+ {
+ // if the key is a sticky key - just set it to off
+ Keyboard.Key curKey = it.next();
+ if(curKey.sticky)
+ {
+ switch(keyboardMapper.getModifierState(curKey.codes[0]))
+ {
+ case KeyboardMapper.KEYSTATE_ON:
+ curKey.on = true;
+ curKey.pressed = false;
+ break;
+
+ case KeyboardMapper.KEYSTATE_OFF:
+ curKey.on = false;
+ curKey.pressed = false;
+ break;
+
+ case KeyboardMapper.KEYSTATE_LOCKED:
+ curKey.on = true;
+ curKey.pressed = true;
+ break;
+ }
+ }
+ }
+
+ // refresh image
+ modifiersKeyboardView.invalidateAllKeys();
+ }
+
+ private void sendDelayedMoveEvent(int x, int y) {
+ if(uiHandler.hasMessages(UIHandler.SEND_MOVE_EVENT))
+ {
+ uiHandler.removeMessages(UIHandler.SEND_MOVE_EVENT);
+ discardedMoveEvents++;
+ }
+ else
+ discardedMoveEvents = 0;
+
+ if(discardedMoveEvents > MAX_DISCARDED_MOVE_EVENTS)
+ LibFreeRDP.sendCursorEvent(session.getInstance(), x, y, Mouse.getMoveEvent());
+ else
+ uiHandler.sendMessageDelayed(Message.obtain(null, UIHandler.SEND_MOVE_EVENT, x, y), SEND_MOVE_EVENT_TIMEOUT);
+ }
+
+ private void cancelDelayedMoveEvent() {
+ uiHandler.removeMessages(UIHandler.SEND_MOVE_EVENT);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.session_menu, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId())
+ {
+ case R.id.session_touch_pointer:
+ {
+ // toggle touch pointer
+ if(touchPointerView.getVisibility() == View.VISIBLE)
+ {
+ touchPointerView.setVisibility(View.INVISIBLE);
+ sessionView.setTouchPointerPadding(0, 0);
+ }
+ else
+ {
+ touchPointerView.setVisibility(View.VISIBLE);
+ sessionView.setTouchPointerPadding(touchPointerView.getPointerWidth(), touchPointerView.getPointerHeight());
+ }
+ break;
+ }
+
+ case R.id.session_sys_keyboard:
+ {
+ showKeyboard(!sysKeyboardVisible, false);
+ break;
+ }
+
+ case R.id.session_ext_keyboard:
+ {
+ showKeyboard(false, !extKeyboardVisible);
+ break;
+ }
+
+ case R.id.session_disconnect:
+ {
+ showKeyboard(false, false);
+ LibFreeRDP.disconnect(session.getInstance());
+ break;
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public void onBackPressed() {
+ // hide keyboards (if any visible) or send alt+f4 to the session
+ if(sysKeyboardVisible || extKeyboardVisible)
+ showKeyboard(false, false);
+ else
+ keyboardMapper.sendAltF4();
+ }
+
+ // android keyboard input handling
+ // We always use the unicode value to process input from the android keyboard except if key modifiers
+ // (like Win, Alt, Ctrl) are activated. In this case we will send the virtual key code to allow key
+ // combinations (like Win + E to open the explorer).
+ @Override
+ public boolean onKeyDown(int keycode, KeyEvent event) {
+ return keyboardMapper.processAndroidKeyEvent(event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keycode, KeyEvent event) {
+ return keyboardMapper.processAndroidKeyEvent(event);
+ }
+
+ // onKeyMultiple is called for input of some special characters like umlauts and some symbol characters
+ @Override
+ public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
+ return keyboardMapper.processAndroidKeyEvent(event);
+ }
+
+ // ****************************************************************************
+ // KeyboardView.KeyboardActionEventListener
+ @Override
+ public void onKey(int primaryCode, int[] keyCodes) {
+ keyboardMapper.processCustomKeyEvent(primaryCode);
+ }
+
+ @Override
+ public void onText(CharSequence text) {
+ }
+
+ @Override
+ public void swipeRight() {
+ }
+
+ @Override
+ public void swipeLeft() {
+ }
+
+ @Override
+ public void swipeDown() {
+ }
+
+ @Override
+ public void swipeUp() {
+ }
+
+ @Override
+ public void onPress(int primaryCode) {
+ }
+
+ @Override
+ public void onRelease(int primaryCode) {
+ }
+
+ // ****************************************************************************
+ // KeyboardMapper.KeyProcessingListener implementation
+ @Override
+ public void processVirtualKey(int virtualKeyCode, boolean down) {
+ LibFreeRDP.sendKeyEvent(session.getInstance(), virtualKeyCode, down);
+ }
+
+ @Override
+ public void processUnicodeKey(int unicodeKey) {
+ LibFreeRDP.sendUnicodeKeyEvent(session.getInstance(), unicodeKey);
+ }
+
+ @Override
+ public void switchKeyboard(int keyboardType) {
+ switch(keyboardType)
+ {
+ case KeyboardMapper.KEYBOARD_TYPE_FUNCTIONKEYS:
+ keyboardView.setKeyboard(specialkeysKeyboard);
+ break;
+
+ case KeyboardMapper.KEYBOARD_TYPE_NUMPAD:
+ keyboardView.setKeyboard(numpadKeyboard);
+ break;
+
+ case KeyboardMapper.KEYBOARD_TYPE_CURSOR:
+ keyboardView.setKeyboard(cursorKeyboard);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ @Override
+ public void modifiersChanged() {
+ updateModifierKeyStates();
+ }
+
+ // ****************************************************************************
+ // LibFreeRDP UI event listener implementation
+ @Override
+ public void OnSettingsChanged(int width, int height, int bpp) {
+
+ if (bpp > 16)
+ bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
+ else
+ bitmap = Bitmap.createBitmap(width, height, Config.RGB_565);
+
+ session.setSurface(new BitmapDrawable(bitmap));
+
+ // check this settings and initial settings - if they are not equal the server doesn't support our settings
+ // FIXME: the additional check (settings.getWidth() != width + 1) is for the RDVH bug fix to avoid accidental notifications
+ // (refer to android_freerdp.c for more info on this problem)
+ BookmarkBase.ScreenSettings settings = session.getBookmark().getActiveScreenSettings();
+ if((settings.getWidth() != width && settings.getWidth() != width + 1) || settings.getHeight() != height || settings.getColors() != bpp)
+ uiHandler.sendMessage(Message.obtain(null, UIHandler.DISPLAY_TOAST, getResources().getText(R.string.info_capabilities_changed)));
+ }
+
+ @Override
+ public void OnGraphicsUpdate(int x, int y, int width, int height)
+ {
+ LibFreeRDP.updateGraphics(session.getInstance(), bitmap, x, y, width, height);
+
+ sessionView.addInvalidRegion(new Rect(x, y, x + width, y + height));
+
+ /* since sessionView can only be modified from the UI thread
+ * any modifications to it need to be scheduled */
+
+ uiHandler.sendEmptyMessage(UIHandler.REFRESH_SESSIONVIEW);
+ }
+
+ @Override
+ public void OnGraphicsResize(int width, int height)
+ {
+ // replace bitmap
+ bitmap = Bitmap.createBitmap(width, height, bitmap.getConfig());
+ session.setSurface(new BitmapDrawable(bitmap));
+
+ /* since sessionView can only be modified from the UI thread
+ * any modifications to it need to be scheduled */
+ uiHandler.sendEmptyMessage(UIHandler.GRAPHICS_CHANGED);
+ }
+
+ private boolean callbackDialogResult;
+
+ @Override
+ public boolean OnAuthenticate(StringBuilder username, StringBuilder domain, StringBuilder password) {
+ // this is where the return code of our dialog will be stored
+ callbackDialogResult = false;
+
+ // set text fields
+ ((EditText)userCredView.findViewById(R.id.editTextUsername)).setText(username);
+ ((EditText)userCredView.findViewById(R.id.editTextDomain)).setText(domain);
+ ((EditText)userCredView.findViewById(R.id.editTextPassword)).setText(password);
+
+ // start dialog in UI thread
+ uiHandler.sendMessage(Message.obtain(null, UIHandler.SHOW_DIALOG, dlgUserCredentials));
+
+ // wait for result
+ try
+ {
+ synchronized(dlgUserCredentials)
+ {
+ dlgUserCredentials.wait();
+ }
+ }
+ catch(InterruptedException e)
+ {
+ }
+
+ // clear buffers
+ username.setLength(0);
+ domain.setLength(0);
+ password.setLength(0);
+
+ // read back user credentials
+ username.append(((EditText)userCredView.findViewById(R.id.editTextUsername)).getText().toString());
+ domain.append(((EditText)userCredView.findViewById(R.id.editTextDomain)).getText().toString());
+ password.append(((EditText)userCredView.findViewById(R.id.editTextPassword)).getText().toString());
+
+ return callbackDialogResult;
+ }
+
+ @Override
+ public boolean OnVerifiyCertificate(String subject, String issuer, String fingerprint) {
+
+ // see if global settings says accept all
+ if(GlobalSettings.getAcceptAllCertificates())
+ return true;
+
+ // this is where the return code of our dialog will be stored
+ callbackDialogResult = false;
+
+ // set message
+ String msg = getResources().getString(R.string.dlg_msg_verify_certificate);
+ msg = msg + "\n\nSubject: " + subject + "\nIssuer: " + issuer + "\nFingerprint: " + fingerprint;
+ dlgVerifyCertificate.setMessage(msg);
+
+ // start dialog in UI thread
+ uiHandler.sendMessage(Message.obtain(null, UIHandler.SHOW_DIALOG, dlgVerifyCertificate));
+
+ // wait for result
+ try
+ {
+ synchronized(dlgVerifyCertificate)
+ {
+ dlgVerifyCertificate.wait();
+ }
+ }
+ catch(InterruptedException e)
+ {
+ }
+
+ return callbackDialogResult;
+ }
+
+
+ // ****************************************************************************
+ // ScrollView2DListener implementation
+ private void resetZoomControlsAutoHideTimeout() {
+ uiHandler.removeMessages(UIHandler.HIDE_ZOOMCONTROLS);
+ uiHandler.sendEmptyMessageDelayed(UIHandler.HIDE_ZOOMCONTROLS, ZOOMCONTROLS_AUTOHIDE_TIMEOUT);
+ }
+
+ @Override
+ public void onScrollChanged(ScrollView2D scrollView, int x, int y, int oldx, int oldy) {
+ zoomControls.setIsZoomInEnabled(!sessionView.isAtMaxZoom());
+ zoomControls.setIsZoomOutEnabled(!sessionView.isAtMinZoom());
+ if(!GlobalSettings.getHideZoomControls() && zoomControls.getVisibility() != View.VISIBLE)
+ zoomControls.show();
+ resetZoomControlsAutoHideTimeout();
+ }
+
+ // ****************************************************************************
+ // SessionView.SessionViewListener
+ @Override
+ public void onSessionViewBeginTouch()
+ {
+ scrollView.setScrollEnabled(false);
+ }
+
+ @Override
+ public void onSessionViewEndTouch()
+ {
+ scrollView.setScrollEnabled(true);
+ }
+
+ @Override
+ public void onSessionViewLeftTouch(int x, int y, boolean down) {
+ if(!down)
+ cancelDelayedMoveEvent();
+
+ LibFreeRDP.sendCursorEvent(session.getInstance(), x, y, toggleMouseButtons ? Mouse.getRightButtonEvent(down) : Mouse.getLeftButtonEvent(down));
+
+ if (!down)
+ toggleMouseButtons = false;
+ }
+
+ public void onSessionViewRightTouch(int x, int y, boolean down) {
+ if (!down)
+ toggleMouseButtons = !toggleMouseButtons;
+ }
+
+ @Override
+ public void onSessionViewMove(int x, int y) {
+ sendDelayedMoveEvent(x, y);
+ }
+
+ @Override
+ public void onSessionViewScroll(boolean down) {
+ LibFreeRDP.sendCursorEvent(session.getInstance(), 0, 0, Mouse.getScrollEvent(down));
+ }
+
+ // ****************************************************************************
+ // TouchPointerView.TouchPointerListener
+ @Override
+ public void onTouchPointerClose() {
+ touchPointerView.setVisibility(View.INVISIBLE);
+ sessionView.setTouchPointerPadding(0, 0);
+ }
+
+ private Point mapScreenCoordToSessionCoord(int x, int y) {
+ int mappedX = (int)((float)(x + scrollView.getScrollX()) / sessionView.getZoom());
+ int mappedY = (int)((float)(y + scrollView.getScrollY()) / sessionView.getZoom());
+ if(mappedX > bitmap.getWidth())
+ mappedX = bitmap.getWidth();
+ if(mappedY > bitmap.getHeight())
+ mappedY = bitmap.getHeight();
+ return new Point(mappedX, mappedY);
+ }
+
+ @Override
+ public void onTouchPointerLeftClick(int x, int y, boolean down) {
+ Point p = mapScreenCoordToSessionCoord(x, y);
+ LibFreeRDP.sendCursorEvent(session.getInstance(), p.x, p.y, Mouse.getLeftButtonEvent(down));
+ }
+
+ @Override
+ public void onTouchPointerRightClick(int x, int y, boolean down) {
+ Point p = mapScreenCoordToSessionCoord(x, y);
+ LibFreeRDP.sendCursorEvent(session.getInstance(), p.x, p.y, Mouse.getRightButtonEvent(down));
+ }
+
+ @Override
+ public void onTouchPointerMove(int x, int y) {
+ Point p = mapScreenCoordToSessionCoord(x, y);
+ LibFreeRDP.sendCursorEvent(session.getInstance(), p.x, p.y, Mouse.getMoveEvent());
+
+ if (autoScrollTouchPointer && !uiHandler.hasMessages(UIHandler.SCROLLING_REQUESTED))
+ {
+ Log.v(TAG, "Starting auto-scroll");
+ uiHandler.sendEmptyMessageDelayed(UIHandler.SCROLLING_REQUESTED, SCROLLING_TIMEOUT);
+ }
+ }
+
+ @Override
+ public void onTouchPointerScroll(boolean down) {
+ LibFreeRDP.sendCursorEvent(session.getInstance(), 0, 0, Mouse.getScrollEvent(down));
+ }
+
+ @Override
+ public void onTouchPointerToggleKeyboard() {
+ showKeyboard(!sysKeyboardVisible, false);
+ }
+
+ @Override
+ public void onTouchPointerToggleExtKeyboard() {
+ showKeyboard(false, !extKeyboardVisible);
+ }
+
+ @Override
+ public void onTouchPointerResetScrollZoom() {
+ sessionView.setZoom(1.0f);
+ scrollView.scrollTo(0, 0);
+ }
+}
diff --git a/client/Android/src/com/freerdp/afreerdp/presentation/SessionView.java b/client/Android/src/com/freerdp/afreerdp/presentation/SessionView.java
new file mode 100644
index 000000000..f1234567f
--- /dev/null
+++ b/client/Android/src/com/freerdp/afreerdp/presentation/SessionView.java
@@ -0,0 +1,342 @@
+/*
+ Android Session view
+
+ Copyright 2013 Thinstuff Technologies GmbH, Author: Martin Fleisz
+
+ This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
+ If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+package com.freerdp.afreerdp.presentation;
+
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.View;
+import android.content.Context;
+import android.graphics.*;
+import android.graphics.drawable.BitmapDrawable;
+
+import java.util.*;
+
+import com.freerdp.afreerdp.application.SessionState;
+import com.freerdp.afreerdp.utils.DoubleGestureDetector;
+import com.freerdp.afreerdp.utils.GestureDetector;
+
+
+public class SessionView extends View
+{
+ public interface SessionViewListener {
+ abstract void onSessionViewBeginTouch();
+ abstract void onSessionViewEndTouch();
+ abstract void onSessionViewLeftTouch(int x, int y, boolean down);
+ abstract void onSessionViewRightTouch(int x, int y, boolean down);
+ abstract void onSessionViewMove(int x, int y);
+ abstract void onSessionViewScroll(boolean down);
+ }
+
+ private int width;
+ private int height;
+ private BitmapDrawable surface;
+ private Stack invalidRegions;
+
+ private int touchPointerPaddingWidth = 0;
+ private int touchPointerPaddingHeight = 0;
+
+ private SessionViewListener sessionViewListener = null;
+
+ public static final float MAX_SCALE_FACTOR = 3.0f;
+ public static final float MIN_SCALE_FACTOR = 1.0f;
+ private static final float SCALE_FACTOR_DELTA = 0.0001f;
+
+ private static final float TOUCH_SCROLL_DELTA = 10.0f;
+
+ // helpers for scaling gesture handling
+ private float scaleFactor = 1.0f;
+ private Matrix scaleMatrix;
+ private Matrix invScaleMatrix;
+ private RectF invalidRegionF;
+
+ //private static final String TAG = "FreeRDP.SessionView";
+
+ private GestureDetector gestureDetector;
+ private DoubleGestureDetector doubleGestureDetector;
+
+ private class SessionGestureListener extends GestureDetector.SimpleOnGestureListener {
+ boolean longPressInProgress = false;
+
+ public boolean onDown(MotionEvent e) {
+ return true;
+ }
+
+ public boolean onUp(MotionEvent e) {
+ sessionViewListener.onSessionViewEndTouch();
+ return true;
+ }
+
+ public void onLongPress(MotionEvent e) {
+ MotionEvent mappedEvent = mapTouchEvent(e);
+ sessionViewListener.onSessionViewBeginTouch();
+ sessionViewListener.onSessionViewLeftTouch((int)mappedEvent.getX(), (int)mappedEvent.getY(), true);
+ longPressInProgress = true;
+ }
+
+ public void onLongPressUp(MotionEvent e) {
+ MotionEvent mappedEvent = mapTouchEvent(e);
+ sessionViewListener.onSessionViewLeftTouch((int)mappedEvent.getX(), (int)mappedEvent.getY(), false);
+ longPressInProgress = false;
+ sessionViewListener.onSessionViewEndTouch();
+ }
+
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+ if(longPressInProgress)
+ {
+ MotionEvent mappedEvent = mapTouchEvent(e2);
+ sessionViewListener.onSessionViewMove((int)mappedEvent.getX(), (int)mappedEvent.getY());
+ return true;
+ }
+
+ return false;
+ }
+
+ public boolean onDoubleTap(MotionEvent e) {
+ // send 2nd click for double click
+ MotionEvent mappedEvent = mapTouchEvent(e);
+ sessionViewListener.onSessionViewLeftTouch((int)mappedEvent.getX(), (int)mappedEvent.getY(), true);
+ sessionViewListener.onSessionViewLeftTouch((int)mappedEvent.getX(), (int)mappedEvent.getY(), false);
+ return true;
+ }
+
+ public boolean onSingleTapUp(MotionEvent e) {
+ // send single click
+ MotionEvent mappedEvent = mapTouchEvent(e);
+ sessionViewListener.onSessionViewBeginTouch();
+ sessionViewListener.onSessionViewLeftTouch((int)mappedEvent.getX(), (int)mappedEvent.getY(), true);
+ sessionViewListener.onSessionViewLeftTouch((int)mappedEvent.getX(), (int)mappedEvent.getY(), false);
+ sessionViewListener.onSessionViewEndTouch();
+ return true;
+ }
+ }
+
+ private class SessionDoubleGestureListener implements DoubleGestureDetector.OnDoubleGestureListener {
+ private MotionEvent prevEvent = null;
+
+ public boolean onDoubleTouchDown(MotionEvent e) {
+ sessionViewListener.onSessionViewBeginTouch();
+ prevEvent = MotionEvent.obtain(e);
+ return true;
+ }
+
+ public boolean onDoubleTouchUp(MotionEvent e) {
+ if(prevEvent != null)
+ {
+ prevEvent.recycle();
+ prevEvent = null;
+ }
+ sessionViewListener.onSessionViewEndTouch();
+ return true;
+ }
+
+ public boolean onDoubleTouchScroll(MotionEvent e1, MotionEvent e2) {
+ // calc if user scrolled up or down (or if any scrolling happened at all)
+ float deltaY = e2.getY() - prevEvent.getY();
+ if(deltaY > TOUCH_SCROLL_DELTA)
+ {
+ sessionViewListener.onSessionViewScroll(true);
+ prevEvent.recycle();
+ prevEvent = MotionEvent.obtain(e2);
+ }
+ else if(deltaY < -TOUCH_SCROLL_DELTA)
+ {
+ sessionViewListener.onSessionViewScroll(false);
+ prevEvent.recycle();
+ prevEvent = MotionEvent.obtain(e2);
+ }
+ return true;
+ }
+
+ public boolean onDoubleTouchSingleTap(MotionEvent e) {
+ // send single click
+ MotionEvent mappedEvent = mapDoubleTouchEvent(e);
+ sessionViewListener.onSessionViewRightTouch((int)mappedEvent.getX(), (int)mappedEvent.getY(), true);
+ sessionViewListener.onSessionViewRightTouch((int)mappedEvent.getX(), (int)mappedEvent.getY(), false);
+ return true;
+ }
+ }
+
+ private void initSessionView(Context context)
+ {
+ invalidRegions = new Stack();
+ gestureDetector = new GestureDetector(context, new SessionGestureListener(), null, true);
+ doubleGestureDetector = new DoubleGestureDetector(context, null, new SessionDoubleGestureListener());
+
+ scaleFactor = 1.0f;
+ scaleMatrix = new Matrix();
+ invScaleMatrix = new Matrix();
+ invalidRegionF = new RectF();
+ }
+
+ public SessionView(Context context) {
+ super(context);
+ initSessionView(context);
+ }
+
+ public SessionView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initSessionView(context);
+ }
+
+ public SessionView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ initSessionView(context);
+ }
+
+ public void setScaleGestureDetector(ScaleGestureDetector scaleGestureDetector) {
+ doubleGestureDetector.setScaleGestureDetector(scaleGestureDetector);
+ }
+
+ public void setSessionViewListener(SessionViewListener sessionViewListener) {
+ this.sessionViewListener = sessionViewListener;
+ }
+
+ public void addInvalidRegion(Rect invalidRegion) {
+ // correctly transform invalid region depending on current scaling
+ invalidRegionF.set(invalidRegion);
+ scaleMatrix.mapRect(invalidRegionF);
+ invalidRegionF.roundOut(invalidRegion);
+
+ invalidRegions.add(invalidRegion);
+ }
+
+ public void invalidateRegion()
+ {
+ invalidate(invalidRegions.pop());
+ }
+
+ public void onSurfaceChange(SessionState session)
+ {
+ surface = session.getSurface();
+ Bitmap bitmap = surface.getBitmap();
+ width = bitmap.getWidth();
+ height = bitmap.getHeight();
+ surface.setBounds(0, 0, width, height);
+
+ setMinimumWidth(width);
+ setMinimumHeight(height);
+
+ requestLayout();
+ }
+
+ public void setZoom(float factor) {
+ // calc scale matrix and inverse scale matrix (to correctly transform the view and moues coordinates)
+ scaleFactor = factor;
+ scaleMatrix.setScale(scaleFactor, scaleFactor);
+ invScaleMatrix.setScale(1.0f / scaleFactor, 1.0f / scaleFactor);
+
+ // update layout
+ requestLayout();
+ }
+
+ public float getZoom() {
+ return scaleFactor;
+ }
+
+ public boolean isAtMaxZoom() {
+ return (scaleFactor > (MAX_SCALE_FACTOR - SCALE_FACTOR_DELTA));
+ }
+
+ public boolean isAtMinZoom() {
+ return (scaleFactor < (MIN_SCALE_FACTOR + SCALE_FACTOR_DELTA));
+ }
+
+ public boolean zoomIn(float factor) {
+ boolean res = true;
+ scaleFactor += factor;
+ if(scaleFactor > (MAX_SCALE_FACTOR - SCALE_FACTOR_DELTA))
+ {
+ scaleFactor = MAX_SCALE_FACTOR;
+ res = false;
+ }
+ setZoom(scaleFactor);
+ return res;
+ }
+
+ public boolean zoomOut(float factor) {
+ boolean res = true;
+ scaleFactor -= factor;
+ if(scaleFactor < (MIN_SCALE_FACTOR + SCALE_FACTOR_DELTA))
+ {
+ scaleFactor = MIN_SCALE_FACTOR;
+ res = false;
+ }
+ setZoom(scaleFactor);
+ return res;
+ }
+
+ public void setTouchPointerPadding(int widht, int height) {
+ touchPointerPaddingWidth = widht;
+ touchPointerPaddingHeight = height;
+ requestLayout();
+ }
+
+ public int getTouchPointerPaddingWidth() {
+ return touchPointerPaddingWidth;
+ }
+
+ public int getTouchPointerPaddingHeight() {
+ return touchPointerPaddingHeight;
+ }
+
+ @Override
+ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ Log.v("SessionView", width + "x" + height);
+ this.setMeasuredDimension((int)(width * scaleFactor) + touchPointerPaddingWidth, (int)(height * scaleFactor) + touchPointerPaddingHeight);
+ }
+
+ @Override
+ public void onDraw(Canvas canvas)
+ {
+ super.onDraw(canvas);
+
+ canvas.save();
+ canvas.concat(scaleMatrix);
+ surface.draw(canvas);
+ canvas.restore();
+ }
+
+ // dirty hack: we call back to our activity and call onBackPressed as this doesn't reach us when the soft keyboard is shown ...
+ @Override
+ public boolean dispatchKeyEventPreIme(KeyEvent event) {
+ if(event.getKeyCode() == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN)
+ ((SessionActivity)this.getContext()).onBackPressed();
+ return super.dispatchKeyEventPreIme(event);
+ }
+
+ // perform mapping on the touch event's coordinates according to the current scaling
+ private MotionEvent mapTouchEvent(MotionEvent event) {
+ MotionEvent mappedEvent = MotionEvent.obtain(event);
+ float[] coordinates = { mappedEvent.getX(), mappedEvent.getY() };
+ invScaleMatrix.mapPoints(coordinates);
+ mappedEvent.setLocation(coordinates[0], coordinates[1]);
+ return mappedEvent;
+ }
+
+ // perform mapping on the double touch event's coordinates according to the current scaling
+ private MotionEvent mapDoubleTouchEvent(MotionEvent event) {
+ MotionEvent mappedEvent = MotionEvent.obtain(event);
+ float[] coordinates = { (mappedEvent.getX(0) + mappedEvent.getX(1)) / 2, (mappedEvent.getY(0) + mappedEvent.getY(1)) / 2 };
+ invScaleMatrix.mapPoints(coordinates);
+ mappedEvent.setLocation(coordinates[0], coordinates[1]);
+ return mappedEvent;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ boolean res = gestureDetector.onTouchEvent(event);
+ res |= doubleGestureDetector.onTouchEvent(event);
+ return res;
+ }
+
+}
diff --git a/client/Android/src/com/freerdp/afreerdp/presentation/ShortcutsActivity.java b/client/Android/src/com/freerdp/afreerdp/presentation/ShortcutsActivity.java
new file mode 100644
index 000000000..947cb05e2
--- /dev/null
+++ b/client/Android/src/com/freerdp/afreerdp/presentation/ShortcutsActivity.java
@@ -0,0 +1,154 @@
+/*
+ Android Shortcut activity
+
+ Copyright 2013 Thinstuff Technologies GmbH, Author: Martin Fleisz
+
+ This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
+ If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+package com.freerdp.afreerdp.presentation;
+
+import java.util.ArrayList;
+
+import com.freerdp.afreerdp.R;
+import com.freerdp.afreerdp.application.GlobalApp;
+import com.freerdp.afreerdp.domain.BookmarkBase;
+import com.freerdp.afreerdp.services.SessionRequestHandlerActivity;
+import com.freerdp.afreerdp.utils.BookmarkArrayAdapter;
+
+import android.app.AlertDialog;
+import android.app.ListActivity;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.AdapterView;
+
+public class ShortcutsActivity extends ListActivity {
+
+ public static final String TAG = "ShortcutsActivity";
+
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+
+ super.onCreate(savedInstanceState);
+
+ Intent intent = getIntent();
+ if(Intent.ACTION_CREATE_SHORTCUT.equals(intent.getAction()))
+ {
+ // set listeners for the list view
+ getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ public void onItemClick(AdapterView> parent, View view, int position, long id)
+ {
+ String refStr = view.getTag().toString();
+ String defLabel = ((TextView)(view.findViewById(R.id.bookmark_text1))).getText().toString();
+ setupShortcut(refStr, defLabel);
+ }
+ });
+ }
+ else
+ {
+ // just exit
+ finish();
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ // create bookmark cursor adapter
+ ArrayList bookmarks = GlobalApp.getManualBookmarkGateway().findAll();
+ BookmarkArrayAdapter bookmarkAdapter = new BookmarkArrayAdapter(this, android.R.layout.simple_list_item_2, bookmarks);
+ getListView().setAdapter(bookmarkAdapter);
+ }
+
+ public void onPause() {
+ super.onPause();
+ getListView().setAdapter(null);
+ }
+
+ /**
+ * This function creates a shortcut and returns it to the caller. There are actually two
+ * intents that you will send back.
+ *
+ * The first intent serves as a container for the shortcut and is returned to the launcher by
+ * setResult(). This intent must contain three fields:
+ *
+ *
+ * - {@link android.content.Intent#EXTRA_SHORTCUT_INTENT} The shortcut intent.
+ * - {@link android.content.Intent#EXTRA_SHORTCUT_NAME} The text that will be displayed with
+ * the shortcut.
+ * - {@link android.content.Intent#EXTRA_SHORTCUT_ICON} The shortcut's icon, if provided as a
+ * bitmap, or {@link android.content.Intent#EXTRA_SHORTCUT_ICON_RESOURCE} if provided as
+ * a drawable resource.
+ *
+ *
+ * If you use a simple drawable resource, note that you must wrapper it using
+ * {@link android.content.Intent.ShortcutIconResource}, as shown below. This is required so
+ * that the launcher can access resources that are stored in your application's .apk file. If
+ * you return a bitmap, such as a thumbnail, you can simply put the bitmap into the extras
+ * bundle using {@link android.content.Intent#EXTRA_SHORTCUT_ICON}.
+ *
+ * The shortcut intent can be any intent that you wish the launcher to send, when the user
+ * clicks on the shortcut. Typically this will be {@link android.content.Intent#ACTION_VIEW}
+ * with an appropriate Uri for your content, but any Intent will work here as long as it
+ * triggers the desired action within your Activity.
+ */
+
+ private void setupShortcut(String strRef, String defaultLabel) {
+ final String paramStrRef = strRef;
+ final String paramDefaultLabel = defaultLabel;
+ final Context paramContext = this;
+
+ // display edit dialog to the user so he can specify the shortcut name
+ final EditText input = new EditText(this);
+ input.setText(defaultLabel);
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(R.string.dlg_title_create_shortcut)
+ .setMessage(R.string.dlg_msg_create_shortcut)
+ .setView(input)
+ .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener()
+ {
+ @Override
+ public void onClick(DialogInterface dialog, int which)
+ {
+ String label = input.getText().toString();
+ if(label.length() == 0)
+ label = paramDefaultLabel;
+
+ Intent shortcutIntent = new Intent(Intent.ACTION_VIEW);
+ shortcutIntent.setClassName(paramContext, SessionRequestHandlerActivity.class.getName());
+ shortcutIntent.setData(Uri.parse(paramStrRef));
+
+ // Then, set up the container intent (the response to the caller)
+ Intent intent = new Intent();
+ intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
+ intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, label);
+ Parcelable iconResource = Intent.ShortcutIconResource.fromContext(paramContext, R.drawable.icon_launcher_freerdp);
+ intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource);
+
+ // Now, return the result to the launcher
+ setResult(RESULT_OK, intent);
+ finish();
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener()
+ {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ }
+ })
+ .create().show();
+
+ }
+
+}
diff --git a/client/Android/src/com/freerdp/afreerdp/presentation/TouchPointerView.java b/client/Android/src/com/freerdp/afreerdp/presentation/TouchPointerView.java
new file mode 100644
index 000000000..7850f9fdf
--- /dev/null
+++ b/client/Android/src/com/freerdp/afreerdp/presentation/TouchPointerView.java
@@ -0,0 +1,360 @@
+/*
+ Android Touch Pointer view
+
+ Copyright 2013 Thinstuff Technologies GmbH, Author: Martin Fleisz
+
+ This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
+ If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+package com.freerdp.afreerdp.presentation;
+
+import com.freerdp.afreerdp.R;
+import com.freerdp.afreerdp.utils.GestureDetector;
+
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.RectF;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.widget.ImageView;
+
+public class TouchPointerView extends ImageView {
+
+ // touch pointer listener - is triggered if an action field is
+ public interface TouchPointerListener {
+ abstract void onTouchPointerClose();
+ abstract void onTouchPointerLeftClick(int x, int y, boolean down);
+ abstract void onTouchPointerRightClick(int x, int y, boolean down);
+ abstract void onTouchPointerMove(int x, int y);
+ abstract void onTouchPointerScroll(boolean down);
+ abstract void onTouchPointerToggleKeyboard();
+ abstract void onTouchPointerToggleExtKeyboard();
+ abstract void onTouchPointerResetScrollZoom();
+ }
+
+
+ private class UIHandler extends Handler {
+
+ UIHandler() {
+ super();
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ setPointerImage(R.drawable.touch_pointer_default);
+ }
+ }
+
+
+
+ // the touch pointer consists of 9 quadrants with the following functionality:
+ //
+ // -------------
+ // | 0 | 1 | 2 |
+ // -------------
+ // | 3 | 4 | 5 |
+ // -------------
+ // | 6 | 7 | 8 |
+ // -------------
+ //
+ // 0 ... contains the actual pointer (the tip must be centered in the quadrant)
+ // 1 ... is left empty
+ // 2, 3, 5, 6, 7, 8 ... function quadrants that issue a callback
+ // 4 ... pointer center used for left clicks and to drag the pointer
+
+ private static final int POINTER_ACTION_CURSOR = 0;
+ private static final int POINTER_ACTION_CLOSE = 3;
+ private static final int POINTER_ACTION_RCLICK = 2;
+ private static final int POINTER_ACTION_LCLICK = 4;
+ private static final int POINTER_ACTION_MOVE = 4;
+ private static final int POINTER_ACTION_SCROLL = 5;
+ private static final int POINTER_ACTION_RESET = 6;
+ private static final int POINTER_ACTION_KEYBOARD = 7;
+ private static final int POINTER_ACTION_EXTKEYBOARD = 8;
+
+ private static final float SCROLL_DELTA = 10.0f;
+
+ private static final int DEFAULT_TOUCH_POINTER_RESTORE_DELAY = 150;
+
+ private RectF pointerRect;
+ private RectF pointerAreaRects[] = new RectF[9];
+ private Matrix translationMatrix;
+ private boolean pointerMoving = false;
+ private boolean pointerScrolling = false;
+ private TouchPointerListener listener = null;
+ private UIHandler uiHandler = new UIHandler();
+
+ // gesture detection
+ private GestureDetector gestureDetector;
+
+ private class TouchPointerGestureListener extends GestureDetector.SimpleOnGestureListener {
+
+ private MotionEvent prevEvent = null;
+
+ public boolean onDown(MotionEvent e) {
+ if(pointerAreaTouched(e, POINTER_ACTION_MOVE))
+ {
+ prevEvent = MotionEvent.obtain(e);
+ pointerMoving = true;
+ }
+ else if(pointerAreaTouched(e, POINTER_ACTION_SCROLL))
+ {
+ prevEvent = MotionEvent.obtain(e);
+ pointerScrolling = true;
+ setPointerImage(R.drawable.touch_pointer_scroll);
+ }
+
+ return true;
+ }
+
+ public boolean onUp(MotionEvent e) {
+ if(prevEvent != null)
+ {
+ prevEvent.recycle();
+ prevEvent = null;
+ }
+
+ if(pointerScrolling)
+ setPointerImage(R.drawable.touch_pointer_default);
+
+ pointerMoving = false;
+ pointerScrolling = false;
+ return true;
+ }
+
+ public void onLongPress(MotionEvent e) {
+ if(pointerAreaTouched(e, POINTER_ACTION_LCLICK))
+ {
+ setPointerImage(R.drawable.touch_pointer_active);
+ pointerMoving = true;
+ RectF rect = getCurrentPointerArea(POINTER_ACTION_CURSOR);
+ listener.onTouchPointerLeftClick((int)rect.centerX(), (int)rect.centerY(), true);
+ }
+ }
+
+ public void onLongPressUp(MotionEvent e) {
+ if(pointerMoving)
+ {
+ setPointerImage(R.drawable.touch_pointer_default);
+ pointerMoving = false;
+ RectF rect = getCurrentPointerArea(POINTER_ACTION_CURSOR);
+ listener.onTouchPointerLeftClick((int)rect.centerX(), (int)rect.centerY(), false);
+ }
+ }
+
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+ if(pointerMoving)
+ {
+ // move pointer graphics
+ movePointer((int)(e2.getX() - prevEvent.getX()), (int)(e2.getY() - prevEvent.getY()));
+ prevEvent.recycle();
+ prevEvent = MotionEvent.obtain(e2);
+
+ // send move notification
+ RectF rect = getCurrentPointerArea(POINTER_ACTION_CURSOR);
+ listener.onTouchPointerMove((int)rect.centerX(), (int)rect.centerY());
+ return true;
+ }
+ else if(pointerScrolling)
+ {
+ // calc if user scrolled up or down (or if any scrolling happened at all)
+ float deltaY = e2.getY() - prevEvent.getY();
+ if(deltaY > SCROLL_DELTA)
+ {
+ listener.onTouchPointerScroll(true);
+ prevEvent.recycle();
+ prevEvent = MotionEvent.obtain(e2);
+ }
+ else if(deltaY < -SCROLL_DELTA)
+ {
+ listener.onTouchPointerScroll(false);
+ prevEvent.recycle();
+ prevEvent = MotionEvent.obtain(e2);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public boolean onSingleTapUp(MotionEvent e) {
+ // look what area got touched and fire actions accordingly
+ if(pointerAreaTouched(e, POINTER_ACTION_CLOSE))
+ listener.onTouchPointerClose();
+ else if(pointerAreaTouched(e, POINTER_ACTION_LCLICK))
+ {
+ displayPointerImageAction(R.drawable.touch_pointer_lclick);
+ RectF rect = getCurrentPointerArea(POINTER_ACTION_CURSOR);
+ listener.onTouchPointerLeftClick((int)rect.centerX(), (int)rect.centerY(), true);
+ listener.onTouchPointerLeftClick((int)rect.centerX(), (int)rect.centerY(), false);
+ }
+ else if(pointerAreaTouched(e, POINTER_ACTION_RCLICK))
+ {
+ displayPointerImageAction(R.drawable.touch_pointer_rclick);
+ RectF rect = getCurrentPointerArea(POINTER_ACTION_CURSOR);
+ listener.onTouchPointerRightClick((int)rect.centerX(), (int)rect.centerY(), true);
+ listener.onTouchPointerRightClick((int)rect.centerX(), (int)rect.centerY(), false);
+ }
+ else if(pointerAreaTouched(e, POINTER_ACTION_KEYBOARD))
+ {
+ displayPointerImageAction(R.drawable.touch_pointer_keyboard);
+ listener.onTouchPointerToggleKeyboard();
+ }
+ else if(pointerAreaTouched(e, POINTER_ACTION_EXTKEYBOARD))
+ {
+ displayPointerImageAction(R.drawable.touch_pointer_extkeyboard);
+ listener.onTouchPointerToggleExtKeyboard();
+ }
+ else if(pointerAreaTouched(e, POINTER_ACTION_RESET))
+ {
+ displayPointerImageAction(R.drawable.touch_pointer_reset);
+ listener.onTouchPointerResetScrollZoom();
+ }
+
+ return true;
+ }
+
+ public boolean onDoubleTap(MotionEvent e) {
+ // issue a double click notification if performed in center quadrant
+ if(pointerAreaTouched(e, POINTER_ACTION_LCLICK))
+ {
+ RectF rect = getCurrentPointerArea(POINTER_ACTION_CURSOR);
+ listener.onTouchPointerLeftClick((int)rect.centerX(), (int)rect.centerY(), true);
+ listener.onTouchPointerLeftClick((int)rect.centerX(), (int)rect.centerY(), false);
+ }
+ return true;
+ }
+ }
+
+ public TouchPointerView(Context context) {
+ super(context);
+ initTouchPointer(context);
+ }
+
+ public TouchPointerView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initTouchPointer(context);
+ }
+
+ public TouchPointerView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ initTouchPointer(context);
+ }
+
+ private void initTouchPointer(Context context) {
+ gestureDetector = new GestureDetector(context, new TouchPointerGestureListener(), null, true);
+ gestureDetector.setLongPressTimeout(500);
+ translationMatrix = new Matrix();
+ setScaleType(ScaleType.MATRIX);
+ setImageMatrix(translationMatrix);
+
+ // init rects
+ final float rectSizeWidth = (float)getDrawable().getIntrinsicWidth() / 3.0f;
+ final float rectSizeHeight = (float)getDrawable().getIntrinsicWidth() / 3.0f;
+ for(int i = 0; i < 3; i++)
+ {
+ for(int j = 0; j < 3; j++)
+ {
+ int left = (int)(j * rectSizeWidth);
+ int top = (int)(i * rectSizeHeight);
+ int right = left + (int)rectSizeWidth;
+ int bottom = top + (int)rectSizeHeight;
+ pointerAreaRects[i * 3 + j] = new RectF(left, top, right, bottom);
+ }
+ }
+ pointerRect = new RectF(0, 0, getDrawable().getIntrinsicWidth(), getDrawable().getIntrinsicHeight());
+ }
+
+ public void setTouchPointerListener(TouchPointerListener listener) {
+ this.listener = listener;
+ }
+
+ public int getPointerWidth() {
+ return getDrawable().getIntrinsicWidth();
+ }
+
+ public int getPointerHeight() {
+ return getDrawable().getIntrinsicHeight();
+ }
+
+ public float[] getPointerPosition() {
+ float []curPos = new float[2];
+ translationMatrix.mapPoints(curPos);
+ return curPos;
+ }
+
+ private void movePointer(float deltaX, float deltaY) {
+ translationMatrix.postTranslate(deltaX, deltaY);
+ setImageMatrix(translationMatrix);
+ }
+
+ private void ensureVisibility(int screen_width, int screen_height)
+ {
+ float []curPos = new float[2];
+ translationMatrix.mapPoints(curPos);
+
+ if (curPos[0] > (screen_width - pointerRect.width()))
+ curPos[0] = screen_width - pointerRect.width();
+ if (curPos[0] < 0)
+ curPos[0] = 0;
+ if (curPos[1] > (screen_height - pointerRect.height()))
+ curPos[1] = screen_height - pointerRect.height();
+ if (curPos[1] < 0)
+ curPos[1] = 0;
+
+ translationMatrix.setTranslate(curPos[0], curPos[1]);
+ setImageMatrix(translationMatrix);
+ }
+
+ private void displayPointerImageAction(int resId)
+ {
+ setPointerImage(resId);
+ uiHandler.sendEmptyMessageDelayed(0, DEFAULT_TOUCH_POINTER_RESTORE_DELAY);
+ }
+
+ private void setPointerImage(int resId)
+ {
+ setImageResource(resId);
+ }
+
+ // returns the pointer area with the current translation matrix applied
+ private RectF getCurrentPointerArea(int area) {
+ RectF transRect = new RectF(pointerAreaRects[area]);
+ translationMatrix.mapRect(transRect);
+ return transRect;
+ }
+
+ private boolean pointerAreaTouched(MotionEvent event, int area) {
+ RectF transRect = new RectF(pointerAreaRects[area]);
+ translationMatrix.mapRect(transRect);
+ if(transRect.contains(event.getX(), event.getY()))
+ return true;
+ return false;
+ }
+
+ private boolean pointerTouched(MotionEvent event) {
+ RectF transRect = new RectF(pointerRect);
+ translationMatrix.mapRect(transRect);
+ if(transRect.contains(event.getX(), event.getY()))
+ return true;
+ return false;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ // check if pointer is being moved or if we are in scroll mode or if the pointer is touched
+ if(!pointerMoving && !pointerScrolling && !pointerTouched(event))
+ return false;
+ return gestureDetector.onTouchEvent(event);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom)
+ {
+ // ensure touch pointer is visible
+ if (changed)
+ ensureVisibility(right - left, bottom - top);
+ }
+}
diff --git a/client/Android/src/com/freerdp/afreerdp/services/BookmarkBaseGateway.java b/client/Android/src/com/freerdp/afreerdp/services/BookmarkBaseGateway.java
new file mode 100644
index 000000000..e600b36e0
--- /dev/null
+++ b/client/Android/src/com/freerdp/afreerdp/services/BookmarkBaseGateway.java
@@ -0,0 +1,407 @@
+/*
+ Helper class to access bookmark database
+
+ Copyright 2013 Thinstuff Technologies GmbH, Author: Martin Fleisz
+
+ This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
+ If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+package com.freerdp.afreerdp.services;
+
+
+import java.util.ArrayList;
+
+import com.freerdp.afreerdp.domain.BookmarkBase;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.util.Log;
+
+public abstract class BookmarkBaseGateway
+{
+ private final static String TAG = "BookmarkBaseGateway";
+ private BookmarkDB bookmarkDB;
+
+ protected abstract BookmarkBase createBookmark();
+ protected abstract String getBookmarkTableName();
+ protected abstract void addBookmarkSpecificColumns(ArrayList columns);
+ protected abstract void addBookmarkSpecificColumns(BookmarkBase bookmark, ContentValues columns);
+ protected abstract void readBookmarkSpecificColumns(BookmarkBase bookmark, Cursor cursor);
+
+ public BookmarkBaseGateway(BookmarkDB bookmarkDB)
+ {
+ this.bookmarkDB = bookmarkDB;
+ }
+
+ public void insert(BookmarkBase bookmark)
+ {
+ // begin transaction
+ SQLiteDatabase db = getWritableDatabase();
+ db.beginTransaction();
+
+ long rowid;
+ ContentValues values = new ContentValues();
+ values.put("label", bookmark.getLabel());
+ values.put("username", bookmark.getUsername());
+ values.put("password", bookmark.getPassword());
+ values.put("domain", bookmark.getDomain());
+ // insert screen and performance settings
+ rowid = insertScreenSettings(db, bookmark.getScreenSettings());
+ values.put("screen_settings", rowid);
+ rowid = insertPerformanceFlags(db, bookmark.getPerformanceFlags());
+ values.put("performance_flags", rowid);
+
+ // advanced settings
+ values.put("enable_3g_settings", bookmark.getAdvancedSettings().getEnable3GSettings());
+ // insert 3G screen and 3G performance settings
+ rowid = insertScreenSettings(db, bookmark.getAdvancedSettings().getScreen3G());
+ values.put("screen_3g", rowid);
+ rowid = insertPerformanceFlags(db, bookmark.getAdvancedSettings().getPerformance3G());
+ values.put("performance_3g", rowid);
+ values.put("security", bookmark.getAdvancedSettings().getSecurity());
+ values.put("console_mode", bookmark.getAdvancedSettings().getConsoleMode());
+ values.put("remote_program", bookmark.getAdvancedSettings().getRemoteProgram());
+ values.put("work_dir", bookmark.getAdvancedSettings().getWorkDir());
+
+ // add any special columns
+ addBookmarkSpecificColumns(bookmark, values);
+
+ // insert bookmark and end transaction
+ db.insertOrThrow(getBookmarkTableName(), null, values);
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ }
+
+ public boolean update(BookmarkBase bookmark)
+ {
+ // start a transaction
+ SQLiteDatabase db = getWritableDatabase();
+ db.beginTransaction();
+
+ // bookmark settings
+ ContentValues values = new ContentValues();
+ values.put("label", bookmark.getLabel());
+ values.put("username", bookmark.getUsername());
+ values.put("password", bookmark.getPassword());
+ values.put("domain", bookmark.getDomain());
+ // update screen and performance settings settings
+ updateScreenSettings(db, bookmark);
+ updatePerformanceFlags(db, bookmark);
+
+ // advanced settings
+ values.put("enable_3g_settings", bookmark.getAdvancedSettings().getEnable3GSettings());
+ // update 3G screen and 3G performance settings settings
+ updateScreenSettings3G(db, bookmark);
+ updatePerformanceFlags3G(db, bookmark);
+ values.put("security", bookmark.getAdvancedSettings().getSecurity());
+ values.put("console_mode", bookmark.getAdvancedSettings().getConsoleMode());
+ values.put("remote_program", bookmark.getAdvancedSettings().getRemoteProgram());
+ values.put("work_dir", bookmark.getAdvancedSettings().getWorkDir());
+
+ addBookmarkSpecificColumns(bookmark, values);
+
+ // update bookmark
+ boolean res = (db.update(getBookmarkTableName(), values, BookmarkDB.ID + " = " + bookmark.getId(), null) == 1);
+
+ // commit
+ db.setTransactionSuccessful();
+ db.endTransaction();
+
+ return res;
+ }
+
+ public void delete(long id)
+ {
+ SQLiteDatabase db = getWritableDatabase();
+ db.delete(getBookmarkTableName(), BookmarkDB.ID + " = " + id, null);
+ }
+
+ public BookmarkBase findById(long id)
+ {
+ Cursor cursor = queryBookmarks(getBookmarkTableName() + "." + BookmarkDB.ID + " = " + id, null);
+ if(cursor.getCount() == 0)
+ {
+ cursor.close();
+ return null;
+ }
+
+ cursor.moveToFirst();
+ BookmarkBase bookmark = getBookmarkFromCursor(cursor);
+ cursor.close();
+ return bookmark;
+ }
+
+ public BookmarkBase findByLabel(String label)
+ {
+ Cursor cursor = queryBookmarks("label = '" + label + "'", "label");
+ if(cursor.getCount() > 1)
+ Log.e(TAG, "More than one bookmark with the same label found!");
+
+ BookmarkBase bookmark = null;
+ if(cursor.moveToFirst())
+ bookmark = getBookmarkFromCursor(cursor);
+
+ cursor.close();
+ return bookmark;
+ }
+
+ public ArrayList findByLabelLike(String pattern)
+ {
+ Cursor cursor = queryBookmarks("label LIKE '%" + pattern + "%'", "label");
+ ArrayList bookmarks = new ArrayList(cursor.getCount());
+
+ if(cursor.moveToFirst())
+ {
+ do
+ {
+ bookmarks.add(getBookmarkFromCursor(cursor));
+ }while(cursor.moveToNext());
+ }
+
+ cursor.close();
+ return bookmarks;
+ }
+
+ public ArrayList findAll()
+ {
+ Cursor cursor = queryBookmarks(null, "label");
+ ArrayList bookmarks = new ArrayList(cursor.getCount());
+
+ if(cursor.moveToFirst())
+ {
+ do
+ {
+ bookmarks.add(getBookmarkFromCursor(cursor));
+ }while(cursor.moveToNext());
+ }
+
+ cursor.close();
+ return bookmarks;
+ }
+
+ protected Cursor queryBookmarks(String whereClause, String orderBy)
+ {
+ // create tables string
+ String ID = BookmarkDB.ID;
+ String bmTable = getBookmarkTableName();
+ String tables = bmTable + " INNER JOIN tbl_screen_settings AS join_screen_settings ON join_screen_settings." + ID + " = " + bmTable + ".screen_settings" +
+ " INNER JOIN tbl_performance_flags AS join_performance_flags ON join_performance_flags." + ID + " = " + bmTable + ".performance_flags" +
+ " INNER JOIN tbl_screen_settings AS join_screen_3G ON join_screen_3G." + ID + " = " + bmTable + ".screen_3g" +
+ " INNER JOIN tbl_performance_flags AS join_performance_3G ON join_performance_3G." + ID + " = " + bmTable + ".performance_3g";
+
+ // create columns list
+ ArrayList columns = new ArrayList(10);
+ addBookmarkColumns(columns);
+ addScreenSettingsColumns(columns);
+ addPerformanceFlagsColumns(columns);
+ addScreenSettings3GColumns(columns);
+ addPerformanceFlags3GColumns(columns);
+
+ String[] cols = new String[columns.size()];
+ SQLiteDatabase db = getReadableDatabase();
+ return db.rawQuery(SQLiteQueryBuilder.buildQueryString(false, tables, columns.toArray(cols), whereClause, null, null, orderBy, null), null);
+ }
+
+ private void addBookmarkColumns(ArrayList columns) {
+ columns.add(getBookmarkTableName() + "." + BookmarkDB.ID + " bookmarkId");
+ columns.add("label");
+ columns.add("username");
+ columns.add("password");
+ columns.add("domain");
+
+ // advanced settings
+ columns.add("enable_3g_settings");
+ columns.add("security");
+ columns.add("console_mode");
+ columns.add("remote_program");
+ columns.add("work_dir");
+
+ addBookmarkSpecificColumns(columns);
+ }
+
+ private void addScreenSettingsColumns(ArrayList columns) {
+ columns.add("join_screen_settings.colors as screenColors");
+ columns.add("join_screen_settings.resolution as screenResolution");
+ columns.add("join_screen_settings.width as screenWidth");
+ columns.add("join_screen_settings.height as screenHeight");
+ }
+
+ private void addPerformanceFlagsColumns(ArrayList columns) {
+ columns.add("join_performance_flags.perf_remotefx as performanceRemoteFX");
+ columns.add("join_performance_flags.perf_wallpaper as performanceWallpaper");
+ columns.add("join_performance_flags.perf_theming as performanceTheming");
+ columns.add("join_performance_flags.perf_full_window_drag as performanceFullWindowDrag");
+ columns.add("join_performance_flags.perf_menu_animations as performanceMenuAnimations");
+ columns.add("join_performance_flags.perf_font_smoothing as performanceFontSmoothing");
+ columns.add("join_performance_flags.perf_desktop_composition performanceDesktopComposition");
+ }
+
+ private void addScreenSettings3GColumns(ArrayList columns) {
+ columns.add("join_screen_3G.colors as screenColors3G");
+ columns.add("join_screen_3G.resolution as screenResolution3G");
+ columns.add("join_screen_3G.width as screenWidth3G");
+ columns.add("join_screen_3G.height as screenHeight3G");
+ }
+
+ private void addPerformanceFlags3GColumns(ArrayList columns) {
+ columns.add("join_performance_3G.perf_remotefx as performanceRemoteFX3G");
+ columns.add("join_performance_3G.perf_wallpaper as performanceWallpaper3G");
+ columns.add("join_performance_3G.perf_theming as performanceTheming3G");
+ columns.add("join_performance_3G.perf_full_window_drag as performanceFullWindowDrag3G");
+ columns.add("join_performance_3G.perf_menu_animations as performanceMenuAnimations3G");
+ columns.add("join_performance_3G.perf_font_smoothing as performanceFontSmoothing3G");
+ columns.add("join_performance_3G.perf_desktop_composition performanceDesktopComposition3G");
+ }
+
+ protected BookmarkBase getBookmarkFromCursor(Cursor cursor)
+ {
+ BookmarkBase bookmark = createBookmark();
+ bookmark.setId(cursor.getLong(cursor.getColumnIndex("bookmarkId")));
+ bookmark.setLabel(cursor.getString(cursor.getColumnIndex("label")));
+ bookmark.setUsername(cursor.getString(cursor.getColumnIndex("username")));
+ bookmark.setPassword(cursor.getString(cursor.getColumnIndex("password")));
+ bookmark.setDomain(cursor.getString(cursor.getColumnIndex("domain")));
+ readScreenSettings(bookmark, cursor);
+ readPerformanceFlags(bookmark, cursor);
+
+ // advanced settings
+ bookmark.getAdvancedSettings().setEnable3GSettings(cursor.getInt(cursor.getColumnIndex("enable_3g_settings")) == 0 ? false : true);
+ readScreenSettings3G(bookmark, cursor);
+ readPerformanceFlags3G(bookmark, cursor);
+ bookmark.getAdvancedSettings().setSecurity(cursor.getInt(cursor.getColumnIndex("security")));
+ bookmark.getAdvancedSettings().setConsoleMode(cursor.getInt(cursor.getColumnIndex("console_mode")) == 0 ? false : true);
+ bookmark.getAdvancedSettings().setRemoteProgram(cursor.getString(cursor.getColumnIndex("remote_program")));
+ bookmark.getAdvancedSettings().setWorkDir(cursor.getString(cursor.getColumnIndex("work_dir")));
+
+ readBookmarkSpecificColumns(bookmark, cursor);
+
+ return bookmark;
+ }
+
+ private void readScreenSettings(BookmarkBase bookmark, Cursor cursor) {
+ BookmarkBase.ScreenSettings screenSettings = bookmark.getScreenSettings();
+ screenSettings.setColors(cursor.getInt(cursor.getColumnIndex("screenColors")));
+ screenSettings.setResolution(cursor.getInt(cursor.getColumnIndex("screenResolution")));
+ screenSettings.setWidth(cursor.getInt(cursor.getColumnIndex("screenWidth")));
+ screenSettings.setHeight(cursor.getInt(cursor.getColumnIndex("screenHeight")));
+ }
+
+ private void readPerformanceFlags(BookmarkBase bookmark, Cursor cursor) {
+ BookmarkBase.PerformanceFlags perfFlags = bookmark.getPerformanceFlags();
+ perfFlags.setRemoteFX(cursor.getInt(cursor.getColumnIndex("performanceRemoteFX")) == 0 ? false : true);
+ perfFlags.setWallpaper(cursor.getInt(cursor.getColumnIndex("performanceWallpaper")) == 0 ? false : true);
+ perfFlags.setTheming(cursor.getInt(cursor.getColumnIndex("performanceTheming")) == 0 ? false : true);
+ perfFlags.setFullWindowDrag(cursor.getInt(cursor.getColumnIndex("performanceFullWindowDrag")) == 0 ? false : true);
+ perfFlags.setMenuAnimations(cursor.getInt(cursor.getColumnIndex("performanceMenuAnimations")) == 0 ? false : true);
+ perfFlags.setFontSmoothing(cursor.getInt(cursor.getColumnIndex("performanceFontSmoothing")) == 0 ? false : true);
+ perfFlags.setDesktopComposition(cursor.getInt(cursor.getColumnIndex("performanceDesktopComposition")) == 0 ? false : true);
+ }
+
+ private void readScreenSettings3G(BookmarkBase bookmark, Cursor cursor) {
+ BookmarkBase.ScreenSettings screenSettings = bookmark.getAdvancedSettings().getScreen3G();
+ screenSettings.setColors(cursor.getInt(cursor.getColumnIndex("screenColors3G")));
+ screenSettings.setResolution(cursor.getInt(cursor.getColumnIndex("screenResolution3G")));
+ screenSettings.setWidth(cursor.getInt(cursor.getColumnIndex("screenWidth3G")));
+ screenSettings.setHeight(cursor.getInt(cursor.getColumnIndex("screenHeight3G")));
+ }
+
+ private void readPerformanceFlags3G(BookmarkBase bookmark, Cursor cursor) {
+ BookmarkBase.PerformanceFlags perfFlags = bookmark.getAdvancedSettings().getPerformance3G();
+ perfFlags.setRemoteFX(cursor.getInt(cursor.getColumnIndex("performanceRemoteFX3G")) == 0 ? false : true);
+ perfFlags.setWallpaper(cursor.getInt(cursor.getColumnIndex("performanceWallpaper3G")) == 0 ? false : true);
+ perfFlags.setTheming(cursor.getInt(cursor.getColumnIndex("performanceTheming3G")) == 0 ? false : true);
+ perfFlags.setFullWindowDrag(cursor.getInt(cursor.getColumnIndex("performanceFullWindowDrag3G")) == 0 ? false : true);
+ perfFlags.setMenuAnimations(cursor.getInt(cursor.getColumnIndex("performanceMenuAnimations3G")) == 0 ? false : true);
+ perfFlags.setFontSmoothing(cursor.getInt(cursor.getColumnIndex("performanceFontSmoothing3G")) == 0 ? false : true);
+ perfFlags.setDesktopComposition(cursor.getInt(cursor.getColumnIndex("performanceDesktopComposition3G")) == 0 ? false : true);
+ }
+
+ private void fillScreenSettingsContentValues(BookmarkBase.ScreenSettings settings, ContentValues values)
+ {
+ values.put("colors", settings.getColors());
+ values.put("resolution", settings.getResolution());
+ values.put("width", settings.getWidth());
+ values.put("height", settings.getHeight());
+ }
+
+ private void fillPerformanceFlagsContentValues(BookmarkBase.PerformanceFlags perfFlags, ContentValues values)
+ {
+ values.put("perf_remotefx", perfFlags.getRemoteFX());
+ values.put("perf_wallpaper", perfFlags.getWallpaper());
+ values.put("perf_theming", perfFlags.getTheming());
+ values.put("perf_full_window_drag", perfFlags.getFullWindowDrag());
+ values.put("perf_menu_animations", perfFlags.getMenuAnimations());
+ values.put("perf_font_smoothing", perfFlags.getFontSmoothing());
+ values.put("perf_desktop_composition", perfFlags.getDesktopComposition());
+ }
+
+ private long insertScreenSettings(SQLiteDatabase db, BookmarkBase.ScreenSettings settings)
+ {
+ ContentValues values = new ContentValues();
+ fillScreenSettingsContentValues(settings, values);
+ return db.insertOrThrow("tbl_screen_settings", null, values);
+ }
+
+ private boolean updateScreenSettings(SQLiteDatabase db, BookmarkBase bookmark)
+ {
+ ContentValues values = new ContentValues();
+ fillScreenSettingsContentValues(bookmark.getScreenSettings(), values);
+ String whereClause = BookmarkDB.ID + " IN " + "(SELECT screen_settings FROM " + getBookmarkTableName() + " WHERE " + BookmarkDB.ID + " = " + bookmark.getId() + ");";
+ return (db.update("tbl_screen_settings", values, whereClause, null) == 1);
+ }
+
+ private boolean updateScreenSettings3G(SQLiteDatabase db, BookmarkBase bookmark)
+ {
+ ContentValues values = new ContentValues();
+ fillScreenSettingsContentValues(bookmark.getAdvancedSettings().getScreen3G(), values);
+ String whereClause = BookmarkDB.ID + " IN " + "(SELECT screen_3g FROM " + getBookmarkTableName() + " WHERE " + BookmarkDB.ID + " = " + bookmark.getId() + ");";
+ return (db.update("tbl_screen_settings", values, whereClause, null) == 1);
+ }
+
+ private long insertPerformanceFlags(SQLiteDatabase db, BookmarkBase.PerformanceFlags perfFlags)
+ {
+ ContentValues values = new ContentValues();
+ fillPerformanceFlagsContentValues(perfFlags, values);
+ return db.insertOrThrow("tbl_performance_flags", null, values);
+ }
+
+ private boolean updatePerformanceFlags(SQLiteDatabase db, BookmarkBase bookmark)
+ {
+ ContentValues values = new ContentValues();
+ fillPerformanceFlagsContentValues(bookmark.getPerformanceFlags(), values);
+ String whereClause = BookmarkDB.ID + " IN " + "(SELECT performance_flags FROM " + getBookmarkTableName() + " WHERE " + BookmarkDB.ID + " = " + bookmark.getId() + ");";
+ return (db.update("tbl_performance_flags", values, whereClause, null) == 1);
+ }
+
+ private boolean updatePerformanceFlags3G(SQLiteDatabase db, BookmarkBase bookmark)
+ {
+ ContentValues values = new ContentValues();
+ fillPerformanceFlagsContentValues(bookmark.getAdvancedSettings().getPerformance3G(), values);
+ String whereClause = BookmarkDB.ID + " IN " + "(SELECT performance_3g FROM " + getBookmarkTableName() + " WHERE " + BookmarkDB.ID + " = " + bookmark.getId() + ");";
+ return (db.update("tbl_performance_flags", values, whereClause, null) == 1);
+ }
+
+ // safety wrappers
+ // in case of getReadableDatabase it could happen that upgradeDB gets called which is
+ // a problem if the DB is only readable
+ private SQLiteDatabase getWritableDatabase()
+ {
+ return bookmarkDB.getWritableDatabase();
+ }
+
+ private SQLiteDatabase getReadableDatabase()
+ {
+ SQLiteDatabase db;
+ try {
+ db = bookmarkDB.getReadableDatabase();
+ }
+ catch(SQLiteException e) {
+ db = bookmarkDB.getWritableDatabase();
+ }
+ return db;
+ }
+}
diff --git a/client/Android/src/com/freerdp/afreerdp/services/BookmarkDB.java b/client/Android/src/com/freerdp/afreerdp/services/BookmarkDB.java
new file mode 100644
index 000000000..02f74440b
--- /dev/null
+++ b/client/Android/src/com/freerdp/afreerdp/services/BookmarkDB.java
@@ -0,0 +1,143 @@
+/*
+ Android Bookmark Database
+
+ Copyright 2013 Thinstuff Technologies GmbH, Author: Martin Fleisz
+
+ This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
+ If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+package com.freerdp.afreerdp.services;
+
+import android.content.Context;
+import android.provider.BaseColumns;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+public class BookmarkDB extends SQLiteOpenHelper
+{
+ private static final int DB_VERSION = 1;
+ private static final String DB_NAME = "bookmarks.db";
+
+ public static final String ID = BaseColumns._ID;
+
+ public BookmarkDB(Context context)
+ {
+ super(context, DB_NAME, null, DB_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db)
+ {
+ String sqlScreenSettings =
+ "CREATE TABLE tbl_screen_settings ("
+ + ID + " INTEGER PRIMARY KEY, "
+ + "colors INTEGER DEFAULT 16, "
+ + "resolution INTEGER DEFAULT 0, "
+ + "width, "
+ + "height);";
+
+ db.execSQL(sqlScreenSettings);
+
+ String sqlPerformanceFlags =
+ "CREATE TABLE tbl_performance_flags ("
+ + ID + " INTEGER PRIMARY KEY, "
+ + "perf_remotefx INTEGER, "
+ + "perf_wallpaper INTEGER, "
+ + "perf_theming INTEGER, "
+ + "perf_full_window_drag INTEGER, "
+ + "perf_menu_animations INTEGER, "
+ + "perf_font_smoothing INTEGER, "
+ + "perf_desktop_composition INTEGER);";
+
+ db.execSQL(sqlPerformanceFlags);
+
+ String sqlManualBookmarks =
+ "CREATE TABLE tbl_manual_bookmarks ("
+ + ID + " INTEGER PRIMARY KEY, "
+ + "label TEXT NOT NULL, "
+ + "hostname TEXT NOT NULL, "
+ + "username TEXT NOT NULL, "
+ + "password TEXT, "
+ + "domain TEXT, "
+ + "port TEXT, "
+ + "screen_settings INTEGER NOT NULL, "
+ + "performance_flags INTEGER NOT NULL, "
+
+ + "enable_3g_settings INTEGER DEFAULT 0, "
+ + "screen_3g INTEGER NOT NULL, "
+ + "performance_3g INTEGER NOT NULL, "
+ + "security INTEGER, "
+ + "remote_program TEXT, "
+ + "work_dir TEXT, "
+ + "console_mode INTEGER, "
+
+ + "FOREIGN KEY(screen_settings) REFERENCES tbl_screen_settings(" + ID + "), "
+ + "FOREIGN KEY(performance_flags) REFERENCES tbl_performance_flags(" + ID + "), "
+ + "FOREIGN KEY(screen_3g) REFERENCES tbl_screen_settings(" + ID + "), "
+ + "FOREIGN KEY(performance_3g) REFERENCES tbl_performance_flags(" + ID + ") "
+
+ + ");";
+
+ db.execSQL(sqlManualBookmarks);
+
+
+ // REMOVE - FOR DEBUGGING ONLY!
+/*
+ String sqlInsertDefaultScreenEntry =
+ "INSERT INTO tbl_screen_settings ("
+ + "colors, "
+ + "resolution, "
+ + "width, "
+ + "height) "
+ + "VALUES ( "
+ + "16, -1, 0, 0);";
+ db.execSQL(sqlInsertDefaultScreenEntry);
+ db.execSQL(sqlInsertDefaultScreenEntry);
+
+ String sqlInsertDefaultPerfFlags =
+ "INSERT INTO tbl_performance_flags ("
+ + "perf_remotefx, "
+ + "perf_wallpaper, "
+ + "perf_theming, "
+ + "perf_full_window_drag, "
+ + "perf_menu_animations, "
+ + "perf_font_smoothing, "
+ + "perf_desktop_composition) "
+ + "VALUES ( "
+ + "0, 0, 0, 0, 0, 0, 0);";
+ db.execSQL(sqlInsertDefaultPerfFlags);
+ db.execSQL(sqlInsertDefaultPerfFlags);
+
+ String sqlInsertDefaultSessionEntry =
+ "INSERT INTO tbl_manual_bookmarks ("
+ + "label, "
+ + "hostname, "
+ + "username, "
+ + "password, "
+ + "domain, "
+ + "port, "
+ + "screen_settings, "
+ + "performance_flags, "
+ + "screen_3g, "
+ + "performance_3g, "
+ + "security, "
+ + "remote_program, "
+ + "work_dir, "
+ + "console_mode) "
+ + "VALUES ( "
+ + "'test', "
+ + "'192.168.50.125', "
+ + "'demo1', "
+ + "'qw', "
+ + "'', "
+ + "3389, "
+ + "1, 1, 2, 2, 0, '', '', 0);";
+ db.execSQL(sqlInsertDefaultSessionEntry); */
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
+ {
+ }
+}
diff --git a/client/Android/src/com/freerdp/afreerdp/services/FreeRDPSuggestionProvider.java b/client/Android/src/com/freerdp/afreerdp/services/FreeRDPSuggestionProvider.java
new file mode 100644
index 000000000..b8144d0b7
--- /dev/null
+++ b/client/Android/src/com/freerdp/afreerdp/services/FreeRDPSuggestionProvider.java
@@ -0,0 +1,123 @@
+/*
+ Suggestion Provider for RDP bookmarks
+
+ Copyright 2013 Thinstuff Technologies GmbH, Author: Martin Fleisz
+
+ This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
+ If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+package com.freerdp.afreerdp.services;
+
+import java.util.ArrayList;
+
+import com.freerdp.afreerdp.R;
+import com.freerdp.afreerdp.application.GlobalApp;
+import com.freerdp.afreerdp.domain.BookmarkBase;
+import com.freerdp.afreerdp.domain.ConnectionReference;
+import com.freerdp.afreerdp.domain.ManualBookmark;
+
+import android.app.SearchManager;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+
+public class FreeRDPSuggestionProvider extends ContentProvider {
+
+ public static final Uri CONTENT_URI = Uri.parse("content://com.freerdp.afreerdp.services.freerdpsuggestionprovider");
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ return "vnd.android.cursor.item/vnd.freerdp.remote";
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+
+ String query = (selectionArgs != null && selectionArgs.length > 0) ? selectionArgs[0] : "";
+
+ // search history
+ ArrayList history = GlobalApp.getQuickConnectHistoryGateway().findHistory(query);
+
+ // search bookmarks
+ ArrayList manualBookmarks;
+ if(query.length() > 0)
+ manualBookmarks = GlobalApp.getManualBookmarkGateway().findByLabelOrHostnameLike(query);
+ else
+ manualBookmarks = GlobalApp.getManualBookmarkGateway().findAll();
+
+ return createResultCursor(history, manualBookmarks);
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection,
+ String[] selectionArgs) {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ private void addBookmarksToCursor(ArrayList bookmarks, MatrixCursor resultCursor) {
+ Object[] row = new Object[5];
+ for(BookmarkBase bookmark : bookmarks)
+ {
+ row[0] = new Long(bookmark.getId());
+ row[1] = bookmark.getLabel();
+ row[2] = bookmark.get().getHostname();
+ row[3] = ConnectionReference.getManualBookmarkReference(bookmark.getId());
+ row[4] = "android.resource://com.freerdp.afreerdp/" + R.drawable.icon_star_on;
+ resultCursor.addRow(row);
+ }
+ }
+
+ private void addHistoryToCursor(ArrayList history, MatrixCursor resultCursor) {
+ Object[] row = new Object[5];
+ for(BookmarkBase bookmark : history)
+ {
+ row[0] = new Integer(1);
+ row[1] = bookmark.getLabel();
+ row[2] = bookmark.getLabel();
+ row[3] = ConnectionReference.getHostnameReference(bookmark.getLabel());
+ row[4] = "android.resource://com.freerdp.afreerdp/" + R.drawable.icon_star_off;
+ resultCursor.addRow(row);
+ }
+ }
+
+ private Cursor createResultCursor(ArrayList history, ArrayList manualBookmarks) {
+
+ // create result matrix cursor
+ int totalCount = history.size() + manualBookmarks.size();
+ String[] columns = { android.provider.BaseColumns._ID, SearchManager.SUGGEST_COLUMN_TEXT_1,
+ SearchManager.SUGGEST_COLUMN_TEXT_2, SearchManager.SUGGEST_COLUMN_INTENT_DATA,
+ SearchManager.SUGGEST_COLUMN_ICON_2 };
+ MatrixCursor matrixCursor = new MatrixCursor(columns, totalCount);
+
+ // populate result matrix
+ if(totalCount > 0)
+ {
+ addHistoryToCursor(history, matrixCursor);
+ addBookmarksToCursor(manualBookmarks, matrixCursor);
+ }
+ return matrixCursor;
+ }
+
+}
diff --git a/client/Android/src/com/freerdp/afreerdp/services/HistoryDB.java b/client/Android/src/com/freerdp/afreerdp/services/HistoryDB.java
new file mode 100644
index 000000000..900336f60
--- /dev/null
+++ b/client/Android/src/com/freerdp/afreerdp/services/HistoryDB.java
@@ -0,0 +1,48 @@
+/*
+ Quick Connect History Database
+
+ Copyright 2013 Thinstuff Technologies GmbH, Author: Martin Fleisz
+
+ This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
+ If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+package com.freerdp.afreerdp.services;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+public class HistoryDB extends SQLiteOpenHelper {
+
+ private static final int DB_VERSION = 1;
+ private static final String DB_NAME = "history.db";
+
+ public static final String QUICK_CONNECT_TABLE_NAME = "quick_connect_history";
+ public static final String QUICK_CONNECT_TABLE_COL_ITEM = "item";
+ public static final String QUICK_CONNECT_TABLE_COL_TIMESTAMP = "timestamp";
+
+ public HistoryDB(Context context)
+ {
+ super(context, DB_NAME, null, DB_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+
+ String sqlQuickConnectHistory =
+ "CREATE TABLE " + QUICK_CONNECT_TABLE_NAME + " ("
+ + QUICK_CONNECT_TABLE_COL_ITEM + " TEXT PRIMARY KEY, "
+ + QUICK_CONNECT_TABLE_COL_TIMESTAMP + " INTEGER"
+ + ");";
+
+ db.execSQL(sqlQuickConnectHistory);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ // TODO Auto-generated method stub
+
+ }
+
+}
diff --git a/client/Android/src/com/freerdp/afreerdp/services/LibFreeRDP.java b/client/Android/src/com/freerdp/afreerdp/services/LibFreeRDP.java
new file mode 100644
index 000000000..8de2cc435
--- /dev/null
+++ b/client/Android/src/com/freerdp/afreerdp/services/LibFreeRDP.java
@@ -0,0 +1,254 @@
+/*
+ Android FreeRDP JNI Wrapper
+
+ Copyright 2013 Thinstuff Technologies GmbH, Author: Martin Fleisz
+
+ This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
+ If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+package com.freerdp.afreerdp.services;
+
+
+import com.freerdp.afreerdp.application.GlobalApp;
+import com.freerdp.afreerdp.application.SessionState;
+import com.freerdp.afreerdp.domain.BookmarkBase;
+import com.freerdp.afreerdp.domain.ManualBookmark;
+
+import android.graphics.Bitmap;
+
+public class LibFreeRDP
+{
+ private static native int freerdp_new();
+ private static native void freerdp_free(int inst);
+ private static native boolean freerdp_connect(int inst);
+ private static native boolean freerdp_disconnect(int inst);
+ private static native void freerdp_cancel_connection(int inst);
+
+ private static native void freerdp_set_connection_info(int inst,
+ String hostname, String username, String password, String domain,
+ int width, int height, int color_depth, int port, boolean console,
+ int security, String certname);
+
+ private static native void freerdp_set_performance_flags(int inst,
+ boolean remotefx, boolean disableWallpaper, boolean disableFullWindowDrag,
+ boolean disableMenuAnimations, boolean disableTheming,
+ boolean enableFontSmoothing, boolean enableDesktopComposition);
+
+ private static native void freerdp_set_advanced_settings(int inst, String remoteProgram, String workDir);
+
+ private static native void freerdp_set_data_directory(int inst, String directory);
+
+ private static native boolean freerdp_update_graphics(int inst,
+ Bitmap bitmap, int x, int y, int width, int height);
+
+ private static native void freerdp_send_cursor_event(int inst, int x, int y, int flags);
+ private static native void freerdp_send_key_event(int inst, int keycode, boolean down);
+ private static native void freerdp_send_unicodekey_event(int inst, int keycode);
+
+ private static native String freerdp_get_version();
+
+ private static final String TAG = "LibFreeRDP";
+
+ public static interface EventListener
+ {
+ void OnConnectionSuccess(int instance);
+ void OnConnectionFailure(int instance);
+ void OnDisconnecting(int instance);
+ void OnDisconnected(int instance);
+ }
+
+ public static interface UIEventListener
+ {
+ void OnSettingsChanged(int width, int height, int bpp);
+ boolean OnAuthenticate(StringBuilder username, StringBuilder domain, StringBuilder password);
+ boolean OnVerifiyCertificate(String subject, String issuer, String fingerprint);
+ void OnGraphicsUpdate(int x, int y, int width, int height);
+ void OnGraphicsResize(int width, int height);
+ }
+
+ private static EventListener listener;
+
+ public static void setEventListener(EventListener l)
+ {
+ listener = l;
+ }
+
+ public static int newInstance()
+ {
+ return freerdp_new();
+ }
+
+ public static void freeInstance(int inst)
+ {
+ freerdp_free(inst);
+ }
+
+ public static boolean connect(int inst)
+ {
+ return freerdp_connect(inst);
+ }
+
+ public static boolean disconnect(int inst)
+ {
+ return freerdp_disconnect(inst);
+ }
+
+ public static void cancelConnection(int inst)
+ {
+ freerdp_cancel_connection(inst);
+ }
+
+ public static boolean setConnectionInfo(int inst, BookmarkBase bookmark)
+ {
+ BookmarkBase.ScreenSettings screenSettings = bookmark.getActiveScreenSettings();
+
+ int port;
+ String hostname;
+ String certName = "";
+ if(bookmark.getType() == BookmarkBase.TYPE_MANUAL)
+ {
+ port = bookmark.get().getPort();
+ hostname = bookmark.get().getHostname();
+ }
+ else
+ {
+ assert false;
+ return false;
+ }
+
+ freerdp_set_connection_info(inst,
+ hostname,
+ bookmark.getUsername(),
+ bookmark.getPassword(),
+ bookmark.getDomain(),
+ screenSettings.getWidth(),
+ screenSettings.getHeight(),
+ screenSettings.getColors(),
+ port,
+ bookmark.getAdvancedSettings().getConsoleMode(),
+ bookmark.getAdvancedSettings().getSecurity(),
+ certName);
+
+ BookmarkBase.PerformanceFlags flags = bookmark.getActivePerformanceFlags();
+ freerdp_set_performance_flags(inst,
+ flags.getRemoteFX(),
+ !flags.getWallpaper(),
+ !flags.getFullWindowDrag(),
+ !flags.getMenuAnimations(),
+ !flags.getTheming(),
+ flags.getFontSmoothing(),
+ flags.getDesktopComposition());
+
+ BookmarkBase.AdvancedSettings advancedSettings = bookmark.getAdvancedSettings();
+ freerdp_set_advanced_settings(inst, advancedSettings.getRemoteProgram(), advancedSettings.getWorkDir());
+
+ return true;
+ }
+
+ public static void setDataDirectory(int inst, String directory)
+ {
+ freerdp_set_data_directory(inst, directory);
+ }
+
+ public static boolean updateGraphics(int inst, Bitmap bitmap, int x, int y, int width, int height)
+ {
+ return freerdp_update_graphics(inst, bitmap, x, y, width, height);
+ }
+
+ public static void sendCursorEvent(int inst, int x, int y, int flags)
+ {
+ freerdp_send_cursor_event(inst, x, y, flags);
+ }
+
+ public static void sendKeyEvent(int inst, int keycode, boolean down)
+ {
+ freerdp_send_key_event(inst, keycode, down);
+ }
+
+ public static void sendUnicodeKeyEvent(int inst, int keycode)
+ {
+ freerdp_send_unicodekey_event(inst, keycode);
+ }
+
+ private static void OnConnectionSuccess(int inst)
+ {
+ if (listener != null)
+ listener.OnConnectionSuccess(inst);
+ }
+
+ private static void OnConnectionFailure(int inst)
+ {
+ if (listener != null)
+ listener.OnConnectionFailure(inst);
+ }
+
+ private static void OnDisconnecting(int inst)
+ {
+ if (listener != null)
+ listener.OnDisconnecting(inst);
+ }
+
+ private static void OnDisconnected(int inst)
+ {
+ if (listener != null)
+ listener.OnDisconnected(inst);
+ }
+
+ private static void OnSettingsChanged(int inst, int width, int height, int bpp)
+ {
+ SessionState s = GlobalApp.getSession(inst);
+ if (s == null)
+ return;
+ UIEventListener uiEventListener = s.getUIEventListener();
+ if (uiEventListener != null)
+ uiEventListener.OnSettingsChanged(width, height, bpp);
+ }
+
+ private static boolean OnAuthenticate(int inst, StringBuilder username, StringBuilder domain, StringBuilder password)
+ {
+ SessionState s = GlobalApp.getSession(inst);
+ if (s == null)
+ return false;
+ UIEventListener uiEventListener = s.getUIEventListener();
+ if (uiEventListener != null)
+ return uiEventListener.OnAuthenticate(username, domain, password);
+ return false;
+ }
+
+ private static boolean OnVerifyCertificate(int inst, String subject, String issuer, String fingerprint)
+ {
+ SessionState s = GlobalApp.getSession(inst);
+ if (s == null)
+ return false;
+ UIEventListener uiEventListener = s.getUIEventListener();
+ if (uiEventListener != null)
+ return uiEventListener.OnVerifiyCertificate(subject, issuer, fingerprint);
+ return false;
+ }
+
+ private static void OnGraphicsUpdate(int inst, int x, int y, int width, int height)
+ {
+ SessionState s = GlobalApp.getSession(inst);
+ if (s == null)
+ return;
+ UIEventListener uiEventListener = s.getUIEventListener();
+ if (uiEventListener != null)
+ uiEventListener.OnGraphicsUpdate(x, y, width, height);
+ }
+
+ private static void OnGraphicsResize(int inst, int width, int height)
+ {
+ SessionState s = GlobalApp.getSession(inst);
+ if (s == null)
+ return;
+ UIEventListener uiEventListener = s.getUIEventListener();
+ if (uiEventListener != null)
+ uiEventListener.OnGraphicsResize(width, height);
+ }
+
+ public static String getVersion()
+ {
+ return freerdp_get_version();
+ }
+}
diff --git a/client/Android/src/com/freerdp/afreerdp/services/ManualBookmarkGateway.java b/client/Android/src/com/freerdp/afreerdp/services/ManualBookmarkGateway.java
new file mode 100644
index 000000000..97ddfe2c3
--- /dev/null
+++ b/client/Android/src/com/freerdp/afreerdp/services/ManualBookmarkGateway.java
@@ -0,0 +1,86 @@
+/*
+ Manual bookmarks database gateway
+
+ Copyright 2013 Thinstuff Technologies GmbH, Author: Martin Fleisz
+
+ This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
+ If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+package com.freerdp.afreerdp.services;
+
+import java.util.ArrayList;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+
+import com.freerdp.afreerdp.domain.BookmarkBase;
+import com.freerdp.afreerdp.domain.ManualBookmark;
+
+public class ManualBookmarkGateway extends BookmarkBaseGateway {
+
+ public ManualBookmarkGateway(BookmarkDB bookmarkDB) {
+ super(bookmarkDB);
+ }
+
+ @Override
+ protected BookmarkBase createBookmark() {
+ return new ManualBookmark();
+ }
+
+ @Override
+ protected String getBookmarkTableName() {
+ return "tbl_manual_bookmarks";
+ }
+
+ @Override
+ protected void addBookmarkSpecificColumns(BookmarkBase bookmark, ContentValues columns) {
+ ManualBookmark bm = (ManualBookmark)bookmark;
+ columns.put("hostname", bm.getHostname());
+ columns.put("port", bm.getPort());
+ }
+
+ @Override
+ protected void addBookmarkSpecificColumns(ArrayList columns) {
+ columns.add("hostname");
+ columns.add("port");
+ }
+
+ @Override
+ protected void readBookmarkSpecificColumns(BookmarkBase bookmark, Cursor cursor) {
+ ManualBookmark bm = (ManualBookmark)bookmark;
+ bm.setHostname(cursor.getString(cursor.getColumnIndex("hostname")));
+ bm.setPort(cursor.getInt(cursor.getColumnIndex("port")));
+ }
+
+ public BookmarkBase findByLabelOrHostname(String pattern)
+ {
+ if(pattern.length() == 0)
+ return null;
+
+ Cursor cursor = queryBookmarks("label = '" + pattern + "' OR hostname = '" + pattern + "'", "label");
+ BookmarkBase bookmark = null;
+ if(cursor.moveToFirst())
+ bookmark = getBookmarkFromCursor(cursor);
+
+ cursor.close();
+ return bookmark;
+ }
+
+ public ArrayList findByLabelOrHostnameLike(String pattern)
+ {
+ Cursor cursor = queryBookmarks("label LIKE '%" + pattern + "%' OR hostname LIKE '%" + pattern + "%'", "label");
+ ArrayList bookmarks = new ArrayList(cursor.getCount());
+
+ if(cursor.moveToFirst())
+ {
+ do
+ {
+ bookmarks.add(getBookmarkFromCursor(cursor));
+ }while(cursor.moveToNext());
+ }
+
+ cursor.close();
+ return bookmarks;
+ }
+}
diff --git a/client/Android/src/com/freerdp/afreerdp/services/QuickConnectHistoryGateway.java b/client/Android/src/com/freerdp/afreerdp/services/QuickConnectHistoryGateway.java
new file mode 100644
index 000000000..7e482b323
--- /dev/null
+++ b/client/Android/src/com/freerdp/afreerdp/services/QuickConnectHistoryGateway.java
@@ -0,0 +1,105 @@
+/*
+ Quick connect history gateway
+
+ Copyright 2013 Thinstuff Technologies GmbH, Author: Martin Fleisz
+
+ This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
+ If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+package com.freerdp.afreerdp.services;
+
+import java.util.ArrayList;
+
+import com.freerdp.afreerdp.domain.BookmarkBase;
+import com.freerdp.afreerdp.domain.QuickConnectBookmark;
+
+import android.database.Cursor;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
+import android.util.Log;
+
+
+public class QuickConnectHistoryGateway {
+ private final static String TAG = "QuickConnectHistoryGateway";
+ private HistoryDB historyDB;
+
+
+ public QuickConnectHistoryGateway(HistoryDB historyDB)
+ {
+ this.historyDB = historyDB;
+ }
+
+ public ArrayList findHistory(String filter)
+ {
+ String[] column = { HistoryDB.QUICK_CONNECT_TABLE_COL_ITEM };
+
+ SQLiteDatabase db = getReadableDatabase();
+ String selection = (filter.length() > 0) ? (HistoryDB.QUICK_CONNECT_TABLE_COL_ITEM + " LIKE '%" + filter + "%'") : null;
+ Cursor cursor = db.query(HistoryDB.QUICK_CONNECT_TABLE_NAME, column, selection, null, null, null, HistoryDB.QUICK_CONNECT_TABLE_COL_TIMESTAMP);
+
+ ArrayList result = new ArrayList(cursor.getCount());
+ if(cursor.moveToFirst())
+ {
+ do
+ {
+ String hostname = cursor.getString(cursor.getColumnIndex(HistoryDB.QUICK_CONNECT_TABLE_COL_ITEM));
+ QuickConnectBookmark bookmark = new QuickConnectBookmark();
+ bookmark.setLabel(hostname);
+ bookmark.setHostname(hostname);
+ result.add(bookmark);
+ } while(cursor.moveToNext());
+ }
+ cursor.close();
+ return result;
+ }
+
+ public void addHistoryItem(String item) {
+ String insertHistoryItem = "INSERT OR REPLACE INTO " + HistoryDB.QUICK_CONNECT_TABLE_NAME + " (" +
+ HistoryDB.QUICK_CONNECT_TABLE_COL_ITEM + ", " + HistoryDB.QUICK_CONNECT_TABLE_COL_TIMESTAMP + ") VALUES('" + item + "', datetime('now'))";
+ SQLiteDatabase db = getWritableDatabase();
+ try
+ {
+ db.execSQL(insertHistoryItem);
+ }
+ catch(SQLException e)
+ {
+ Log.v(TAG, e.toString());
+ }
+ }
+
+ public boolean historyItemExists(String item) {
+ String[] column = { HistoryDB.QUICK_CONNECT_TABLE_COL_ITEM };
+ SQLiteDatabase db = getReadableDatabase();
+ Cursor cursor = db.query(HistoryDB.QUICK_CONNECT_TABLE_NAME, column, HistoryDB.QUICK_CONNECT_TABLE_COL_ITEM + " = '" + item + "'", null, null, null, null);
+ boolean exists = (cursor.getCount() == 1);
+ cursor.close();
+ return exists;
+ }
+
+ public void removeHistoryItem(String hostname) {
+ SQLiteDatabase db = getWritableDatabase();
+ db.delete(HistoryDB.QUICK_CONNECT_TABLE_NAME, HistoryDB.QUICK_CONNECT_TABLE_COL_ITEM + " = '" + hostname + "'", null);
+ }
+
+ // safety wrappers
+ // in case of getReadableDatabase it could happen that upgradeDB gets called which is
+ // a problem if the DB is only readable
+ private SQLiteDatabase getWritableDatabase()
+ {
+ return historyDB.getWritableDatabase();
+ }
+
+ private SQLiteDatabase getReadableDatabase()
+ {
+ SQLiteDatabase db;
+ try {
+ db = historyDB.getReadableDatabase();
+ }
+ catch(SQLiteException e) {
+ db = historyDB.getWritableDatabase();
+ }
+ return db;
+ }
+}
diff --git a/client/Android/src/com/freerdp/afreerdp/services/SessionRequestHandlerActivity.java b/client/Android/src/com/freerdp/afreerdp/services/SessionRequestHandlerActivity.java
new file mode 100644
index 000000000..ac41df7c1
--- /dev/null
+++ b/client/Android/src/com/freerdp/afreerdp/services/SessionRequestHandlerActivity.java
@@ -0,0 +1,71 @@
+/*
+ Activity for handling connection requests
+
+ Copyright 2013 Thinstuff Technologies GmbH, Author: Martin Fleisz
+
+ This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
+ If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+package com.freerdp.afreerdp.services;
+
+import com.freerdp.afreerdp.domain.ConnectionReference;
+import com.freerdp.afreerdp.presentation.BookmarkActivity;
+import com.freerdp.afreerdp.presentation.SessionActivity;
+
+import android.app.Activity;
+import android.app.SearchManager;
+import android.content.Intent;
+import android.os.Bundle;
+
+
+public class SessionRequestHandlerActivity extends Activity {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ handleIntent(getIntent());
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ setIntent(intent);
+ handleIntent(intent);
+ }
+
+ private void startSessionWithConnectionReference(String refStr) {
+
+ Bundle bundle = new Bundle();
+ bundle.putString(SessionActivity.PARAM_CONNECTION_REFERENCE, refStr);
+ Intent sessionIntent = new Intent(this, SessionActivity.class);
+ sessionIntent.putExtras(bundle);
+
+ startActivityForResult(sessionIntent, 0);
+ }
+
+ private void editBookmarkWithConnectionReference(String refStr) {
+ Bundle bundle = new Bundle();
+ bundle.putString(BookmarkActivity.PARAM_CONNECTION_REFERENCE, refStr);
+ Intent bookmarkIntent = new Intent(this.getApplicationContext(), BookmarkActivity.class);
+ bookmarkIntent.putExtras(bundle);
+ startActivityForResult(bookmarkIntent, 0);
+ }
+
+ private void handleIntent(Intent intent) {
+
+ String action = intent.getAction();
+ if(Intent.ACTION_SEARCH.equals(action))
+ startSessionWithConnectionReference(ConnectionReference.getHostnameReference(intent.getStringExtra(SearchManager.QUERY)));
+ else if(Intent.ACTION_VIEW.equals(action))
+ startSessionWithConnectionReference(intent.getDataString());
+ else if(Intent.ACTION_EDIT.equals(action))
+ editBookmarkWithConnectionReference(intent.getDataString());
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ this.setResult(resultCode);
+ this.finish();
+ }
+}
diff --git a/client/Android/src/com/freerdp/afreerdp/utils/BookmarkArrayAdapter.java b/client/Android/src/com/freerdp/afreerdp/utils/BookmarkArrayAdapter.java
new file mode 100644
index 000000000..50d2cf66b
--- /dev/null
+++ b/client/Android/src/com/freerdp/afreerdp/utils/BookmarkArrayAdapter.java
@@ -0,0 +1,133 @@
+/*
+ ArrayAdapter for bookmark lists
+
+ Copyright 2013 Thinstuff Technologies GmbH, Author: Martin Fleisz
+
+ This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
+ If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+package com.freerdp.afreerdp.utils;
+
+import java.util.List;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.freerdp.afreerdp.R;
+import com.freerdp.afreerdp.domain.BookmarkBase;
+import com.freerdp.afreerdp.domain.ConnectionReference;
+import com.freerdp.afreerdp.domain.ManualBookmark;
+import com.freerdp.afreerdp.domain.PlaceholderBookmark;
+import com.freerdp.afreerdp.presentation.BookmarkActivity;
+
+public class BookmarkArrayAdapter extends ArrayAdapter
+{
+
+ public BookmarkArrayAdapter(Context context, int textViewResourceId, List objects)
+ {
+ super(context, textViewResourceId, objects);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent)
+ {
+ View curView = convertView;
+ if (curView == null)
+ {
+ LayoutInflater vi = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ curView = vi.inflate(R.layout.bookmark_list_item, null);
+ }
+
+ BookmarkBase bookmark = getItem(position);
+ TextView label = (TextView) curView.findViewById(R.id.bookmark_text1);
+ TextView hostname = (TextView) curView.findViewById(R.id.bookmark_text2);
+ ImageView star_icon = (ImageView) curView.findViewById(R.id.bookmark_icon2);
+ assert label != null;
+ assert hostname != null;
+
+ label.setText(bookmark.getLabel());
+ star_icon.setVisibility(View.VISIBLE);
+
+ String refStr;
+ if(bookmark.getType() == BookmarkBase.TYPE_MANUAL)
+ {
+ hostname.setText(bookmark.get().getHostname());
+ refStr = ConnectionReference.getManualBookmarkReference(bookmark.getId());
+ star_icon.setImageResource(R.drawable.icon_star_on);
+ }
+ else if(bookmark.getType() == BookmarkBase.TYPE_QUICKCONNECT)
+ {
+ // just set an empty hostname (with a blank) - the hostname is already displayed in the label
+ // and in case we just set it to "" the textview will shrunk
+ hostname.setText(" ");
+ refStr = ConnectionReference.getHostnameReference(bookmark.getLabel());
+ star_icon.setImageResource(R.drawable.icon_star_off);
+ }
+ else if(bookmark.getType() == BookmarkBase.TYPE_PLACEHOLDER)
+ {
+ hostname.setText(" ");
+ refStr = ConnectionReference.getPlaceholderReference(bookmark.get().getName());
+ star_icon.setVisibility(View.GONE);
+ }
+ else
+ {
+ // unknown bookmark type...
+ refStr = "";
+ assert false;
+ }
+
+ star_icon.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // start bookmark editor
+ Bundle bundle = new Bundle();
+ String refStr = v.getTag().toString();
+ bundle.putString(BookmarkActivity.PARAM_CONNECTION_REFERENCE, refStr);
+
+ Intent bookmarkIntent = new Intent(getContext(), BookmarkActivity.class);
+ bookmarkIntent.putExtras(bundle);
+ getContext().startActivity(bookmarkIntent);
+ }
+ });
+
+ curView.setTag(refStr);
+ star_icon.setTag(refStr);
+
+ return curView;
+ }
+
+ public void addItems(List newItems)
+ {
+ for(BookmarkBase item : newItems)
+ add(item);
+ }
+
+ public void replaceItems(List newItems)
+ {
+ clear();
+ for(BookmarkBase item : newItems)
+ add(item);
+ }
+
+ public void remove(long bookmarkId)
+ {
+ for(int i = 0; i < getCount(); i++)
+ {
+ BookmarkBase bm = getItem(i);
+ if(bm.getId() == bookmarkId)
+ {
+ remove(bm);
+ return;
+ }
+ }
+ }
+}
diff --git a/client/Android/src/com/freerdp/afreerdp/utils/ButtonPreference.java b/client/Android/src/com/freerdp/afreerdp/utils/ButtonPreference.java
new file mode 100644
index 000000000..fb9cbe261
--- /dev/null
+++ b/client/Android/src/com/freerdp/afreerdp/utils/ButtonPreference.java
@@ -0,0 +1,91 @@
+/*
+ Custom preference item showing a button on the right side
+
+ Copyright 2013 Thinstuff Technologies GmbH, Author: Martin Fleisz
+
+ This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
+ If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+package com.freerdp.afreerdp.utils;
+
+import com.freerdp.afreerdp.R;
+
+import android.content.Context;
+import android.preference.Preference;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.LinearLayout;
+
+public class ButtonPreference extends Preference {
+
+ private OnClickListener buttonOnClickListener;
+ private String buttonText;
+ private Button button;
+
+ public ButtonPreference(Context context) {
+ super(context);
+ init();
+ }
+
+ public ButtonPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public ButtonPreference(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init();
+ }
+
+ private void init()
+ {
+ setLayoutResource(R.layout.button_preference);
+ button = null;
+ buttonText = null;
+ buttonOnClickListener = null;
+ }
+
+ @Override
+ public View getView(View convertView, ViewGroup parent)
+ {
+ View v = super.getView(convertView, parent);
+ button = (Button)v.findViewById(R.id.preference_button);
+ if (buttonText != null)
+ button.setText(buttonText);
+ if (buttonOnClickListener != null)
+ button.setOnClickListener(buttonOnClickListener);
+
+ // additional init for ICS - make widget frame visible
+ // refer to http://stackoverflow.com/questions/8762984/custom-preference-broken-in-honeycomb-ics
+ LinearLayout widgetFrameView = ((LinearLayout)v.findViewById(android.R.id.widget_frame));
+ widgetFrameView.setVisibility(View.VISIBLE);
+
+ return v;
+ }
+
+ public void setButtonText(int resId)
+ {
+ buttonText = getContext().getResources().getString(resId);
+ if (button != null)
+ button.setText(buttonText);
+ }
+
+ public void setButtonText(String text)
+ {
+ buttonText = text;
+ if (button != null)
+ button.setText(text);
+ }
+
+ public void setButtonOnClickListener(OnClickListener listener)
+ {
+ if (button != null)
+ button.setOnClickListener(listener);
+ else
+ buttonOnClickListener = listener;
+ }
+}
diff --git a/client/Android/src/com/freerdp/afreerdp/utils/DoubleGestureDetector.java b/client/Android/src/com/freerdp/afreerdp/utils/DoubleGestureDetector.java
new file mode 100644
index 000000000..e837a6a2e
--- /dev/null
+++ b/client/Android/src/com/freerdp/afreerdp/utils/DoubleGestureDetector.java
@@ -0,0 +1,348 @@
+/*
+ 2 finger gesture detector
+
+ Copyright 2013 Thinstuff Technologies GmbH, Author: Martin Fleisz
+
+ This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
+ If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+package com.freerdp.afreerdp.utils;
+
+import android.content.Context;
+import android.os.Handler;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+
+import com.freerdp.afreerdp.utils.GestureDetector.OnGestureListener;
+
+public class DoubleGestureDetector {
+ /**
+ * The listener that is used to notify when gestures occur.
+ * If you want to listen for all the different gestures then implement
+ * this interface. If you only want to listen for a subset it might
+ * be easier to extend {@link SimpleOnGestureListener}.
+ */
+ public interface OnDoubleGestureListener {
+
+ /**
+ * Notified when a multi tap event starts
+ */
+ boolean onDoubleTouchDown(MotionEvent e);
+
+ /**
+ * Notified when a multi tap event ends
+ */
+ boolean onDoubleTouchUp(MotionEvent e);
+
+ /**
+ * Notified when a tap occurs with the up {@link MotionEvent}
+ * that triggered it.
+ *
+ * @param e The up motion event that completed the first tap
+ * @return true if the event is consumed, else false
+ */
+ boolean onDoubleTouchSingleTap(MotionEvent e);
+
+ /**
+ * Notified when a scroll occurs with the initial on down {@link MotionEvent} and the
+ * current move {@link MotionEvent}. The distance in x and y is also supplied for
+ * convenience.
+ *
+ * @param e1 The first down motion event that started the scrolling.
+ * @param e2 The move motion event that triggered the current onScroll.
+ * @param distanceX The distance along the X axis that has been scrolled since the last
+ * call to onScroll. This is NOT the distance between {@code e1}
+ * and {@code e2}.
+ * @param distanceY The distance along the Y axis that has been scrolled since the last
+ * call to onScroll. This is NOT the distance between {@code e1}
+ * and {@code e2}.
+ * @return true if the event is consumed, else false
+ */
+ boolean onDoubleTouchScroll(MotionEvent e1, MotionEvent e2);
+ }
+
+
+ private int mPointerDistanceSquare;
+
+ // timeout during that the second finger has to touch the screen before the double finger detection is cancelled
+ private static final long DOUBLE_TOUCH_TIMEOUT = 100;
+
+ // timeout during that an UP event will trigger a single double touch event
+ private static final long SINGLE_DOUBLE_TOUCH_TIMEOUT = 1000;
+
+ // constants for Message.what used by GestureHandler below
+ private static final int TAP = 1;
+
+ // different detection modes
+ private static final int MODE_UNKNOWN = 0;
+ private static final int MODE_PINCH_ZOOM = 1;
+ private static final int MODE_SCROLL = 2;
+
+ private final OnDoubleGestureListener mListener;
+
+ private int mCurrentMode;
+ private int mScrollDetectionScore;
+ private static final int SCROLL_SCORE_TO_REACH = 20;
+
+ private ScaleGestureDetector scaleGestureDetector;
+
+ private boolean mCancelDetection;
+ private boolean mDoubleInProgress;
+
+ private GestureHandler mHandler;
+
+ private MotionEvent mCurrentDownEvent;
+ private MotionEvent mCurrentDoubleDownEvent;
+ private MotionEvent mPreviousUpEvent;
+ private MotionEvent mPreviousPointerUpEvent;
+
+ private class GestureHandler extends Handler {
+ GestureHandler() {
+ super();
+ }
+
+ GestureHandler(Handler handler) {
+ super(handler.getLooper());
+ }
+
+ }
+
+ /**
+ * Creates a GestureDetector with the supplied listener.
+ * You may only use this constructor from a UI thread (this is the usual situation).
+ * @see android.os.Handler#Handler()
+ *
+ * @param context the application's context
+ * @param listener the listener invoked for all the callbacks, this must
+ * not be null.
+ *
+ * @throws NullPointerException if {@code listener} is null.
+ */
+ public DoubleGestureDetector(Context context, Handler handler, OnDoubleGestureListener listener) {
+ mListener = listener;
+ init(context, handler);
+ }
+
+ private void init(Context context, Handler handler) {
+ if (mListener == null) {
+ throw new NullPointerException("OnGestureListener must not be null");
+ }
+
+ if(handler != null)
+ mHandler = new GestureHandler(handler);
+ else
+ mHandler = new GestureHandler();
+
+ // we use 1cm distance to decide between scroll and pinch zoom
+ // - first convert cm to inches
+ // - then multiply inches by dots per inch
+ float distInches = 0.5f / 2.54f;
+ float distPixelsX = distInches * context.getResources().getDisplayMetrics().xdpi;
+ float distPixelsY = distInches * context.getResources().getDisplayMetrics().ydpi;
+
+ mPointerDistanceSquare = (int)(distPixelsX * distPixelsX + distPixelsY * distPixelsY);
+ }
+
+ /**
+ * Set scale gesture detector
+ * @param scaleGestureDetector
+ */
+ public void setScaleGestureDetector(ScaleGestureDetector scaleGestureDetector) {
+ this.scaleGestureDetector = scaleGestureDetector;
+ }
+
+ /**
+ * Analyzes the given motion event and if applicable triggers the
+ * appropriate callbacks on the {@link OnGestureListener} supplied.
+ *
+ * @param ev The current motion event.
+ * @return true if the {@link OnGestureListener} consumed the event,
+ * else false.
+ */
+ public boolean onTouchEvent(MotionEvent ev)
+ {
+ boolean handled = false;
+ final int action = ev.getAction();
+ //dumpEvent(ev);
+
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN:
+ if (mCurrentDownEvent != null)
+ mCurrentDownEvent.recycle();
+
+ mCurrentMode = MODE_UNKNOWN;
+ mCurrentDownEvent = MotionEvent.obtain(ev);
+ mCancelDetection = false;
+ mDoubleInProgress = false;
+ mScrollDetectionScore = 0;
+ handled = true;
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP:
+ if(mPreviousPointerUpEvent != null)
+ mPreviousPointerUpEvent.recycle();
+ mPreviousPointerUpEvent = MotionEvent.obtain(ev);
+ break;
+
+ case MotionEvent.ACTION_POINTER_DOWN:
+ // more than 2 fingers down? cancel
+ // 2nd finger touched too late? cancel
+ if(ev.getPointerCount() > 2 || (ev.getEventTime() - mCurrentDownEvent.getEventTime()) > DOUBLE_TOUCH_TIMEOUT)
+ {
+ cancel();
+ break;
+ }
+
+ // detection cancelled?
+ if(mCancelDetection)
+ break;
+
+ // double touch gesture in progress
+ mDoubleInProgress = true;
+ if (mCurrentDoubleDownEvent != null)
+ mCurrentDoubleDownEvent.recycle();
+ mCurrentDoubleDownEvent = MotionEvent.obtain(ev);
+
+ // set detection mode to unkown and send a TOUCH timeout event to detect single taps
+ mCurrentMode = MODE_UNKNOWN;
+ mHandler.sendEmptyMessageDelayed(TAP, SINGLE_DOUBLE_TOUCH_TIMEOUT);
+
+ handled |= mListener.onDoubleTouchDown(ev);
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+
+ // detection cancelled or not active?
+ if(mCancelDetection || !mDoubleInProgress || ev.getPointerCount() != 2)
+ break;
+
+ // determine mode
+ if(mCurrentMode == MODE_UNKNOWN)
+ {
+ // did the pointer distance change?
+ if(pointerDistanceChanged(mCurrentDoubleDownEvent, ev))
+ {
+ handled |= scaleGestureDetector.onTouchEvent(mCurrentDownEvent);
+ MotionEvent e = MotionEvent.obtain(ev);
+ e.setAction(mCurrentDoubleDownEvent.getAction());
+ handled |= scaleGestureDetector.onTouchEvent(e);
+ mCurrentMode = MODE_PINCH_ZOOM;
+ break;
+ }
+ else
+ {
+ mScrollDetectionScore++;
+ if(mScrollDetectionScore >= SCROLL_SCORE_TO_REACH)
+ mCurrentMode = MODE_SCROLL;
+ }
+ }
+
+ switch(mCurrentMode)
+ {
+ case MODE_PINCH_ZOOM:
+ if(scaleGestureDetector != null)
+ handled |= scaleGestureDetector.onTouchEvent(ev);
+ break;
+
+ case MODE_SCROLL:
+ handled = mListener.onDoubleTouchScroll(mCurrentDownEvent, ev);
+ break;
+
+ default:
+ handled = true;
+ break;
+ }
+
+ break;
+
+ case MotionEvent.ACTION_UP:
+ // fingers were not removed equally? cancel
+ if(mPreviousPointerUpEvent != null && (ev.getEventTime() - mPreviousPointerUpEvent.getEventTime()) > DOUBLE_TOUCH_TIMEOUT)
+ {
+ mPreviousPointerUpEvent.recycle();
+ mPreviousPointerUpEvent = null;
+ cancel();
+ break;
+ }
+
+ // detection cancelled or not active?
+ if(mCancelDetection || !mDoubleInProgress)
+ break;
+
+ boolean hasTapEvent = mHandler.hasMessages(TAP);
+ MotionEvent currentUpEvent = MotionEvent.obtain(ev);
+ if (mCurrentMode == MODE_UNKNOWN && hasTapEvent)
+ handled = mListener.onDoubleTouchSingleTap(mCurrentDoubleDownEvent);
+ else if(mCurrentMode == MODE_PINCH_ZOOM)
+ handled = scaleGestureDetector.onTouchEvent(ev);
+
+ if (mPreviousUpEvent != null)
+ mPreviousUpEvent.recycle();
+
+ // Hold the event we obtained above - listeners may have changed the original.
+ mPreviousUpEvent = currentUpEvent;
+ handled |= mListener.onDoubleTouchUp(ev);
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ cancel();
+ break;
+ }
+
+ if((action == MotionEvent.ACTION_MOVE) && handled == false)
+ handled = true;
+
+ return handled;
+ }
+
+ private void cancel() {
+ mHandler.removeMessages(TAP);
+ mCurrentMode = MODE_UNKNOWN;
+ mCancelDetection = true;
+ mDoubleInProgress = false;
+ }
+
+ /*
+ private void dumpEvent(MotionEvent event) {
+ String names[] = { "DOWN" , "UP" , "MOVE" , "CANCEL" , "OUTSIDE" ,
+ "POINTER_DOWN" , "POINTER_UP" , "7?" , "8?" , "9?" };
+ StringBuilder sb = new StringBuilder();
+ int action = event.getAction();
+ int actionCode = action & MotionEvent.ACTION_MASK;
+ sb.append("event ACTION_" ).append(names[actionCode]);
+ if (actionCode == MotionEvent.ACTION_POINTER_DOWN
+ || actionCode == MotionEvent.ACTION_POINTER_UP) {
+ sb.append("(pid " ).append(
+ action >> MotionEvent.ACTION_POINTER_ID_SHIFT);
+ sb.append(")" );
+ }
+ sb.append("[" );
+ for (int i = 0; i < event.getPointerCount(); i++) {
+ sb.append("#" ).append(i);
+ sb.append("(pid " ).append(event.getPointerId(i));
+ sb.append(")=" ).append((int) event.getX(i));
+ sb.append("," ).append((int) event.getY(i));
+ if (i + 1 < event.getPointerCount())
+ sb.append(";" );
+ }
+ sb.append("]" );
+ Log.d("DoubleDetector", sb.toString());
+ }
+ */
+
+ // returns true of the distance between the two pointers changed
+ private boolean pointerDistanceChanged(MotionEvent oldEvent, MotionEvent newEvent)
+ {
+ int deltaX1 = Math.abs((int)oldEvent.getX(0) - (int)oldEvent.getX(1));
+ int deltaX2 = Math.abs((int)newEvent.getX(0) - (int)newEvent.getX(1));
+ int distXSquare = (deltaX2 - deltaX1) * (deltaX2 - deltaX1);
+
+ int deltaY1 = Math.abs((int)oldEvent.getY(0) - (int)oldEvent.getY(1));
+ int deltaY2 = Math.abs((int)newEvent.getY(0) - (int)newEvent.getY(1));
+ int distYSquare = (deltaY2 - deltaY1) * (deltaY2 - deltaY1);
+
+ return (distXSquare + distYSquare) > mPointerDistanceSquare;
+ }
+
+}
diff --git a/client/Android/src/com/freerdp/afreerdp/utils/GestureDetector.java b/client/Android/src/com/freerdp/afreerdp/utils/GestureDetector.java
new file mode 100644
index 000000000..e656bab77
--- /dev/null
+++ b/client/Android/src/com/freerdp/afreerdp/utils/GestureDetector.java
@@ -0,0 +1,562 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Modified for aFreeRDP by Martin Fleisz (mfleisz@thinstuff.at)
+ */
+
+package com.freerdp.afreerdp.utils;
+
+import android.content.Context;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Message;
+import android.util.DisplayMetrics;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+public class GestureDetector {
+
+ /**
+ * The listener that is used to notify when gestures occur.
+ * If you want to listen for all the different gestures then implement
+ * this interface. If you only want to listen for a subset it might
+ * be easier to extend {@link SimpleOnGestureListener}.
+ */
+ public interface OnGestureListener {
+
+ /**
+ * Notified when a tap occurs with the down {@link MotionEvent}
+ * that triggered it. This will be triggered immediately for
+ * every down event. All other events should be preceded by this.
+ *
+ * @param e The down motion event.
+ */
+ boolean onDown(MotionEvent e);
+
+ /**
+ * Notified when a tap finishes with the up {@link MotionEvent}
+ * that triggered it. This will be triggered immediately for
+ * every up event. All other events should be preceded by this.
+ *
+ * @param e The up motion event.
+ */
+ boolean onUp(MotionEvent e);
+
+ /**
+ * The user has performed a down {@link MotionEvent} and not performed
+ * a move or up yet. This event is commonly used to provide visual
+ * feedback to the user to let them know that their action has been
+ * recognized i.e. highlight an element.
+ *
+ * @param e The down motion event
+ */
+ void onShowPress(MotionEvent e);
+
+ /**
+ * Notified when a tap occurs with the up {@link MotionEvent}
+ * that triggered it.
+ *
+ * @param e The up motion event that completed the first tap
+ * @return true if the event is consumed, else false
+ */
+ boolean onSingleTapUp(MotionEvent e);
+
+ /**
+ * Notified when a scroll occurs with the initial on down {@link MotionEvent} and the
+ * current move {@link MotionEvent}. The distance in x and y is also supplied for
+ * convenience.
+ *
+ * @param e1 The first down motion event that started the scrolling.
+ * @param e2 The move motion event that triggered the current onScroll.
+ * @param distanceX The distance along the X axis that has been scrolled since the last
+ * call to onScroll. This is NOT the distance between {@code e1}
+ * and {@code e2}.
+ * @param distanceY The distance along the Y axis that has been scrolled since the last
+ * call to onScroll. This is NOT the distance between {@code e1}
+ * and {@code e2}.
+ * @return true if the event is consumed, else false
+ */
+ boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);
+
+ /**
+ * Notified when a long press occurs with the initial on down {@link MotionEvent}
+ * that trigged it.
+ *
+ * @param e The initial on down motion event that started the longpress.
+ */
+ void onLongPress(MotionEvent e);
+
+ /**
+ * Notified when a long press ends with the final {@link MotionEvent}.
+ *
+ * @param e The up motion event that ended the longpress.
+ */
+ void onLongPressUp(MotionEvent e);
+ }
+
+ /**
+ * The listener that is used to notify when a double-tap or a confirmed
+ * single-tap occur.
+ */
+ public interface OnDoubleTapListener {
+ /**
+ * Notified when a single-tap occurs.
+ *
+ * Unlike {@link OnGestureListener#onSingleTapUp(MotionEvent)}, this
+ * will only be called after the detector is confident that the user's
+ * first tap is not followed by a second tap leading to a double-tap
+ * gesture.
+ *
+ * @param e The down motion event of the single-tap.
+ * @return true if the event is consumed, else false
+ */
+ boolean onSingleTapConfirmed(MotionEvent e);
+
+ /**
+ * Notified when a double-tap occurs.
+ *
+ * @param e The down motion event of the first tap of the double-tap.
+ * @return true if the event is consumed, else false
+ */
+ boolean onDoubleTap(MotionEvent e);
+
+ /**
+ * Notified when an event within a double-tap gesture occurs, including
+ * the down, move, and up events.
+ *
+ * @param e The motion event that occurred during the double-tap gesture.
+ * @return true if the event is consumed, else false
+ */
+ boolean onDoubleTapEvent(MotionEvent e);
+ }
+
+ /**
+ * A convenience class to extend when you only want to listen for a subset
+ * of all the gestures. This implements all methods in the
+ * {@link OnGestureListener} and {@link OnDoubleTapListener} but does
+ * nothing and return {@code false} for all applicable methods.
+ */
+ public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener {
+ public boolean onSingleTapUp(MotionEvent e) {
+ return false;
+ }
+
+ public void onLongPress(MotionEvent e) {
+ }
+
+ public void onLongPressUp(MotionEvent e) {
+ }
+
+ public boolean onScroll(MotionEvent e1, MotionEvent e2,
+ float distanceX, float distanceY) {
+ return false;
+ }
+
+ public void onShowPress(MotionEvent e) {
+ }
+
+ public boolean onDown(MotionEvent e) {
+ return false;
+ }
+
+ public boolean onUp(MotionEvent e) {
+ return false;
+ }
+
+ public boolean onDoubleTap(MotionEvent e) {
+ return false;
+ }
+
+ public boolean onDoubleTapEvent(MotionEvent e) {
+ return false;
+ }
+
+ public boolean onSingleTapConfirmed(MotionEvent e) {
+ return false;
+ }
+ }
+
+ private int mTouchSlopSquare;
+ private int mLargeTouchSlopSquare;
+ private int mDoubleTapSlopSquare;
+
+ private int mLongpressTimeout = 100;
+ private static final int TAP_TIMEOUT = 100;
+ private static final int DOUBLE_TAP_TIMEOUT = 200;
+
+ // Distance a touch can wander before we think the user is the first touch in a sequence of double tap
+ private static final int LARGE_TOUCH_SLOP = 18;
+
+ // Distance between the first touch and second touch to still be considered a double tap
+ private static final int DOUBLE_TAP_SLOP = 100;
+
+ // constants for Message.what used by GestureHandler below
+ private static final int SHOW_PRESS = 1;
+ private static final int LONG_PRESS = 2;
+ private static final int TAP = 3;
+
+ private final Handler mHandler;
+ private final OnGestureListener mListener;
+ private OnDoubleTapListener mDoubleTapListener;
+
+ private boolean mStillDown;
+ private boolean mInLongPress;
+ private boolean mAlwaysInTapRegion;
+ private boolean mAlwaysInBiggerTapRegion;
+
+ private MotionEvent mCurrentDownEvent;
+ private MotionEvent mPreviousUpEvent;
+
+ /**
+ * True when the user is still touching for the second tap (down, move, and
+ * up events). Can only be true if there is a double tap listener attached.
+ */
+ private boolean mIsDoubleTapping;
+
+ private float mLastMotionY;
+ private float mLastMotionX;
+
+ private boolean mIsLongpressEnabled;
+
+ /**
+ * True if we are at a target API level of >= Froyo or the developer can
+ * explicitly set it. If true, input events with > 1 pointer will be ignored
+ * so we can work side by side with multitouch gesture detectors.
+ */
+ private boolean mIgnoreMultitouch;
+
+ private class GestureHandler extends Handler {
+ GestureHandler() {
+ super();
+ }
+
+ GestureHandler(Handler handler) {
+ super(handler.getLooper());
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case SHOW_PRESS:
+ mListener.onShowPress(mCurrentDownEvent);
+ break;
+
+ case LONG_PRESS:
+ dispatchLongPress();
+ break;
+
+ case TAP:
+ // If the user's finger is still down, do not count it as a tap
+ if (mDoubleTapListener != null && !mStillDown) {
+ mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
+ }
+ break;
+
+ default:
+ throw new RuntimeException("Unknown message " + msg); //never
+ }
+ }
+ }
+
+ /**
+ * Creates a GestureDetector with the supplied listener.
+ * You may only use this constructor from a UI thread (this is the usual situation).
+ * @see android.os.Handler#Handler()
+ *
+ * @param context the application's context
+ * @param listener the listener invoked for all the callbacks, this must
+ * not be null.
+ *
+ * @throws NullPointerException if {@code listener} is null.
+ */
+ public GestureDetector(Context context, OnGestureListener listener) {
+ this(context, listener, null);
+ }
+
+ /**
+ * Creates a GestureDetector with the supplied listener.
+ * You may only use this constructor from a UI thread (this is the usual situation).
+ * @see android.os.Handler#Handler()
+ *
+ * @param context the application's context
+ * @param listener the listener invoked for all the callbacks, this must
+ * not be null.
+ * @param handler the handler to use
+ *
+ * @throws NullPointerException if {@code listener} is null.
+ */
+ public GestureDetector(Context context, OnGestureListener listener, Handler handler) {
+ this(context, listener, handler, context != null &&
+ context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.FROYO);
+ }
+
+ /**
+ * Creates a GestureDetector with the supplied listener.
+ * You may only use this constructor from a UI thread (this is the usual situation).
+ * @see android.os.Handler#Handler()
+ *
+ * @param context the application's context
+ * @param listener the listener invoked for all the callbacks, this must
+ * not be null.
+ * @param handler the handler to use
+ * @param ignoreMultitouch whether events involving more than one pointer should
+ * be ignored.
+ *
+ * @throws NullPointerException if {@code listener} is null.
+ */
+ public GestureDetector(Context context, OnGestureListener listener, Handler handler,
+ boolean ignoreMultitouch) {
+ if (handler != null) {
+ mHandler = new GestureHandler(handler);
+ } else {
+ mHandler = new GestureHandler();
+ }
+ mListener = listener;
+ if (listener instanceof OnDoubleTapListener) {
+ setOnDoubleTapListener((OnDoubleTapListener) listener);
+ }
+ init(context, ignoreMultitouch);
+ }
+
+ private void init(Context context, boolean ignoreMultitouch) {
+ if (mListener == null) {
+ throw new NullPointerException("OnGestureListener must not be null");
+ }
+ mIsLongpressEnabled = true;
+ mIgnoreMultitouch = ignoreMultitouch;
+
+ // Fallback to support pre-donuts releases
+ int touchSlop, largeTouchSlop, doubleTapSlop;
+ if (context == null) {
+ //noinspection deprecation
+ touchSlop = ViewConfiguration.getTouchSlop();
+ largeTouchSlop = touchSlop + 2;
+ doubleTapSlop = DOUBLE_TAP_SLOP;
+ } else {
+ final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
+ final float density = metrics.density;
+ final ViewConfiguration configuration = ViewConfiguration.get(context);
+ touchSlop = configuration.getScaledTouchSlop();
+ largeTouchSlop = (int) (density * LARGE_TOUCH_SLOP + 0.5f);
+ doubleTapSlop = configuration.getScaledDoubleTapSlop();
+ }
+ mTouchSlopSquare = touchSlop * touchSlop;
+ mLargeTouchSlopSquare = largeTouchSlop * largeTouchSlop;
+ mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop;
+ }
+
+ /**
+ * Sets the listener which will be called for double-tap and related
+ * gestures.
+ *
+ * @param onDoubleTapListener the listener invoked for all the callbacks, or
+ * null to stop listening for double-tap gestures.
+ */
+ public void setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener) {
+ mDoubleTapListener = onDoubleTapListener;
+ }
+
+ /**
+ * Set whether longpress is enabled, if this is enabled when a user
+ * presses and holds down you get a longpress event and nothing further.
+ * If it's disabled the user can press and hold down and then later
+ * moved their finger and you will get scroll events. By default
+ * longpress is enabled.
+ *
+ * @param isLongpressEnabled whether longpress should be enabled.
+ */
+ public void setIsLongpressEnabled(boolean isLongpressEnabled) {
+ mIsLongpressEnabled = isLongpressEnabled;
+ }
+
+ /**
+ * @return true if longpress is enabled, else false.
+ */
+ public boolean isLongpressEnabled() {
+ return mIsLongpressEnabled;
+ }
+
+ public void setLongPressTimeout(int timeout) {
+ mLongpressTimeout = timeout;
+ }
+
+ /**
+ * Analyzes the given motion event and if applicable triggers the
+ * appropriate callbacks on the {@link OnGestureListener} supplied.
+ *
+ * @param ev The current motion event.
+ * @return true if the {@link OnGestureListener} consumed the event,
+ * else false.
+ */
+ public boolean onTouchEvent(MotionEvent ev) {
+ final int action = ev.getAction();
+ final float y = ev.getY();
+ final float x = ev.getX();
+
+ boolean handled = false;
+
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_POINTER_DOWN:
+ if (mIgnoreMultitouch) {
+ // Multitouch event - abort.
+ cancel();
+ }
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP:
+ // Ending a multitouch gesture and going back to 1 finger
+ if (mIgnoreMultitouch && ev.getPointerCount() == 2) {
+ int index = (((action & MotionEvent.ACTION_POINTER_INDEX_MASK)
+ >> MotionEvent.ACTION_POINTER_INDEX_SHIFT) == 0) ? 1 : 0;
+ mLastMotionX = ev.getX(index);
+ mLastMotionY = ev.getY(index);
+ }
+ break;
+
+ case MotionEvent.ACTION_DOWN:
+ if (mDoubleTapListener != null) {
+ boolean hadTapMessage = mHandler.hasMessages(TAP);
+ if (hadTapMessage) mHandler.removeMessages(TAP);
+ if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null) && hadTapMessage &&
+ isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) {
+ // This is a second tap
+ mIsDoubleTapping = true;
+ // Give a callback with the first tap of the double-tap
+ handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);
+ // Give a callback with down event of the double-tap
+ handled |= mDoubleTapListener.onDoubleTapEvent(ev);
+ } else {
+ // This is a first tap
+ mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT);
+ }
+ }
+
+ mLastMotionX = x;
+ mLastMotionY = y;
+ if (mCurrentDownEvent != null) {
+ mCurrentDownEvent.recycle();
+ }
+ mCurrentDownEvent = MotionEvent.obtain(ev);
+ mAlwaysInTapRegion = true;
+ mAlwaysInBiggerTapRegion = true;
+ mStillDown = true;
+ mInLongPress = false;
+
+ if (mIsLongpressEnabled) {
+ mHandler.removeMessages(LONG_PRESS);
+ mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime()
+ + TAP_TIMEOUT + mLongpressTimeout);
+ }
+ mHandler.sendEmptyMessageAtTime(SHOW_PRESS, mCurrentDownEvent.getDownTime() + TAP_TIMEOUT);
+ handled |= mListener.onDown(ev);
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ if (mIgnoreMultitouch && ev.getPointerCount() > 1) {
+ break;
+ }
+ final float scrollX = mLastMotionX - x;
+ final float scrollY = mLastMotionY - y;
+ if (mIsDoubleTapping) {
+ // Give the move events of the double-tap
+ handled |= mDoubleTapListener.onDoubleTapEvent(ev);
+ } else if (mAlwaysInTapRegion) {
+ final int deltaX = (int) (x - mCurrentDownEvent.getX());
+ final int deltaY = (int) (y - mCurrentDownEvent.getY());
+ int distance = (deltaX * deltaX) + (deltaY * deltaY);
+ if (distance > mTouchSlopSquare) {
+ mLastMotionX = x;
+ mLastMotionY = y;
+ mAlwaysInTapRegion = false;
+ mHandler.removeMessages(TAP);
+ mHandler.removeMessages(SHOW_PRESS);
+ mHandler.removeMessages(LONG_PRESS);
+ }
+ if (distance > mLargeTouchSlopSquare) {
+ mAlwaysInBiggerTapRegion = false;
+ }
+ handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
+ } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {
+ handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
+ mLastMotionX = x;
+ mLastMotionY = y;
+ }
+ break;
+
+ case MotionEvent.ACTION_UP:
+ mStillDown = false;
+ MotionEvent currentUpEvent = MotionEvent.obtain(ev);
+ if (mIsDoubleTapping) {
+ // Finally, give the up event of the double-tap
+ handled |= mDoubleTapListener.onDoubleTapEvent(ev);
+ } else if (mInLongPress) {
+ mHandler.removeMessages(TAP);
+ mListener.onLongPressUp(ev);
+ mInLongPress = false;
+ } else if (mAlwaysInTapRegion) {
+ handled = mListener.onSingleTapUp(mCurrentDownEvent);
+ } else {
+ // A fling must travel the minimum tap distance
+ }
+ if (mPreviousUpEvent != null) {
+ mPreviousUpEvent.recycle();
+ }
+ // Hold the event we obtained above - listeners may have changed the original.
+ mPreviousUpEvent = currentUpEvent;
+ mIsDoubleTapping = false;
+ mHandler.removeMessages(SHOW_PRESS);
+ mHandler.removeMessages(LONG_PRESS);
+ handled |= mListener.onUp(ev);
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ cancel();
+ break;
+ }
+ return handled;
+ }
+
+ private void cancel() {
+ mHandler.removeMessages(SHOW_PRESS);
+ mHandler.removeMessages(LONG_PRESS);
+ mHandler.removeMessages(TAP);
+ mAlwaysInTapRegion = false; // ensures that we won't receive an OnSingleTap notification when a 2-Finger tap is performed
+ mIsDoubleTapping = false;
+ mStillDown = false;
+ if (mInLongPress) {
+ mInLongPress = false;
+ }
+ }
+
+ private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp,
+ MotionEvent secondDown) {
+ if (!mAlwaysInBiggerTapRegion) {
+ return false;
+ }
+
+ if (secondDown.getEventTime() - firstUp.getEventTime() > DOUBLE_TAP_TIMEOUT) {
+ return false;
+ }
+
+ int deltaX = (int) firstDown.getX() - (int) secondDown.getX();
+ int deltaY = (int) firstDown.getY() - (int) secondDown.getY();
+ return (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare);
+ }
+
+ private void dispatchLongPress() {
+ mHandler.removeMessages(TAP);
+ mInLongPress = true;
+ mListener.onLongPress(mCurrentDownEvent);
+ }
+}
diff --git a/client/Android/src/com/freerdp/afreerdp/utils/IntEditTextPreference.java b/client/Android/src/com/freerdp/afreerdp/utils/IntEditTextPreference.java
new file mode 100644
index 000000000..4ce494467
--- /dev/null
+++ b/client/Android/src/com/freerdp/afreerdp/utils/IntEditTextPreference.java
@@ -0,0 +1,97 @@
+/*
+ EditTextPreference to store/load integer values
+
+ Copyright 2013 Thinstuff Technologies GmbH, Author: Martin Fleisz
+
+ This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
+ If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+package com.freerdp.afreerdp.utils;
+
+import com.freerdp.afreerdp.R;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.preference.EditTextPreference;
+import android.util.AttributeSet;
+
+public class IntEditTextPreference extends EditTextPreference {
+
+ private int bounds_min, bounds_max, bounds_default;
+
+ public IntEditTextPreference(Context context)
+ {
+ super(context);
+ init(context, null);
+ }
+
+ public IntEditTextPreference(Context context, AttributeSet attrs)
+ {
+ super(context, attrs);
+ init(context, attrs);
+ }
+
+ public IntEditTextPreference(Context context, AttributeSet attrs, int defStyle)
+ {
+ super(context, attrs, defStyle);
+ init(context, attrs);
+ }
+
+ private void init(Context context, AttributeSet attrs)
+ {
+ if (attrs != null)
+ {
+ TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.IntEditTextPreference, 0, 0);
+ bounds_min = array.getInt(R.styleable.IntEditTextPreference_bounds_min, Integer.MIN_VALUE);
+ bounds_max = array.getInt(R.styleable.IntEditTextPreference_bounds_max, Integer.MAX_VALUE);
+ bounds_default = array.getInt(R.styleable.IntEditTextPreference_bounds_default, 0);
+ array.recycle();
+ }
+ else
+ {
+ bounds_min = Integer.MIN_VALUE;
+ bounds_max = Integer.MAX_VALUE;
+ bounds_default = 0;
+ }
+ }
+
+ public void setBounds(int min, int max, int defaultValue)
+ {
+ bounds_min = min;
+ bounds_max = max;
+ bounds_default = defaultValue;
+ }
+
+ @Override
+ protected String getPersistedString(String defaultReturnValue) {
+ int value = getPersistedInt(-1);
+ if (value > bounds_max || value < bounds_min)
+ value = bounds_default;
+ return String.valueOf(value);
+ }
+
+ @Override
+ protected boolean persistString(String value) {
+ return persistInt(Integer.valueOf(value));
+ }
+
+ @Override
+ protected void onDialogClosed(boolean positiveResult)
+ {
+ if (positiveResult)
+ {
+ // prevent exception when an empty value is persisted
+ if (getEditText().getText().length() == 0)
+ getEditText().setText("0");
+
+ // check bounds
+ int value = Integer.valueOf(getEditText().getText().toString());
+ if (value > bounds_max || value < bounds_min)
+ value = bounds_default;
+ getEditText().setText(String.valueOf(value));
+ }
+
+ super.onDialogClosed(positiveResult);
+ }
+}
diff --git a/client/Android/src/com/freerdp/afreerdp/utils/IntListPreference.java b/client/Android/src/com/freerdp/afreerdp/utils/IntListPreference.java
new file mode 100644
index 000000000..718a69ae7
--- /dev/null
+++ b/client/Android/src/com/freerdp/afreerdp/utils/IntListPreference.java
@@ -0,0 +1,37 @@
+/*
+ ListPreference to store/load integer values
+
+ Copyright 2013 Thinstuff Technologies GmbH, Author: Martin Fleisz
+
+ This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
+ If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+package com.freerdp.afreerdp.utils;
+
+import android.content.Context;
+import android.preference.ListPreference;
+import android.util.AttributeSet;
+
+public class IntListPreference extends ListPreference {
+
+ public IntListPreference(Context context)
+ {
+ super(context);
+ }
+
+ public IntListPreference(Context context, AttributeSet attrs)
+ {
+ super(context, attrs);
+ }
+
+ @Override
+ protected String getPersistedString(String defaultReturnValue) {
+ return String.valueOf(getPersistedInt(-1));
+ }
+
+ @Override
+ protected boolean persistString(String value) {
+ return persistInt(Integer.valueOf(value));
+ }
+}
diff --git a/client/Android/src/com/freerdp/afreerdp/utils/KeyboardMapper.java b/client/Android/src/com/freerdp/afreerdp/utils/KeyboardMapper.java
new file mode 100644
index 000000000..ae72de873
--- /dev/null
+++ b/client/Android/src/com/freerdp/afreerdp/utils/KeyboardMapper.java
@@ -0,0 +1,690 @@
+/*
+ Android Keyboard Mapping
+
+ Copyright 2013 Thinstuff Technologies GmbH, Author: Martin Fleisz
+
+ This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
+ If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+
+package com.freerdp.afreerdp.utils;
+
+import com.freerdp.afreerdp.R;
+
+import android.content.Context;
+import android.view.KeyEvent;
+
+public class KeyboardMapper
+{
+ public static final int KEYBOARD_TYPE_FUNCTIONKEYS = 1;
+ public static final int KEYBOARD_TYPE_NUMPAD = 2;
+ public static final int KEYBOARD_TYPE_CURSOR = 3;
+
+ // defines key states for modifier keys - locked means on and no auto-release if an other key is pressed
+ public static final int KEYSTATE_ON = 1;
+ public static final int KEYSTATE_LOCKED = 2;
+ public static final int KEYSTATE_OFF = 3;
+
+ // interface that gets called for input handling
+ public interface KeyProcessingListener {
+ abstract void processVirtualKey(int virtualKeyCode, boolean down);
+ abstract void processUnicodeKey(int unicodeKey);
+ abstract void switchKeyboard(int keyboardType);
+ abstract void modifiersChanged();
+ }
+
+ private KeyProcessingListener listener = null;
+
+ private static int[] keymapAndroid;
+ private static int[] keymapExt;
+ private static boolean initialized = false;
+
+ final static int VK_LBUTTON = 0x01;
+ final static int VK_RBUTTON = 0x02;
+ final static int VK_CANCEL = 0x03;
+ final static int VK_MBUTTON = 0x04;
+ final static int VK_XBUTTON1 = 0x05;
+ final static int VK_XBUTTON2 = 0x06;
+ final static int VK_BACK = 0x08;
+ final static int VK_TAB = 0x09;
+ final static int VK_CLEAR = 0x0C;
+ final static int VK_RETURN = 0x0D;
+ final static int VK_SHIFT = 0x10;
+ final static int VK_CONTROL = 0x11;
+ final static int VK_MENU = 0x12;
+ final static int VK_PAUSE = 0x13;
+ final static int VK_CAPITAL = 0x14;
+ final static int VK_KANA = 0x15;
+ final static int VK_HANGUEL = 0x15;
+ final static int VK_HANGUL = 0x15;
+ final static int VK_JUNJA = 0x17;
+ final static int VK_FINAL = 0x18;
+ final static int VK_HANJA = 0x19;
+ final static int VK_KANJI = 0x19;
+ final static int VK_ESCAPE = 0x1B;
+ final static int VK_CONVERT = 0x1C;
+ final static int VK_NONCONVERT = 0x1D;
+ final static int VK_ACCEPT = 0x1E;
+ final static int VK_MODECHANGE = 0x1F;
+ final static int VK_SPACE = 0x20;
+ final static int VK_PRIOR = 0x21;
+ final static int VK_NEXT = 0x22;
+ final static int VK_END = 0x23;
+ final static int VK_HOME = 0x24;
+ final static int VK_LEFT = 0x25;
+ final static int VK_UP = 0x26;
+ final static int VK_RIGHT = 0x27;
+ final static int VK_DOWN = 0x28;
+ final static int VK_SELECT = 0x29;
+ final static int VK_PRINT = 0x2A;
+ final static int VK_EXECUTE = 0x2B;
+ final static int VK_SNAPSHOT = 0x2C;
+ final static int VK_INSERT = 0x2D;
+ final static int VK_DELETE = 0x2E;
+ final static int VK_HELP = 0x2F;
+ final static int VK_KEY_0 = 0x30;
+ final static int VK_KEY_1 = 0x31;
+ final static int VK_KEY_2 = 0x32;
+ final static int VK_KEY_3 = 0x33;
+ final static int VK_KEY_4 = 0x34;
+ final static int VK_KEY_5 = 0x35;
+ final static int VK_KEY_6 = 0x36;
+ final static int VK_KEY_7 = 0x37;
+ final static int VK_KEY_8 = 0x38;
+ final static int VK_KEY_9 = 0x39;
+ final static int VK_KEY_A = 0x41;
+ final static int VK_KEY_B = 0x42;
+ final static int VK_KEY_C = 0x43;
+ final static int VK_KEY_D = 0x44;
+ final static int VK_KEY_E = 0x45;
+ final static int VK_KEY_F = 0x46;
+ final static int VK_KEY_G = 0x47;
+ final static int VK_KEY_H = 0x48;
+ final static int VK_KEY_I = 0x49;
+ final static int VK_KEY_J = 0x4A;
+ final static int VK_KEY_K = 0x4B;
+ final static int VK_KEY_L = 0x4C;
+ final static int VK_KEY_M = 0x4D;
+ final static int VK_KEY_N = 0x4E;
+ final static int VK_KEY_O = 0x4F;
+ final static int VK_KEY_P = 0x50;
+ final static int VK_KEY_Q = 0x51;
+ final static int VK_KEY_R = 0x52;
+ final static int VK_KEY_S = 0x53;
+ final static int VK_KEY_T = 0x54;
+ final static int VK_KEY_U = 0x55;
+ final static int VK_KEY_V = 0x56;
+ final static int VK_KEY_W = 0x57;
+ final static int VK_KEY_X = 0x58;
+ final static int VK_KEY_Y = 0x59;
+ final static int VK_KEY_Z = 0x5A;
+ final static int VK_LWIN = 0x5B;
+ final static int VK_RWIN = 0x5C;
+ final static int VK_APPS = 0x5D;
+ final static int VK_SLEEP = 0x5F;
+ final static int VK_NUMPAD0 = 0x60;
+ final static int VK_NUMPAD1 = 0x61;
+ final static int VK_NUMPAD2 = 0x62;
+ final static int VK_NUMPAD3 = 0x63;
+ final static int VK_NUMPAD4 = 0x64;
+ final static int VK_NUMPAD5 = 0x65;
+ final static int VK_NUMPAD6 = 0x66;
+ final static int VK_NUMPAD7 = 0x67;
+ final static int VK_NUMPAD8 = 0x68;
+ final static int VK_NUMPAD9 = 0x69;
+ final static int VK_MULTIPLY = 0x6A;
+ final static int VK_ADD = 0x6B;
+ final static int VK_SEPARATOR = 0x6C;
+ final static int VK_SUBTRACT = 0x6D;
+ final static int VK_DECIMAL = 0x6E;
+ final static int VK_DIVIDE = 0x6F;
+ final static int VK_F1 = 0x70;
+ final static int VK_F2 = 0x71;
+ final static int VK_F3 = 0x72;
+ final static int VK_F4 = 0x73;
+ final static int VK_F5 = 0x74;
+ final static int VK_F6 = 0x75;
+ final static int VK_F7 = 0x76;
+ final static int VK_F8 = 0x77;
+ final static int VK_F9 = 0x78;
+ final static int VK_F10 = 0x79;
+ final static int VK_F11 = 0x7A;
+ final static int VK_F12 = 0x7B;
+ final static int VK_F13 = 0x7C;
+ final static int VK_F14 = 0x7D;
+ final static int VK_F15 = 0x7E;
+ final static int VK_F16 = 0x7F;
+ final static int VK_F17 = 0x80;
+ final static int VK_F18 = 0x81;
+ final static int VK_F19 = 0x82;
+ final static int VK_F20 = 0x83;
+ final static int VK_F21 = 0x84;
+ final static int VK_F22 = 0x85;
+ final static int VK_F23 = 0x86;
+ final static int VK_F24 = 0x87;
+ final static int VK_NUMLOCK = 0x90;
+ final static int VK_SCROLL = 0x91;
+ final static int VK_LSHIFT = 0xA0;
+ final static int VK_RSHIFT = 0xA1;
+ final static int VK_LCONTROL = 0xA2;
+ final static int VK_RCONTROL = 0xA3;
+ final static int VK_LMENU = 0xA4;
+ final static int VK_RMENU = 0xA5;
+ final static int VK_BROWSER_BACK = 0xA6;
+ final static int VK_BROWSER_FORWARD = 0xA7;
+ final static int VK_BROWSER_REFRESH = 0xA8;
+ final static int VK_BROWSER_STOP = 0xA9;
+ final static int VK_BROWSER_SEARCH = 0xAA;
+ final static int VK_BROWSER_FAVORITES = 0xAB;
+ final static int VK_BROWSER_HOME = 0xAC;
+ final static int VK_VOLUME_MUTE = 0xAD;
+ final static int VK_VOLUME_DOWN = 0xAE;
+ final static int VK_VOLUME_UP = 0xAF;
+ final static int VK_MEDIA_NEXT_TRACK = 0xB0;
+ final static int VK_MEDIA_PREV_TRACK = 0xB1;
+ final static int VK_MEDIA_STOP = 0xB2;
+ final static int VK_MEDIA_PLAY_PAUSE = 0xB3;
+ final static int VK_LAUNCH_MAIL = 0xB4;
+ final static int VK_LAUNCH_MEDIA_SELECT = 0xB5;
+ final static int VK_LAUNCH_APP1 = 0xB6;
+ final static int VK_LAUNCH_APP2 = 0xB7;
+ final static int VK_OEM_1 = 0xBA;
+ final static int VK_OEM_PLUS = 0xBB;
+ final static int VK_OEM_COMMA = 0xBC;
+ final static int VK_OEM_MINUS = 0xBD;
+ final static int VK_OEM_PERIOD = 0xBE;
+ final static int VK_OEM_2 = 0xBF;
+ final static int VK_OEM_3 = 0xC0;
+ final static int VK_ABNT_C1 = 0xC1;
+ final static int VK_ABNT_C2 = 0xC2;
+ final static int VK_OEM_4 = 0xDB;
+ final static int VK_OEM_5 = 0xDC;
+ final static int VK_OEM_6 = 0xDD;
+ final static int VK_OEM_7 = 0xDE;
+ final static int VK_OEM_8 = 0xDF;
+ final static int VK_OEM_102 = 0xE2;
+ final static int VK_PROCESSKEY = 0xE5;
+ final static int VK_PACKET = 0xE7;
+ final static int VK_ATTN = 0xF6;
+ final static int VK_CRSEL = 0xF7;
+ final static int VK_EXSEL = 0xF8;
+ final static int VK_EREOF = 0xF9;
+ final static int VK_PLAY = 0xFA;
+ final static int VK_ZOOM = 0xFB;
+ final static int VK_NONAME = 0xFC;
+ final static int VK_PA1 = 0xFD;
+ final static int VK_OEM_CLEAR = 0xFE;
+ final static int VK_UNICODE = 0x80000000;
+
+ // key codes to switch between custom keyboard
+ private final static int EXTKEY_KBFUNCTIONKEYS = 0x1100;
+ private final static int EXTKEY_KBNUMPAD = 0x1101;
+ private final static int EXTKEY_KBCURSOR = 0x1102;
+
+ // this flag indicates if we got a VK or a unicode character in our translation map
+ private static final int KEY_FLAG_UNICODE = 0x80000000;
+
+ // this flag indicates if the key is a toggle key (remains down when pressed and goes up if pressed again)
+ private static final int KEY_FLAG_TOGGLE = 0x40000000;
+
+ private boolean shiftPressed = false;
+ private boolean ctrlPressed = false;
+ private boolean altPressed = false;
+ private boolean winPressed = false;
+
+ private long lastModifierTime;
+ private int lastModifierKeyCode = -1;
+
+ private boolean isShiftLocked = false;
+ private boolean isCtrlLocked = false;
+ private boolean isAltLocked = false;
+ private boolean isWinLocked = false;
+
+ public void init(Context context)
+ {
+ if(initialized == true)
+ return;
+
+ keymapAndroid = new int[256];
+
+ keymapAndroid[KeyEvent.KEYCODE_0] = VK_KEY_0;
+ keymapAndroid[KeyEvent.KEYCODE_1] = VK_KEY_1;
+ keymapAndroid[KeyEvent.KEYCODE_2] = VK_KEY_2;
+ keymapAndroid[KeyEvent.KEYCODE_3] = VK_KEY_3;
+ keymapAndroid[KeyEvent.KEYCODE_4] = VK_KEY_4;
+ keymapAndroid[KeyEvent.KEYCODE_5] = VK_KEY_5;
+ keymapAndroid[KeyEvent.KEYCODE_6] = VK_KEY_6;
+ keymapAndroid[KeyEvent.KEYCODE_7] = VK_KEY_7;
+ keymapAndroid[KeyEvent.KEYCODE_8] = VK_KEY_8;
+ keymapAndroid[KeyEvent.KEYCODE_9] = VK_KEY_9;
+
+ keymapAndroid[KeyEvent.KEYCODE_A] = VK_KEY_A;
+ keymapAndroid[KeyEvent.KEYCODE_B] = VK_KEY_B;
+ keymapAndroid[KeyEvent.KEYCODE_C] = VK_KEY_C;
+ keymapAndroid[KeyEvent.KEYCODE_D] = VK_KEY_D;
+ keymapAndroid[KeyEvent.KEYCODE_E] = VK_KEY_E;
+ keymapAndroid[KeyEvent.KEYCODE_F] = VK_KEY_F;
+ keymapAndroid[KeyEvent.KEYCODE_G] = VK_KEY_G;
+ keymapAndroid[KeyEvent.KEYCODE_H] = VK_KEY_H;
+ keymapAndroid[KeyEvent.KEYCODE_I] = VK_KEY_I;
+ keymapAndroid[KeyEvent.KEYCODE_J] = VK_KEY_J;
+ keymapAndroid[KeyEvent.KEYCODE_K] = VK_KEY_K;
+ keymapAndroid[KeyEvent.KEYCODE_L] = VK_KEY_L;
+ keymapAndroid[KeyEvent.KEYCODE_M] = VK_KEY_M;
+ keymapAndroid[KeyEvent.KEYCODE_N] = VK_KEY_N;
+ keymapAndroid[KeyEvent.KEYCODE_O] = VK_KEY_O;
+ keymapAndroid[KeyEvent.KEYCODE_P] = VK_KEY_P;
+ keymapAndroid[KeyEvent.KEYCODE_Q] = VK_KEY_Q;
+ keymapAndroid[KeyEvent.KEYCODE_R] = VK_KEY_R;
+ keymapAndroid[KeyEvent.KEYCODE_S] = VK_KEY_S;
+ keymapAndroid[KeyEvent.KEYCODE_T] = VK_KEY_T;
+ keymapAndroid[KeyEvent.KEYCODE_U] = VK_KEY_U;
+ keymapAndroid[KeyEvent.KEYCODE_V] = VK_KEY_V;
+ keymapAndroid[KeyEvent.KEYCODE_W] = VK_KEY_W;
+ keymapAndroid[KeyEvent.KEYCODE_X] = VK_KEY_X;
+ keymapAndroid[KeyEvent.KEYCODE_Y] = VK_KEY_Y;
+ keymapAndroid[KeyEvent.KEYCODE_Z] = VK_KEY_Z;
+
+ keymapAndroid[KeyEvent.KEYCODE_DEL] = VK_BACK;
+ keymapAndroid[KeyEvent.KEYCODE_ENTER] = VK_RETURN;
+ keymapAndroid[KeyEvent.KEYCODE_SPACE] = VK_SPACE;
+// keymapAndroid[KeyEvent.KEYCODE_SHIFT_LEFT] = VK_LSHIFT;
+// keymapAndroid[KeyEvent.KEYCODE_SHIFT_RIGHT] = VK_RSHIFT;
+
+// keymapAndroid[KeyEvent.KEYCODE_DPAD_DOWN] = VK_DOWN;
+// keymapAndroid[KeyEvent.KEYCODE_DPAD_LEFT] = VK_LEFT;
+// keymapAndroid[KeyEvent.KEYCODE_DPAD_RIGHT] = VK_RIGHT;
+// keymapAndroid[KeyEvent.KEYCODE_DPAD_UP] = VK_UP;
+
+// keymapAndroid[KeyEvent.KEYCODE_COMMA] = VK_OEM_COMMA;
+// keymapAndroid[KeyEvent.KEYCODE_PERIOD] = VK_OEM_PERIOD;
+// keymapAndroid[KeyEvent.KEYCODE_MINUS] = VK_OEM_MINUS;
+// keymapAndroid[KeyEvent.KEYCODE_PLUS] = VK_OEM_PLUS;
+
+// keymapAndroid[KeyEvent.KEYCODE_ALT_LEFT] = VK_LMENU;
+// keymapAndroid[KeyEvent.KEYCODE_ALT_RIGHT] = VK_RMENU;
+
+// keymapAndroid[KeyEvent.KEYCODE_AT] = (KEY_FLAG_UNICODE | 64);
+// keymapAndroid[KeyEvent.KEYCODE_APOSTROPHE] = (KEY_FLAG_UNICODE | 39);
+// keymapAndroid[KeyEvent.KEYCODE_BACKSLASH] = (KEY_FLAG_UNICODE | 92);
+// keymapAndroid[KeyEvent.KEYCODE_COMMA] = (KEY_FLAG_UNICODE | 44);
+// keymapAndroid[KeyEvent.KEYCODE_EQUALS] = (KEY_FLAG_UNICODE | 61);
+// keymapAndroid[KeyEvent.KEYCODE_GRAVE] = (KEY_FLAG_UNICODE | 96);
+// keymapAndroid[KeyEvent.KEYCODE_LEFT_BRACKET] = (KEY_FLAG_UNICODE | 91);
+// keymapAndroid[KeyEvent.KEYCODE_RIGHT_BRACKET] = (KEY_FLAG_UNICODE | 93);
+// keymapAndroid[KeyEvent.KEYCODE_MINUS] = (KEY_FLAG_UNICODE | 45);
+// keymapAndroid[KeyEvent.KEYCODE_PERIOD] = (KEY_FLAG_UNICODE | 46);
+// keymapAndroid[KeyEvent.KEYCODE_PLUS] = (KEY_FLAG_UNICODE | 43);
+// keymapAndroid[KeyEvent.KEYCODE_POUND] = (KEY_FLAG_UNICODE | 35);
+// keymapAndroid[KeyEvent.KEYCODE_SEMICOLON] = (KEY_FLAG_UNICODE | 59);
+// keymapAndroid[KeyEvent.KEYCODE_SLASH] = (KEY_FLAG_UNICODE | 47);
+// keymapAndroid[KeyEvent.KEYCODE_STAR] = (KEY_FLAG_UNICODE | 42);
+
+ // special keys mapping
+ keymapExt = new int[256];
+ keymapExt[context.getResources().getInteger(R.integer.keycode_F1)] = VK_F1;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_F2)] = VK_F2;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_F3)] = VK_F3;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_F4)] = VK_F4;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_F5)] = VK_F5;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_F6)] = VK_F6;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_F7)] = VK_F7;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_F8)] = VK_F8;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_F9)] = VK_F9;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_F10)] = VK_F10;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_F11)] = VK_F11;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_F12)] = VK_F12;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_tab)] = VK_TAB;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_print)] = VK_PRINT;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_insert)] = VK_INSERT;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_delete)] = VK_DELETE;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_home)] = VK_HOME;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_end)] = VK_END;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_pgup)] = VK_PRIOR;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_pgdn)] = VK_NEXT;
+
+ // numpad mapping
+ keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_0)] = VK_NUMPAD0;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_1)] = VK_NUMPAD1;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_2)] = VK_NUMPAD2;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_3)] = VK_NUMPAD3;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_4)] = VK_NUMPAD4;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_5)] = VK_NUMPAD5;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_6)] = VK_NUMPAD6;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_7)] = VK_NUMPAD7;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_8)] = VK_NUMPAD8;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_9)] = VK_NUMPAD9;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_numlock)] = VK_NUMLOCK;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_add)] = VK_ADD;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_comma)] = VK_DECIMAL;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_divide)] = VK_DIVIDE;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_enter)] = VK_RETURN;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_multiply)] = VK_MULTIPLY;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_subtract)] = VK_SUBTRACT;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_equals)] = (KEY_FLAG_UNICODE | 61);
+ keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_left_paren)] = (KEY_FLAG_UNICODE | 40);
+ keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_right_paren)] = (KEY_FLAG_UNICODE | 41);
+
+ // cursor key codes
+ keymapExt[context.getResources().getInteger(R.integer.keycode_up)] = VK_UP;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_down)] = VK_DOWN;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_left)] = VK_LEFT;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_right)] = VK_RIGHT;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_enter)] = VK_RETURN;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_backspace)] = VK_BACK;
+
+ // shared keys
+ keymapExt[context.getResources().getInteger(R.integer.keycode_win)] = VK_LWIN;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_menu)] = VK_APPS;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_esc)] = VK_ESCAPE;
+
+/* keymapExt[context.getResources().getInteger(R.integer.keycode_modifier_ctrl)] = VK_LCONTROL;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_modifier_alt)] = VK_LMENU;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_modifier_shift)] = VK_LSHIFT;
+*/
+ // get custom keyboard key codes
+ keymapExt[context.getResources().getInteger(R.integer.keycode_specialkeys_keyboard)] = EXTKEY_KBFUNCTIONKEYS;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_keyboard)] = EXTKEY_KBNUMPAD;
+ keymapExt[context.getResources().getInteger(R.integer.keycode_cursor_keyboard)] = EXTKEY_KBCURSOR;
+
+ keymapExt[context.getResources().getInteger(R.integer.keycode_toggle_shift)] = (KEY_FLAG_TOGGLE | VK_LSHIFT);
+ keymapExt[context.getResources().getInteger(R.integer.keycode_toggle_ctrl)] = (KEY_FLAG_TOGGLE | VK_LCONTROL);
+ keymapExt[context.getResources().getInteger(R.integer.keycode_toggle_alt)] = (KEY_FLAG_TOGGLE | VK_LMENU);
+ keymapExt[context.getResources().getInteger(R.integer.keycode_toggle_win)] = (KEY_FLAG_TOGGLE | VK_LWIN);
+
+ initialized = true;
+ }
+
+ public void reset(KeyProcessingListener listener) {
+ shiftPressed = false;
+ ctrlPressed = false;
+ altPressed = false;
+ winPressed = false;
+ setKeyProcessingListener(listener);
+ }
+
+ public void setKeyProcessingListener(KeyProcessingListener listener) {
+ this.listener = listener;
+ }
+
+ public boolean processAndroidKeyEvent(KeyEvent event) {
+ switch(event.getAction())
+ {
+ // we only process down events
+ case KeyEvent.ACTION_UP:
+ {
+ return false;
+ }
+
+ case KeyEvent.ACTION_DOWN:
+ {
+ boolean modifierActive = isModifierPressed();
+ // if a modifier is pressed we will send a VK event (if possible) so that key combinations will be
+ // recognized correctly. Otherwise we will send the unicode key. At the end we will reset all modifiers
+ // and notifiy our listener.
+ int vkcode = getVirtualKeyCode(event.getKeyCode());
+ if((vkcode & KEY_FLAG_UNICODE) != 0)
+ listener.processUnicodeKey(vkcode & (~KEY_FLAG_UNICODE));
+ // if we got a valid vkcode send it - except for letters/numbers if a modifier is active
+ else if (vkcode > 0 && (event.getMetaState() & (KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON | KeyEvent.META_SYM_ON)) == 0)
+ {
+ listener.processVirtualKey(vkcode, true);
+ listener.processVirtualKey(vkcode, false);
+ }
+ else if(Character.isUpperCase((char)event.getUnicodeChar()))
+ {
+ listener.processVirtualKey(VK_SHIFT, true);
+ listener.processVirtualKey(vkcode, true);
+ listener.processVirtualKey(vkcode, false);
+ listener.processVirtualKey(VK_SHIFT, false);
+ }
+ else if(event.getUnicodeChar() != 0)
+ listener.processUnicodeKey(event.getUnicodeChar());
+ else
+ return false;
+
+ // reset any pending toggle states if a modifier was pressed
+ if(modifierActive)
+ resetModifierKeysAfterInput(false);
+ return true;
+ }
+
+ case KeyEvent.ACTION_MULTIPLE:
+ {
+ String str = event.getCharacters();
+ for(int i = 0; i < str.length(); i++)
+ listener.processUnicodeKey(str.charAt(i));
+ return true;
+ }
+
+ default:
+ break;
+ }
+ return false;
+ }
+
+ public void processCustomKeyEvent(int keycode) {
+ int extCode = getExtendedKeyCode(keycode);
+ if(extCode == 0)
+ return;
+
+ // toggle button pressed?
+ if((extCode & KEY_FLAG_TOGGLE) != 0)
+ {
+ processToggleButton(extCode & (~KEY_FLAG_TOGGLE));
+ return;
+ }
+
+ // keyboard switch button pressed?
+ if(extCode == EXTKEY_KBFUNCTIONKEYS || extCode == EXTKEY_KBNUMPAD || extCode == EXTKEY_KBCURSOR)
+ {
+ switchKeyboard(extCode);
+ return;
+ }
+
+ // nope - see if we got a unicode or vk
+ if((extCode & KEY_FLAG_UNICODE) != 0)
+ listener.processUnicodeKey(extCode & (~KEY_FLAG_UNICODE));
+ else
+ {
+ listener.processVirtualKey(extCode, true);
+ listener.processVirtualKey(extCode, false);
+ }
+
+ resetModifierKeysAfterInput(false);
+ }
+
+ public void sendAltF4()
+ {
+ listener.processVirtualKey(VK_LMENU, true);
+ listener.processVirtualKey(VK_F4, true);
+ listener.processVirtualKey(VK_F4, false);
+ listener.processVirtualKey(VK_LMENU, false);
+ }
+
+ private boolean isModifierPressed() {
+ return (shiftPressed || ctrlPressed || altPressed || winPressed);
+ }
+
+ public int getModifierState(int keycode) {
+ int modifierCode = getExtendedKeyCode(keycode);
+
+ // check and get real modifier keycode
+ if((modifierCode & KEY_FLAG_TOGGLE) == 0)
+ return -1;
+ modifierCode = modifierCode & (~KEY_FLAG_TOGGLE);
+
+ switch(modifierCode)
+ {
+ case VK_LSHIFT:
+ {
+ return (shiftPressed ? (isShiftLocked ? KEYSTATE_LOCKED : KEYSTATE_ON) : KEYSTATE_OFF);
+ }
+ case VK_LCONTROL:
+ {
+ return (ctrlPressed ? (isCtrlLocked ? KEYSTATE_LOCKED : KEYSTATE_ON) : KEYSTATE_OFF);
+ }
+ case VK_LMENU:
+ {
+ return (altPressed ? (isAltLocked ? KEYSTATE_LOCKED : KEYSTATE_ON) : KEYSTATE_OFF);
+ }
+ case VK_LWIN:
+ {
+ return (winPressed ? (isWinLocked ? KEYSTATE_LOCKED : KEYSTATE_ON) : KEYSTATE_OFF);
+ }
+ }
+
+ return -1;
+ }
+
+ private int getVirtualKeyCode(int keycode) {
+ if(keycode >= 0 && keycode <= 0xFF)
+ return keymapAndroid[keycode];
+ return 0;
+ }
+
+ private int getExtendedKeyCode(int keycode) {
+ if(keycode >= 0 && keycode <= 0xFF)
+ return keymapExt[keycode];
+ return 0;
+ }
+
+ private void processToggleButton(int keycode) {
+ switch(keycode)
+ {
+ case VK_LSHIFT:
+ {
+ if(!checkToggleModifierLock(VK_LSHIFT))
+ {
+ isShiftLocked = false;
+ shiftPressed = !shiftPressed;
+ listener.processVirtualKey(VK_LSHIFT, shiftPressed);
+ }
+ else
+ isShiftLocked = true;
+ break;
+ }
+ case VK_LCONTROL:
+ {
+ if(!checkToggleModifierLock(VK_LCONTROL))
+ {
+ isCtrlLocked = false;
+ ctrlPressed = !ctrlPressed;
+ listener.processVirtualKey(VK_LCONTROL, ctrlPressed);
+ }
+ else
+ isCtrlLocked = true;
+ break;
+ }
+ case VK_LMENU:
+ {
+ if(!checkToggleModifierLock(VK_LMENU))
+ {
+ isAltLocked = false;
+ altPressed = !altPressed;
+ listener.processVirtualKey(VK_LMENU, altPressed);
+ }
+ else
+ isAltLocked = true;
+ break;
+ }
+ case VK_LWIN:
+ {
+ if(!checkToggleModifierLock(VK_LMENU))
+ {
+ isWinLocked = false;
+ winPressed = !winPressed;
+ listener.processVirtualKey(VK_LWIN, winPressed);
+ }
+ else
+ isWinLocked = true;
+ break;
+ }
+ }
+ listener.modifiersChanged();
+ }
+
+ public void clearlAllModifiers()
+ {
+ resetModifierKeysAfterInput(true);
+ }
+
+ private void resetModifierKeysAfterInput(boolean force) {
+ if(shiftPressed && (!isShiftLocked || force))
+ {
+ listener.processVirtualKey(VK_LSHIFT, false);
+ shiftPressed = false;
+ }
+ if(ctrlPressed && (!isCtrlLocked || force))
+ {
+ listener.processVirtualKey(VK_LCONTROL, false);
+ ctrlPressed = false;
+ }
+ if(altPressed && (!isAltLocked || force))
+ {
+ listener.processVirtualKey(VK_LMENU, false);
+ altPressed = false;
+ }
+ if(winPressed && (!isWinLocked || force))
+ {
+ listener.processVirtualKey(VK_LWIN, false);
+ winPressed = false;
+ }
+
+ if(listener != null)
+ listener.modifiersChanged();
+ }
+
+ private void switchKeyboard(int keycode) {
+ switch(keycode)
+ {
+ case EXTKEY_KBFUNCTIONKEYS:
+ {
+ listener.switchKeyboard(KEYBOARD_TYPE_FUNCTIONKEYS);
+ break;
+ }
+
+ case EXTKEY_KBNUMPAD:
+ {
+ listener.switchKeyboard(KEYBOARD_TYPE_NUMPAD);
+ break;
+ }
+
+ case EXTKEY_KBCURSOR:
+ {
+ listener.switchKeyboard(KEYBOARD_TYPE_CURSOR);
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+
+ private boolean checkToggleModifierLock(int keycode) {
+ long now = System.currentTimeMillis();
+
+ // was the same modifier hit?
+ if(lastModifierKeyCode != keycode)
+ {
+ lastModifierKeyCode = keycode;
+ lastModifierTime = now;
+ return false;
+ }
+
+ // within a certain time interval?
+ if(lastModifierTime + 800 > now)
+ {
+ lastModifierTime = 0;
+ return true;
+ }
+ else
+ {
+ lastModifierTime = now;
+ return false;
+ }
+ }
+
+}
+
diff --git a/client/Android/src/com/freerdp/afreerdp/utils/Mouse.java b/client/Android/src/com/freerdp/afreerdp/utils/Mouse.java
new file mode 100644
index 000000000..202c4d5de
--- /dev/null
+++ b/client/Android/src/com/freerdp/afreerdp/utils/Mouse.java
@@ -0,0 +1,57 @@
+/*
+ Android Mouse Input Mapping
+
+ Copyright 2013 Thinstuff Technologies GmbH, Author: Martin Fleisz
+
+ This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
+ If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+package com.freerdp.afreerdp.utils;
+
+import com.freerdp.afreerdp.application.GlobalSettings;
+
+public class Mouse {
+
+ private final static int PTRFLAGS_LBUTTON = 0x1000;
+ private final static int PTRFLAGS_RBUTTON = 0x2000;
+
+ private final static int PTRFLAGS_DOWN = 0x8000;
+ private final static int PTRFLAGS_MOVE = 0x0800;
+
+ private final static int PTRFLAGS_WHEEL = 0x0200;
+ private final static int PTRFLAGS_WHEEL_NEGATIVE = 0x0100;
+
+ public static int getLeftButtonEvent(boolean down) {
+ if(GlobalSettings.getSwapMouseButtons())
+ return (PTRFLAGS_RBUTTON | (down ? PTRFLAGS_DOWN : 0));
+ else
+ return (PTRFLAGS_LBUTTON | (down ? PTRFLAGS_DOWN : 0));
+ }
+
+ public static int getRightButtonEvent(boolean down) {
+ if(GlobalSettings.getSwapMouseButtons())
+ return (PTRFLAGS_LBUTTON | (down ? PTRFLAGS_DOWN : 0));
+ else
+ return (PTRFLAGS_RBUTTON | (down ? PTRFLAGS_DOWN : 0));
+ }
+
+ public static int getMoveEvent() {
+ return PTRFLAGS_MOVE;
+ }
+
+ public static int getScrollEvent(boolean down) {
+ int flags = PTRFLAGS_WHEEL;
+
+ // invert scrolling?
+ if(GlobalSettings.getInvertScrolling())
+ down = !down;
+
+ if(down)
+ flags |= (PTRFLAGS_WHEEL_NEGATIVE | 0x0088);
+ else
+ flags |= 0x0078;
+ return flags;
+ }
+
+}
diff --git a/client/Android/src/com/freerdp/afreerdp/utils/SeparatedListAdapter.java b/client/Android/src/com/freerdp/afreerdp/utils/SeparatedListAdapter.java
new file mode 100644
index 000000000..9ee8894c1
--- /dev/null
+++ b/client/Android/src/com/freerdp/afreerdp/utils/SeparatedListAdapter.java
@@ -0,0 +1,185 @@
+/*
+ Separated List Adapter
+ Taken from http://jsharkey.org/blog/2008/08/18/separating-lists-with-headers-in-android-09/
+
+ Copyright Jeff Sharkey
+
+ This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
+ If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+package com.freerdp.afreerdp.utils;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import com.freerdp.afreerdp.R;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Adapter;
+import android.widget.ArrayAdapter;
+import android.widget.BaseAdapter;
+
+public class SeparatedListAdapter extends BaseAdapter {
+
+ public final Map sections = new LinkedHashMap();
+ public final ArrayAdapter headers;
+ public final static int TYPE_SECTION_HEADER = 0;
+
+ public SeparatedListAdapter(Context context) {
+ headers = new ArrayAdapter(context, R.layout.list_header);
+ }
+
+ public void addSection(String section, Adapter adapter) {
+ this.headers.add(section);
+ this.sections.put(section, adapter);
+ }
+
+ public void setSectionTitle(int section, String title) {
+ String oldTitle = this.headers.getItem(section);
+
+ // remove/add to headers array
+ this.headers.remove(oldTitle);
+ this.headers.insert(title, section);
+
+ // remove/add to section map
+ Adapter adapter = this.sections.get(oldTitle);
+ this.sections.remove(oldTitle);
+ this.sections.put(title, adapter);
+ }
+
+ public Object getItem(int position) {
+ for(int i = 0; i < headers.getCount(); i++) {
+ String section = headers.getItem(i);
+ Adapter adapter = sections.get(section);
+
+ // ignore empty sections
+ if(adapter.getCount() > 0)
+ {
+ int size = adapter.getCount() + 1;
+
+ // check if position inside this section
+ if(position == 0) return section;
+ if(position < size) return adapter.getItem(position - 1);
+
+ // otherwise jump into next section
+ position -= size;
+ }
+ }
+ return null;
+ }
+
+ public int getCount() {
+ // total together all sections, plus one for each section header (except if the section is empty)
+ int total = 0;
+ for(Adapter adapter : this.sections.values())
+ total += ((adapter.getCount() > 0) ? adapter.getCount() + 1 : 0);
+ return total;
+ }
+
+ public int getViewTypeCount() {
+ // assume that headers count as one, then total all sections
+ int total = 1;
+ for(Adapter adapter : this.sections.values())
+ total += adapter.getViewTypeCount();
+ return total;
+ }
+
+ public int getItemViewType(int position) {
+ int type = 1;
+ for(int i = 0; i < headers.getCount(); i++) {
+ String section = headers.getItem(i);
+ Adapter adapter = sections.get(section);
+
+ // skip empty sections
+ if(adapter.getCount() > 0)
+ {
+ int size = adapter.getCount() + 1;
+
+ // check if position inside this section
+ if(position == 0) return TYPE_SECTION_HEADER;
+ if(position < size) return type + adapter.getItemViewType(position - 1);
+
+ // otherwise jump into next section
+ position -= size;
+ type += adapter.getViewTypeCount();
+ }
+ }
+ return -1;
+ }
+
+ public boolean areAllItemsSelectable() {
+ return false;
+ }
+
+ public boolean isEnabled(int position) {
+ return (getItemViewType(position) != TYPE_SECTION_HEADER);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ int sectionnum = 0;
+ for(int i = 0; i < headers.getCount(); i++) {
+ String section = headers.getItem(i);
+ Adapter adapter = sections.get(section);
+
+ // skip empty sections
+ if(adapter.getCount() > 0)
+ {
+ int size = adapter.getCount() + 1;
+
+ // check if position inside this section
+ if(position == 0) return headers.getView(sectionnum, convertView, parent);
+ if(position < size) return adapter.getView(position - 1, null, parent);
+
+ // otherwise jump into next section
+ position -= size;
+ }
+ sectionnum++;
+ }
+ return null;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ for(int i = 0; i < headers.getCount(); i++)
+ {
+ String section = headers.getItem(i);
+ Adapter adapter = sections.get(section);
+ if(adapter.getCount() > 0)
+ {
+ int size = adapter.getCount() + 1;
+
+ // check if position inside this section
+ if(position < size) return adapter.getItemId(position - 1);
+
+ // otherwise jump into next section
+ position -= size;
+ }
+ }
+ return -1;
+ }
+
+ public String getSectionForPosition(int position) {
+ int curPos = 0;
+ for(int i = 0; i < headers.getCount(); i++)
+ {
+ String section = headers.getItem(i);
+ Adapter adapter = sections.get(section);
+ if(adapter.getCount() > 0)
+ {
+ int size = adapter.getCount() + 1;
+
+ // check if position inside this section
+ if(position >= curPos && position < (curPos + size)) return section.toString();
+
+ // otherwise jump into next section
+ curPos += size;
+ }
+ }
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/cmake/AndroidToolchain.cmake b/cmake/AndroidToolchain.cmake
index e6271d9d8..30b9e2507 100644
--- a/cmake/AndroidToolchain.cmake
+++ b/cmake/AndroidToolchain.cmake
@@ -1,1125 +1,1568 @@
-# ------------------------------------------------------------------------------
-# Android CMake toolchain file, for use with the Android NDK r5-r8
-# Requires cmake 2.6.3 or newer (2.8.5 or newer is recommended).
-# See home page: http://code.google.com/p/android-cmake/
-#
-# The file is mantained by the OpenCV project. And also can be found at
-# http://code.opencv.org/projects/opencv/repository/revisions/master/changes/android/android.toolchain.cmake
-#
-# Usage Linux:
-# $ export ANDROID_NDK=/absolute/path/to/the/android-ndk
-# $ mkdir build && cd build
-# $ cmake -DCMAKE_TOOLCHAIN_FILE=path/to/the/android.toolchain.cmake ..
-# $ make -j8
-#
-# Usage Linux (using standalone toolchain):
-# $ export ANDROID_STANDALONE_TOOLCHAIN=/absolute/path/to/android-toolchain
-# $ mkdir build && cd build
-# $ cmake -DCMAKE_TOOLCHAIN_FILE=path/to/the/android.toolchain.cmake ..
-# $ make -j8
-#
-# Usage Windows:
-# You need native port of make to build your project.
-# Android NDK r7 (or newer) already has make.exe on board.
-# For older NDK you have to install it separately.
-# For example, this one: http://gnuwin32.sourceforge.net/packages/make.htm
-#
-# $ SET ANDROID_NDK=C:\absolute\path\to\the\android-ndk
-# $ mkdir build && cd build
-# $ cmake.exe -G"MinGW Makefiles"
-# -DCMAKE_TOOLCHAIN_FILE=path\to\the\android.toolchain.cmake
-# -DCMAKE_MAKE_PROGRAM="%ANDROID_NDK%\prebuilt\windows\bin\make.exe" ..
-# $ "%ANDROID_NDK%\prebuilt\windows\bin\make.exe"
-#
-#
-# Options (can be set as cmake parameters: -D=):
-# ANDROID_NDK=/opt/android-ndk - path to the NDK root.
-# Can be set as environment variable. Can be set only at first cmake run.
-#
-# ANDROID_STANDALONE_TOOLCHAIN=/opt/android-toolchain - path to the
-# standalone toolchain. This option is not used if full NDK is found
-# (ignored if ANDROID_NDK is set).
-# Can be set as environment variable. Can be set only at first cmake run.
-#
-# ANDROID_ABI=armeabi-v7a - specifies the target Application Binary
-# Interface (ABI). This option nearly matches to the APP_ABI variable
-# used by ndk-build tool from Android NDK.
-#
-# Possible targets are:
-# "armeabi" - matches to the NDK ABI with the same name.
-# See ${ANDROID_NDK}/docs/CPU-ARCH-ABIS.html for the documentation.
-# "armeabi-v7a" - matches to the NDK ABI with the same name.
-# See ${ANDROID_NDK}/docs/CPU-ARCH-ABIS.html for the documentation.
-# "armeabi-v7a with NEON" - same as armeabi-v7a, but
-# sets NEON as floating-point unit
-# "armeabi-v7a with VFPV3" - same as armeabi-v7a, but
-# sets VFPV3 as floating-point unit (has 32 registers instead of 16).
-# "armeabi-v6 with VFP" - tuned for ARMv6 processors having VFP.
-# "x86" - matches to the NDK ABI with the same name.
-# See ${ANDROID_NDK}/docs/CPU-ARCH-ABIS.html for the documentation.
-# "mips" - matches to the NDK ABI with the same name
-# (not testes on real devices)
-#
-# ANDROID_NATIVE_API_LEVEL=android-8 - level of Android API compile for.
-# Option is read-only when standalone toolchain used.
-#
-# ANDROID_FORCE_ARM_BUILD=OFF - set true to generate 32-bit ARM instructions
-# instead of Thumb-1. Is not available for "x86" (inapplicable) and
-# "armeabi-v6 with VFP" (forced) ABIs.
-#
-# ANDROID_NO_UNDEFINED=ON - set true to show all undefined symbols as linker
-# errors even if they are not used.
-#
-# ANDROID_SO_UNDEFINED=OFF - set true to allow undefined symbols in shared
-# libraries. Automatically turned on for NDK r5x and r6x due to GLESv2
-# problems.
-#
-# LIBRARY_OUTPUT_PATH_ROOT=${CMAKE_SOURCE_DIR} - where to output binary
-# files. See additional details below.
-#
-# ANDROID_SET_OBSOLETE_VARIABLES=ON - it set, then toolchain defines some
-# obsolete variables which were set by previous versions of this file for
-# backward compatibility.
-#
-#
-# What?:
-# android-cmake toolchain searches for NDK/toolchain in the following order:
-# ANDROID_NDK - cmake parameter
-# ANDROID_NDK - environment variable
-# ANDROID_STANDALONE_TOOLCHAIN - cmake parameter
-# ANDROID_STANDALONE_TOOLCHAIN - environment variable
-# ANDROID_NDK - default locations
-# ANDROID_STANDALONE_TOOLCHAIN - default locations
-#
-# Make sure to do the following in your scripts:
-# SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${my_cxx_flags}" )
-# SET( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${my_cxx_flags}" )
-# The flags will be prepopulated with critical flags, so don't loose them.
-# Also be aware that toolchain also sets configuration-specific compiler
-# flags and linker flags.
-#
-# ANDROID and BUILD_ANDROID will be set to true, you may test any of these
-# variables to make necessary Android-specific configuration changes.
-#
-# Also ARMEABI or ARMEABI_V7A or X86 will be set true, mutually exclusive.
-# NEON option will be set true if VFP is set to NEON.
-#
-# LIBRARY_OUTPUT_PATH_ROOT should be set in cache to determine where Android
-# libraries will be installed.
-# Default is ${CMAKE_SOURCE_DIR}, and the android libs will always be
-# under the ${LIBRARY_OUTPUT_PATH_ROOT}/libs/${ANDROID_NDK_ABI_NAME}
-# (depending on the target ABI). This is convenient for Android packaging.
-#
-# Authors:
-# Ethan Rublee ethan.ruble@gmail.com
-# Andrey Kamaev andrey.kamaev@itseez.com
-#
-# Change Log:
-# - initial version December 2010
-# - modified April 2011
-# [+] added possibility to build with NDK (without standalone toolchain)
-# [+] support cross-compilation on Windows (native, no cygwin support)
-# [+] added compiler option to force "char" type to be signed
-# [+] added toolchain option to compile to 32-bit ARM instructions
-# [+] added toolchain option to disable SWIG search
-# [+] added platform "armeabi-v7a with VFPV3"
-# [~] ARM_TARGETS renamed to ARM_TARGET
-# [+] EXECUTABLE_OUTPUT_PATH is set by toolchain (required on Windows)
-# [~] Fixed bug with ANDROID_API_LEVEL variable
-# [~] turn off SWIG search if it is not found first time
-# - modified May 2011
-# [~] ANDROID_LEVEL is renamed to ANDROID_API_LEVEL
-# [+] ANDROID_API_LEVEL is detected by toolchain if not specified
-# [~] added guard to prevent changing of output directories on the first
-# cmake pass
-# [~] toolchain exits with error if ARM_TARGET is not recognized
-# - modified June 2011
-# [~] default NDK path is updated for version r5c
-# [+] variable CMAKE_SYSTEM_PROCESSOR is set based on ARM_TARGET
-# [~] toolchain install directory is added to linker paths
-# [-] removed SWIG-related stuff from toolchain
-# [+] added macro find_host_package, find_host_program to search
-# packages/programs on the host system
-# [~] fixed path to STL library
-# - modified July 2011
-# [~] fixed options caching
-# [~] search for all supported NDK versions
-# [~] allowed spaces in NDK path
-# - modified September 2011
-# [~] updated for NDK r6b
-# - modified November 2011
-# [*] rewritten for NDK r7
-# [+] x86 toolchain support (experimental)
-# [+] added "armeabi-v6 with VFP" ABI for ARMv6 processors.
-# [~] improved compiler and linker flags management
-# [+] support different build flags for Release and Debug configurations
-# [~] by default compiler flags the same as used by ndk-build (but only
-# where reasonable)
-# [~] ANDROID_NDK_TOOLCHAIN_ROOT is splitted to ANDROID_STANDALONE_TOOLCHAIN
-# and ANDROID_TOOLCHAIN_ROOT
-# [~] ARM_TARGET is renamed to ANDROID_ABI
-# [~] ARMEABI_NDK_NAME is renamed to ANDROID_NDK_ABI_NAME
-# [~] ANDROID_API_LEVEL is renamed to ANDROID_NATIVE_API_LEVEL
-# - modified January 2012
-# [+] added stlport_static support (experimental)
-# [+] added special check for cygwin
-# [+] filtered out hidden files (starting with .) while globbing inside NDK
-# [+] automatically applied GLESv2 linkage fix for NDK revisions 5-6
-# [+] added ANDROID_GET_ABI_RAWNAME to get NDK ABI names by CMake flags
-# - modified February 2012
-# [+] updated for NDK r7b
-# [~] fixed cmake try_compile() command
-# [~] Fix for missing install_name_tool on OS X
-# - modified March 2012
-# [~] fixed incorrect C compiler flags
-# [~] fixed CMAKE_SYSTEM_PROCESSOR change on ANDROID_ABI change
-# [+] improved toolchain loading speed
-# [+] added assembler language support (.S)
-# [+] allowed preset search paths and extra search suffixes
-# - modified April 2012
-# [+] updated for NDK r7c
-# [~] fixed most of problems with compiler/linker flags and caching
-# [+] added option ANDROID_FUNCTION_LEVEL_LINKING
-# - modified May 2012
-# [+] updated for NDK r8
-# [+] added mips architecture support
-# - modified August 2012
-# [+] updated for NDK r8b
-# [~] all intermediate files generated by toolchain are moved into CMakeFiles
-# [~] libstdc++ and libsupc are removed from explicit link libraries
-# ------------------------------------------------------------------------------
-
-cmake_minimum_required( VERSION 2.6.3 )
-
-if( DEFINED CMAKE_CROSSCOMPILING )
- # subsequent toolchain loading is not really needed
- return()
-endif()
-
-get_property(_CMAKE_IN_TRY_COMPILE GLOBAL PROPERTY IN_TRY_COMPILE)
-if( _CMAKE_IN_TRY_COMPILE )
- include( "${CMAKE_CURRENT_SOURCE_DIR}/../android.toolchain.config.cmake" OPTIONAL )
-endif()
-
-# this one is important
-set( CMAKE_SYSTEM_NAME Linux )
-# this one not so much
-set( CMAKE_SYSTEM_VERSION 1 )
-
-set( ANDROID_SUPPORTED_NDK_VERSIONS ${ANDROID_EXTRA_NDK_VERSIONS} -r8b -r8 -r7c -r7b -r7 -r6b -r6 -r5c -r5b -r5 "" )
-if(NOT DEFINED ANDROID_NDK_SEARCH_PATHS)
- if( CMAKE_HOST_WIN32 )
- file( TO_CMAKE_PATH "$ENV{PROGRAMFILES}" ANDROID_NDK_SEARCH_PATHS )
- set( ANDROID_NDK_SEARCH_PATHS "${ANDROID_NDK_SEARCH_PATHS}/android-ndk" "$ENV{SystemDrive}/NVPACK/android-ndk" )
- else()
- file( TO_CMAKE_PATH "$ENV{HOME}" ANDROID_NDK_SEARCH_PATHS )
- set( ANDROID_NDK_SEARCH_PATHS /opt/android-ndk "${ANDROID_NDK_SEARCH_PATHS}/NVPACK/android-ndk" )
- endif()
-endif()
-if(NOT DEFINED ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH)
- set( ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH /opt/android-toolchain )
-endif()
-
-set( ANDROID_SUPPORTED_ABIS_arm "armeabi-v7a;armeabi;armeabi-v7a with NEON;armeabi-v7a with VFPV3;armeabi-v6 with VFP" )
-set( ANDROID_SUPPORTED_ABIS_x86 "x86" )
-set( ANDROID_SUPPORTED_ABIS_mipsel "mips" )
-
-set( ANDROID_DEFAULT_NDK_API_LEVEL 8 )
-set( ANDROID_DEFAULT_NDK_API_LEVEL_x86 9 )
-set( ANDROID_DEFAULT_NDK_API_LEVEL_mips 9 )
-
-
-macro( __LIST_FILTER listvar regex )
- if( ${listvar} )
- foreach( __val ${${listvar}} )
- if( __val MATCHES "${regex}" )
- list( REMOVE_ITEM ${listvar} "${__val}" )
- endif()
- endforeach()
- endif()
-endmacro()
-
-macro( __INIT_VARIABLE var_name )
- set( __test_path 0 )
- foreach( __var ${ARGN} )
- if( __var STREQUAL "PATH" )
- set( __test_path 1 )
- break()
- endif()
- endforeach()
- if( __test_path AND NOT EXISTS "${${var_name}}" )
- unset( ${var_name} CACHE )
- endif()
- if( "${${var_name}}" STREQUAL "" )
- set( __values 0 )
- foreach( __var ${ARGN} )
- if( __var STREQUAL "VALUES" )
- set( __values 1 )
- elseif( NOT __var STREQUAL "PATH" )
- set( __obsolete 0 )
- if( __var MATCHES "^OBSOLETE_.*$" )
- string( REPLACE "OBSOLETE_" "" __var "${__var}" )
- set( __obsolete 1 )
- endif()
- if( __var MATCHES "^ENV_.*$" )
- string( REPLACE "ENV_" "" __var "${__var}" )
- set( __value "$ENV{${__var}}" )
- elseif( DEFINED ${__var} )
- set( __value "${${__var}}" )
- else()
- if( __values )
- set( __value "${__var}" )
- else()
- set( __value "" )
- endif()
- endif()
- if( NOT "${__value}" STREQUAL "" )
- if( __test_path )
- if( EXISTS "${__value}" )
- set( ${var_name} "${__value}" )
- if( __obsolete )
- message( WARNING "Using value of obsolete variable ${__var} as initial value for ${var_name}. Please note, that ${__var} can be completely removed in future versions of the toolchain." )
- endif()
- break()
- endif()
- else()
- set( ${var_name} "${__value}" )
- if( __obsolete )
- message( WARNING "Using value of obsolete variable ${__var} as initial value for ${var_name}. Please note, that ${__var} can be completely removed in future versions of the toolchain." )
- endif()
- break()
- endif()
- endif()
- endif()
- endforeach()
- unset( __value )
- unset( __values )
- unset( __obsolete )
- endif()
- unset( __test_path )
-endmacro()
-
-macro( __DETECT_NATIVE_API_LEVEL _var _path )
- SET( __ndkApiLevelRegex "^[\t ]*#define[\t ]+__ANDROID_API__[\t ]+([0-9]+)[\t ]*$" )
- FILE( STRINGS ${_path} __apiFileContent REGEX "${__ndkApiLevelRegex}" )
- if( NOT __apiFileContent )
- message( SEND_ERROR "Could not get Android native API level. Probably you have specified invalid level value, or your copy of NDK/toolchain is broken." )
- endif()
- string( REGEX REPLACE "${__ndkApiLevelRegex}" "\\1" ${_var} "${__apiFileContent}" )
- unset( __apiFileContent )
- unset( __ndkApiLevelRegex )
-endmacro()
-
-macro( __DETECT_TOOLCHAIN_MACHINE_NAME _var _root )
- file( GLOB __gccExePath "${_root}/bin/*-gcc${TOOL_OS_SUFFIX}" )
- __LIST_FILTER( __gccExePath "bin/[.].*-gcc${TOOL_OS_SUFFIX}$" )
- list( LENGTH __gccExePath __gccExePathsCount )
- if( NOT __gccExePathsCount EQUAL 1 )
- message( WARNING "Could not uniquely determine machine name for compiler from ${_root}." )
- set( ${_var} "" )
- else()
- get_filename_component( __gccExeName "${__gccExePath}" NAME_WE )
- string( REPLACE "-gcc" "" ${_var} "${__gccExeName}" )
- endif()
- unset( __gccExePath )
- unset( __gccExePathsCount )
- unset( __gccExeName )
-endmacro()
-
-macro( __COPY_IF_DIFFERENT _source _destination )
- execute_process( COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${_source}" "${_destination}" RESULT_VARIABLE __fileCopyProcess )
- if( NOT __fileCopyProcess EQUAL 0 OR NOT EXISTS "${_destination}")
- message( SEND_ERROR "Failed copying of ${_source} to the ${_destination}" )
- endif()
- unset( __fileCopyProcess )
-endmacro()
-
-
-# stl version: by default gnustl_static will be used
-set( ANDROID_USE_STLPORT FALSE CACHE BOOL "Experimental: use stlport_static instead of gnustl_static")
-mark_as_advanced( ANDROID_USE_STLPORT )
-
-# fight against cygwin
-set( ANDROID_FORBID_SYGWIN TRUE CACHE BOOL "Prevent cmake from working under cygwin and using cygwin tools")
-mark_as_advanced( ANDROID_FORBID_SYGWIN )
-if( ANDROID_FORBID_SYGWIN )
- if( CYGWIN )
- message( FATAL_ERROR "Android NDK and android-cmake toolchain are not welcome Cygwin. It is unlikely that this cmake toolchain will work under cygwin. But if you want to try then you can set cmake variable ANDROID_FORBID_SYGWIN to FALSE and rerun cmake." )
- endif()
-
- if( CMAKE_HOST_WIN32 )
- # remove cygwin from PATH
- set( __new_path "$ENV{PATH}")
- __LIST_FILTER( __new_path "cygwin" )
- set(ENV{PATH} "${__new_path}")
- unset(__new_path)
- endif()
-endif()
-
-# detect current host platform
-set( TOOL_OS_SUFFIX "" )
-if( CMAKE_HOST_APPLE )
- set( ANDROID_NDK_HOST_SYSTEM_NAME "darwin-x86" )
-elseif( CMAKE_HOST_WIN32 )
- set( ANDROID_NDK_HOST_SYSTEM_NAME "windows" )
- set( TOOL_OS_SUFFIX ".exe" )
-elseif( CMAKE_HOST_UNIX )
- set( ANDROID_NDK_HOST_SYSTEM_NAME "linux-x86" )
-else()
- message( FATAL_ERROR "Cross-compilation on your platform is not supported by this cmake toolchain" )
-endif()
-
-# see if we have path to Android NDK
-__INIT_VARIABLE( ANDROID_NDK PATH ENV_ANDROID_NDK )
-if( NOT ANDROID_NDK )
- # see if we have path to Android standalone toolchain
- __INIT_VARIABLE( ANDROID_STANDALONE_TOOLCHAIN PATH ENV_ANDROID_STANDALONE_TOOLCHAIN OBSOLETE_ANDROID_NDK_TOOLCHAIN_ROOT OBSOLETE_ENV_ANDROID_NDK_TOOLCHAIN_ROOT )
-
- if( NOT ANDROID_STANDALONE_TOOLCHAIN )
- #try to find Android NDK in one of the the default locations
- set( __ndkSearchPaths )
- foreach( __ndkSearchPath ${ANDROID_NDK_SEARCH_PATHS} )
- foreach( suffix ${ANDROID_SUPPORTED_NDK_VERSIONS} )
- list( APPEND __ndkSearchPaths "${__ndkSearchPath}${suffix}" )
- endforeach()
- endforeach()
- __INIT_VARIABLE( ANDROID_NDK PATH VALUES ${__ndkSearchPaths} )
- unset( __ndkSearchPaths )
-
- if( ANDROID_NDK )
- message( STATUS "Using default path for Android NDK: ${ANDROID_NDK}" )
- message( STATUS " If you prefer to use a different location, please define a cmake or environment variable: ANDROID_NDK" )
- else()
- #try to find Android standalone toolchain in one of the the default locations
- __INIT_VARIABLE( ANDROID_STANDALONE_TOOLCHAIN PATH ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH )
-
- if( ANDROID_STANDALONE_TOOLCHAIN )
- message( STATUS "Using default path for standalone toolchain ${ANDROID_STANDALONE_TOOLCHAIN}" )
- message( STATUS " If you prefer to use a different location, please define the variable: ANDROID_STANDALONE_TOOLCHAIN" )
- endif( ANDROID_STANDALONE_TOOLCHAIN )
- endif( ANDROID_NDK )
- endif( NOT ANDROID_STANDALONE_TOOLCHAIN )
-endif( NOT ANDROID_NDK )
-
-# remember found paths
-if( ANDROID_NDK )
- get_filename_component( ANDROID_NDK "${ANDROID_NDK}" ABSOLUTE )
- # try to detect change
- if( CMAKE_AR )
- string( LENGTH "${ANDROID_NDK}" __length )
- string( SUBSTRING "${CMAKE_AR}" 0 ${__length} __androidNdkPreviousPath )
- if( NOT __androidNdkPreviousPath STREQUAL ANDROID_NDK )
- message( FATAL_ERROR "It is not possible to change path to the NDK on subsequent run." )
- endif()
- unset( __androidNdkPreviousPath )
- unset( __length )
- endif()
- set( ANDROID_NDK "${ANDROID_NDK}" CACHE INTERNAL "Path of the Android NDK" )
- set( BUILD_WITH_ANDROID_NDK True )
-elseif( ANDROID_STANDALONE_TOOLCHAIN )
- get_filename_component( ANDROID_STANDALONE_TOOLCHAIN "${ANDROID_STANDALONE_TOOLCHAIN}" ABSOLUTE )
- # try to detect change
- if( CMAKE_AR )
- string( LENGTH "${ANDROID_STANDALONE_TOOLCHAIN}" __length )
- string( SUBSTRING "${CMAKE_AR}" 0 ${__length} __androidStandaloneToolchainPreviousPath )
- if( NOT __androidStandaloneToolchainPreviousPath STREQUAL ANDROID_STANDALONE_TOOLCHAIN )
- message( FATAL_ERROR "It is not possible to change path to the Android standalone toolchain on subsequent run." )
- endif()
- unset( __androidStandaloneToolchainPreviousPath )
- unset( __length )
- endif()
- set( ANDROID_STANDALONE_TOOLCHAIN "${ANDROID_STANDALONE_TOOLCHAIN}" CACHE INTERNAL "Path of the Android standalone toolchain" )
- set( BUILD_WITH_STANDALONE_TOOLCHAIN True )
-else()
- list(GET ANDROID_NDK_SEARCH_PATHS 0 ANDROID_NDK_SEARCH_PATH)
- message( FATAL_ERROR "Could not find neither Android NDK nor Android standalone toolcahin.
- You should either set an environment variable:
- export ANDROID_NDK=~/my-android-ndk
- or
- export ANDROID_STANDALONE_TOOLCHAIN=~/my-android-toolchain
- or put the toolchain or NDK in the default path:
- sudo ln -s ~/my-android-ndk ${ANDROID_NDK_SEARCH_PATH}
- sudo ln -s ~/my-android-toolchain ${ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH}" )
-endif()
-
-# get all the details about standalone toolchain
-if( BUILD_WITH_STANDALONE_TOOLCHAIN )
- __DETECT_NATIVE_API_LEVEL( ANDROID_SUPPORTED_NATIVE_API_LEVELS "${ANDROID_STANDALONE_TOOLCHAIN}/sysroot/usr/include/android/api-level.h" )
- set( ANDROID_STANDALONE_TOOLCHAIN_API_LEVEL ${ANDROID_SUPPORTED_NATIVE_API_LEVELS} )
- set( __availableToolchains "standalone" )
- __DETECT_TOOLCHAIN_MACHINE_NAME( __availableToolchainMachines "${ANDROID_STANDALONE_TOOLCHAIN}" )
- if( NOT __availableToolchainMachines )
- message( FATAL_ERROR "Could not determine machine name of your toolchain. Probably your Android standalone toolchain is broken." )
- endif()
- if( __availableToolchainMachines MATCHES i686 )
- set( __availableToolchainArchs "x86" )
- elseif( __availableToolchainMachines MATCHES arm )
- set( __availableToolchainArchs "arm" )
- elseif( __availableToolchainMachines MATCHES mipsel )
- set( __availableToolchainArchs "mipsel" )
- endif()
- if( ANDROID_COMPILER_VERSION )
- # do not run gcc every time because it is relatevely expencive
- set( __availableToolchainCompilerVersions "${ANDROID_COMPILER_VERSION}" )
- else()
- execute_process( COMMAND "${ANDROID_STANDALONE_TOOLCHAIN}/bin/${__availableToolchainMachines}-gcc${TOOL_OS_SUFFIX}" --version
- OUTPUT_VARIABLE __availableToolchainCompilerVersions OUTPUT_STRIP_TRAILING_WHITESPACE )
- string( REGEX MATCH "[0-9]+.[0-9]+.[0-9]+" __availableToolchainCompilerVersions "${__availableToolchainCompilerVersions}" )
- endif()
-endif()
-
-# get all the details about NDK
-if( BUILD_WITH_ANDROID_NDK )
- file( GLOB ANDROID_SUPPORTED_NATIVE_API_LEVELS RELATIVE "${ANDROID_NDK}/platforms" "${ANDROID_NDK}/platforms/android-*" )
- string( REPLACE "android-" "" ANDROID_SUPPORTED_NATIVE_API_LEVELS "${ANDROID_SUPPORTED_NATIVE_API_LEVELS}" )
- file( GLOB __availableToolchains RELATIVE "${ANDROID_NDK}/toolchains" "${ANDROID_NDK}/toolchains/*" )
- __LIST_FILTER( __availableToolchains "^[.]" )
- set( __availableToolchainMachines "" )
- set( __availableToolchainArchs "" )
- set( __availableToolchainCompilerVersions "" )
- foreach( __toolchain ${__availableToolchains} )
- __DETECT_TOOLCHAIN_MACHINE_NAME( __machine "${ANDROID_NDK}/toolchains/${__toolchain}/prebuilt/${ANDROID_NDK_HOST_SYSTEM_NAME}" )
- if( __machine )
- string( REGEX MATCH "[0-9]+[.][0-9]+[.]*[0-9]*$" __version "${__toolchain}" )
- string( REGEX MATCH "^[^-]+" __arch "${__toolchain}" )
- list( APPEND __availableToolchainMachines "${__machine}" )
- list( APPEND __availableToolchainArchs "${__arch}" )
- list( APPEND __availableToolchainCompilerVersions "${__version}" )
- else()
- list( REMOVE_ITEM __availableToolchains "${__toolchain}" )
- endif()
- endforeach()
- if( NOT __availableToolchains )
- message( FATAL_ERROR "Could not any working toolchain in the NDK. Probably your Android NDK is broken." )
- endif()
-endif()
-
-# build list of available ABIs
-if( NOT ANDROID_SUPPORTED_ABIS )
- set( ANDROID_SUPPORTED_ABIS "" )
- set( __uniqToolchainArchNames ${__availableToolchainArchs} )
- list( REMOVE_DUPLICATES __uniqToolchainArchNames )
- list( SORT __uniqToolchainArchNames )
- foreach( __arch ${__uniqToolchainArchNames} )
- list( APPEND ANDROID_SUPPORTED_ABIS ${ANDROID_SUPPORTED_ABIS_${__arch}} )
- endforeach()
- unset( __uniqToolchainArchNames )
- if( NOT ANDROID_SUPPORTED_ABIS )
- message( FATAL_ERROR "No one of known Android ABIs is supported by this cmake toolchain." )
- endif()
-endif()
-
-# choose target ABI
-__INIT_VARIABLE( ANDROID_ABI OBSOLETE_ARM_TARGET OBSOLETE_ARM_TARGETS VALUES ${ANDROID_SUPPORTED_ABIS} )
-# verify that target ABI is supported
-list( FIND ANDROID_SUPPORTED_ABIS "${ANDROID_ABI}" __androidAbiIdx )
-if( __androidAbiIdx EQUAL -1 )
- string( REPLACE ";" "\", \"", PRINTABLE_ANDROID_SUPPORTED_ABIS "${ANDROID_SUPPORTED_ABIS}" )
- message( FATAL_ERROR "Specified ANDROID_ABI = \"${ANDROID_ABI}\" is not supported by this cmake toolchain or your NDK/toolchain.
- Supported values are: \"${PRINTABLE_ANDROID_SUPPORTED_ABIS}\"
- " )
-endif()
-unset( __androidAbiIdx )
-
-# remember target ABI
-set( ANDROID_ABI "${ANDROID_ABI}" CACHE STRING "The target ABI for Android. If arm, then armeabi-v7a is recommended for hardware floating point." FORCE )
-
-# set target ABI options
-if( ANDROID_ABI STREQUAL "x86" )
- set( X86 true )
- set( ANDROID_NDK_ABI_NAME "x86" )
- set( ANDROID_ARCH_NAME "x86" )
- set( ANDROID_ARCH_FULLNAME "x86" )
- set( CMAKE_SYSTEM_PROCESSOR "i686" )
-elseif( ANDROID_ABI STREQUAL "mips" )
- set( MIPS true )
- set( ANDROID_NDK_ABI_NAME "mips" )
- set( ANDROID_ARCH_NAME "mips" )
- set( ANDROID_ARCH_FULLNAME "mipsel" )
- set( CMAKE_SYSTEM_PROCESSOR "mips" )
-elseif( ANDROID_ABI STREQUAL "armeabi" )
- set( ARMEABI true )
- set( ANDROID_NDK_ABI_NAME "armeabi" )
- set( ANDROID_ARCH_NAME "arm" )
- set( ANDROID_ARCH_FULLNAME "arm" )
- set( CMAKE_SYSTEM_PROCESSOR "armv5te" )
-elseif( ANDROID_ABI STREQUAL "armeabi-v6 with VFP" )
- set( ARMEABI_V6 true )
- set( ANDROID_NDK_ABI_NAME "armeabi" )
- set( ANDROID_ARCH_NAME "arm" )
- set( ANDROID_ARCH_FULLNAME "arm" )
- set( CMAKE_SYSTEM_PROCESSOR "armv6" )
- # need always fallback to older platform
- set( ARMEABI true )
-elseif( ANDROID_ABI STREQUAL "armeabi-v7a")
- set( ARMEABI_V7A true )
- set( ANDROID_NDK_ABI_NAME "armeabi-v7a" )
- set( ANDROID_ARCH_NAME "arm" )
- set( ANDROID_ARCH_FULLNAME "arm" )
- set( CMAKE_SYSTEM_PROCESSOR "armv7-a" )
-elseif( ANDROID_ABI STREQUAL "armeabi-v7a with VFPV3" )
- set( ARMEABI_V7A true )
- set( ANDROID_NDK_ABI_NAME "armeabi-v7a" )
- set( ANDROID_ARCH_NAME "arm" )
- set( ANDROID_ARCH_FULLNAME "arm" )
- set( CMAKE_SYSTEM_PROCESSOR "armv7-a" )
- set( VFPV3 true )
-elseif( ANDROID_ABI STREQUAL "armeabi-v7a with NEON" )
- set( ARMEABI_V7A true )
- set( ANDROID_NDK_ABI_NAME "armeabi-v7a" )
- set( ANDROID_ARCH_NAME "arm" )
- set( ANDROID_ARCH_FULLNAME "arm" )
- set( CMAKE_SYSTEM_PROCESSOR "armv7-a" )
- set( VFPV3 true )
- set( NEON true )
-else()
- message( SEND_ERROR "Unknown ANDROID_ABI=\"${ANDROID_ABI}\" is specified." )
-endif()
-
-if( CMAKE_BINARY_DIR AND EXISTS "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeSystem.cmake" )
- # really dirty hack
- # it is not possible to change CMAKE_SYSTEM_PROCESSOR after the first run...
- file( APPEND "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeSystem.cmake" "SET(CMAKE_SYSTEM_PROCESSOR \"${CMAKE_SYSTEM_PROCESSOR}\")\n" )
-endif()
-
-set( ANDROID_SUPPORTED_ABIS ${ANDROID_SUPPORTED_ABIS_${ANDROID_ARCH_FULLNAME}} CACHE INTERNAL "ANDROID_ABI can be changed only to one of these ABIs. Changing to any other ABI requires to reset cmake cache." )
-if( CMAKE_VERSION VERSION_GREATER "2.8" )
- list( SORT ANDROID_SUPPORTED_ABIS_${ANDROID_ARCH_FULLNAME} )
- set_property( CACHE ANDROID_ABI PROPERTY STRINGS ${ANDROID_SUPPORTED_ABIS_${ANDROID_ARCH_FULLNAME}} )
-endif()
-
-if( ANDROID_ARCH_NAME STREQUAL "arm" AND NOT ARMEABI_V6 )
- __INIT_VARIABLE( ANDROID_FORCE_ARM_BUILD OBSOLETE_FORCE_ARM VALUES OFF )
- set( ANDROID_FORCE_ARM_BUILD ${ANDROID_FORCE_ARM_BUILD} CACHE BOOL "Use 32-bit ARM instructions instead of Thumb-1" FORCE )
- mark_as_advanced( ANDROID_FORCE_ARM_BUILD )
-else()
- unset( ANDROID_FORCE_ARM_BUILD CACHE )
-endif()
-
-# choose toolchain
-if( ANDROID_TOOLCHAIN_NAME )
- list( FIND __availableToolchains "${ANDROID_TOOLCHAIN_NAME}" __toolchainIdx )
- if( __toolchainIdx EQUAL -1 )
- message( FATAL_ERROR "Previously selected toolchain \"${ANDROID_TOOLCHAIN_NAME}\" is missing. You need to remove CMakeCache.txt and rerun cmake manually to change the toolchain" )
- endif()
- list( GET __availableToolchainArchs ${__toolchainIdx} __toolchainArch )
- if( NOT __toolchainArch STREQUAL ANDROID_ARCH_FULLNAME )
- message( SEND_ERROR "Previously selected toolchain \"${ANDROID_TOOLCHAIN_NAME}\" is not able to compile binaries for the \"${ANDROID_ARCH_NAME}\" platform." )
- endif()
-else()
- set( __toolchainIdx -1 )
- set( __applicableToolchains "" )
- set( __toolchainMaxVersion "0.0.0" )
- list( LENGTH __availableToolchains __availableToolchainsCount )
- math( EXPR __availableToolchainsCount "${__availableToolchainsCount}-1" )
- foreach( __idx RANGE ${__availableToolchainsCount} )
- list( GET __availableToolchainArchs ${__idx} __toolchainArch )
- if( __toolchainArch STREQUAL ANDROID_ARCH_FULLNAME )
- list( GET __availableToolchainCompilerVersions ${__idx} __toolchainVersion )
- if( __toolchainVersion VERSION_GREATER __toolchainMaxVersion )
- set( __toolchainMaxVersion "${__toolchainVersion}" )
- set( __toolchainIdx ${__idx} )
- endif()
- endif()
- endforeach()
- unset( __availableToolchainsCount )
- unset( __toolchainMaxVersion )
- unset( __toolchainVersion )
-endif()
-unset( __toolchainArch )
-if( __toolchainIdx EQUAL -1 )
- message( FATAL_ERROR "No one of available compiler toolchains is able to compile for ${ANDROID_ARCH_NAME} platform." )
-endif()
-list( GET __availableToolchains ${__toolchainIdx} ANDROID_TOOLCHAIN_NAME )
-list( GET __availableToolchainMachines ${__toolchainIdx} ANDROID_TOOLCHAIN_MACHINE_NAME )
-list( GET __availableToolchainCompilerVersions ${__toolchainIdx} ANDROID_COMPILER_VERSION )
-set( ANDROID_TOOLCHAIN_NAME "${ANDROID_TOOLCHAIN_NAME}" CACHE INTERNAL "Name of toolchain used" )
-set( ANDROID_COMPILER_VERSION "${ANDROID_COMPILER_VERSION}" CACHE INTERNAL "compiler version from selected toolchain" )
-unset( __toolchainIdx )
-unset( __availableToolchains )
-unset( __availableToolchainMachines )
-unset( __availableToolchainArchs )
-unset( __availableToolchainCompilerVersions )
-
-# choose native API level
-__INIT_VARIABLE( ANDROID_NATIVE_API_LEVEL ENV_ANDROID_NATIVE_API_LEVEL ANDROID_API_LEVEL ENV_ANDROID_API_LEVEL ANDROID_STANDALONE_TOOLCHAIN_API_LEVEL ANDROID_DEFAULT_NDK_API_LEVEL_${ANDROID_ARCH_NAME} ANDROID_DEFAULT_NDK_API_LEVEL )
-string( REGEX MATCH "[0-9]+" ANDROID_NATIVE_API_LEVEL "${ANDROID_NATIVE_API_LEVEL}" )
-# validate
-list( FIND ANDROID_SUPPORTED_NATIVE_API_LEVELS "${ANDROID_NATIVE_API_LEVEL}" __levelIdx )
-if( __levelIdx EQUAL -1 )
- message( SEND_ERROR "Specified Android native API level (${ANDROID_NATIVE_API_LEVEL}) is not supported by your NDK/toolchain." )
-endif()
-unset( __levelIdx )
-if( BUILD_WITH_ANDROID_NDK )
- __DETECT_NATIVE_API_LEVEL( __realApiLevel "${ANDROID_NDK}/platforms/android-${ANDROID_NATIVE_API_LEVEL}/arch-${ANDROID_ARCH_NAME}/usr/include/android/api-level.h" )
- if( NOT __realApiLevel EQUAL ANDROID_NATIVE_API_LEVEL )
- message( SEND_ERROR "Specified Android API level (${ANDROID_NATIVE_API_LEVEL}) does not match to the level found (${__realApiLevel}). Probably your copy of NDK is broken." )
- endif()
- unset( __realApiLevel )
-endif()
-set( ANDROID_NATIVE_API_LEVEL "${ANDROID_NATIVE_API_LEVEL}" CACHE STRING "Android API level for native code" FORCE )
-if( CMAKE_VERSION VERSION_GREATER "2.8" )
- list( SORT ANDROID_SUPPORTED_NATIVE_API_LEVELS )
- set_property( CACHE ANDROID_NATIVE_API_LEVEL PROPERTY STRINGS ${ANDROID_SUPPORTED_NATIVE_API_LEVELS} )
-endif()
-
-# setup paths
-if( BUILD_WITH_STANDALONE_TOOLCHAIN )
- set( ANDROID_TOOLCHAIN_ROOT "${ANDROID_STANDALONE_TOOLCHAIN}" )
- set( ANDROID_SYSROOT "${ANDROID_STANDALONE_TOOLCHAIN}/sysroot" )
- set( __stlLibPath "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib" )
-endif()
-if( BUILD_WITH_ANDROID_NDK )
- set( ANDROID_TOOLCHAIN_ROOT "${ANDROID_NDK}/toolchains/${ANDROID_TOOLCHAIN_NAME}/prebuilt/${ANDROID_NDK_HOST_SYSTEM_NAME}" )
- set( ANDROID_SYSROOT "${ANDROID_NDK}/platforms/android-${ANDROID_NATIVE_API_LEVEL}/arch-${ANDROID_ARCH_NAME}" )
- if( ANDROID_USE_STLPORT )
- set( __stlIncludePath "${ANDROID_NDK}/sources/cxx-stl/stlport/stlport" )
- set( __stlLibPath "${ANDROID_NDK}/sources/cxx-stl/stlport/libs/${ANDROID_NDK_ABI_NAME}" )
- else()
- if( EXISTS "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/${ANDROID_COMPILER_VERSION}" )
- set( __stlIncludePath "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/${ANDROID_COMPILER_VERSION}/include" )
- set( __stlLibPath "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/${ANDROID_COMPILER_VERSION}/libs/${ANDROID_NDK_ABI_NAME}" )
- else()
- set( __stlIncludePath "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/include" )
- set( __stlLibPath "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/libs/${ANDROID_NDK_ABI_NAME}" )
- endif()
- endif()
-endif()
-
-# specify the cross compiler
-set( CMAKE_C_COMPILER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc${TOOL_OS_SUFFIX}" CACHE PATH "gcc" )
-set( CMAKE_CXX_COMPILER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-g++${TOOL_OS_SUFFIX}" CACHE PATH "g++" )
-set( CMAKE_ASM_COMPILER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc${TOOL_OS_SUFFIX}" CACHE PATH "Assembler" )
-if( CMAKE_VERSION VERSION_LESS 2.8.5 )
- set( CMAKE_ASM_COMPILER_ARG1 "-c" )
-endif()
-# there may be a way to make cmake deduce these TODO deduce the rest of the tools
-set( CMAKE_STRIP "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-strip${TOOL_OS_SUFFIX}" CACHE PATH "strip" )
-set( CMAKE_AR "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-ar${TOOL_OS_SUFFIX}" CACHE PATH "archive" )
-set( CMAKE_LINKER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-ld${TOOL_OS_SUFFIX}" CACHE PATH "linker" )
-set( CMAKE_NM "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-nm${TOOL_OS_SUFFIX}" CACHE PATH "nm" )
-set( CMAKE_OBJCOPY "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-objcopy${TOOL_OS_SUFFIX}" CACHE PATH "objcopy" )
-set( CMAKE_OBJDUMP "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-objdump${TOOL_OS_SUFFIX}" CACHE PATH "objdump" )
-set( CMAKE_RANLIB "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-ranlib${TOOL_OS_SUFFIX}" CACHE PATH "ranlib" )
-set( _CMAKE_TOOLCHAIN_PREFIX "${ANDROID_TOOLCHAIN_MACHINE_NAME}-" )
-if( APPLE )
- find_program( CMAKE_INSTALL_NAME_TOOL NAMES install_name_tool )
- if( NOT CMAKE_INSTALL_NAME_TOOL )
- message( FATAL_ERROR "Could not find install_name_tool, please check your installation." )
- endif()
- mark_as_advanced( CMAKE_INSTALL_NAME_TOOL )
-endif()
-
-# export directories
-set( ANDROID_SYSTEM_INCLUDE_DIRS "" )
-set( ANDROID_SYSTEM_LIB_DIRS "" )
-
-# setup output directories
-set( LIBRARY_OUTPUT_PATH_ROOT ${CMAKE_SOURCE_DIR} CACHE PATH "root for library output, set this to change where android libs are installed to" )
-set( CMAKE_INSTALL_PREFIX "${ANDROID_TOOLCHAIN_ROOT}/user" CACHE STRING "path for installing" )
-
-if(NOT _CMAKE_IN_TRY_COMPILE)
- if( EXISTS "${CMAKE_SOURCE_DIR}/jni/CMakeLists.txt" )
- set( EXECUTABLE_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/bin/${ANDROID_NDK_ABI_NAME}" CACHE PATH "Output directory for applications" )
- else()
- set( EXECUTABLE_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/bin" CACHE PATH "Output directory for applications" )
- endif()
- set( LIBRARY_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/libs/${ANDROID_NDK_ABI_NAME}" CACHE PATH "path for android libs" )
-endif()
-
-# includes
-list( APPEND ANDROID_SYSTEM_INCLUDE_DIRS "${ANDROID_SYSROOT}/usr/include" )
-if( __stlIncludePath AND EXISTS "${__stlIncludePath}" )
- list( APPEND ANDROID_SYSTEM_INCLUDE_DIRS "${__stlIncludePath}" )
-endif()
-
-# c++ bits includes
-if( __stlLibPath AND EXISTS "${__stlLibPath}/include" )
- list( APPEND ANDROID_SYSTEM_INCLUDE_DIRS "${__stlLibPath}/include" )
-endif()
-if( ANDROID_ARCH_NAME STREQUAL "arm" AND EXISTS "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/include/c++/${ANDROID_COMPILER_VERSION}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/${CMAKE_SYSTEM_PROCESSOR}/thumb/bits" )
- list( APPEND ANDROID_SYSTEM_INCLUDE_DIRS "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/include/c++/${ANDROID_COMPILER_VERSION}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/${CMAKE_SYSTEM_PROCESSOR}/thumb" )
-elseif( EXISTS "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/include/c++/${ANDROID_COMPILER_VERSION}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/${CMAKE_SYSTEM_PROCESSOR}/bits" )
- list( APPEND ANDROID_SYSTEM_INCLUDE_DIRS "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/include/c++/${ANDROID_COMPILER_VERSION}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/${CMAKE_SYSTEM_PROCESSOR}" )
-elseif( ANDROID_ARCH_NAME STREQUAL "arm" AND EXISTS "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/include/c++/${ANDROID_COMPILER_VERSION}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/thumb/bits" )
- list( APPEND ANDROID_SYSTEM_INCLUDE_DIRS "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/include/c++/${ANDROID_COMPILER_VERSION}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/thumb" )
-elseif( EXISTS "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/include/c++/${ANDROID_COMPILER_VERSION}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/bits" )
- list( APPEND ANDROID_SYSTEM_INCLUDE_DIRS "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/include/c++/${ANDROID_COMPILER_VERSION}/${ANDROID_TOOLCHAIN_MACHINE_NAME}" )
-endif()
-
-# flags and definitions
-if(ANDROID_SYSROOT MATCHES "[ ;\"]")
- set( ANDROID_CXX_FLAGS "--sysroot=\"${ANDROID_SYSROOT}\"" )
- # quotes will break try_compile and compiler identification
- message(WARNING "Your Android system root has non-alphanumeric symbols. It can break compiler features detection and the whole build.")
-else()
- set( ANDROID_CXX_FLAGS "--sysroot=${ANDROID_SYSROOT}" )
-endif()
-
-remove_definitions( -DANDROID )
-add_definitions( -DANDROID )
-
-# Force set compilers because standard identification works badly for us
-include( CMakeForceCompiler )
-CMAKE_FORCE_C_COMPILER( "${CMAKE_C_COMPILER}" GNU )
-set( CMAKE_C_PLATFORM_ID Linux )
-set( CMAKE_C_SIZEOF_DATA_PTR 4 )
-set( CMAKE_C_HAS_ISYSROOT 1 )
-set( CMAKE_C_COMPILER_ABI ELF )
-CMAKE_FORCE_CXX_COMPILER( "${CMAKE_CXX_COMPILER}" GNU )
-set( CMAKE_CXX_PLATFORM_ID Linux )
-set( CMAKE_CXX_SIZEOF_DATA_PTR 4 )
-set( CMAKE_CXX_HAS_ISYSROOT 1 )
-set( CMAKE_CXX_COMPILER_ABI ELF )
-# force ASM compiler (required for CMake < 2.8.5)
-set( CMAKE_ASM_COMPILER_ID_RUN TRUE )
-set( CMAKE_ASM_COMPILER_ID GNU )
-set( CMAKE_ASM_COMPILER_WORKS TRUE )
-set( CMAKE_ASM_COMPILER_FORCED TRUE )
-set( CMAKE_COMPILER_IS_GNUASM 1)
-
-# NDK flags
-if( ARMEABI OR ARMEABI_V7A )
- # NDK also defines -ffunction-sections -funwind-tables but they result in worse OpenCV performance
- set( _CMAKE_CXX_FLAGS "-fPIC -Wno-psabi" )
- set( _CMAKE_C_FLAGS "-fPIC -Wno-psabi" )
- remove_definitions( -D__ARM_ARCH_5__ -D__ARM_ARCH_5T__ -D__ARM_ARCH_5E__ -D__ARM_ARCH_5TE__ )
- add_definitions( -D__ARM_ARCH_5__ -D__ARM_ARCH_5T__ -D__ARM_ARCH_5E__ -D__ARM_ARCH_5TE__ )
- # extra arm-specific flags
- set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fsigned-char" )
-elseif( X86 )
- set( _CMAKE_CXX_FLAGS "-funwind-tables" )
- set( _CMAKE_C_FLAGS "-funwind-tables" )
-elseif( MIPS )
- set( _CMAKE_CXX_FLAGS "-fpic -Wno-psabi -fno-strict-aliasing -finline-functions -ffunction-sections -funwind-tables -fmessage-length=0 -fno-inline-functions-called-once -fgcse-after-reload -frerun-cse-after-loop -frename-registers" )
- set( _CMAKE_CXX_FLAGS "-fpic -Wno-psabi -fno-strict-aliasing -finline-functions -ffunction-sections -funwind-tables -fmessage-length=0 -fno-inline-functions-called-once -fgcse-after-reload -frerun-cse-after-loop -frename-registers" )
- set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fsigned-char" )
-else()
- set( _CMAKE_CXX_FLAGS "" )
- set( _CMAKE_C_FLAGS "" )
-endif()
-
-if( ANDROID_USE_STLPORT )
- set( _CMAKE_CXX_FLAGS "${_CMAKE_CXX_FLAGS} -fno-rtti -fno-exceptions" )
- set( _CMAKE_C_FLAGS "${_CMAKE_C_FLAGS} -fno-exceptions" )
-else()
- set( _CMAKE_CXX_FLAGS "${_CMAKE_CXX_FLAGS} -frtti -fexceptions" )
- set( _CMAKE_C_FLAGS "${_CMAKE_C_FLAGS} -fexceptions" )
-endif()
-
-# release and debug flags
-if( ARMEABI OR ARMEABI_V7A )
- if( NOT ANDROID_FORCE_ARM_BUILD AND NOT ARMEABI_V6 )
- # It is recommended to use the -mthumb compiler flag to force the generation
- # of 16-bit Thumb-1 instructions (the default being 32-bit ARM ones).
- # O3 instead of O2/Os in release mode - like cmake sets for desktop gcc
- set( _CMAKE_CXX_FLAGS_RELEASE "-mthumb -O3" )
- set( _CMAKE_C_FLAGS_RELEASE "-mthumb -O3" )
- set( _CMAKE_CXX_FLAGS_DEBUG "-marm -Os -finline-limit=64" )
- set( _CMAKE_C_FLAGS_DEBUG "-marm -Os -finline-limit=64" )
- else()
- # always compile ARMEABI_V6 in arm mode; otherwise there is no difference from ARMEABI
- # O3 instead of O2/Os in release mode - like cmake sets for desktop gcc
- set( _CMAKE_CXX_FLAGS_RELEASE "-marm -O3 -fstrict-aliasing" )
- set( _CMAKE_C_FLAGS_RELEASE "-marm -O3 -fstrict-aliasing" )
- set( _CMAKE_CXX_FLAGS_DEBUG "-marm -O0 -finline-limit=300" )
- set( _CMAKE_C_FLAGS_DEBUG "-marm -O0 -finline-limit=300" )
- endif()
-elseif( X86 )
- set( _CMAKE_CXX_FLAGS_RELEASE "-O3 -fstrict-aliasing" )
- set( _CMAKE_C_FLAGS_RELEASE "-O3 -fstrict-aliasing" )
- set( _CMAKE_CXX_FLAGS_DEBUG "-O0 -finline-limit=300" )
- set( _CMAKE_C_FLAGS_DEBUG "-O0 -finline-limit=300" )
-elseif( MIPS )
- set( _CMAKE_CXX_FLAGS_RELEASE "-O3 -funswitch-loops -finline-limit=300" )
- set( _CMAKE_C_FLAGS_RELEASE "-O3 -funswitch-loops -finline-limit=300" )
- set( _CMAKE_CXX_FLAGS_DEBUG "-O0 -g" )
- set( _CMAKE_C_FLAGS_DEBUG "-O0 -g" )
-endif()
-set( _CMAKE_CXX_FLAGS_RELEASE "${_CMAKE_CXX_FLAGS_RELEASE} -fomit-frame-pointer -DNDEBUG" )
-set( _CMAKE_C_FLAGS_RELEASE "${_CMAKE_C_FLAGS_RELEASE} -fomit-frame-pointer -DNDEBUG" )
-set( _CMAKE_CXX_FLAGS_DEBUG "${_CMAKE_CXX_FLAGS_DEBUG} -fno-strict-aliasing -fno-omit-frame-pointer -DDEBUG -D_DEBUG" )
-set( _CMAKE_C_FLAGS_DEBUG "${_CMAKE_C_FLAGS_DEBUG} -fno-strict-aliasing -fno-omit-frame-pointer -DDEBUG -D_DEBUG" )
-
-# ABI-specific flags
-if( ARMEABI_V7A )
- set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -march=armv7-a -mfloat-abi=softfp" )
- if( NEON )
- set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -mfpu=neon" )
- elseif( VFPV3 )
- set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -mfpu=vfpv3" )
- else()
- set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -mfpu=vfp" )
- endif()
-elseif( ARMEABI_V6 )
- set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -march=armv6 -mfloat-abi=softfp -mfpu=vfp" )
-elseif( ARMEABI )
- set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -march=armv5te -mtune=xscale -msoft-float" )
-elseif( X86 )
- set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS}" )#sse?
-endif()
-
-# linker flags
-if( NOT DEFINED __ndklibspath )
- set( __ndklibspath "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/ndklibs/${ANDROID_NDK_ABI_NAME}" )
-endif()
-list( APPEND ANDROID_SYSTEM_LIB_DIRS "${CMAKE_INSTALL_PREFIX}/libs/${ANDROID_NDK_ABI_NAME}" )
-set( ANDROID_LINKER_FLAGS "" )
-
-# STL
-if( ANDROID_USE_STLPORT )
- if( EXISTS "${__stlLibPath}/libstlport_static.a" )
- set( CMAKE_CXX_CREATE_SHARED_LIBRARY " -o \"${__stlLibPath}/libstlport_static.a\"")
- set( CMAKE_CXX_CREATE_SHARED_MODULE " -o \"${__stlLibPath}/libstlport_static.a\"")
- endif()
-else( ANDROID_USE_STLPORT )
- if( EXISTS "${__stlLibPath}/libgnustl_static.a" )
- __COPY_IF_DIFFERENT( "${__stlLibPath}/libgnustl_static.a" "${__ndklibspath}/libstdc++.a" )
- elseif( ANDROID_ARCH_NAME STREQUAL "arm" AND EXISTS "${__stlLibPath}/${CMAKE_SYSTEM_PROCESSOR}/thumb/libstdc++.a" )
- __COPY_IF_DIFFERENT( "${__stlLibPath}/${CMAKE_SYSTEM_PROCESSOR}/thumb/libstdc++.a" "${__ndklibspath}/libstdc++.a" )
- elseif( ANDROID_ARCH_NAME STREQUAL "arm" AND EXISTS "${__stlLibPath}/${CMAKE_SYSTEM_PROCESSOR}/libstdc++.a" )
- __COPY_IF_DIFFERENT( "${__stlLibPath}/${CMAKE_SYSTEM_PROCESSOR}/libstdc++.a" "${__ndklibspath}/libstdc++.a" )
- elseif( ANDROID_ARCH_NAME STREQUAL "arm" AND EXISTS "${__stlLibPath}/thumb/libstdc++.a" )
- __COPY_IF_DIFFERENT( "${__stlLibPath}/thumb/libstdc++.a" "${__ndklibspath}/libstdc++.a" )
- elseif( EXISTS "${__stlLibPath}/libstdc++.a" )
- __COPY_IF_DIFFERENT( "${__stlLibPath}/libstdc++.a" "${__ndklibspath}/libstdc++.a" )
- endif()
- if( EXISTS "${__stlLibPath}/libsupc++.a" )
- __COPY_IF_DIFFERENT( "${__stlLibPath}/libsupc++.a" "${__ndklibspath}/libsupc++.a" )
- elseif( ANDROID_ARCH_NAME STREQUAL "arm" AND EXISTS "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/thumb/libsupc++.a" )
- __COPY_IF_DIFFERENT( "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/thumb/libsupc++.a" "${__ndklibspath}/libsupc++.a" )
- elseif( ANDROID_ARCH_NAME STREQUAL "arm" AND EXISTS "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/libsupc++.a" )
- __COPY_IF_DIFFERENT( "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/libsupc++.a" "${__ndklibspath}/libsupc++.a" )
- elseif( ANDROID_ARCH_NAME STREQUAL "arm" AND EXISTS "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb/libsupc++.a" )
- __COPY_IF_DIFFERENT( "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb/libsupc++.a" "${__ndklibspath}/libsupc++.a" )
- elseif( EXISTS "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/libsupc++.a" )
- __COPY_IF_DIFFERENT( "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/libsupc++.a" "${__ndklibspath}/libsupc++.a" )
- endif()
- list( APPEND ANDROID_SYSTEM_LIB_DIRS "${__ndklibspath}" )
-endif( ANDROID_USE_STLPORT )
-
-# cleanup for STL search
-unset( __stlIncludePath )
-unset( __stlLibPath )
-
-# other linker flags
-__INIT_VARIABLE( ANDROID_NO_UNDEFINED OBSOLETE_NO_UNDEFINED VALUES ON )
-set( ANDROID_NO_UNDEFINED ${ANDROID_NO_UNDEFINED} CACHE BOOL "Show all undefined symbols as linker errors" FORCE )
-mark_as_advanced( ANDROID_NO_UNDEFINED )
-if( ANDROID_NO_UNDEFINED )
- set( ANDROID_LINKER_FLAGS "-Wl,--no-undefined ${ANDROID_LINKER_FLAGS}" )
-endif()
-
-if (ANDROID_NDK MATCHES "-r[56].?$")
- # libGLESv2.so in NDK's prior to r7 refers to exteranal symbols. So this flag option is required for all projects using OpenGL from native.
- __INIT_VARIABLE( ANDROID_SO_UNDEFINED VALUES ON )
-else()
- __INIT_VARIABLE( ANDROID_SO_UNDEFINED VALUES OFF )
-endif()
-
-set( ANDROID_SO_UNDEFINED ${ANDROID_SO_UNDEFINED} CACHE BOOL "Allows or disallows undefined symbols in shared libraries" FORCE )
-mark_as_advanced( ANDROID_SO_UNDEFINED )
-if( ANDROID_SO_UNDEFINED )
- set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,-allow-shlib-undefined" )
-endif()
-
-__INIT_VARIABLE( ANDROID_FUNCTION_LEVEL_LINKING VALUES ON )
-set( ANDROID_FUNCTION_LEVEL_LINKING ON CACHE BOOL "Allows or disallows undefined symbols in shared libraries" FORCE )
-mark_as_advanced( ANDROID_FUNCTION_LEVEL_LINKING )
-if( ANDROID_FUNCTION_LEVEL_LINKING )
- set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fdata-sections -ffunction-sections" )
- set( ANDROID_LINKER_FLAGS "-Wl,--gc-sections ${ANDROID_LINKER_FLAGS}" )
-endif()
-
-if( ARMEABI_V7A )
- # this is *required* to use the following linker flags that routes around
- # a CPU bug in some Cortex-A8 implementations:
- set( ANDROID_LINKER_FLAGS "-Wl,--fix-cortex-a8 ${ANDROID_LINKER_FLAGS}" )
-endif()
-
-# cache flags
-set( CMAKE_CXX_FLAGS "${_CMAKE_CXX_FLAGS}" CACHE STRING "c++ flags" )
-set( CMAKE_C_FLAGS "${_CMAKE_C_FLAGS}" CACHE STRING "c flags" )
-set( CMAKE_CXX_FLAGS_RELEASE "${_CMAKE_CXX_FLAGS_RELEASE}" CACHE STRING "c++ Release flags" )
-set( CMAKE_C_FLAGS_RELEASE "${_CMAKE_C_FLAGS_RELEASE}" CACHE STRING "c Release flags" )
-set( CMAKE_CXX_FLAGS_DEBUG "${_CMAKE_CXX_FLAGS_DEBUG}" CACHE STRING "c++ Debug flags" )
-set( CMAKE_C_FLAGS_DEBUG "${_CMAKE_C_FLAGS_DEBUG}" CACHE STRING "c Debug flags" )
-set( CMAKE_SHARED_LINKER_FLAGS "" CACHE STRING "linker flags" )
-set( CMAKE_MODULE_LINKER_FLAGS "" CACHE STRING "linker flags" )
-set( CMAKE_EXE_LINKER_FLAGS "-Wl,-z,nocopyreloc" CACHE STRING "linker flags" )
-
-include_directories( SYSTEM ${ANDROID_SYSTEM_INCLUDE_DIRS} )
-link_directories( ${ANDROID_SYSTEM_LIB_DIRS} )
-
-# finish flags
-set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS}" CACHE INTERNAL "Extra Android compiler flags")
-set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS}" CACHE INTERNAL "Extra Android linker flags")
-set( CMAKE_CXX_FLAGS "${ANDROID_CXX_FLAGS} ${CMAKE_CXX_FLAGS}" )
-set( CMAKE_C_FLAGS "${ANDROID_CXX_FLAGS} ${CMAKE_C_FLAGS}" )
-if( MIPS AND BUILD_WITH_ANDROID_NDK )
- set( CMAKE_SHARED_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK}/toolchains/${ANDROID_TOOLCHAIN_NAME}/mipself.xsc ${ANDROID_LINKER_FLAGS} ${CMAKE_SHARED_LINKER_FLAGS}" )
- set( CMAKE_MODULE_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK}/toolchains/${ANDROID_TOOLCHAIN_NAME}/mipself.xsc ${ANDROID_LINKER_FLAGS} ${CMAKE_MODULE_LINKER_FLAGS}" )
- set( CMAKE_EXE_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK}/toolchains/${ANDROID_TOOLCHAIN_NAME}/mipself.x ${ANDROID_LINKER_FLAGS} ${CMAKE_EXE_LINKER_FLAGS}" )
-else()
- set( CMAKE_SHARED_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} ${CMAKE_SHARED_LINKER_FLAGS}" )
- set( CMAKE_MODULE_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} ${CMAKE_MODULE_LINKER_FLAGS}" )
- set( CMAKE_EXE_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} ${CMAKE_EXE_LINKER_FLAGS}" )
-endif()
-
-# set these global flags for cmake client scripts to change behavior
-set( ANDROID True )
-set( BUILD_ANDROID True )
-
-# where is the target environment
-set( CMAKE_FIND_ROOT_PATH "${ANDROID_TOOLCHAIN_ROOT}/bin" "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}" "${ANDROID_SYSROOT}" "${CMAKE_INSTALL_PREFIX}" "${CMAKE_INSTALL_PREFIX}/share" )
-
-# only search for libraries and includes in the ndk toolchain
-set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY )
-set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY )
-set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY )
-
-
-# macro to find packages on the host OS
-macro( find_host_package )
- set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER )
- set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER )
- set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER )
- if( CMAKE_HOST_WIN32 )
- SET( WIN32 1 )
- SET( UNIX )
- elseif( CMAKE_HOST_APPLE )
- SET( APPLE 1 )
- SET( UNIX )
- endif()
- find_package( ${ARGN} )
- SET( WIN32 )
- SET( APPLE )
- SET( UNIX 1 )
- set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY )
- set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY )
- set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY )
-endmacro()
-
-
-# macro to find programs on the host OS
-macro( find_host_program )
- set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER )
- set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER )
- set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER )
- if( CMAKE_HOST_WIN32 )
- SET( WIN32 1 )
- SET( UNIX )
- elseif( CMAKE_HOST_APPLE )
- SET( APPLE 1 )
- SET( UNIX )
- endif()
- find_program( ${ARGN} )
- SET( WIN32 )
- SET( APPLE )
- SET( UNIX 1 )
- set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY )
- set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY )
- set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY )
-endmacro()
-
-
-macro( ANDROID_GET_ABI_RAWNAME TOOLCHAIN_FLAG VAR )
- if( "${TOOLCHAIN_FLAG}" STREQUAL "ARMEABI" )
- set( ${VAR} "armeabi" )
- elseif( "${TOOLCHAIN_FLAG}" STREQUAL "ARMEABI_V7A" )
- set( ${VAR} "armeabi-v7a" )
- elseif( "${TOOLCHAIN_FLAG}" STREQUAL "X86" )
- set( ${VAR} "x86" )
- else()
- set( ${VAR} "unknown" )
- endif()
-endmacro()
-
-
-# export toolchain settings for the try_compile() command
-if( NOT PROJECT_NAME STREQUAL "CMAKE_TRY_COMPILE" )
- set( __toolchain_config "")
- foreach( __var ANDROID_ABI ANDROID_FORCE_ARM_BUILD ANDROID_NATIVE_API_LEVEL ANDROID_NO_UNDEFINED ANDROID_SO_UNDEFINED ANDROID_SET_OBSOLETE_VARIABLES LIBRARY_OUTPUT_PATH_ROOT ANDROID_USE_STLPORT ANDROID_FORBID_SYGWIN ANDROID_NDK ANDROID_STANDALONE_TOOLCHAIN ANDROID_FUNCTION_LEVEL_LINKING __ndklibspath )
- if( DEFINED ${__var} )
- if( "${__var}" MATCHES " ")
- set( __toolchain_config "${__toolchain_config}set( ${__var} \"${${__var}}\" CACHE INTERNAL \"\" )\n" )
- else()
- set( __toolchain_config "${__toolchain_config}set( ${__var} ${${__var}} CACHE INTERNAL \"\" )\n" )
- endif()
- endif()
- endforeach()
- file( WRITE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/android.toolchain.config.cmake" "${__toolchain_config}" )
- unset( __toolchain_config )
- unset( __ndklibspath )
-endif()
-
-
-# set some obsolete variables for backward compatibility
-set( ANDROID_SET_OBSOLETE_VARIABLES ON CACHE BOOL "Define obsolete Andrid-specific cmake variables" )
-mark_as_advanced( ANDROID_SET_OBSOLETE_VARIABLES )
-if( ANDROID_SET_OBSOLETE_VARIABLES )
- set( ANDROID_API_LEVEL ${ANDROID_NATIVE_API_LEVEL} )
- set( ARM_TARGET "${ANDROID_ABI}" )
- set( ARMEABI_NDK_NAME "${ANDROID_NDK_ABI_NAME}" )
-endif()
-
-
-# Variables controlling behavior or set by cmake toolchain:
-# ANDROID_ABI : "armeabi-v7a" (default), "armeabi", "armeabi-v7a with NEON", "armeabi-v7a with VFPV3", "armeabi-v6 with VFP", "x86", "mips"
-# ANDROID_NATIVE_API_LEVEL : 3,4,5,8,9,14 (depends on NDK version)
-# ANDROID_SET_OBSOLETE_VARIABLES : ON/OFF
-# ANDROID_USE_STLPORT : OFF/ON - EXPERIMENTAL!!!
-# ANDROID_FORBID_SYGWIN : ON/OFF
-# ANDROID_NO_UNDEFINED : ON/OFF
-# ANDROID_SO_UNDEFINED : OFF/ON (default depends on NDK version)
-# ANDROID_FUNCTION_LEVEL_LINKING : ON/OFF
-# Variables that takes effect only at first run:
-# ANDROID_FORCE_ARM_BUILD : ON/OFF
-# LIBRARY_OUTPUT_PATH_ROOT :
-# Can be set only at the first run:
-# ANDROID_NDK
-# ANDROID_STANDALONE_TOOLCHAIN
-# ANDROID_TOOLCHAIN_NAME : "arm-linux-androideabi-4.4.3" or "arm-linux-androideabi-4.6" or "mipsel-linux-android-4.4.3" or "mipsel-linux-android-4.6" or "x86-4.4.3" or "x86-4.6"
-# Obsolete:
-# ANDROID_API_LEVEL : superseded by ANDROID_NATIVE_API_LEVEL
-# ARM_TARGET : superseded by ANDROID_ABI
-# ARM_TARGETS : superseded by ANDROID_ABI (can be set only)
-# ANDROID_NDK_TOOLCHAIN_ROOT : superseded by ANDROID_STANDALONE_TOOLCHAIN (can be set only)
-# ANDROID_LEVEL : superseded by ANDROID_NATIVE_API_LEVEL (completely removed)
-#
-# Primary read-only variables:
-# ANDROID : always TRUE
-# ARMEABI : TRUE for arm v6 and older devices
-# ARMEABI_V6 : TRUE for arm v6
-# ARMEABI_V7A : TRUE for arm v7a
-# NEON : TRUE if NEON unit is enabled
-# VFPV3 : TRUE if VFP version 3 is enabled
-# X86 : TRUE if configured for x86
-# BUILD_ANDROID : always TRUE
-# BUILD_WITH_ANDROID_NDK : TRUE if NDK is used
-# BUILD_WITH_STANDALONE_TOOLCHAIN : TRUE if standalone toolchain is used
-# ANDROID_NDK_HOST_SYSTEM_NAME : "windows", "linux-x86" or "darwin-x86" depending on host platform
-# ANDROID_NDK_ABI_NAME : "armeabi", "armeabi-v7a" or "x86" depending on ANDROID_ABI
-# ANDROID_ARCH_NAME : "arm" or "x86" or "mips" depending on ANDROID_ABI
-# TOOL_OS_SUFFIX : "" or ".exe" depending on host platform
-# ANDROID_SYSROOT : path to the compiler sysroot
-# ANDROID_SYSTEM_INCLUDE_DIRS
-# ANDROID_SYSTEM_LIB_DIRS
-# Obsolete:
-# ARMEABI_NDK_NAME : superseded by ANDROID_NDK_ABI_NAME
-#
-# Secondary (less stable) read-only variables:
-# ANDROID_COMPILER_VERSION : GCC version used
-# ANDROID_CXX_FLAGS : C/C++ compiler flags required by Android platform
-# ANDROID_SUPPORTED_ABIS : list of currently allowed values for ANDROID_ABI
-# ANDROID_TOOLCHAIN_MACHINE_NAME : "arm-linux-androideabi", "arm-eabi" or "i686-android-linux"
-# ANDROID_TOOLCHAIN_ROOT : path to the top level of toolchain (standalone or placed inside NDK)
-# ANDROID_SUPPORTED_NATIVE_API_LEVELS : list of native API levels found inside NDK
-#
-# Defaults:
-# ANDROID_DEFAULT_NDK_API_LEVEL
-# ANDROID_DEFAULT_NDK_API_LEVEL_${ARCH}
-# ANDROID_NDK_SEARCH_PATHS
-# ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH
-# ANDROID_SUPPORTED_ABIS_${ARCH}
-# ANDROID_SUPPORTED_NDK_VERSIONS
+# Copyright (c) 2010-2011, Ethan Rublee
+# Copyright (c) 2011-2012, Andrey Kamaev
+# 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.
+#
+# 3. The name of the copyright holders may be used to endorse or promote
+# products derived from this software without specific prior written
+# permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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.
+
+# ------------------------------------------------------------------------------
+# Android CMake toolchain file, for use with the Android NDK r5-r8
+# Requires cmake 2.6.3 or newer (2.8.5 or newer is recommended).
+# See home page: https://github.com/taka-no-me/android-cmake
+#
+# The file is mantained by the OpenCV project. The latest version can be get at
+# http://code.opencv.org/projects/opencv/repository/revisions/master/changes/android/android.toolchain.cmake
+# Git commit: 084b1c796900b0825fc4e0593ab3143da3f3a90e
+#
+# Usage Linux:
+# $ export ANDROID_NDK=/absolute/path/to/the/android-ndk
+# $ mkdir build && cd build
+# $ cmake -DCMAKE_TOOLCHAIN_FILE=path/to/the/android.toolchain.cmake ..
+# $ make -j8
+#
+# Usage Linux (using standalone toolchain):
+# $ export ANDROID_STANDALONE_TOOLCHAIN=/absolute/path/to/android-toolchain
+# $ mkdir build && cd build
+# $ cmake -DCMAKE_TOOLCHAIN_FILE=path/to/the/android.toolchain.cmake ..
+# $ make -j8
+#
+# Usage Windows:
+# You need native port of make to build your project.
+# Android NDK r7 (or newer) already has make.exe on board.
+# For older NDK you have to install it separately.
+# For example, this one: http://gnuwin32.sourceforge.net/packages/make.htm
+#
+# $ SET ANDROID_NDK=C:\absolute\path\to\the\android-ndk
+# $ mkdir build && cd build
+# $ cmake.exe -G"MinGW Makefiles"
+# -DCMAKE_TOOLCHAIN_FILE=path\to\the\android.toolchain.cmake
+# -DCMAKE_MAKE_PROGRAM="%ANDROID_NDK%\prebuilt\windows\bin\make.exe" ..
+# $ cmake.exe --build .
+#
+#
+# Options (can be set as cmake parameters: -D=):
+# ANDROID_NDK=/opt/android-ndk - path to the NDK root.
+# Can be set as environment variable. Can be set only at first cmake run.
+#
+# ANDROID_STANDALONE_TOOLCHAIN=/opt/android-toolchain - path to the
+# standalone toolchain. This option is not used if full NDK is found
+# (ignored if ANDROID_NDK is set).
+# Can be set as environment variable. Can be set only at first cmake run.
+#
+# ANDROID_ABI=armeabi-v7a - specifies the target Application Binary
+# Interface (ABI). This option nearly matches to the APP_ABI variable
+# used by ndk-build tool from Android NDK.
+#
+# Possible targets are:
+# "armeabi" - matches to the NDK ABI with the same name.
+# See ${ANDROID_NDK}/docs/CPU-ARCH-ABIS.html for the documentation.
+# "armeabi-v7a" - matches to the NDK ABI with the same name.
+# See ${ANDROID_NDK}/docs/CPU-ARCH-ABIS.html for the documentation.
+# "armeabi-v7a with NEON" - same as armeabi-v7a, but
+# sets NEON as floating-point unit
+# "armeabi-v7a with VFPV3" - same as armeabi-v7a, but
+# sets VFPV3 as floating-point unit (has 32 registers instead of 16).
+# "armeabi-v6 with VFP" - tuned for ARMv6 processors having VFP.
+# "x86" - matches to the NDK ABI with the same name.
+# See ${ANDROID_NDK}/docs/CPU-ARCH-ABIS.html for the documentation.
+# "mips" - matches to the NDK ABI with the same name
+# (It is not tested on real devices by the authos of this toolchain)
+# See ${ANDROID_NDK}/docs/CPU-ARCH-ABIS.html for the documentation.
+#
+# ANDROID_NATIVE_API_LEVEL=android-8 - level of Android API compile for.
+# Option is read-only when standalone toolchain is used.
+#
+# ANDROID_TOOLCHAIN_NAME=arm-linux-androideabi-4.6 - the name of compiler
+# toolchain to be used. The list of possible values depends on the NDK
+# version. For NDK r8c the possible values are:
+#
+# * arm-linux-androideabi-4.4.3
+# * arm-linux-androideabi-4.6
+# * arm-linux-androideabi-clang3.1
+# * mipsel-linux-android-4.4.3
+# * mipsel-linux-android-4.6
+# * mipsel-linux-android-clang3.1
+# * x86-4.4.3
+# * x86-4.6
+# * x86-clang3.1
+#
+# ANDROID_FORCE_ARM_BUILD=OFF - set ON to generate 32-bit ARM instructions
+# instead of Thumb. Is not available for "x86" (inapplicable) and
+# "armeabi-v6 with VFP" (is forced to be ON) ABIs.
+#
+# ANDROID_NO_UNDEFINED=ON - set ON to show all undefined symbols as linker
+# errors even if they are not used.
+#
+# ANDROID_SO_UNDEFINED=OFF - set ON to allow undefined symbols in shared
+# libraries. Automatically turned for NDK r5x and r6x due to GLESv2
+# problems.
+#
+# LIBRARY_OUTPUT_PATH_ROOT=${CMAKE_SOURCE_DIR} - where to output binary
+# files. See additional details below.
+#
+# ANDROID_SET_OBSOLETE_VARIABLES=ON - if set, then toolchain defines some
+# obsolete variables which were used by previous versions of this file for
+# backward compatibility.
+#
+# ANDROID_STL=gnustl_static - specify the runtime to use.
+#
+# Possible values are:
+# none -> Do not configure the runtime.
+# system -> Use the default minimal system C++ runtime library.
+# Implies -fno-rtti -fno-exceptions.
+# Is not available for standalone toolchain.
+# system_re -> Use the default minimal system C++ runtime library.
+# Implies -frtti -fexceptions.
+# Is not available for standalone toolchain.
+# gabi++_static -> Use the GAbi++ runtime as a static library.
+# Implies -frtti -fno-exceptions.
+# Available for NDK r7 and newer.
+# Is not available for standalone toolchain.
+# gabi++_shared -> Use the GAbi++ runtime as a shared library.
+# Implies -frtti -fno-exceptions.
+# Available for NDK r7 and newer.
+# Is not available for standalone toolchain.
+# stlport_static -> Use the STLport runtime as a static library.
+# Implies -fno-rtti -fno-exceptions for NDK before r7.
+# Implies -frtti -fno-exceptions for NDK r7 and newer.
+# Is not available for standalone toolchain.
+# stlport_shared -> Use the STLport runtime as a shared library.
+# Implies -fno-rtti -fno-exceptions for NDK before r7.
+# Implies -frtti -fno-exceptions for NDK r7 and newer.
+# Is not available for standalone toolchain.
+# gnustl_static -> Use the GNU STL as a static library.
+# Implies -frtti -fexceptions.
+# gnustl_shared -> Use the GNU STL as a shared library.
+# Implies -frtti -fno-exceptions.
+# Available for NDK r7b and newer.
+# Silently degrades to gnustl_static if not available.
+#
+# ANDROID_STL_FORCE_FEATURES=ON - turn rtti and exceptions support based on
+# chosen runtime. If disabled, then the user is responsible for settings
+# these options.
+#
+# What?:
+# android-cmake toolchain searches for NDK/toolchain in the following order:
+# ANDROID_NDK - cmake parameter
+# ANDROID_NDK - environment variable
+# ANDROID_STANDALONE_TOOLCHAIN - cmake parameter
+# ANDROID_STANDALONE_TOOLCHAIN - environment variable
+# ANDROID_NDK - default locations
+# ANDROID_STANDALONE_TOOLCHAIN - default locations
+#
+# Make sure to do the following in your scripts:
+# SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${my_cxx_flags}" )
+# SET( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${my_cxx_flags}" )
+# The flags will be prepopulated with critical flags, so don't loose them.
+# Also be aware that toolchain also sets configuration-specific compiler
+# flags and linker flags.
+#
+# ANDROID and BUILD_ANDROID will be set to true, you may test any of these
+# variables to make necessary Android-specific configuration changes.
+#
+# Also ARMEABI or ARMEABI_V7A or X86 or MIPS will be set true, mutually
+# exclusive. NEON option will be set true if VFP is set to NEON.
+#
+# LIBRARY_OUTPUT_PATH_ROOT should be set in cache to determine where Android
+# libraries will be installed.
+# Default is ${CMAKE_SOURCE_DIR}, and the android libs will always be
+# under the ${LIBRARY_OUTPUT_PATH_ROOT}/libs/${ANDROID_NDK_ABI_NAME}
+# (depending on the target ABI). This is convenient for Android packaging.
+#
+# Change Log:
+# - initial version December 2010
+# - April 2011
+# [+] added possibility to build with NDK (without standalone toolchain)
+# [+] support cross-compilation on Windows (native, no cygwin support)
+# [+] added compiler option to force "char" type to be signed
+# [+] added toolchain option to compile to 32-bit ARM instructions
+# [+] added toolchain option to disable SWIG search
+# [+] added platform "armeabi-v7a with VFPV3"
+# [~] ARM_TARGETS renamed to ARM_TARGET
+# [+] EXECUTABLE_OUTPUT_PATH is set by toolchain (required on Windows)
+# [~] Fixed bug with ANDROID_API_LEVEL variable
+# [~] turn off SWIG search if it is not found first time
+# - May 2011
+# [~] ANDROID_LEVEL is renamed to ANDROID_API_LEVEL
+# [+] ANDROID_API_LEVEL is detected by toolchain if not specified
+# [~] added guard to prevent changing of output directories on the first
+# cmake pass
+# [~] toolchain exits with error if ARM_TARGET is not recognized
+# - June 2011
+# [~] default NDK path is updated for version r5c
+# [+] variable CMAKE_SYSTEM_PROCESSOR is set based on ARM_TARGET
+# [~] toolchain install directory is added to linker paths
+# [-] removed SWIG-related stuff from toolchain
+# [+] added macro find_host_package, find_host_program to search
+# packages/programs on the host system
+# [~] fixed path to STL library
+# - July 2011
+# [~] fixed options caching
+# [~] search for all supported NDK versions
+# [~] allowed spaces in NDK path
+# - September 2011
+# [~] updated for NDK r6b
+# - November 2011
+# [*] rewritten for NDK r7
+# [+] x86 toolchain support (experimental)
+# [+] added "armeabi-v6 with VFP" ABI for ARMv6 processors.
+# [~] improved compiler and linker flags management
+# [+] support different build flags for Release and Debug configurations
+# [~] by default compiler flags the same as used by ndk-build (but only
+# where reasonable)
+# [~] ANDROID_NDK_TOOLCHAIN_ROOT is splitted to ANDROID_STANDALONE_TOOLCHAIN
+# and ANDROID_TOOLCHAIN_ROOT
+# [~] ARM_TARGET is renamed to ANDROID_ABI
+# [~] ARMEABI_NDK_NAME is renamed to ANDROID_NDK_ABI_NAME
+# [~] ANDROID_API_LEVEL is renamed to ANDROID_NATIVE_API_LEVEL
+# - January 2012
+# [+] added stlport_static support (experimental)
+# [+] added special check for cygwin
+# [+] filtered out hidden files (starting with .) while globbing inside NDK
+# [+] automatically applied GLESv2 linkage fix for NDK revisions 5-6
+# [+] added ANDROID_GET_ABI_RAWNAME to get NDK ABI names by CMake flags
+# - February 2012
+# [+] updated for NDK r7b
+# [~] fixed cmake try_compile() command
+# [~] Fix for missing install_name_tool on OS X
+# - March 2012
+# [~] fixed incorrect C compiler flags
+# [~] fixed CMAKE_SYSTEM_PROCESSOR change on ANDROID_ABI change
+# [+] improved toolchain loading speed
+# [+] added assembler language support (.S)
+# [+] allowed preset search paths and extra search suffixes
+# - April 2012
+# [+] updated for NDK r7c
+# [~] fixed most of problems with compiler/linker flags and caching
+# [+] added option ANDROID_FUNCTION_LEVEL_LINKING
+# - May 2012
+# [+] updated for NDK r8
+# [+] added mips architecture support
+# - August 2012
+# [+] updated for NDK r8b
+# [~] all intermediate files generated by toolchain are moved to CMakeFiles
+# [~] libstdc++ and libsupc are removed from explicit link libraries
+# [+] added CCache support (via NDK_CCACHE environment or cmake variable)
+# [+] added gold linker support for NDK r8b
+# [~] fixed mips linker flags for NDK r8b
+# - September 2012
+# [+] added NDK release name detection (see ANDROID_NDK_RELEASE)
+# [+] added support for all C++ runtimes from NDK
+# (system, gabi++, stlport, gnustl)
+# [+] improved warnings on known issues of NDKs
+# [~] use gold linker as default if available (NDK r8b)
+# [~] globally turned off rpath
+# [~] compiler options are aligned with NDK r8b
+# - October 2012
+# [~] fixed C++ linking: explicitly link with math library (OpenCV #2426)
+# - November 2012
+# [+] updated for NDK r8c
+# [+] added support for clang compiler
+# - December 2012
+# [~] fixed ccache full path search
+# [+] updated for NDK r8d
+# ------------------------------------------------------------------------------
+
+cmake_minimum_required( VERSION 2.6.3 )
+
+if( DEFINED CMAKE_CROSSCOMPILING )
+ # subsequent toolchain loading is not really needed
+ return()
+endif()
+
+get_property( _CMAKE_IN_TRY_COMPILE GLOBAL PROPERTY IN_TRY_COMPILE )
+if( _CMAKE_IN_TRY_COMPILE )
+ include( "${CMAKE_CURRENT_SOURCE_DIR}/../android.toolchain.config.cmake" OPTIONAL )
+endif()
+
+# this one is important
+set( CMAKE_SYSTEM_NAME Linux )
+# this one not so much
+set( CMAKE_SYSTEM_VERSION 1 )
+
+# rpath makes low sence for Android
+set( CMAKE_SKIP_RPATH TRUE CACHE BOOL "If set, runtime paths are not added when using shared libraries." )
+
+set( ANDROID_SUPPORTED_NDK_VERSIONS ${ANDROID_EXTRA_NDK_VERSIONS} -r8d -r8c -r8b -r8 -r7c -r7b -r7 -r6b -r6 -r5c -r5b -r5 "" )
+if(NOT DEFINED ANDROID_NDK_SEARCH_PATHS)
+ if( CMAKE_HOST_WIN32 )
+ file( TO_CMAKE_PATH "$ENV{PROGRAMFILES}" ANDROID_NDK_SEARCH_PATHS )
+ set( ANDROID_NDK_SEARCH_PATHS "${ANDROID_NDK_SEARCH_PATHS}/android-ndk" "$ENV{SystemDrive}/NVPACK/android-ndk" )
+ else()
+ file( TO_CMAKE_PATH "$ENV{HOME}" ANDROID_NDK_SEARCH_PATHS )
+ set( ANDROID_NDK_SEARCH_PATHS /opt/android-ndk "${ANDROID_NDK_SEARCH_PATHS}/NVPACK/android-ndk" )
+ endif()
+endif()
+if(NOT DEFINED ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH)
+ set( ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH /opt/android-toolchain )
+endif()
+
+set( ANDROID_SUPPORTED_ABIS_arm "armeabi-v7a;armeabi;armeabi-v7a with NEON;armeabi-v7a with VFPV3;armeabi-v6 with VFP" )
+set( ANDROID_SUPPORTED_ABIS_x86 "x86" )
+set( ANDROID_SUPPORTED_ABIS_mipsel "mips" )
+
+set( ANDROID_DEFAULT_NDK_API_LEVEL 8 )
+set( ANDROID_DEFAULT_NDK_API_LEVEL_x86 9 )
+set( ANDROID_DEFAULT_NDK_API_LEVEL_mips 9 )
+
+
+macro( __LIST_FILTER listvar regex )
+ if( ${listvar} )
+ foreach( __val ${${listvar}} )
+ if( __val MATCHES "${regex}" )
+ list( REMOVE_ITEM ${listvar} "${__val}" )
+ endif()
+ endforeach()
+ endif()
+endmacro()
+
+macro( __INIT_VARIABLE var_name )
+ set( __test_path 0 )
+ foreach( __var ${ARGN} )
+ if( __var STREQUAL "PATH" )
+ set( __test_path 1 )
+ break()
+ endif()
+ endforeach()
+ if( __test_path AND NOT EXISTS "${${var_name}}" )
+ unset( ${var_name} CACHE )
+ endif()
+ if( "${${var_name}}" STREQUAL "" )
+ set( __values 0 )
+ foreach( __var ${ARGN} )
+ if( __var STREQUAL "VALUES" )
+ set( __values 1 )
+ elseif( NOT __var STREQUAL "PATH" )
+ set( __obsolete 0 )
+ if( __var MATCHES "^OBSOLETE_.*$" )
+ string( REPLACE "OBSOLETE_" "" __var "${__var}" )
+ set( __obsolete 1 )
+ endif()
+ if( __var MATCHES "^ENV_.*$" )
+ string( REPLACE "ENV_" "" __var "${__var}" )
+ set( __value "$ENV{${__var}}" )
+ elseif( DEFINED ${__var} )
+ set( __value "${${__var}}" )
+ else()
+ if( __values )
+ set( __value "${__var}" )
+ else()
+ set( __value "" )
+ endif()
+ endif()
+ if( NOT "${__value}" STREQUAL "" )
+ if( __test_path )
+ if( EXISTS "${__value}" )
+ file( TO_CMAKE_PATH "${__value}" ${var_name} )
+ if( __obsolete AND NOT _CMAKE_IN_TRY_COMPILE )
+ message( WARNING "Using value of obsolete variable ${__var} as initial value for ${var_name}. Please note, that ${__var} can be completely removed in future versions of the toolchain." )
+ endif()
+ break()
+ endif()
+ else()
+ set( ${var_name} "${__value}" )
+ if( __obsolete AND NOT _CMAKE_IN_TRY_COMPILE )
+ message( WARNING "Using value of obsolete variable ${__var} as initial value for ${var_name}. Please note, that ${__var} can be completely removed in future versions of the toolchain." )
+ endif()
+ break()
+ endif()
+ endif()
+ endif()
+ endforeach()
+ unset( __value )
+ unset( __values )
+ unset( __obsolete )
+ elseif( __test_path )
+ file( TO_CMAKE_PATH "${${var_name}}" ${var_name} )
+ endif()
+ unset( __test_path )
+endmacro()
+
+macro( __DETECT_NATIVE_API_LEVEL _var _path )
+ SET( __ndkApiLevelRegex "^[\t ]*#define[\t ]+__ANDROID_API__[\t ]+([0-9]+)[\t ]*$" )
+ FILE( STRINGS ${_path} __apiFileContent REGEX "${__ndkApiLevelRegex}" )
+ if( NOT __apiFileContent )
+ message( SEND_ERROR "Could not get Android native API level. Probably you have specified invalid level value, or your copy of NDK/toolchain is broken." )
+ endif()
+ string( REGEX REPLACE "${__ndkApiLevelRegex}" "\\1" ${_var} "${__apiFileContent}" )
+ unset( __apiFileContent )
+ unset( __ndkApiLevelRegex )
+endmacro()
+
+macro( __DETECT_TOOLCHAIN_MACHINE_NAME _var _root )
+ if( EXISTS "${_root}" )
+ file( GLOB __gccExePath RELATIVE "${_root}/bin/" "${_root}/bin/*-gcc${TOOL_OS_SUFFIX}" )
+ __LIST_FILTER( __gccExePath "^[.].*" )
+ list( LENGTH __gccExePath __gccExePathsCount )
+ if( NOT __gccExePathsCount EQUAL 1 AND NOT _CMAKE_IN_TRY_COMPILE )
+ message( WARNING "Could not determine machine name for compiler from ${_root}" )
+ set( ${_var} "" )
+ else()
+ get_filename_component( __gccExeName "${__gccExePath}" NAME_WE )
+ string( REPLACE "-gcc" "" ${_var} "${__gccExeName}" )
+ endif()
+ unset( __gccExePath )
+ unset( __gccExePathsCount )
+ unset( __gccExeName )
+ else()
+ set( ${_var} "" )
+ endif()
+endmacro()
+
+
+# fight against cygwin
+set( ANDROID_FORBID_SYGWIN TRUE CACHE BOOL "Prevent cmake from working under cygwin and using cygwin tools")
+mark_as_advanced( ANDROID_FORBID_SYGWIN )
+if( ANDROID_FORBID_SYGWIN )
+ if( CYGWIN )
+ message( FATAL_ERROR "Android NDK and android-cmake toolchain are not welcome Cygwin. It is unlikely that this cmake toolchain will work under cygwin. But if you want to try then you can set cmake variable ANDROID_FORBID_SYGWIN to FALSE and rerun cmake." )
+ endif()
+
+ if( CMAKE_HOST_WIN32 )
+ # remove cygwin from PATH
+ set( __new_path "$ENV{PATH}")
+ __LIST_FILTER( __new_path "cygwin" )
+ set(ENV{PATH} "${__new_path}")
+ unset(__new_path)
+ endif()
+endif()
+
+# detect current host platform
+set( TOOL_OS_SUFFIX "" )
+if( CMAKE_HOST_APPLE )
+ set( ANDROID_NDK_HOST_SYSTEM_NAME "darwin-x86" )
+elseif( CMAKE_HOST_WIN32 )
+ set( ANDROID_NDK_HOST_SYSTEM_NAME "windows" )
+ set( TOOL_OS_SUFFIX ".exe" )
+elseif( CMAKE_HOST_UNIX )
+ set( ANDROID_NDK_HOST_SYSTEM_NAME "linux-x86" )
+else()
+ message( FATAL_ERROR "Cross-compilation on your platform is not supported by this cmake toolchain" )
+endif()
+
+# see if we have path to Android NDK
+__INIT_VARIABLE( ANDROID_NDK PATH ENV_ANDROID_NDK )
+if( NOT ANDROID_NDK )
+ # see if we have path to Android standalone toolchain
+ __INIT_VARIABLE( ANDROID_STANDALONE_TOOLCHAIN PATH ENV_ANDROID_STANDALONE_TOOLCHAIN OBSOLETE_ANDROID_NDK_TOOLCHAIN_ROOT OBSOLETE_ENV_ANDROID_NDK_TOOLCHAIN_ROOT )
+
+ if( NOT ANDROID_STANDALONE_TOOLCHAIN )
+ #try to find Android NDK in one of the the default locations
+ set( __ndkSearchPaths )
+ foreach( __ndkSearchPath ${ANDROID_NDK_SEARCH_PATHS} )
+ foreach( suffix ${ANDROID_SUPPORTED_NDK_VERSIONS} )
+ list( APPEND __ndkSearchPaths "${__ndkSearchPath}${suffix}" )
+ endforeach()
+ endforeach()
+ __INIT_VARIABLE( ANDROID_NDK PATH VALUES ${__ndkSearchPaths} )
+ unset( __ndkSearchPaths )
+
+ if( ANDROID_NDK )
+ message( STATUS "Using default path for Android NDK: ${ANDROID_NDK}" )
+ message( STATUS " If you prefer to use a different location, please define a cmake or environment variable: ANDROID_NDK" )
+ else()
+ #try to find Android standalone toolchain in one of the the default locations
+ __INIT_VARIABLE( ANDROID_STANDALONE_TOOLCHAIN PATH ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH )
+
+ if( ANDROID_STANDALONE_TOOLCHAIN )
+ message( STATUS "Using default path for standalone toolchain ${ANDROID_STANDALONE_TOOLCHAIN}" )
+ message( STATUS " If you prefer to use a different location, please define the variable: ANDROID_STANDALONE_TOOLCHAIN" )
+ endif( ANDROID_STANDALONE_TOOLCHAIN )
+ endif( ANDROID_NDK )
+ endif( NOT ANDROID_STANDALONE_TOOLCHAIN )
+endif( NOT ANDROID_NDK )
+# remember found paths
+if( ANDROID_NDK )
+ get_filename_component( ANDROID_NDK "${ANDROID_NDK}" ABSOLUTE )
+ # try to detect change
+ if( CMAKE_AR )
+ string( LENGTH "${ANDROID_NDK}" __length )
+ string( SUBSTRING "${CMAKE_AR}" 0 ${__length} __androidNdkPreviousPath )
+ if( NOT __androidNdkPreviousPath STREQUAL ANDROID_NDK )
+ message( FATAL_ERROR "It is not possible to change the path to the NDK on subsequent CMake run. You must remove all generated files from your build folder first.
+ " )
+ endif()
+ unset( __androidNdkPreviousPath )
+ unset( __length )
+ endif()
+ set( ANDROID_NDK "${ANDROID_NDK}" CACHE INTERNAL "Path of the Android NDK" FORCE )
+ set( BUILD_WITH_ANDROID_NDK True )
+ file( STRINGS "${ANDROID_NDK}/RELEASE.TXT" ANDROID_NDK_RELEASE LIMIT_COUNT 1 REGEX r[0-9]+[a-z]? )
+elseif( ANDROID_STANDALONE_TOOLCHAIN )
+ get_filename_component( ANDROID_STANDALONE_TOOLCHAIN "${ANDROID_STANDALONE_TOOLCHAIN}" ABSOLUTE )
+ # try to detect change
+ if( CMAKE_AR )
+ string( LENGTH "${ANDROID_STANDALONE_TOOLCHAIN}" __length )
+ string( SUBSTRING "${CMAKE_AR}" 0 ${__length} __androidStandaloneToolchainPreviousPath )
+ if( NOT __androidStandaloneToolchainPreviousPath STREQUAL ANDROID_STANDALONE_TOOLCHAIN )
+ message( FATAL_ERROR "It is not possible to change path to the Android standalone toolchain on subsequent run." )
+ endif()
+ unset( __androidStandaloneToolchainPreviousPath )
+ unset( __length )
+ endif()
+ set( ANDROID_STANDALONE_TOOLCHAIN "${ANDROID_STANDALONE_TOOLCHAIN}" CACHE INTERNAL "Path of the Android standalone toolchain" FORCE )
+ set( BUILD_WITH_STANDALONE_TOOLCHAIN True )
+else()
+ list(GET ANDROID_NDK_SEARCH_PATHS 0 ANDROID_NDK_SEARCH_PATH)
+ message( FATAL_ERROR "Could not find neither Android NDK nor Android standalone toolchain.
+ You should either set an environment variable:
+ export ANDROID_NDK=~/my-android-ndk
+ or
+ export ANDROID_STANDALONE_TOOLCHAIN=~/my-android-toolchain
+ or put the toolchain or NDK in the default path:
+ sudo ln -s ~/my-android-ndk ${ANDROID_NDK_SEARCH_PATH}
+ sudo ln -s ~/my-android-toolchain ${ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH}" )
+endif()
+
+# get all the details about standalone toolchain
+if( BUILD_WITH_STANDALONE_TOOLCHAIN )
+ __DETECT_NATIVE_API_LEVEL( ANDROID_SUPPORTED_NATIVE_API_LEVELS "${ANDROID_STANDALONE_TOOLCHAIN}/sysroot/usr/include/android/api-level.h" )
+ set( ANDROID_STANDALONE_TOOLCHAIN_API_LEVEL ${ANDROID_SUPPORTED_NATIVE_API_LEVELS} )
+ set( __availableToolchains "standalone" )
+ __DETECT_TOOLCHAIN_MACHINE_NAME( __availableToolchainMachines "${ANDROID_STANDALONE_TOOLCHAIN}" )
+ if( NOT __availableToolchainMachines )
+ message( FATAL_ERROR "Could not determine machine name of your toolchain. Probably your Android standalone toolchain is broken." )
+ endif()
+ if( __availableToolchainMachines MATCHES i686 )
+ set( __availableToolchainArchs "x86" )
+ elseif( __availableToolchainMachines MATCHES arm )
+ set( __availableToolchainArchs "arm" )
+ elseif( __availableToolchainMachines MATCHES mipsel )
+ set( __availableToolchainArchs "mipsel" )
+ endif()
+ execute_process( COMMAND "${ANDROID_STANDALONE_TOOLCHAIN}/bin/${__availableToolchainMachines}-gcc${TOOL_OS_SUFFIX}" -dumpversion
+ OUTPUT_VARIABLE __availableToolchainCompilerVersions OUTPUT_STRIP_TRAILING_WHITESPACE )
+ string( REGEX MATCH "[0-9]+[.][0-9]+([.][0-9]+)?" __availableToolchainCompilerVersions "${__availableToolchainCompilerVersions}" )
+ if( EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/bin/clang${TOOL_OS_SUFFIX}" )
+ list( APPEND __availableToolchains "standalone-clang" )
+ list( APPEND __availableToolchainMachines ${__availableToolchainMachines} )
+ list( APPEND __availableToolchainArchs ${__availableToolchainArchs} )
+ list( APPEND __availableToolchainCompilerVersions ${__availableToolchainCompilerVersions} )
+ endif()
+endif()
+
+macro( __GLOB_NDK_TOOLCHAINS __availableToolchainsVar )
+ foreach( __toolchain ${${__availableToolchainsVar}} )
+ if( "${__toolchain}" MATCHES "-clang3[.][0-9]$" AND NOT EXISTS "${ANDROID_NDK}/toolchains/${__toolchain}/prebuilt/" )
+ string( REGEX REPLACE "-clang3[.][0-9]$" "-4.6" __gcc_toolchain "${__toolchain}" )
+ else()
+ set( __gcc_toolchain "${__toolchain}" )
+ endif()
+ __DETECT_TOOLCHAIN_MACHINE_NAME( __machine "${ANDROID_NDK}/toolchains/${__gcc_toolchain}/prebuilt/${ANDROID_NDK_HOST_SYSTEM_NAME}" )
+ if( __machine )
+ string( REGEX MATCH "[0-9]+[.][0-9]+([.][0-9]+)?$" __version "${__gcc_toolchain}" )
+ string( REGEX MATCH "^[^-]+" __arch "${__gcc_toolchain}" )
+ list( APPEND __availableToolchainMachines "${__machine}" )
+ list( APPEND __availableToolchainArchs "${__arch}" )
+ list( APPEND __availableToolchainCompilerVersions "${__version}" )
+ else()
+ list( REMOVE_ITEM ${__availableToolchainsVar} "${__toolchain}" )
+ endif()
+ unset( __gcc_toolchain )
+ endforeach()
+endmacro()
+
+# get all the details about NDK
+if( BUILD_WITH_ANDROID_NDK )
+ file( GLOB ANDROID_SUPPORTED_NATIVE_API_LEVELS RELATIVE "${ANDROID_NDK}/platforms" "${ANDROID_NDK}/platforms/android-*" )
+ string( REPLACE "android-" "" ANDROID_SUPPORTED_NATIVE_API_LEVELS "${ANDROID_SUPPORTED_NATIVE_API_LEVELS}" )
+ set( __availableToolchains "" )
+ set( __availableToolchainMachines "" )
+ set( __availableToolchainArchs "" )
+ set( __availableToolchainCompilerVersions "" )
+ if( ANDROID_TOOLCHAIN_NAME AND EXISTS "${ANDROID_NDK}/toolchains/${ANDROID_TOOLCHAIN_NAME}/" )
+ # do not go through all toolchains if we know the name
+ set( __availableToolchains "${ANDROID_TOOLCHAIN_NAME}" )
+ __GLOB_NDK_TOOLCHAINS( __availableToolchains )
+ endif()
+ if( NOT __availableToolchains )
+ file( GLOB __availableToolchains RELATIVE "${ANDROID_NDK}/toolchains" "${ANDROID_NDK}/toolchains/*" )
+ if( __availableToolchains )
+ list(SORT __availableToolchains) # we need clang to go after gcc
+ endif()
+ __LIST_FILTER( __availableToolchains "^[.]" )
+ __LIST_FILTER( __availableToolchains "llvm" )
+ __GLOB_NDK_TOOLCHAINS( __availableToolchains )
+ endif()
+ if( NOT __availableToolchains )
+ message( FATAL_ERROR "Could not find any working toolchain in the NDK. Probably your Android NDK is broken." )
+ endif()
+endif()
+
+# build list of available ABIs
+set( ANDROID_SUPPORTED_ABIS "" )
+set( __uniqToolchainArchNames ${__availableToolchainArchs} )
+list( REMOVE_DUPLICATES __uniqToolchainArchNames )
+list( SORT __uniqToolchainArchNames )
+foreach( __arch ${__uniqToolchainArchNames} )
+list( APPEND ANDROID_SUPPORTED_ABIS ${ANDROID_SUPPORTED_ABIS_${__arch}} )
+endforeach()
+unset( __uniqToolchainArchNames )
+if( NOT ANDROID_SUPPORTED_ABIS )
+message( FATAL_ERROR "No one of known Android ABIs is supported by this cmake toolchain." )
+endif()
+
+# choose target ABI
+__INIT_VARIABLE( ANDROID_ABI OBSOLETE_ARM_TARGET OBSOLETE_ARM_TARGETS VALUES ${ANDROID_SUPPORTED_ABIS} )
+# verify that target ABI is supported
+list( FIND ANDROID_SUPPORTED_ABIS "${ANDROID_ABI}" __androidAbiIdx )
+if( __androidAbiIdx EQUAL -1 )
+ string( REPLACE ";" "\", \"", PRINTABLE_ANDROID_SUPPORTED_ABIS "${ANDROID_SUPPORTED_ABIS}" )
+ message( FATAL_ERROR "Specified ANDROID_ABI = \"${ANDROID_ABI}\" is not supported by this cmake toolchain or your NDK/toolchain.
+ Supported values are: \"${PRINTABLE_ANDROID_SUPPORTED_ABIS}\"
+ " )
+endif()
+unset( __androidAbiIdx )
+
+# set target ABI options
+if( ANDROID_ABI STREQUAL "x86" )
+ set( X86 true )
+ set( ANDROID_NDK_ABI_NAME "x86" )
+ set( ANDROID_ARCH_NAME "x86" )
+ set( ANDROID_ARCH_FULLNAME "x86" )
+ set( ANDROID_LLVM_TRIPLE "i686-none-linux-android" )
+ set( CMAKE_SYSTEM_PROCESSOR "i686" )
+elseif( ANDROID_ABI STREQUAL "mips" )
+ set( MIPS true )
+ set( ANDROID_NDK_ABI_NAME "mips" )
+ set( ANDROID_ARCH_NAME "mips" )
+ set( ANDROID_ARCH_FULLNAME "mipsel" )
+ set( ANDROID_LLVM_TRIPLE "mipsel-none-linux-android" )
+ set( CMAKE_SYSTEM_PROCESSOR "mips" )
+elseif( ANDROID_ABI STREQUAL "armeabi" )
+ set( ARMEABI true )
+ set( ANDROID_NDK_ABI_NAME "armeabi" )
+ set( ANDROID_ARCH_NAME "arm" )
+ set( ANDROID_ARCH_FULLNAME "arm" )
+ set( ANDROID_LLVM_TRIPLE "armv5te-none-linux-androideabi" )
+ set( CMAKE_SYSTEM_PROCESSOR "armv5te" )
+elseif( ANDROID_ABI STREQUAL "armeabi-v6 with VFP" )
+ set( ARMEABI_V6 true )
+ set( ANDROID_NDK_ABI_NAME "armeabi" )
+ set( ANDROID_ARCH_NAME "arm" )
+ set( ANDROID_ARCH_FULLNAME "arm" )
+ set( ANDROID_LLVM_TRIPLE "armv5te-none-linux-androideabi" )
+ set( CMAKE_SYSTEM_PROCESSOR "armv6" )
+ # need always fallback to older platform
+ set( ARMEABI true )
+elseif( ANDROID_ABI STREQUAL "armeabi-v7a")
+ set( ARMEABI_V7A true )
+ set( ANDROID_NDK_ABI_NAME "armeabi-v7a" )
+ set( ANDROID_ARCH_NAME "arm" )
+ set( ANDROID_ARCH_FULLNAME "arm" )
+ set( ANDROID_LLVM_TRIPLE "armv7-none-linux-androideabi" )
+ set( CMAKE_SYSTEM_PROCESSOR "armv7-a" )
+elseif( ANDROID_ABI STREQUAL "armeabi-v7a with VFPV3" )
+ set( ARMEABI_V7A true )
+ set( ANDROID_NDK_ABI_NAME "armeabi-v7a" )
+ set( ANDROID_ARCH_NAME "arm" )
+ set( ANDROID_ARCH_FULLNAME "arm" )
+ set( ANDROID_LLVM_TRIPLE "armv7-none-linux-androideabi" )
+ set( CMAKE_SYSTEM_PROCESSOR "armv7-a" )
+ set( VFPV3 true )
+elseif( ANDROID_ABI STREQUAL "armeabi-v7a with NEON" )
+ set( ARMEABI_V7A true )
+ set( ANDROID_NDK_ABI_NAME "armeabi-v7a" )
+ set( ANDROID_ARCH_NAME "arm" )
+ set( ANDROID_ARCH_FULLNAME "arm" )
+ set( ANDROID_LLVM_TRIPLE "armv7-none-linux-androideabi" )
+ set( CMAKE_SYSTEM_PROCESSOR "armv7-a" )
+ set( VFPV3 true )
+ set( NEON true )
+else()
+ message( SEND_ERROR "Unknown ANDROID_ABI=\"${ANDROID_ABI}\" is specified." )
+endif()
+
+if( CMAKE_BINARY_DIR AND EXISTS "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeSystem.cmake" )
+ # really dirty hack
+ # it is not possible to change CMAKE_SYSTEM_PROCESSOR after the first run...
+ file( APPEND "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeSystem.cmake" "SET(CMAKE_SYSTEM_PROCESSOR \"${CMAKE_SYSTEM_PROCESSOR}\")\n" )
+endif()
+
+if( ANDROID_ARCH_NAME STREQUAL "arm" AND NOT ARMEABI_V6 )
+ __INIT_VARIABLE( ANDROID_FORCE_ARM_BUILD OBSOLETE_FORCE_ARM VALUES OFF )
+ set( ANDROID_FORCE_ARM_BUILD ${ANDROID_FORCE_ARM_BUILD} CACHE BOOL "Use 32-bit ARM instructions instead of Thumb-1" FORCE )
+ mark_as_advanced( ANDROID_FORCE_ARM_BUILD )
+else()
+ unset( ANDROID_FORCE_ARM_BUILD CACHE )
+endif()
+
+# choose toolchain
+if( ANDROID_TOOLCHAIN_NAME )
+ list( FIND __availableToolchains "${ANDROID_TOOLCHAIN_NAME}" __toolchainIdx )
+ if( __toolchainIdx EQUAL -1 )
+ list( SORT __availableToolchains )
+ string( REPLACE ";" "\n * " toolchains_list "${__availableToolchains}" )
+ set( toolchains_list " * ${toolchains_list}")
+ message( FATAL_ERROR "Specified toolchain \"${ANDROID_TOOLCHAIN_NAME}\" is missing in your NDK or broken. Please verify that your NDK is working or select another compiler toolchain.
+To configure the toolchain set CMake variable ANDROID_TOOLCHAIN_NAME to one of the following values:\n${toolchains_list}\n" )
+ endif()
+ list( GET __availableToolchainArchs ${__toolchainIdx} __toolchainArch )
+ if( NOT __toolchainArch STREQUAL ANDROID_ARCH_FULLNAME )
+ message( SEND_ERROR "Selected toolchain \"${ANDROID_TOOLCHAIN_NAME}\" is not able to compile binaries for the \"${ANDROID_ARCH_NAME}\" platform." )
+ endif()
+else()
+ set( __toolchainIdx -1 )
+ set( __applicableToolchains "" )
+ set( __toolchainMaxVersion "0.0.0" )
+ list( LENGTH __availableToolchains __availableToolchainsCount )
+ math( EXPR __availableToolchainsCount "${__availableToolchainsCount}-1" )
+ foreach( __idx RANGE ${__availableToolchainsCount} )
+ list( GET __availableToolchainArchs ${__idx} __toolchainArch )
+ if( __toolchainArch STREQUAL ANDROID_ARCH_FULLNAME )
+ list( GET __availableToolchainCompilerVersions ${__idx} __toolchainVersion )
+ if( __toolchainVersion VERSION_GREATER __toolchainMaxVersion )
+ set( __toolchainMaxVersion "${__toolchainVersion}" )
+ set( __toolchainIdx ${__idx} )
+ endif()
+ endif()
+ endforeach()
+ unset( __availableToolchainsCount )
+ unset( __toolchainMaxVersion )
+ unset( __toolchainVersion )
+endif()
+unset( __toolchainArch )
+if( __toolchainIdx EQUAL -1 )
+ message( FATAL_ERROR "No one of available compiler toolchains is able to compile for ${ANDROID_ARCH_NAME} platform." )
+endif()
+list( GET __availableToolchains ${__toolchainIdx} ANDROID_TOOLCHAIN_NAME )
+list( GET __availableToolchainMachines ${__toolchainIdx} ANDROID_TOOLCHAIN_MACHINE_NAME )
+list( GET __availableToolchainCompilerVersions ${__toolchainIdx} ANDROID_COMPILER_VERSION )
+
+unset( __toolchainIdx )
+unset( __availableToolchains )
+unset( __availableToolchainMachines )
+unset( __availableToolchainArchs )
+unset( __availableToolchainCompilerVersions )
+
+# choose native API level
+__INIT_VARIABLE( ANDROID_NATIVE_API_LEVEL ENV_ANDROID_NATIVE_API_LEVEL ANDROID_API_LEVEL ENV_ANDROID_API_LEVEL ANDROID_STANDALONE_TOOLCHAIN_API_LEVEL ANDROID_DEFAULT_NDK_API_LEVEL_${ANDROID_ARCH_NAME} ANDROID_DEFAULT_NDK_API_LEVEL )
+string( REGEX MATCH "[0-9]+" ANDROID_NATIVE_API_LEVEL "${ANDROID_NATIVE_API_LEVEL}" )
+# TODO: filter out unsupported levels
+# validate
+list( FIND ANDROID_SUPPORTED_NATIVE_API_LEVELS "${ANDROID_NATIVE_API_LEVEL}" __levelIdx )
+if( __levelIdx EQUAL -1 )
+ message( SEND_ERROR "Specified Android native API level (${ANDROID_NATIVE_API_LEVEL}) is not supported by your NDK/toolchain." )
+else()
+ if( BUILD_WITH_ANDROID_NDK )
+ __DETECT_NATIVE_API_LEVEL( __realApiLevel "${ANDROID_NDK}/platforms/android-${ANDROID_NATIVE_API_LEVEL}/arch-${ANDROID_ARCH_NAME}/usr/include/android/api-level.h" )
+ if( NOT __realApiLevel EQUAL ANDROID_NATIVE_API_LEVEL )
+ message( SEND_ERROR "Specified Android API level (${ANDROID_NATIVE_API_LEVEL}) does not match to the level found (${__realApiLevel}). Probably your copy of NDK is broken." )
+ endif()
+ unset( __realApiLevel )
+ endif()
+ set( ANDROID_NATIVE_API_LEVEL "${ANDROID_NATIVE_API_LEVEL}" CACHE STRING "Android API level for native code" FORCE )
+ if( CMAKE_VERSION VERSION_GREATER "2.8" )
+ list( SORT ANDROID_SUPPORTED_NATIVE_API_LEVELS )
+ set_property( CACHE ANDROID_NATIVE_API_LEVEL PROPERTY STRINGS ${ANDROID_SUPPORTED_NATIVE_API_LEVELS} )
+ endif()
+endif()
+unset( __levelIdx )
+
+
+# remember target ABI
+set( ANDROID_ABI "${ANDROID_ABI}" CACHE STRING "The target ABI for Android. If arm, then armeabi-v7a is recommended for hardware floating point." FORCE )
+if( CMAKE_VERSION VERSION_GREATER "2.8" )
+ list( SORT ANDROID_SUPPORTED_ABIS_${ANDROID_ARCH_FULLNAME} )
+ set_property( CACHE ANDROID_ABI PROPERTY STRINGS ${ANDROID_SUPPORTED_ABIS_${ANDROID_ARCH_FULLNAME}} )
+endif()
+
+
+# runtime choice (STL, rtti, exceptions)
+if( NOT ANDROID_STL )
+ # honor legacy ANDROID_USE_STLPORT
+ if( DEFINED ANDROID_USE_STLPORT )
+ if( ANDROID_USE_STLPORT )
+ set( ANDROID_STL stlport_static )
+ endif()
+ message( WARNING "You are using an obsolete variable ANDROID_USE_STLPORT to select the STL variant. Use -DANDROID_STL=stlport_static instead." )
+ endif()
+ if( NOT ANDROID_STL )
+ set( ANDROID_STL gnustl_static )
+ endif()
+endif()
+set( ANDROID_STL "${ANDROID_STL}" CACHE STRING "C++ runtime" )
+set( ANDROID_STL_FORCE_FEATURES ON CACHE BOOL "automatically configure rtti and exceptions support based on C++ runtime" )
+mark_as_advanced( ANDROID_STL ANDROID_STL_FORCE_FEATURES )
+
+if( BUILD_WITH_ANDROID_NDK )
+ if( NOT "${ANDROID_STL}" MATCHES "^(none|system|system_re|gabi\\+\\+_static|gabi\\+\\+_shared|stlport_static|stlport_shared|gnustl_static|gnustl_shared)$")
+ message( FATAL_ERROR "ANDROID_STL is set to invalid value \"${ANDROID_STL}\".
+The possible values are:
+ none -> Do not configure the runtime.
+ system -> Use the default minimal system C++ runtime library.
+ system_re -> Same as system but with rtti and exceptions.
+ gabi++_static -> Use the GAbi++ runtime as a static library.
+ gabi++_shared -> Use the GAbi++ runtime as a shared library.
+ stlport_static -> Use the STLport runtime as a static library.
+ stlport_shared -> Use the STLport runtime as a shared library.
+ gnustl_static -> (default) Use the GNU STL as a static library.
+ gnustl_shared -> Use the GNU STL as a shared library.
+" )
+ endif()
+elseif( BUILD_WITH_STANDALONE_TOOLCHAIN )
+ if( NOT "${ANDROID_STL}" MATCHES "^(none|gnustl_static|gnustl_shared)$")
+ message( FATAL_ERROR "ANDROID_STL is set to invalid value \"${ANDROID_STL}\".
+The possible values are:
+ none -> Do not configure the runtime.
+ gnustl_static -> (default) Use the GNU STL as a static library.
+ gnustl_shared -> Use the GNU STL as a shared library.
+" )
+ endif()
+endif()
+
+unset( ANDROID_RTTI )
+unset( ANDROID_EXCEPTIONS )
+unset( ANDROID_STL_INCLUDE_DIRS )
+unset( __libstl )
+unset( __libsupcxx )
+
+if( NOT _CMAKE_IN_TRY_COMPILE AND ANDROID_NDK_RELEASE STREQUAL "r7b" AND ARMEABI_V7A AND NOT VFPV3 AND ANDROID_STL MATCHES "gnustl" )
+ message( WARNING "The GNU STL armeabi-v7a binaries from NDK r7b can crash non-NEON devices. The files provided with NDK r7b were not configured properly, resulting in crashes on Tegra2-based devices and others when trying to use certain floating-point functions (e.g., cosf, sinf, expf).
+You are strongly recommended to switch to another NDK release.
+" )
+endif()
+
+if( NOT _CMAKE_IN_TRY_COMPILE AND X86 AND ANDROID_STL MATCHES "gnustl" AND ANDROID_NDK_RELEASE STREQUAL "r6" )
+ message( WARNING "The x86 system header file from NDK r6 has incorrect definition for ptrdiff_t. You are recommended to upgrade to a newer NDK release or manually patch the header:
+See https://android.googlesource.com/platform/development.git f907f4f9d4e56ccc8093df6fee54454b8bcab6c2
+ diff --git a/ndk/platforms/android-9/arch-x86/include/machine/_types.h b/ndk/platforms/android-9/arch-x86/include/machine/_types.h
+ index 5e28c64..65892a1 100644
+ --- a/ndk/platforms/android-9/arch-x86/include/machine/_types.h
+ +++ b/ndk/platforms/android-9/arch-x86/include/machine/_types.h
+ @@ -51,7 +51,11 @@ typedef long int ssize_t;
+ #endif
+ #ifndef _PTRDIFF_T
+ #define _PTRDIFF_T
+ -typedef long ptrdiff_t;
+ +# ifdef __ANDROID__
+ + typedef int ptrdiff_t;
+ +# else
+ + typedef long ptrdiff_t;
+ +# endif
+ #endif
+" )
+endif()
+
+
+# setup paths and STL for standalone toolchain
+if( BUILD_WITH_STANDALONE_TOOLCHAIN )
+ set( ANDROID_TOOLCHAIN_ROOT "${ANDROID_STANDALONE_TOOLCHAIN}" )
+ set( ANDROID_CLANG_TOOLCHAIN_ROOT "${ANDROID_STANDALONE_TOOLCHAIN}" )
+ set( ANDROID_SYSROOT "${ANDROID_STANDALONE_TOOLCHAIN}/sysroot" )
+
+ if( NOT ANDROID_STL STREQUAL "none" )
+ set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/include/c++/${ANDROID_COMPILER_VERSION}" )
+ if( ARMEABI_V7A AND EXISTS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/${CMAKE_SYSTEM_PROCESSOR}/bits" )
+ list( APPEND ANDROID_STL_INCLUDE_DIRS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/${CMAKE_SYSTEM_PROCESSOR}" )
+ elseif( ARMEABI AND NOT ANDROID_FORCE_ARM_BUILD AND EXISTS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/thumb/bits" )
+ list( APPEND ANDROID_STL_INCLUDE_DIRS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/thumb" )
+ else()
+ list( APPEND ANDROID_STL_INCLUDE_DIRS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}" )
+ endif()
+ # always search static GNU STL to get the location of libsupc++.a
+ if( ARMEABI_V7A AND NOT ANDROID_FORCE_ARM_BUILD AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/thumb/libstdc++.a" )
+ set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/thumb" )
+ elseif( ARMEABI_V7A AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/libstdc++.a" )
+ set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}" )
+ elseif( ARMEABI AND NOT ANDROID_FORCE_ARM_BUILD AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb/libstdc++.a" )
+ set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb" )
+ elseif( EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/libstdc++.a" )
+ set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib" )
+ endif()
+ if( __libstl )
+ set( __libsupcxx "${__libstl}/libsupc++.a" )
+ set( __libstl "${__libstl}/libstdc++.a" )
+ endif()
+ if( NOT EXISTS "${__libsupcxx}" )
+ message( FATAL_ERROR "The required libstdsupc++.a is missing in your standalone toolchain.
+ Usually it happens because of bug in make-standalone-toolchain.sh script from NDK r7, r7b and r7c.
+ You need to either upgrade to newer NDK or manually copy
+ $ANDROID_NDK/sources/cxx-stl/gnu-libstdc++/libs/${ANDROID_NDK_ABI_NAME}/libsupc++.a
+ to
+ ${__libsupcxx}
+ " )
+ endif()
+ if( ANDROID_STL STREQUAL "gnustl_shared" )
+ if( ARMEABI_V7A AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/libgnustl_shared.so" )
+ set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/libgnustl_shared.so" )
+ elseif( ARMEABI AND NOT ANDROID_FORCE_ARM_BUILD AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb/libgnustl_shared.so" )
+ set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb/libgnustl_shared.so" )
+ elseif( EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/libgnustl_shared.so" )
+ set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/libgnustl_shared.so" )
+ endif()
+ endif()
+ endif()
+endif()
+
+# clang
+if( "${ANDROID_TOOLCHAIN_NAME}" STREQUAL "standalone-clang" )
+ set( ANDROID_COMPILER_IS_CLANG 1 )
+ execute_process( COMMAND "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/clang${TOOL_OS_SUFFIX}" --version OUTPUT_VARIABLE ANDROID_CLANG_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE )
+ string( REGEX MATCH "[0-9]+[.][0-9]+" ANDROID_CLANG_VERSION "${ANDROID_CLANG_VERSION}")
+elseif( "${ANDROID_TOOLCHAIN_NAME}" MATCHES "-clang3[.][0-9]?$" )
+ string( REGEX MATCH "3[.][0-9]$" ANDROID_CLANG_VERSION "${ANDROID_TOOLCHAIN_NAME}")
+ string( REGEX REPLACE "-clang${ANDROID_CLANG_VERSION}$" "-4.6" ANDROID_GCC_TOOLCHAIN_NAME "${ANDROID_TOOLCHAIN_NAME}" )
+ if( NOT EXISTS "${ANDROID_NDK}/toolchains/llvm-${ANDROID_CLANG_VERSION}/prebuilt/${ANDROID_NDK_HOST_SYSTEM_NAME}/bin/clang${TOOL_OS_SUFFIX}" )
+ message( FATAL_ERROR "Could not find the " )
+ endif()
+ set( ANDROID_COMPILER_IS_CLANG 1 )
+ set( ANDROID_CLANG_TOOLCHAIN_ROOT "${ANDROID_NDK}/toolchains/llvm-${ANDROID_CLANG_VERSION}/prebuilt/${ANDROID_NDK_HOST_SYSTEM_NAME}" )
+else()
+ set( ANDROID_GCC_TOOLCHAIN_NAME "${ANDROID_TOOLCHAIN_NAME}" )
+ unset( ANDROID_COMPILER_IS_CLANG CACHE )
+endif()
+
+string( REPLACE "." "" _clang_name "clang${ANDROID_CLANG_VERSION}" )
+if( NOT EXISTS "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}${TOOL_OS_SUFFIX}" )
+ set( _clang_name "clang" )
+endif()
+
+
+# setup paths and STL for NDK
+if( BUILD_WITH_ANDROID_NDK )
+ set( ANDROID_TOOLCHAIN_ROOT "${ANDROID_NDK}/toolchains/${ANDROID_GCC_TOOLCHAIN_NAME}/prebuilt/${ANDROID_NDK_HOST_SYSTEM_NAME}" )
+ set( ANDROID_SYSROOT "${ANDROID_NDK}/platforms/android-${ANDROID_NATIVE_API_LEVEL}/arch-${ANDROID_ARCH_NAME}" )
+
+ if( ANDROID_STL STREQUAL "none" )
+ # do nothing
+ elseif( ANDROID_STL STREQUAL "system" )
+ set( ANDROID_RTTI OFF )
+ set( ANDROID_EXCEPTIONS OFF )
+ set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_NDK}/sources/cxx-stl/system/include" )
+ elseif( ANDROID_STL STREQUAL "system_re" )
+ set( ANDROID_RTTI ON )
+ set( ANDROID_EXCEPTIONS ON )
+ set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_NDK}/sources/cxx-stl/system/include" )
+ elseif( ANDROID_STL MATCHES "gabi" )
+ if( ANDROID_NDK_RELEASE STRLESS "r7" )
+ message( FATAL_ERROR "gabi++ is not awailable in your NDK. You have to upgrade to NDK r7 or newer to use gabi++.")
+ endif()
+ set( ANDROID_RTTI ON )
+ set( ANDROID_EXCEPTIONS OFF )
+ set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_NDK}/sources/cxx-stl/gabi++/include" )
+ set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gabi++/libs/${ANDROID_NDK_ABI_NAME}/libgabi++_static.a" )
+ elseif( ANDROID_STL MATCHES "stlport" )
+ if( NOT ANDROID_NDK_RELEASE STRLESS "r8d" )
+ set( ANDROID_EXCEPTIONS ON )
+ else()
+ set( ANDROID_EXCEPTIONS OFF )
+ endif()
+ if( ANDROID_NDK_RELEASE STRLESS "r7" )
+ set( ANDROID_RTTI OFF )
+ else()
+ set( ANDROID_RTTI ON )
+ endif()
+ set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_NDK}/sources/cxx-stl/stlport/stlport" )
+ set( __libstl "${ANDROID_NDK}/sources/cxx-stl/stlport/libs/${ANDROID_NDK_ABI_NAME}/libstlport_static.a" )
+ elseif( ANDROID_STL MATCHES "gnustl" )
+ set( ANDROID_EXCEPTIONS ON )
+ set( ANDROID_RTTI ON )
+ if( EXISTS "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/${ANDROID_COMPILER_VERSION}" )
+ if( ARMEABI_V7A AND ANDROID_COMPILER_VERSION VERSION_EQUAL "4.7" AND ANDROID_NDK_RELEASE STREQUAL "r8d" )
+ # gnustl binary for 4.7 compiler is buggy :(
+ # TODO: look for right fix
+ set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/4.6" )
+ else()
+ set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/${ANDROID_COMPILER_VERSION}" )
+ endif()
+ else()
+ set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++" )
+ endif()
+ set( ANDROID_STL_INCLUDE_DIRS "${__libstl}/include" "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/include" )
+ if( EXISTS "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/libgnustl_static.a" )
+ set( __libstl "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/libgnustl_static.a" )
+ else()
+ set( __libstl "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/libstdc++.a" )
+ endif()
+ else()
+ message( FATAL_ERROR "Unknown runtime: ${ANDROID_STL}" )
+ endif()
+ # find libsupc++.a - rtti & exceptions
+ if( ANDROID_STL STREQUAL "system_re" OR ANDROID_STL MATCHES "gnustl" )
+ if( ANDROID_NDK_RELEASE STRGREATER "r8" ) # r8b
+ set( __libsupcxx "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/${ANDROID_COMPILER_VERSION}/libs/${ANDROID_NDK_ABI_NAME}/libsupc++.a" )
+ elseif( NOT ANDROID_NDK_RELEASE STRLESS "r7" AND ANDROID_NDK_RELEASE STRLESS "r8b")
+ set( __libsupcxx "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/libs/${ANDROID_NDK_ABI_NAME}/libsupc++.a" )
+ else( ANDROID_NDK_RELEASE STRLESS "r7" )
+ if( ARMEABI_V7A )
+ if( ANDROID_FORCE_ARM_BUILD )
+ set( __libsupcxx "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/libsupc++.a" )
+ else()
+ set( __libsupcxx "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/thumb/libsupc++.a" )
+ endif()
+ elseif( ARMEABI AND NOT ANDROID_FORCE_ARM_BUILD )
+ set( __libsupcxx "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb/libsupc++.a" )
+ else()
+ set( __libsupcxx "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/libsupc++.a" )
+ endif()
+ endif()
+ if( NOT EXISTS "${__libsupcxx}")
+ message( ERROR "Could not find libsupc++.a for a chosen platform. Either your NDK is not supported or is broken.")
+ endif()
+ endif()
+endif()
+
+
+# case of shared STL linkage
+if( ANDROID_STL MATCHES "shared" AND DEFINED __libstl )
+ string( REPLACE "_static.a" "_shared.so" __libstl "${__libstl}" )
+ if( NOT _CMAKE_IN_TRY_COMPILE AND __libstl MATCHES "[.]so$" )
+ get_filename_component( __libstlname "${__libstl}" NAME )
+ execute_process( COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${__libstl}" "${LIBRARY_OUTPUT_PATH}/${__libstlname}" RESULT_VARIABLE __fileCopyProcess )
+ if( NOT __fileCopyProcess EQUAL 0 OR NOT EXISTS "${LIBRARY_OUTPUT_PATH}/${__libstlname}")
+ message( SEND_ERROR "Failed copying of ${__libstl} to the ${LIBRARY_OUTPUT_PATH}/${__libstlname}" )
+ endif()
+ unset( __fileCopyProcess )
+ unset( __libstlname )
+ endif()
+endif()
+
+
+# ccache support
+__INIT_VARIABLE( _ndk_ccache NDK_CCACHE ENV_NDK_CCACHE )
+if( _ndk_ccache )
+ if( DEFINED NDK_CCACHE AND NOT EXISTS NDK_CCACHE )
+ unset( NDK_CCACHE CACHE )
+ endif()
+ find_program( NDK_CCACHE "${_ndk_ccache}" DOC "The path to ccache binary")
+else()
+ unset( NDK_CCACHE CACHE )
+endif()
+unset( _ndk_ccache )
+
+
+# setup the cross-compiler
+if( NOT CMAKE_C_COMPILER )
+ if( NDK_CCACHE )
+ set( CMAKE_C_COMPILER "${NDK_CCACHE}" CACHE PATH "ccache as C compiler" )
+ set( CMAKE_CXX_COMPILER "${NDK_CCACHE}" CACHE PATH "ccache as C++ compiler" )
+ if( ANDROID_COMPILER_IS_CLANG )
+ set( CMAKE_C_COMPILER_ARG1 "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}${TOOL_OS_SUFFIX}" CACHE PATH "C compiler")
+ set( CMAKE_CXX_COMPILER_ARG1 "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}++${TOOL_OS_SUFFIX}" CACHE PATH "C++ compiler")
+ else()
+ set( CMAKE_C_COMPILER_ARG1 "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc${TOOL_OS_SUFFIX}" CACHE PATH "C compiler")
+ set( CMAKE_CXX_COMPILER_ARG1 "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-g++${TOOL_OS_SUFFIX}" CACHE PATH "C++ compiler")
+ endif()
+ else()
+ if( ANDROID_COMPILER_IS_CLANG )
+ set( CMAKE_C_COMPILER "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}${TOOL_OS_SUFFIX}" CACHE PATH "C compiler")
+ set( CMAKE_CXX_COMPILER "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}++${TOOL_OS_SUFFIX}" CACHE PATH "C++ compiler")
+ else()
+ set( CMAKE_C_COMPILER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc${TOOL_OS_SUFFIX}" CACHE PATH "C compiler" )
+ set( CMAKE_CXX_COMPILER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-g++${TOOL_OS_SUFFIX}" CACHE PATH "C++ compiler" )
+ endif()
+ endif()
+ set( CMAKE_ASM_COMPILER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc${TOOL_OS_SUFFIX}" CACHE PATH "assembler" )
+ set( CMAKE_STRIP "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-strip${TOOL_OS_SUFFIX}" CACHE PATH "strip" )
+ set( CMAKE_AR "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-ar${TOOL_OS_SUFFIX}" CACHE PATH "archive" )
+ set( CMAKE_LINKER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-ld${TOOL_OS_SUFFIX}" CACHE PATH "linker" )
+ set( CMAKE_NM "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-nm${TOOL_OS_SUFFIX}" CACHE PATH "nm" )
+ set( CMAKE_OBJCOPY "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-objcopy${TOOL_OS_SUFFIX}" CACHE PATH "objcopy" )
+ set( CMAKE_OBJDUMP "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-objdump${TOOL_OS_SUFFIX}" CACHE PATH "objdump" )
+ set( CMAKE_RANLIB "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-ranlib${TOOL_OS_SUFFIX}" CACHE PATH "ranlib" )
+endif()
+
+set( _CMAKE_TOOLCHAIN_PREFIX "${ANDROID_TOOLCHAIN_MACHINE_NAME}-" )
+if( CMAKE_VERSION VERSION_LESS 2.8.5 )
+ set( CMAKE_ASM_COMPILER_ARG1 "-c" )
+endif()
+if( APPLE )
+ find_program( CMAKE_INSTALL_NAME_TOOL NAMES install_name_tool )
+ if( NOT CMAKE_INSTALL_NAME_TOOL )
+ message( FATAL_ERROR "Could not find install_name_tool, please check your installation." )
+ endif()
+ mark_as_advanced( CMAKE_INSTALL_NAME_TOOL )
+endif()
+
+# Force set compilers because standard identification works badly for us
+include( CMakeForceCompiler )
+CMAKE_FORCE_C_COMPILER( "${CMAKE_C_COMPILER}" GNU )
+if( ANDROID_COMPILER_IS_CLANG )
+ set( CMAKE_C_COMPILER_ID Clang)
+endif()
+set( CMAKE_C_PLATFORM_ID Linux )
+set( CMAKE_C_SIZEOF_DATA_PTR 4 )
+set( CMAKE_C_HAS_ISYSROOT 1 )
+set( CMAKE_C_COMPILER_ABI ELF )
+CMAKE_FORCE_CXX_COMPILER( "${CMAKE_CXX_COMPILER}" GNU )
+if( ANDROID_COMPILER_IS_CLANG )
+ set( CMAKE_CXX_COMPILER_ID Clang)
+endif()
+set( CMAKE_CXX_PLATFORM_ID Linux )
+set( CMAKE_CXX_SIZEOF_DATA_PTR 4 )
+set( CMAKE_CXX_HAS_ISYSROOT 1 )
+set( CMAKE_CXX_COMPILER_ABI ELF )
+set( CMAKE_CXX_SOURCE_FILE_EXTENSIONS cc cp cxx cpp CPP c++ C )
+# force ASM compiler (required for CMake < 2.8.5)
+set( CMAKE_ASM_COMPILER_ID_RUN TRUE )
+set( CMAKE_ASM_COMPILER_ID GNU )
+set( CMAKE_ASM_COMPILER_WORKS TRUE )
+set( CMAKE_ASM_COMPILER_FORCED TRUE )
+set( CMAKE_COMPILER_IS_GNUASM 1)
+set( CMAKE_ASM_SOURCE_FILE_EXTENSIONS s S asm )
+
+# flags and definitions
+remove_definitions( -DANDROID )
+add_definitions( -DANDROID )
+
+if(ANDROID_SYSROOT MATCHES "[ ;\"]")
+ set( ANDROID_CXX_FLAGS "--sysroot=\"${ANDROID_SYSROOT}\"" )
+ if( NOT _CMAKE_IN_TRY_COMPILE )
+ # quotes will break try_compile and compiler identification
+ message(WARNING "Your Android system root has non-alphanumeric symbols. It can break compiler features detection and the whole build.")
+ endif()
+else()
+ set( ANDROID_CXX_FLAGS "--sysroot=${ANDROID_SYSROOT}" )
+endif()
+
+# NDK flags
+if( ARMEABI OR ARMEABI_V7A )
+ set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fpic -D__ARM_ARCH_5__ -D__ARM_ARCH_5T__ -D__ARM_ARCH_5E__ -D__ARM_ARCH_5TE__" )
+ if( NOT ANDROID_FORCE_ARM_BUILD AND NOT ARMEABI_V6 )
+ # It is recommended to use the -mthumb compiler flag to force the generation
+ # of 16-bit Thumb-1 instructions (the default being 32-bit ARM ones).
+ set( ANDROID_CXX_FLAGS_RELEASE "-mthumb" )
+ set( ANDROID_CXX_FLAGS_DEBUG "-marm -finline-limit=64" )
+ else()
+ # always compile ARMEABI_V6 in arm mode; otherwise there is no difference from ARMEABI
+ # O3 instead of O2/Os in release mode - like cmake sets for desktop gcc
+ set( ANDROID_CXX_FLAGS_RELEASE "-marm" )
+ set( ANDROID_CXX_FLAGS_DEBUG "-marm -finline-limit=300" )
+ endif()
+elseif( X86 )
+ set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funwind-tables" )
+ set( ANDROID_CXX_FLAGS_RELEASE "" )
+ set( ANDROID_CXX_FLAGS_DEBUG "-finline-limit=300" )
+elseif( MIPS )
+ set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fpic -funwind-tables -fmessage-length=0 -fno-inline-functions-called-once -frename-registers" )
+ set( ANDROID_CXX_FLAGS_RELEASE "-finline-limit=300 -fno-strict-aliasing" )
+ set( ANDROID_CXX_FLAGS_DEBUG "-finline-functions -fgcse-after-reload -frerun-cse-after-loop" )
+elseif()
+ set( ANDROID_CXX_FLAGS_RELEASE "" )
+ set( ANDROID_CXX_FLAGS_DEBUG "" )
+endif()
+
+if( NOT X86 )
+ set( ANDROID_CXX_FLAGS "-Wno-psabi ${ANDROID_CXX_FLAGS}" )
+endif()
+
+set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fsigned-char" ) # good/necessary when porting desktop libraries
+set( ANDROID_CXX_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE} -fomit-frame-pointer" )
+set( ANDROID_CXX_FLAGS_DEBUG "${ANDROID_CXX_FLAGS_DEBUG} -fno-strict-aliasing -fno-omit-frame-pointer" )
+
+# ABI-specific flags
+if( ARMEABI_V7A )
+ set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -march=armv7-a -mfloat-abi=softfp" )
+ if( NEON )
+ set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -mfpu=neon" )
+ elseif( VFPV3 )
+ set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -mfpu=vfpv3" )
+ else()
+ set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -mfpu=vfpv3-d16" )
+ endif()
+elseif( ARMEABI_V6 )
+ set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -march=armv6 -mfloat-abi=softfp -mfpu=vfp" ) # vfp == vfpv2
+elseif( ARMEABI )
+ set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -march=armv5te -mtune=xscale -msoft-float" )
+endif()
+
+# STL
+if( EXISTS "${__libstl}" OR EXISTS "${__libsupcxx}" )
+ if( ANDROID_STL MATCHES "gnustl" )
+ set( CMAKE_CXX_CREATE_SHARED_LIBRARY " -o " )
+ set( CMAKE_CXX_CREATE_SHARED_MODULE " -o " )
+ set( CMAKE_CXX_LINK_EXECUTABLE " -o " )
+ else()
+ set( CMAKE_CXX_CREATE_SHARED_LIBRARY " -o " )
+ set( CMAKE_CXX_CREATE_SHARED_MODULE " -o " )
+ set( CMAKE_CXX_LINK_EXECUTABLE " -o " )
+ endif()
+ if ( X86 AND ANDROID_STL MATCHES "gnustl" AND ANDROID_NDK_RELEASE STREQUAL "r6" )
+ # workaround "undefined reference to `__dso_handle'" problem
+ set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} \"${ANDROID_SYSROOT}/usr/lib/crtbegin_so.o\"" )
+ set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} \"${ANDROID_SYSROOT}/usr/lib/crtbegin_so.o\"" )
+ endif()
+ if( EXISTS "${__libstl}" )
+ set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} \"${__libstl}\"" )
+ set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} \"${__libstl}\"" )
+ set( CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} \"${__libstl}\"" )
+ endif()
+ if( EXISTS "${__libsupcxx}" )
+ set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} \"${__libsupcxx}\"" )
+ set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} \"${__libsupcxx}\"" )
+ set( CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} \"${__libsupcxx}\"" )
+ # C objects:
+ set( CMAKE_C_CREATE_SHARED_LIBRARY " -o " )
+ set( CMAKE_C_CREATE_SHARED_MODULE " -o " )
+ set( CMAKE_C_LINK_EXECUTABLE " -o " )
+ set( CMAKE_C_CREATE_SHARED_LIBRARY "${CMAKE_C_CREATE_SHARED_LIBRARY} \"${__libsupcxx}\"" )
+ set( CMAKE_C_CREATE_SHARED_MODULE "${CMAKE_C_CREATE_SHARED_MODULE} \"${__libsupcxx}\"" )
+ set( CMAKE_C_LINK_EXECUTABLE "${CMAKE_C_LINK_EXECUTABLE} \"${__libsupcxx}\"" )
+ endif()
+ if( ANDROID_STL MATCHES "gnustl" )
+ set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} -lm" )
+ set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} -lm" )
+ set( CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} -lm" )
+ endif()
+endif()
+
+# variables controlling optional build flags
+if (ANDROID_NDK_RELEASE STRLESS "r7")
+ # libGLESv2.so in NDK's prior to r7 refers to missing external symbols.
+ # So this flag option is required for all projects using OpenGL from native.
+ __INIT_VARIABLE( ANDROID_SO_UNDEFINED VALUES ON )
+else()
+ __INIT_VARIABLE( ANDROID_SO_UNDEFINED VALUES OFF )
+endif()
+__INIT_VARIABLE( ANDROID_NO_UNDEFINED OBSOLETE_NO_UNDEFINED VALUES ON )
+__INIT_VARIABLE( ANDROID_FUNCTION_LEVEL_LINKING VALUES ON )
+__INIT_VARIABLE( ANDROID_GOLD_LINKER VALUES ON )
+__INIT_VARIABLE( ANDROID_NOEXECSTACK VALUES ON )
+__INIT_VARIABLE( ANDROID_RELRO VALUES ON )
+
+set( ANDROID_NO_UNDEFINED ${ANDROID_NO_UNDEFINED} CACHE BOOL "Show all undefined symbols as linker errors" )
+set( ANDROID_SO_UNDEFINED ${ANDROID_SO_UNDEFINED} CACHE BOOL "Allows or disallows undefined symbols in shared libraries" )
+set( ANDROID_FUNCTION_LEVEL_LINKING ${ANDROID_FUNCTION_LEVEL_LINKING} CACHE BOOL "Allows or disallows undefined symbols in shared libraries" )
+set( ANDROID_GOLD_LINKER ${ANDROID_GOLD_LINKER} CACHE BOOL "Enables gold linker (only avaialble for NDK r8b for ARM and x86 architectures on linux-86 and darwin-x86 hosts)" )
+set( ANDROID_NOEXECSTACK ${ANDROID_NOEXECSTACK} CACHE BOOL "Allows or disallows undefined symbols in shared libraries" )
+set( ANDROID_RELRO ${ANDROID_RELRO} CACHE BOOL "Enables RELRO - a memory corruption mitigation technique" )
+mark_as_advanced( ANDROID_NO_UNDEFINED ANDROID_SO_UNDEFINED ANDROID_FUNCTION_LEVEL_LINKING ANDROID_GOLD_LINKER ANDROID_NOEXECSTACK ANDROID_RELRO )
+
+# linker flags
+set( ANDROID_LINKER_FLAGS "" )
+
+if( ARMEABI_V7A )
+ # this is *required* to use the following linker flags that routes around
+ # a CPU bug in some Cortex-A8 implementations:
+ set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,--fix-cortex-a8" )
+endif()
+
+if( ANDROID_NO_UNDEFINED )
+ set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,--no-undefined" )
+endif()
+
+if( ANDROID_SO_UNDEFINED )
+ set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,-allow-shlib-undefined" )
+endif()
+
+if( ANDROID_FUNCTION_LEVEL_LINKING )
+ set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fdata-sections -ffunction-sections" )
+ set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,--gc-sections" )
+endif()
+
+if( ANDROID_COMPILER_VERSION VERSION_EQUAL "4.6" )
+ if( ANDROID_GOLD_LINKER AND (CMAKE_HOST_UNIX OR ANDROID_NDK_RELEASE STRGREATER "r8b") AND (ARMEABI OR ARMEABI_V7A OR X86) )
+ set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -fuse-ld=gold" )
+ elseif( ANDROID_NDK_RELEASE STRGREATER "r8b")
+ set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -fuse-ld=bfd" )
+ elseif( ANDROID_NDK_RELEASE STREQUAL "r8b" AND ARMEABI AND NOT _CMAKE_IN_TRY_COMPILE )
+ message( WARNING "The default bfd linker from arm GCC 4.6 toolchain can fail with 'unresolvable R_ARM_THM_CALL relocation' error message. See https://code.google.com/p/android/issues/detail?id=35342
+ On Linux and OS X host platform you can workaround this problem using gold linker (default).
+ Rerun cmake with -DANDROID_GOLD_LINKER=ON option in case of problems.
+" )
+ endif()
+endif() # version 4.6
+
+if( ANDROID_NOEXECSTACK )
+ if( ANDROID_COMPILER_IS_CLANG )
+ set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -Xclang -mnoexecstack" )
+ else()
+ set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -Wa,--noexecstack" )
+ endif()
+ set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,-z,noexecstack" )
+endif()
+
+if( ANDROID_RELRO )
+ set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,-z,relro -Wl,-z,now" )
+endif()
+
+if( ANDROID_COMPILER_IS_CLANG )
+ set( ANDROID_CXX_FLAGS "-Qunused-arguments ${ANDROID_CXX_FLAGS}" )
+ if( ARMEABI_V7A AND NOT ANDROID_FORCE_ARM_BUILD )
+ set( ANDROID_CXX_FLAGS_RELEASE "-target thumbv7-none-linux-androideabi ${ANDROID_CXX_FLAGS_RELEASE}" )
+ set( ANDROID_CXX_FLAGS_DEBUG "-target ${ANDROID_LLVM_TRIPLE} ${ANDROID_CXX_FLAGS_DEBUG}" )
+ else()
+ set( ANDROID_CXX_FLAGS "-target ${ANDROID_LLVM_TRIPLE} ${ANDROID_CXX_FLAGS}" )
+ endif()
+ if( BUILD_WITH_ANDROID_NDK )
+ if(ANDROID_ARCH_NAME STREQUAL "arm" )
+ set( ANDROID_CXX_FLAGS "-isystem ${ANDROID_CLANG_TOOLCHAIN_ROOT}/lib/clang/${ANDROID_CLANG_VERSION}/include ${ANDROID_CXX_FLAGS}" )
+ endif()
+ set( ANDROID_CXX_FLAGS "-gcc-toolchain ${ANDROID_TOOLCHAIN_ROOT} ${ANDROID_CXX_FLAGS}" )
+ endif()
+endif()
+
+# cache flags
+set( CMAKE_CXX_FLAGS "" CACHE STRING "c++ flags" )
+set( CMAKE_C_FLAGS "" CACHE STRING "c flags" )
+set( CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG" CACHE STRING "c++ Release flags" )
+set( CMAKE_C_FLAGS_RELEASE "-O3 -DNDEBUG" CACHE STRING "c Release flags" )
+set( CMAKE_CXX_FLAGS_DEBUG "-O0 -g -DDEBUG -D_DEBUG" CACHE STRING "c++ Debug flags" )
+set( CMAKE_C_FLAGS_DEBUG "-O0 -g -DDEBUG -D_DEBUG" CACHE STRING "c Debug flags" )
+set( CMAKE_SHARED_LINKER_FLAGS "" CACHE STRING "shared linker flags" )
+set( CMAKE_MODULE_LINKER_FLAGS "" CACHE STRING "module linker flags" )
+set( CMAKE_EXE_LINKER_FLAGS "-Wl,-z,nocopyreloc" CACHE STRING "executable linker flags" )
+
+# finish flags
+set( CMAKE_CXX_FLAGS "${ANDROID_CXX_FLAGS} ${CMAKE_CXX_FLAGS}" )
+set( CMAKE_C_FLAGS "${ANDROID_CXX_FLAGS} ${CMAKE_C_FLAGS}" )
+set( CMAKE_CXX_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE} ${CMAKE_CXX_FLAGS_RELEASE}" )
+set( CMAKE_C_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE} ${CMAKE_C_FLAGS_RELEASE}" )
+set( CMAKE_CXX_FLAGS_DEBUG "${ANDROID_CXX_FLAGS_DEBUG} ${CMAKE_CXX_FLAGS_DEBUG}" )
+set( CMAKE_C_FLAGS_DEBUG "${ANDROID_CXX_FLAGS_DEBUG} ${CMAKE_C_FLAGS_DEBUG}" )
+set( CMAKE_SHARED_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} ${CMAKE_SHARED_LINKER_FLAGS}" )
+set( CMAKE_MODULE_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} ${CMAKE_MODULE_LINKER_FLAGS}" )
+set( CMAKE_EXE_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} ${CMAKE_EXE_LINKER_FLAGS}" )
+
+if( MIPS AND BUILD_WITH_ANDROID_NDK AND ANDROID_NDK_RELEASE STREQUAL "r8" )
+ set( CMAKE_SHARED_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK}/toolchains/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.xsc ${CMAKE_SHARED_LINKER_FLAGS}" )
+ set( CMAKE_MODULE_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK}/toolchains/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.xsc ${CMAKE_MODULE_LINKER_FLAGS}" )
+ set( CMAKE_EXE_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK}/toolchains/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.x ${CMAKE_EXE_LINKER_FLAGS}" )
+endif()
+
+# configure rtti
+if( DEFINED ANDROID_RTTI AND ANDROID_STL_FORCE_FEATURES )
+ if( ANDROID_RTTI )
+ set( CMAKE_CXX_FLAGS "-frtti ${CMAKE_CXX_FLAGS}" )
+ else()
+ set( CMAKE_CXX_FLAGS "-fno-rtti ${CMAKE_CXX_FLAGS}" )
+ endif()
+endif()
+
+# configure exceptios
+if( DEFINED ANDROID_EXCEPTIONS AND ANDROID_STL_FORCE_FEATURES )
+ if( ANDROID_EXCEPTIONS )
+ set( CMAKE_CXX_FLAGS "-fexceptions ${CMAKE_CXX_FLAGS}" )
+ set( CMAKE_C_FLAGS "-fexceptions ${CMAKE_C_FLAGS}" )
+ else()
+ set( CMAKE_CXX_FLAGS "-fno-exceptions ${CMAKE_CXX_FLAGS}" )
+ set( CMAKE_C_FLAGS "-fno-exceptions ${CMAKE_C_FLAGS}" )
+ endif()
+endif()
+
+# global includes and link directories
+include_directories( SYSTEM "${ANDROID_SYSROOT}/usr/include" ${ANDROID_STL_INCLUDE_DIRS} )
+link_directories( "${CMAKE_INSTALL_PREFIX}/libs/${ANDROID_NDK_ABI_NAME}" )
+
+# setup output directories
+set( LIBRARY_OUTPUT_PATH_ROOT ${CMAKE_SOURCE_DIR} CACHE PATH "root for library output, set this to change where android libs are installed to" )
+set( CMAKE_INSTALL_PREFIX "${ANDROID_TOOLCHAIN_ROOT}/user" CACHE STRING "path for installing" )
+
+if(NOT _CMAKE_IN_TRY_COMPILE)
+ if( EXISTS "${CMAKE_SOURCE_DIR}/jni/CMakeLists.txt" )
+ set( EXECUTABLE_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/bin/${ANDROID_NDK_ABI_NAME}" CACHE PATH "Output directory for applications" )
+ else()
+ set( EXECUTABLE_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/bin" CACHE PATH "Output directory for applications" )
+ endif()
+ set( LIBRARY_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/libs/${ANDROID_NDK_ABI_NAME}" CACHE PATH "path for android libs" )
+endif()
+
+# set these global flags for cmake client scripts to change behavior
+set( ANDROID True )
+set( BUILD_ANDROID True )
+
+# where is the target environment
+set( CMAKE_FIND_ROOT_PATH "${ANDROID_TOOLCHAIN_ROOT}/bin" "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}" "${ANDROID_SYSROOT}" "${CMAKE_INSTALL_PREFIX}" "${CMAKE_INSTALL_PREFIX}/share" )
+
+# only search for libraries and includes in the ndk toolchain
+set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY )
+set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY )
+set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY )
+
+
+# macro to find packages on the host OS
+macro( find_host_package )
+ set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER )
+ set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER )
+ set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER )
+ if( CMAKE_HOST_WIN32 )
+ SET( WIN32 1 )
+ SET( UNIX )
+ elseif( CMAKE_HOST_APPLE )
+ SET( APPLE 1 )
+ SET( UNIX )
+ endif()
+ find_package( ${ARGN} )
+ SET( WIN32 )
+ SET( APPLE )
+ SET( UNIX 1 )
+ set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY )
+ set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY )
+ set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY )
+endmacro()
+
+
+# macro to find programs on the host OS
+macro( find_host_program )
+ set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER )
+ set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER )
+ set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER )
+ if( CMAKE_HOST_WIN32 )
+ SET( WIN32 1 )
+ SET( UNIX )
+ elseif( CMAKE_HOST_APPLE )
+ SET( APPLE 1 )
+ SET( UNIX )
+ endif()
+ find_program( ${ARGN} )
+ SET( WIN32 )
+ SET( APPLE )
+ SET( UNIX 1 )
+ set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY )
+ set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY )
+ set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY )
+endmacro()
+
+
+macro( ANDROID_GET_ABI_RAWNAME TOOLCHAIN_FLAG VAR )
+ if( "${TOOLCHAIN_FLAG}" STREQUAL "ARMEABI" )
+ set( ${VAR} "armeabi" )
+ elseif( "${TOOLCHAIN_FLAG}" STREQUAL "ARMEABI_V7A" )
+ set( ${VAR} "armeabi-v7a" )
+ elseif( "${TOOLCHAIN_FLAG}" STREQUAL "X86" )
+ set( ${VAR} "x86" )
+ elseif( "${TOOLCHAIN_FLAG}" STREQUAL "MIPS" )
+ set( ${VAR} "mips" )
+ else()
+ set( ${VAR} "unknown" )
+ endif()
+endmacro()
+
+
+# export toolchain settings for the try_compile() command
+if( NOT PROJECT_NAME STREQUAL "CMAKE_TRY_COMPILE" )
+ set( __toolchain_config "")
+ foreach( __var NDK_CCACHE LIBRARY_OUTPUT_PATH_ROOT ANDROID_FORBID_SYGWIN ANDROID_SET_OBSOLETE_VARIABLES
+ ANDROID_NDK
+ ANDROID_STANDALONE_TOOLCHAIN
+ ANDROID_TOOLCHAIN_NAME
+ ANDROID_ABI
+ ANDROID_NATIVE_API_LEVEL
+ ANDROID_STL
+ ANDROID_STL_FORCE_FEATURES
+ ANDROID_FORCE_ARM_BUILD
+ ANDROID_NO_UNDEFINED
+ ANDROID_SO_UNDEFINED
+ ANDROID_FUNCTION_LEVEL_LINKING
+ ANDROID_GOLD_LINKER
+ ANDROID_NOEXECSTACK
+ ANDROID_RELRO
+ )
+ if( DEFINED ${__var} )
+ if( "${__var}" MATCHES " ")
+ set( __toolchain_config "${__toolchain_config}set( ${__var} \"${${__var}}\" CACHE INTERNAL \"\" )\n" )
+ else()
+ set( __toolchain_config "${__toolchain_config}set( ${__var} ${${__var}} CACHE INTERNAL \"\" )\n" )
+ endif()
+ endif()
+ endforeach()
+ file( WRITE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/android.toolchain.config.cmake" "${__toolchain_config}" )
+ unset( __toolchain_config )
+endif()
+
+
+# set some obsolete variables for backward compatibility
+set( ANDROID_SET_OBSOLETE_VARIABLES ON CACHE BOOL "Define obsolete Andrid-specific cmake variables" )
+mark_as_advanced( ANDROID_SET_OBSOLETE_VARIABLES )
+if( ANDROID_SET_OBSOLETE_VARIABLES )
+ set( ANDROID_API_LEVEL ${ANDROID_NATIVE_API_LEVEL} )
+ set( ARM_TARGET "${ANDROID_ABI}" )
+ set( ARMEABI_NDK_NAME "${ANDROID_NDK_ABI_NAME}" )
+endif()
+
+
+# Variables controlling behavior or set by cmake toolchain:
+# ANDROID_ABI : "armeabi-v7a" (default), "armeabi", "armeabi-v7a with NEON", "armeabi-v7a with VFPV3", "armeabi-v6 with VFP", "x86", "mips"
+# ANDROID_NATIVE_API_LEVEL : 3,4,5,8,9,14 (depends on NDK version)
+# ANDROID_STL : gnustl_static/gnustl_shared/stlport_static/stlport_shared/gabi++_static/gabi++_shared/system_re/system/none
+# ANDROID_FORBID_SYGWIN : ON/OFF
+# ANDROID_NO_UNDEFINED : ON/OFF
+# ANDROID_SO_UNDEFINED : OFF/ON (default depends on NDK version)
+# ANDROID_FUNCTION_LEVEL_LINKING : ON/OFF
+# ANDROID_GOLD_LINKER : ON/OFF
+# ANDROID_NOEXECSTACK : ON/OFF
+# ANDROID_RELRO : ON/OFF
+# ANDROID_FORCE_ARM_BUILD : ON/OFF
+# ANDROID_STL_FORCE_FEATURES : ON/OFF
+# ANDROID_SET_OBSOLETE_VARIABLES : ON/OFF
+# Can be set only at the first run:
+# ANDROID_NDK
+# ANDROID_STANDALONE_TOOLCHAIN
+# ANDROID_TOOLCHAIN_NAME : the NDK name of compiler toolchain
+# LIBRARY_OUTPUT_PATH_ROOT :
+# NDK_CCACHE :
+# Obsolete:
+# ANDROID_API_LEVEL : superseded by ANDROID_NATIVE_API_LEVEL
+# ARM_TARGET : superseded by ANDROID_ABI
+# ARM_TARGETS : superseded by ANDROID_ABI (can be set only)
+# ANDROID_NDK_TOOLCHAIN_ROOT : superseded by ANDROID_STANDALONE_TOOLCHAIN (can be set only)
+# ANDROID_USE_STLPORT : superseded by ANDROID_STL=stlport_static
+# ANDROID_LEVEL : superseded by ANDROID_NATIVE_API_LEVEL (completely removed)
+#
+# Primary read-only variables:
+# ANDROID : always TRUE
+# ARMEABI : TRUE for arm v6 and older devices
+# ARMEABI_V6 : TRUE for arm v6
+# ARMEABI_V7A : TRUE for arm v7a
+# NEON : TRUE if NEON unit is enabled
+# VFPV3 : TRUE if VFP version 3 is enabled
+# X86 : TRUE if configured for x86
+# MIPS : TRUE if configured for mips
+# BUILD_ANDROID : always TRUE
+# BUILD_WITH_ANDROID_NDK : TRUE if NDK is used
+# BUILD_WITH_STANDALONE_TOOLCHAIN : TRUE if standalone toolchain is used
+# ANDROID_NDK_HOST_SYSTEM_NAME : "windows", "linux-x86" or "darwin-x86" depending on host platform
+# ANDROID_NDK_ABI_NAME : "armeabi", "armeabi-v7a", "x86" or "mips" depending on ANDROID_ABI
+# ANDROID_NDK_RELEASE : one of r5, r5b, r5c, r6, r6b, r7, r7b, r7c, r8, r8b, r8c, r8d; set only for NDK
+# ANDROID_ARCH_NAME : "arm" or "x86" or "mips" depending on ANDROID_ABI
+# ANDROID_SYSROOT : path to the compiler sysroot
+# TOOL_OS_SUFFIX : "" or ".exe" depending on host platform
+# ANDROID_COMPILER_IS_CLANG : TRUE if clang compiler is used
+# Obsolete:
+# ARMEABI_NDK_NAME : superseded by ANDROID_NDK_ABI_NAME
+#
+# Secondary (less stable) read-only variables:
+# ANDROID_COMPILER_VERSION : GCC version used
+# ANDROID_CXX_FLAGS : C/C++ compiler flags required by Android platform
+# ANDROID_SUPPORTED_ABIS : list of currently allowed values for ANDROID_ABI
+# ANDROID_TOOLCHAIN_MACHINE_NAME : "arm-linux-androideabi", "arm-eabi" or "i686-android-linux"
+# ANDROID_TOOLCHAIN_ROOT : path to the top level of toolchain (standalone or placed inside NDK)
+# ANDROID_CLANG_TOOLCHAIN_ROOT : path to clang tools
+# ANDROID_SUPPORTED_NATIVE_API_LEVELS : list of native API levels found inside NDK
+# ANDROID_STL_INCLUDE_DIRS : stl include paths
+# ANDROID_RTTI : if rtti is enabled by the runtime
+# ANDROID_EXCEPTIONS : if exceptions are enabled by the runtime
+# ANDROID_GCC_TOOLCHAIN_NAME : read-only, differs from ANDROID_TOOLCHAIN_NAME only if clang is used
+# ANDROID_CLANG_VERSION : version of clang compiler if clang is used
+#
+# Defaults:
+# ANDROID_DEFAULT_NDK_API_LEVEL
+# ANDROID_DEFAULT_NDK_API_LEVEL_${ARCH}
+# ANDROID_NDK_SEARCH_PATHS
+# ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH
+# ANDROID_SUPPORTED_ABIS_${ARCH}
+# ANDROID_SUPPORTED_NDK_VERSIONS
diff --git a/cmake/ConfigOptions.cmake b/cmake/ConfigOptions.cmake
index c207611a5..ab5125866 100644
--- a/cmake/ConfigOptions.cmake
+++ b/cmake/ConfigOptions.cmake
@@ -99,3 +99,6 @@ option(WITH_DEBUG_X11_LOCAL_MOVESIZE "Print X11 Client local movesize debug mess
option(WITH_DEBUG_X11 "Print X11 Client debug messages" ${DEFAULT_DEBUG_OPTION})
option(WITH_DEBUG_XV "Print XVideo debug messages" ${DEFAULT_DEBUG_OPTION})
+if(ANDROID)
+include(ConfigOptionsAndroid)
+endif(ANDROID)
diff --git a/cmake/ConfigOptionsAndroid.cmake b/cmake/ConfigOptionsAndroid.cmake
new file mode 100644
index 000000000..535092f73
--- /dev/null
+++ b/cmake/ConfigOptionsAndroid.cmake
@@ -0,0 +1,20 @@
+# FreeRDP cmake android options
+#
+# Copyright 2013 Thinstuff Technologies GmbH
+# Copyright 2013 Bernhard Miklautz
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+option(WITH_DEBUG_ANDROID_JNI "Enable debug output for android jni bindings" ${DEFAULT_DEBUG_OPTION})
+option(ANDROID_BUILD_JAVA "Automatically android java code - build type depends on CMAKE_BUILD_TYPE" ON)
+option(ANDROID_BUILD_JAVA_DEBUG "Create a android debug package" ON)
diff --git a/cmake/FindOpenSSL.cmake b/cmake/FindOpenSSL.cmake
index cb47de37e..9ae015828 100644
--- a/cmake/FindOpenSSL.cmake
+++ b/cmake/FindOpenSSL.cmake
@@ -43,6 +43,8 @@ SET(_OPENSSL_ROOT_PATHS
"C:/OpenSSL/"
"C:/OpenSSL-Win32/"
"C:/OpenSSL-Win64/"
+ "/obj/local/armeabi/"
+ "/obj/local/armeabi-v7a/"
)
SET(_OPENSSL_ROOT_HINTS_AND_PATHS
HINTS ${_OPENSSL_ROOT_HINTS}
diff --git a/config.h.in b/config.h.in
index 1bb2b647f..d8717f3a5 100644
--- a/config.h.in
+++ b/config.h.in
@@ -72,4 +72,5 @@
#cmakedefine WITH_DEBUG_X11_CLIPRDR
#cmakedefine WITH_DEBUG_X11_LOCAL_MOVESIZE
#cmakedefine WITH_DEBUG_XV
+#cmakedefine WITH_DEBUG_ANDROID_JNI
#endif
diff --git a/docs/README.android b/docs/README.android
new file mode 100644
index 000000000..44a4dbd67
--- /dev/null
+++ b/docs/README.android
@@ -0,0 +1,158 @@
+Overview
+========
+
+The FreeRDP Android port consists of two parts: the Android Java GUI (client/Android)
+and the JNI bindings (client/Android/jni) which are written in C and wrap FreeRDP to
+be used with Java. The JNI is directly integrated within FreeRDPs build system.
+For building the GUI part there are three possibilities:
+* integrated ant build - have cmake to operate ant and build everything
+* manual ant build - build JNI with cmake and invoke ant manually for the GUI
+* IDE builds using your favourite IDE - use cmake for building JNI and your IDE for GUI
+
+Manual build and IDE build are mostly used for development.
+
+
+Build requirements
+=================
+
+For the Android port some additional dependencies need to be fulfilled:
+
+* for JNI
+- Android NDK (>= r8d)
+- prebuild static openssl libraries (see below)
+
+* for the Java GUI (if build with ant)
+- ant
+- Android SDK - version >= 21
+
+FreeRDP requires openssl libraries for building but they are not part of the
+Android NDK and therefore they need to be prebuild manually.
+Multiple source versions and builds of static openssl libraries are floating around.
+At the time of writing we have tested and used:
+https://github.com/bmiklautz/Android-external-openssl-ndk-static.
+However, any other static build should work as well.
+
+To build openssl:
+
+git clone git@github.com:bmiklautz/Android-external-openssl-ndk-static.git
+cd Android-external-openssl-ndk-static
+ndk-build # found in the Android NDK
+
+
+Building
+========
+
+Integrated build
+----------------
+
+Run the following commands in the top level freerdp directory. Don't
+forget to set ANDROID_NDK, ANDROID_SDK and FREERDP_ANDROID_EXTERNAL_SSL_PATH
+to the absolut paths on your machine:
+
+cmake -DCMAKE_TOOLCHAIN_FILE=cmake/AndroidToolchain.cmake \
+-DANDROID_NDK="_your_ndk_path_here_" \
+-DFREERDP_ANDROID_EXTERNAL_SSL_PATH="_your_ssl_build_root_path_" \
+-DANDROID_SDK="_your_sdk_path_here_"
+make
+
+After that you should have a client/Android/bin/aFreeRDP-debug.apk.
+
+Manual ant builds
+-----------------
+First run cmake to prepare the build and build JNI.
+Don't forget to set ANDROID_NDK, ANDROID_SDK and FREERDP_ANDROID_EXTERNAL_SSL_PATH
+to the absolut paths on your machine:
+
+cmake -DCMAKE_TOOLCHAIN_FILE=cmake/AndroidToolchain.cmake \
+-DANDROID_NDK="_your_ndk_path_here_" \
+-DFREERDP_ANDROID_EXTERNAL_SSL_PATH="_your_ssl_build_root_path_" \
+-DANDROID_SDK="_your_sdk_path_here_" -DANDROID_BUILD_JAVA=OFF
+make
+
+Now you can run your favourite ant command in client/Android like this:
+
+cd client/Android
+ant debug install
+
+Using an IDE
+------------
+Here is an example on how to use Eclipse for developing/building the Java GUI.
+For this the "Android development tools for Eclipse" are required (available via
+the eclipse marketplace).
+
+cmake -DCMAKE_TOOLCHAIN_FILE=cmake/AndroidToolchain.cmake \
+-DANDROID_NDK="_your_ndk_path_here_" \
+-DFREERDP_ANDROID_EXTERNAL_SSL_PATH="_your_ssl_build_root_path_" \
+-DANDROID_BUILD_JAVA=OFF
+make
+
+Open Eclipse and choose:
+File -> Import -> General -> Existing Projects into Workspace
+
+Browse to the client/Android directory in your FreeRDP folder and click finish.
+If you get something like "Project 'aFreeRDP' is missing required source
+folder: 'gen'" just build the project once and you are good to go.
+
+Side note: If you add -G "Eclipse CDT4 - Unix Makefiles" when running cmake
+you can also import FreeRDP into Eclipse too. Then you have one Java project and one c/c++
+project you can work on. This requires CDT to be installed in Eclipse.
+
+
+cmake variables
+===============
+
+JNI related
+-----------
+
+CMAKE_TOOLCHAIN_FILE
+* the toolchain file to use must be cmake/AndroidToolchain.cmake
+
+ANDROID_NDK (used from toolchain file)
+* absolute path to the NDK
+
+ANDROID_ABI (used from toolchain file)
+* Android ABI to build for (default is armeabi-v7a)
+
+FREERDP_ANDROID_EXTERNAL_SSL_PATH (used by FindOpenSSL)
+* absolut root path to the prebuild static openssl libraries
+
+WITH_DEBUG_ANDROID_JNI
+- enable debugging for JNI
+
+The libraries must be located in obj/local/armeabi/ or obj/local/armeabi-v7a/.
+Cmake searches in armeabi first then armeabi-v7a. It is possible to build FreeRDP
+for armeabi-v7a and link against armeabi openssl but not the other way around.
+Therefore it is suggested to build openssl for armeabi.
+
+Java GUI related
+----------------
+
+ANDROID_BUILD_JAVA (used by client/Android/CMakeLists.txt)
+* can be ON/OFF (default OFF) whether or not to build the GUI with cmake
+
+ANDROID_SDK (used by client/Android/CMakeLists.txt)
+* absolute path to the Android SDK to use
+
+This is used to generate local.properties needed by ant. Needs to be set
+if you build manually with ant or do integrated builds.
+
+
+Development
+===========
+
+Updating JNI headers
+--------------------
+
+Whenever the FreeRDP API changes or you need some extra functionality in your Java
+GUI the JNI needs to be updated. The JNI headers are generated from the compiled
+Java classes:
+
+* edit client/Android/src/com/freerdp/afreerdp/services/LibFreeRDP.Java to
+ reflect your changes
+* build the Android Java project as described above
+* run ./scripts/regenerate_JNI_headers.sh from your FreeRDP checkout top level
+ directory
+
+After that you need to implement the functionality in client/Android/jni/android_freerdp.c and add
+the call to client/Android/jni/generated/android_freerdp_jni.c.
+After that FreeRDP and the Android package need to be rebuilt to include the latest libfreerdp-android in the package.
diff --git a/include/freerdp/primitives.h b/include/freerdp/primitives.h
index 917a0159b..8bcd14c7b 100644
--- a/include/freerdp/primitives.h
+++ b/include/freerdp/primitives.h
@@ -23,6 +23,8 @@
#include
#include
+#include
+
typedef INT32 pstatus_t; /* match IppStatus. */
#define PRIMITIVES_SUCCESS (0) /* match ippStsNoErr */
diff --git a/include/freerdp/utils/bitmap.h b/include/freerdp/utils/bitmap.h
index d9425a823..b9d4dcf94 100644
--- a/include/freerdp/utils/bitmap.h
+++ b/include/freerdp/utils/bitmap.h
@@ -22,6 +22,14 @@
#include
+#ifdef __cplusplus
+extern "C" {
+#endif
+
FREERDP_API void freerdp_bitmap_write(char* filename, void* data, int width, int height, int bpp);
+#ifdef __cplusplus
+}
+#endif
+
#endif /* FREERDP_UTILS_BITMAP_H */
diff --git a/include/freerdp/utils/dsp.h b/include/freerdp/utils/dsp.h
index 9741ebf66..e2e209059 100644
--- a/include/freerdp/utils/dsp.h
+++ b/include/freerdp/utils/dsp.h
@@ -69,9 +69,17 @@ struct _FREERDP_DSP_CONTEXT
const BYTE* src, int size, int channels, int block_size);
};
+#ifdef __cplusplus
+extern "C" {
+#endif
+
FREERDP_API FREERDP_DSP_CONTEXT* freerdp_dsp_context_new(void);
FREERDP_API void freerdp_dsp_context_free(FREERDP_DSP_CONTEXT* context);
#define freerdp_dsp_context_reset_adpcm(_c) memset(&_c->adpcm, 0, sizeof(ADPCM))
+#ifdef __cplusplus
+}
+#endif
+
#endif /* FREERDP_UTILS_DSP_H */
diff --git a/include/freerdp/utils/event.h b/include/freerdp/utils/event.h
index 42b0d8a00..1e0c98595 100644
--- a/include/freerdp/utils/event.h
+++ b/include/freerdp/utils/event.h
@@ -23,8 +23,16 @@
#include
#include
+#ifdef __cplusplus
+extern "C" {
+#endif
+
FREERDP_API RDP_EVENT* freerdp_event_new(UINT16 event_class, UINT16 event_type,
RDP_EVENT_CALLBACK on_event_free_callback, void* user_data);
FREERDP_API void freerdp_event_free(RDP_EVENT* event);
+#ifdef __cplusplus
+}
+#endif
+
#endif /* FREERDP_UTILS_EVENT_H */
diff --git a/include/freerdp/utils/file.h b/include/freerdp/utils/file.h
index 80b0b1af0..45198e4b1 100644
--- a/include/freerdp/utils/file.h
+++ b/include/freerdp/utils/file.h
@@ -24,6 +24,10 @@
#include
#include
+#ifdef __cplusplus
+extern "C" {
+#endif
+
FREERDP_API void freerdp_mkdir(char* path);
FREERDP_API BOOL freerdp_check_file_exists(char* file);
FREERDP_API char* freerdp_get_home_path(rdpSettings* settings);
@@ -35,4 +39,8 @@ FREERDP_API char* freerdp_get_parent_path(char* base_path, int depth);
FREERDP_API BOOL freerdp_path_contains_separator(char* path);
FREERDP_API void freerdp_detect_paths(rdpSettings* settings);
+#ifdef __cplusplus
+}
+#endif
+
#endif /* FREERDP_UTILS_FILE_H */
diff --git a/include/freerdp/utils/list.h b/include/freerdp/utils/list.h
index bc93d786a..79a3ef350 100644
--- a/include/freerdp/utils/list.h
+++ b/include/freerdp/utils/list.h
@@ -40,6 +40,10 @@ struct _LIST
LIST_ITEM* tail;
};
+#ifdef __cplusplus
+extern "C" {
+#endif
+
FREERDP_API LIST* list_new(void);
FREERDP_API void list_free(LIST* list);
FREERDP_API void list_enqueue(LIST* list, void* data);
@@ -50,4 +54,8 @@ FREERDP_API void* list_next(LIST* list, void* data);
FREERDP_API void* list_remove(LIST* list, void* data);
FREERDP_API int list_size(LIST* list);
+#ifdef __cplusplus
+}
+#endif
+
#endif /* FREERDP_UTILS_LIST_H */
diff --git a/include/freerdp/utils/msusb.h b/include/freerdp/utils/msusb.h
index 3f0867e58..c48cf23fb 100644
--- a/include/freerdp/utils/msusb.h
+++ b/include/freerdp/utils/msusb.h
@@ -120,6 +120,10 @@ struct _MSUSB_CONFIG_DESCRIPTOR
int MsOutSize;
} __attribute__((packed));
+#ifdef __cplusplus
+extern "C" {
+#endif
+
/* MSUSB_PIPE exported functions */
FREERDP_API void msusb_mspipes_replace(MSUSB_INTERFACE_DESCRIPTOR* MsInterface, MSUSB_PIPE_DESCRIPTOR** NewMsPipes, UINT32 NewNumberOfPipes);
@@ -135,4 +139,8 @@ FREERDP_API MSUSB_CONFIG_DESCRIPTOR* msusb_msconfig_read(BYTE* data, UINT32 data
FREERDP_API int msusb_msconfig_write(MSUSB_CONFIG_DESCRIPTOR* MsConfg, BYTE* data, int * offset);
FREERDP_API void msusb_msconfig_dump(MSUSB_CONFIG_DESCRIPTOR* MsConfg);
+#ifdef __cplusplus
+}
+#endif
+
#endif /* FREERDP_UTILS_MSCONFIG_H */
diff --git a/include/freerdp/utils/passphrase.h b/include/freerdp/utils/passphrase.h
index 61260c4a0..24424bf40 100644
--- a/include/freerdp/utils/passphrase.h
+++ b/include/freerdp/utils/passphrase.h
@@ -23,6 +23,14 @@
#include
#include
+#ifdef __cplusplus
+extern "C" {
+#endif
+
FREERDP_API char* freerdp_passphrase_read(const char* prompt, char* buf, size_t bufsiz, int from_stdin);
+#ifdef __cplusplus
+}
+#endif
+
#endif /* FREERDP_UTILS_PASSPHRASE_H */
diff --git a/include/freerdp/utils/pcap.h b/include/freerdp/utils/pcap.h
index 1dcb33655..bf98bb5d3 100644
--- a/include/freerdp/utils/pcap.h
+++ b/include/freerdp/utils/pcap.h
@@ -68,6 +68,10 @@ struct rdp_pcap
};
typedef struct rdp_pcap rdpPcap;
+#ifdef __cplusplus
+extern "C" {
+#endif
+
FREERDP_API rdpPcap* pcap_open(char* name, BOOL write);
FREERDP_API void pcap_close(rdpPcap* pcap);
@@ -78,4 +82,8 @@ FREERDP_API BOOL pcap_get_next_record_header(rdpPcap* pcap, pcap_record* record)
FREERDP_API BOOL pcap_get_next_record_content(rdpPcap* pcap, pcap_record* record);
FREERDP_API void pcap_flush(rdpPcap* pcap);
+#ifdef __cplusplus
+}
+#endif
+
#endif /* FREERDP_UTILS_PCAP_H */
diff --git a/include/freerdp/utils/profiler.h b/include/freerdp/utils/profiler.h
index 742999f56..b4635317a 100644
--- a/include/freerdp/utils/profiler.h
+++ b/include/freerdp/utils/profiler.h
@@ -30,6 +30,10 @@ struct _PROFILER
};
typedef struct _PROFILER PROFILER;
+#ifdef __cplusplus
+extern "C" {
+#endif
+
FREERDP_API PROFILER* profiler_create(char* name);
FREERDP_API void profiler_free(PROFILER* profiler);
@@ -40,6 +44,10 @@ FREERDP_API void profiler_print_header();
FREERDP_API void profiler_print(PROFILER* profiler);
FREERDP_API void profiler_print_footer();
+#ifdef __cplusplus
+}
+#endif
+
#ifdef WITH_PROFILER
#define IF_PROFILER(then) then
#define PROFILER_DEFINE(prof) PROFILER* prof
diff --git a/include/freerdp/utils/rail.h b/include/freerdp/utils/rail.h
index e3888fa4d..80f7f6746 100644
--- a/include/freerdp/utils/rail.h
+++ b/include/freerdp/utils/rail.h
@@ -24,6 +24,10 @@
#include
#include
+#ifdef __cplusplus
+extern "C" {
+#endif
+
FREERDP_API void rail_unicode_string_alloc(RAIL_UNICODE_STRING* unicode_string, UINT16 cbString);
FREERDP_API void rail_unicode_string_free(RAIL_UNICODE_STRING* unicode_string);
FREERDP_API BOOL rail_read_unicode_string(STREAM* s, RAIL_UNICODE_STRING* unicode_string);
@@ -32,4 +36,8 @@ FREERDP_API void rail_write_unicode_string_value(STREAM* s, RAIL_UNICODE_STRING*
FREERDP_API void* rail_clone_order(UINT32 event_type, void* order);
FREERDP_API void rail_free_cloned_order(UINT32 event_type, void* order);
+#ifdef __cplusplus
+}
+#endif
+
#endif /* FREERDP_UTILS_RAIL_H */
diff --git a/include/freerdp/utils/signal.h b/include/freerdp/utils/signal.h
index c257d4406..3dce2be25 100644
--- a/include/freerdp/utils/signal.h
+++ b/include/freerdp/utils/signal.h
@@ -31,6 +31,14 @@ extern struct termios orig_flags;
extern struct termios new_flags;
#endif
+#ifdef __cplusplus
+extern "C" {
+#endif
+
FREERDP_API int freerdp_handle_signals(void);
+#ifdef __cplusplus
+}
+#endif
+
#endif /* FREERDP_UTILS_SIGNAL_H */
diff --git a/include/freerdp/utils/stopwatch.h b/include/freerdp/utils/stopwatch.h
index 7911a6e71..a0bd1cf43 100644
--- a/include/freerdp/utils/stopwatch.h
+++ b/include/freerdp/utils/stopwatch.h
@@ -34,6 +34,10 @@ struct _STOPWATCH
};
typedef struct _STOPWATCH STOPWATCH;
+#ifdef __cplusplus
+extern "C" {
+#endif
+
FREERDP_API STOPWATCH* stopwatch_create();
FREERDP_API void stopwatch_free(STOPWATCH* stopwatch);
@@ -44,4 +48,8 @@ FREERDP_API void stopwatch_reset(STOPWATCH* stopwatch);
FREERDP_API double stopwatch_get_elapsed_time_in_seconds(STOPWATCH* stopwatch);
FREERDP_API void stopwatch_get_elapsed_time_in_useconds(STOPWATCH* stopwatch, UINT32* sec, UINT32* usec);
+#ifdef __cplusplus
+}
+#endif
+
#endif /* FREERDP_UTILS_STOPWATCH_H */
diff --git a/include/freerdp/utils/stream.h b/include/freerdp/utils/stream.h
index c4b43ac47..8e004eae8 100644
--- a/include/freerdp/utils/stream.h
+++ b/include/freerdp/utils/stream.h
@@ -33,6 +33,10 @@ struct _STREAM
};
typedef struct _STREAM STREAM;
+#ifdef __cplusplus
+extern "C" {
+#endif
+
FREERDP_API STREAM* stream_new(int size);
FREERDP_API void stream_free(STREAM* stream);
@@ -178,5 +182,10 @@ static INLINE BOOL stream_skip(STREAM* s, int sz) {
stream_seek(s, sz);
return TRUE;
}
+
+#ifdef __cplusplus
+}
+#endif
+
#endif /* FREERDP_UTILS_STREAM_H */
diff --git a/include/freerdp/utils/string.h b/include/freerdp/utils/string.h
index 68f805c80..4c3e2d9d3 100644
--- a/include/freerdp/utils/string.h
+++ b/include/freerdp/utils/string.h
@@ -32,7 +32,15 @@ struct rdp_string
};
typedef struct rdp_string rdpString;
+#ifdef __cplusplus
+extern "C" {
+#endif
+
FREERDP_API BOOL freerdp_string_read_length32(STREAM* s, rdpString* string);
FREERDP_API void freerdp_string_free(rdpString* string);
+#ifdef __cplusplus
+}
+#endif
+
#endif /* FREERDP_UTILS_STRING_H */
diff --git a/include/freerdp/utils/svc_plugin.h b/include/freerdp/utils/svc_plugin.h
index d905aed30..81060a7f3 100644
--- a/include/freerdp/utils/svc_plugin.h
+++ b/include/freerdp/utils/svc_plugin.h
@@ -50,10 +50,18 @@ struct rdp_svc_plugin
rdpSvcPluginPrivate* priv;
};
+#ifdef __cplusplus
+extern "C" {
+#endif
+
FREERDP_API void svc_plugin_init(rdpSvcPlugin* plugin, CHANNEL_ENTRY_POINTS* pEntryPoints);
FREERDP_API int svc_plugin_send(rdpSvcPlugin* plugin, STREAM* data_out);
FREERDP_API int svc_plugin_send_event(rdpSvcPlugin* plugin, RDP_EVENT* event);
+#ifdef __cplusplus
+}
+#endif
+
#ifdef WITH_DEBUG_SVC
#define DEBUG_SVC(fmt, ...) DEBUG_CLASS(SVC, fmt, ## __VA_ARGS__)
#else
diff --git a/include/freerdp/utils/tcp.h b/include/freerdp/utils/tcp.h
index 3a47708f5..3e3f0682a 100644
--- a/include/freerdp/utils/tcp.h
+++ b/include/freerdp/utils/tcp.h
@@ -23,6 +23,10 @@
#include
#include
+#ifdef __cplusplus
+extern "C" {
+#endif
+
FREERDP_API int freerdp_tcp_connect(const char* hostname, int port);
FREERDP_API int freerdp_tcp_read(int sockfd, BYTE* data, int length);
FREERDP_API int freerdp_tcp_write(int sockfd, BYTE* data, int length);
@@ -35,4 +39,8 @@ FREERDP_API int freerdp_tcp_set_no_delay(int sockfd, BOOL no_delay);
FREERDP_API int freerdp_wsa_startup();
FREERDP_API int freerdp_wsa_cleanup();
+#ifdef __cplusplus
+}
+#endif
+
#endif /* FREERDP_TCP_UTILS_H */
diff --git a/include/freerdp/utils/thread.h b/include/freerdp/utils/thread.h
index 35997348b..4d21b493e 100644
--- a/include/freerdp/utils/thread.h
+++ b/include/freerdp/utils/thread.h
@@ -41,11 +41,19 @@ struct _freerdp_thread
int status;
};
+#ifdef __cplusplus
+extern "C" {
+#endif
+
FREERDP_API freerdp_thread* freerdp_thread_new(void);
FREERDP_API void freerdp_thread_start(freerdp_thread* thread, void* func, void* arg);
FREERDP_API void freerdp_thread_stop(freerdp_thread* thread);
FREERDP_API void freerdp_thread_free(freerdp_thread* thread);
+#ifdef __cplusplus
+}
+#endif
+
#define freerdp_thread_wait(_t) ((WaitForMultipleObjects(_t->num_signals, _t->signals, FALSE, INFINITE) == WAIT_FAILED) ? -1 : 0)
#define freerdp_thread_wait_timeout(_t, _timeout) ((WaitForMultipleObjects(_t->num_signals, _t->signals, FALSE, _timeout) == WAIT_FAILED) ? -1 : 0)
#define freerdp_thread_is_stopped(_t) (WaitForSingleObject(_t->signals[0], 0) == WAIT_OBJECT_0)
diff --git a/include/freerdp/utils/time.h b/include/freerdp/utils/time.h
index 7d47ecdef..2fd74cf0a 100644
--- a/include/freerdp/utils/time.h
+++ b/include/freerdp/utils/time.h
@@ -30,8 +30,16 @@
#include
#include
+#ifdef __cplusplus
+extern "C" {
+#endif
+
FREERDP_API UINT64 freerdp_windows_gmtime();
FREERDP_API UINT64 freerdp_get_windows_time_from_unix_time(time_t unix_time);
FREERDP_API time_t freerdp_get_unix_time_from_windows_time(UINT64 windows_time);
+#ifdef __cplusplus
+}
+#endif
+
#endif /* FREERDP_TIME_UTILS_H */
diff --git a/include/freerdp/utils/uds.h b/include/freerdp/utils/uds.h
index 81961c064..2c6a747e7 100644
--- a/include/freerdp/utils/uds.h
+++ b/include/freerdp/utils/uds.h
@@ -23,6 +23,14 @@
#include
#include
+#ifdef __cplusplus
+extern "C" {
+#endif
+
FREERDP_API int freerdp_uds_connect(const char* path);
+#ifdef __cplusplus
+}
+#endif
+
#endif /* FREERDP_UTILS_UDS_H */
diff --git a/libfreerdp/codec/CMakeLists.txt b/libfreerdp/codec/CMakeLists.txt
index 0276ac1aa..3e7717977 100644
--- a/libfreerdp/codec/CMakeLists.txt
+++ b/libfreerdp/codec/CMakeLists.txt
@@ -100,7 +100,7 @@ set_complex_link_libraries(VARIABLE ${MODULE_PREFIX}_LIBS
set_complex_link_libraries(VARIABLE ${MODULE_PREFIX}_LIBS
MONOLITHIC ${MONOLITHIC_BUILD}
MODULE winpr
- MODULES winpr-crt winpr-pool winpr-registry winpr-utils)
+ MODULES winpr-crt winpr-pool winpr-registry winpr-sysinfo winpr-utils)
if(MONOLITHIC_BUILD)
set(FREERDP_LIBS ${FREERDP_LIBS} ${${MODULE_PREFIX}_LIBS} PARENT_SCOPE)
diff --git a/libfreerdp/codec/rfx.c b/libfreerdp/codec/rfx.c
index 71e40caa7..c23171ef3 100644
--- a/libfreerdp/codec/rfx.c
+++ b/libfreerdp/codec/rfx.c
@@ -30,6 +30,8 @@
#endif
#include
+#include
+#include
#include
#include