* #34178: new scroll container with parallax effect
diff --git a/res/values/psv__attr.xml b/res/values/psv__attr.xml
new file mode 100644
index 0000000..6ccc1a8
--- /dev/null
+++ b/res/values/psv__attr.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <declare-styleable name="ParallaxScrollView">
+
+        <!-- gravity -->
+        <attr name="parallexOffset" format="float" />
+    </declare-styleable>
+
+</resources>
\ No newline at end of file
diff --git a/src/org/sflphone/views/parallaxscrollview/AnotherView.java b/src/org/sflphone/views/parallaxscrollview/AnotherView.java
new file mode 100644
index 0000000..1b43d3c
--- /dev/null
+++ b/src/org/sflphone/views/parallaxscrollview/AnotherView.java
@@ -0,0 +1,49 @@
+package org.sflphone.views.parallaxscrollview;

+

+import android.content.Context;

+import android.graphics.Canvas;

+import android.util.AttributeSet;

+import android.widget.LinearLayout;

+

+public class AnotherView extends LinearLayout{

+

+	private ScrollCallbacks mCallbacks;

+

+    public AnotherView(Context context, AttributeSet attrs)

+    {

+        super(context, attrs);

+    }

+

+    @Override

+    protected void onScrollChanged(int l, int t, int oldl, int oldt)

+    {

+        super.onScrollChanged(l, t, oldl, oldt);

+        if (mCallbacks != null)

+        {

+            mCallbacks.onScrollChanged(l, t, oldl, oldt);

+        }

+    }

+

+    @Override

+    public int computeVerticalScrollRange()

+    {

+        return super.computeVerticalScrollRange();

+    }

+

+    public void setCallbacks(ScrollCallbacks listener)

+    {

+        mCallbacks = listener;

+    }

+

+    @Override

+    public void draw(Canvas canvas)

+    {

+        super.draw(canvas);

+    }

+

+    static interface ScrollCallbacks

+    {

+        public void onScrollChanged(int l, int t, int oldl, int oldt);

+    }

+	

+}

diff --git a/src/org/sflphone/views/parallaxscrollview/ObservableScrollView.java b/src/org/sflphone/views/parallaxscrollview/ObservableScrollView.java
new file mode 100644
index 0000000..921121a
--- /dev/null
+++ b/src/org/sflphone/views/parallaxscrollview/ObservableScrollView.java
@@ -0,0 +1,48 @@
+package org.sflphone.views.parallaxscrollview;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.widget.ScrollView;
+
+public class ObservableScrollView extends ScrollView
+{
+    private ScrollCallbacks mCallbacks;
+
+    public ObservableScrollView(Context context, AttributeSet attrs)
+    {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onScrollChanged(int l, int t, int oldl, int oldt)
+    {
+        super.onScrollChanged(l, t, oldl, oldt);
+        if (mCallbacks != null)
+        {
+            mCallbacks.onScrollChanged(l, t, oldl, oldt);
+        }
+    }
+
+    @Override
+    public int computeVerticalScrollRange()
+    {
+        return super.computeVerticalScrollRange();
+    }
+
+    public void setCallbacks(ScrollCallbacks listener)
+    {
+        mCallbacks = listener;
+    }
+
+    @Override
+    public void draw(Canvas canvas)
+    {
+        super.draw(canvas);
+    }
+
+    static interface ScrollCallbacks
+    {
+        public void onScrollChanged(int l, int t, int oldl, int oldt);
+    }
+}
\ No newline at end of file
diff --git a/src/org/sflphone/views/parallaxscrollview/ParallaxScrollView.java b/src/org/sflphone/views/parallaxscrollview/ParallaxScrollView.java
new file mode 100644
index 0000000..4102d10
--- /dev/null
+++ b/src/org/sflphone/views/parallaxscrollview/ParallaxScrollView.java
@@ -0,0 +1,396 @@
+package org.sflphone.views.parallaxscrollview;
+
+import org.sflphone.R;
+import org.sflphone.views.parallaxscrollview.ObservableScrollView.ScrollCallbacks;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ScrollView;
+
+/**
+ * A custom ScrollView that can accept a scroll listener.
+ */
+public class ParallaxScrollView extends ViewGroup
+{
+    private static final int DEFAULT_CHILD_GRAVITY = Gravity.CENTER_HORIZONTAL;
+
+    private static final String TAG = "ParallaxScrollView";
+
+    private static float PARALLAX_OFFSET_DEFAULT = 0.2f;
+
+    /**
+     * By how much should the background move to the foreground
+     */
+    private float mParallaxOffset = PARALLAX_OFFSET_DEFAULT;
+
+    private View mBackground;
+    private ObservableScrollView mScrollView;
+    private final ScrollCallbacks mScrollCallbacks = new ScrollCallbacks()
+    {
+        @Override
+        public void onScrollChanged(int l, int t, int oldl, int oldt)
+        {
+            requestLayout();
+        }
+    };
+
+    // Layout stuff
+
+    private int mBackgroundRight;
+    private int mBackgroundBottom;
+    /**
+     * Height of the Foreground ScrollView Content
+     */
+    private int mScrollContentHeight = 0;
+    /**
+     * Height of the ScrollView, should be the same as this view
+     */
+    private int mScrollViewHeight = 0;
+    /**
+     * The multipler by how much to move the background to the foreground
+     */
+    private float mScrollDiff = 0f;
+
+    public ParallaxScrollView(Context context, AttributeSet attrs, int defStyle)
+    {
+        super(context, attrs, defStyle);
+
+        TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
+                R.styleable.ParallaxScrollView, 0, 0);
+
+        try
+        {
+            setParallaxOffset(a.getFloat(R.styleable.ParallaxScrollView_parallexOffset,
+                    PARALLAX_OFFSET_DEFAULT));
+        }
+        catch (Exception e)
+        {
+        }
+        finally
+        {
+            a.recycle();
+        }
+    }
+
+    public ParallaxScrollView(Context context, AttributeSet attrs)
+    {
+        this(context, attrs, 0);
+    }
+
+    public ParallaxScrollView(Context context)
+    {
+        this(context, null);
+    }
+
+    @Override
+    public void addView(View child)
+    {
+        if (getChildCount() > 1)
+            throw new IllegalStateException("ParallaxScrollView can host only two direct children");
+
+        super.addView(child);
+    }
+
+    @Override
+    public void addView(View child, int index)
+    {
+        if (getChildCount() > 1)
+            throw new IllegalStateException("ParallaxScrollView can host only two direct children");
+        super.addView(child, index);
+    }
+
+    @Override
+    public void addView(View child, int index, android.view.ViewGroup.LayoutParams params)
+    {
+        if (getChildCount() > 1)
+            throw new IllegalStateException("ParallaxScrollView can host only two direct children");
+        super.addView(child, index, params);
+    }
+
+    @Override
+    public void addView(View child, int width, int height)
+    {
+        if (getChildCount() > 1)
+            throw new IllegalStateException("ParallaxScrollView can host only two direct children");
+        super.addView(child, width, height);
+    }
+
+    @Override
+    public void addView(View child, android.view.ViewGroup.LayoutParams params)
+    {
+        if (getChildCount() > 1)
+            throw new IllegalStateException("ParallaxScrollView can host only two direct children");
+        super.addView(child, params);
+    }
+
+    /**
+     * Set the offset
+     * 
+     * @param offset
+     *            a number greater than 0.0
+     */
+    public void setParallaxOffset(float offset)
+    {
+        // Make sure we only get to .05 of a floating number
+        offset = (float) Math.rint(offset * 100) / 100;
+        if (offset > 0.0)
+            mParallaxOffset = offset;
+        else
+            mParallaxOffset = PARALLAX_OFFSET_DEFAULT;
+
+        requestLayout();
+    }
+
+    /**
+     * Get the current offset
+     * 
+     * @return
+     */
+    public float getParallaxOffset()
+    {
+        return mParallaxOffset;
+    }
+
+    /**
+     * Sort the views out after they have been inflated from a layout
+     */
+    @Override
+    protected void onFinishInflate()
+    {
+        super.onFinishInflate();
+
+        if (getChildCount() > 2)
+        {
+            throw new IllegalStateException("ParallaxScrollView can host only two direct children");
+        }
+        organiseViews();
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
+    {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        if (mScrollView != null)
+        {
+            measureChild(mScrollView, MeasureSpec.makeMeasureSpec(
+                    MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.AT_MOST),
+                    MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec),
+                            MeasureSpec.AT_MOST));
+
+            mScrollContentHeight = mScrollView.getChildAt(0).getMeasuredHeight();
+            mScrollViewHeight = mScrollView.getMeasuredHeight();
+
+        }
+        if (mBackground != null)
+        {
+            int minHeight = 0;
+            minHeight = (int) (mScrollViewHeight + mParallaxOffset
+                    * (mScrollContentHeight - mScrollViewHeight));
+            minHeight = Math.max(minHeight, MeasureSpec.getSize(heightMeasureSpec));
+
+            measureChild(mBackground, MeasureSpec.makeMeasureSpec(
+                    MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY),
+                    MeasureSpec.makeMeasureSpec(minHeight, MeasureSpec.EXACTLY));
+
+            mBackgroundRight = getLeft() + mBackground.getMeasuredWidth();
+            mBackgroundBottom = getTop() + mBackground.getMeasuredHeight();
+
+            mScrollDiff = (float) (mBackground.getMeasuredHeight() - mScrollViewHeight)
+                    / (float) (mScrollContentHeight - mScrollViewHeight);
+        }
+
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom)
+    {
+        final int parentLeft = getPaddingLeft();
+        final int parentRight = right - left - getPaddingRight();
+        final int parentTop = getPaddingTop();
+        final int parentBottom = bottom - top - getPaddingBottom();
+        if (mScrollView != null && mScrollView.getVisibility() != GONE)
+        {
+            final FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mScrollView
+                    .getLayoutParams();
+
+            final int width = mScrollView.getMeasuredWidth();
+            final int height = mScrollView.getMeasuredHeight();
+
+            int childLeft;
+            int childTop;
+
+            int gravity = lp.gravity;
+            if (gravity == -1)
+            {
+                gravity = DEFAULT_CHILD_GRAVITY;
+            }
+
+            final int horizontalGravity = gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+            final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
+
+            switch (horizontalGravity)
+            {
+                case Gravity.LEFT:
+                    childLeft = parentLeft + lp.leftMargin;
+                    break;
+                case Gravity.CENTER_HORIZONTAL:
+                    childLeft = parentLeft + (parentRight - parentLeft - width) / 2 + lp.leftMargin
+                            - lp.rightMargin;
+                    break;
+                case Gravity.RIGHT:
+                    childLeft = parentRight - width - lp.rightMargin;
+                    break;
+                default:
+                    childLeft = parentLeft + lp.leftMargin;
+            }
+
+            switch (verticalGravity)
+            {
+                case Gravity.TOP:
+                    childTop = parentTop + lp.topMargin;
+                    break;
+                case Gravity.CENTER_VERTICAL:
+                    childTop = parentTop + (parentBottom - parentTop - height) / 2 + lp.topMargin
+                            - lp.bottomMargin;
+                    break;
+                case Gravity.BOTTOM:
+                    childTop = parentBottom - height - lp.bottomMargin;
+                    break;
+                default:
+                    childTop = parentTop + lp.topMargin;
+            }
+
+            mScrollView.layout(childLeft, childTop, childLeft + width, childTop + height);
+
+        }
+
+        if (mBackground != null)
+        {
+            final int scrollYCenterOffset = mScrollView.getScrollY();
+            final int offset = (int) (scrollYCenterOffset * mScrollDiff);
+            // Log.d(TAG, "Layout Scroll Y: " + scrollYCenterOffset +
+            // " ScrollDiff: " + mScrollDiff
+            // + " Background Offset:" + offset);
+            mBackground.layout(getLeft(), offset, mBackgroundRight, offset + mBackgroundBottom);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public LayoutParams generateLayoutParams(AttributeSet attrs)
+    {
+        return new FrameLayout.LayoutParams(getContext(), attrs);
+    }
+
+    @Override
+    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p)
+    {
+        return new FrameLayout.LayoutParams(p);
+    }
+
+    @Override
+    protected LayoutParams generateDefaultLayoutParams()
+    {
+        return new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
+                Gravity.CENTER_HORIZONTAL);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p)
+    {
+        return p instanceof FrameLayout.LayoutParams;
+    }
+
+    /**
+     * Take the direct children and sort them
+     */
+    private void organiseViews()
+    {
+        if (getChildCount() <= 0) return;
+
+        if (getChildCount() == 1)
+        {
+            // Get the only child
+            final View forground = getChildAt(0);
+            organiseBackgroundView(null);
+            organiseForgroundView(forground);
+        }
+        else if (getChildCount() == 2)
+        {
+            final View background = getChildAt(0);
+            final View foreground = getChildAt(1);
+
+            organiseBackgroundView(background);
+            organiseForgroundView(foreground);
+        }
+        else
+        {
+            throw new IllegalStateException("ParallaxScrollView can host only two direct children");
+        }
+    }
+
+    private void organiseBackgroundView(final View background)
+    {
+        mBackground = background;
+    }
+
+    private void organiseForgroundView(final View forground)
+    {
+        final int insertPos = getChildCount() - 1;
+
+        // See if its a observable scroll view?
+        if (forground instanceof ObservableScrollView)
+        {
+            // Attach the callback to it.
+            mScrollView = (ObservableScrollView) forground;
+        }
+        else if (forground instanceof ViewGroup && !(forground instanceof ScrollView))
+        {
+            // See if it is a view group but not a scroll view and wrap it
+            // with an observable ScrollView
+            mScrollView = new ObservableScrollView(getContext(), null);
+            removeView(forground);
+            mScrollView.addView(forground);
+            addView(mScrollView, insertPos);
+        }
+        else if (forground instanceof ScrollView)
+        {
+            final View child;
+            if (((ScrollView) forground).getChildCount() > 0)
+                child = ((ScrollView) forground).getChildAt(0);
+            else
+                child = null;
+
+            mScrollView = new ObservableScrollView(getContext(), null);
+            removeView(forground);
+            if (child != null) mScrollView.addView(child);
+            addView(mScrollView, insertPos);
+        }
+        else if (forground instanceof View)
+        {
+            mScrollView = new ObservableScrollView(getContext(), null);
+            removeView(forground);
+            mScrollView.addView(forground);
+            addView(mScrollView, insertPos);
+
+        }
+        if (mScrollView != null)
+        {
+            mScrollView.setLayoutParams(forground.getLayoutParams());
+            mScrollView.setCallbacks(mScrollCallbacks);
+            mScrollView.setFillViewport(true);
+        }
+    }
+
+}
\ No newline at end of file