1f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka/*
2f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka * Copyright (C) 2013 The Android Open Source Project
3f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka *
4f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka * Licensed under the Apache License, Version 2.0 (the "License");
5f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka * you may not use this file except in compliance with the License.
6f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka * You may obtain a copy of the License at
7f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka *
8f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka *      http://www.apache.org/licenses/LICENSE-2.0
9f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka *
10f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka * Unless required by applicable law or agreed to in writing, software
11f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka * distributed under the License is distributed on an "AS IS" BASIS,
12f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka * See the License for the specific language governing permissions and
14f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka * limitations under the License.
15f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka */
16f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
17748dfe025b128ea88ba626c8bfb7da315d5af29cSunny Goyalpackage com.android.wallpaperpicker;
18f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
19f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurkaimport android.content.Context;
20f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurkaimport android.graphics.Matrix;
21f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurkaimport android.graphics.Point;
22f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurkaimport android.graphics.RectF;
23f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurkaimport android.util.AttributeSet;
24f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurkaimport android.view.MotionEvent;
25f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurkaimport android.view.ScaleGestureDetector;
26f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurkaimport android.view.ScaleGestureDetector.OnScaleGestureListener;
27f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurkaimport android.view.ViewConfiguration;
28f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurkaimport android.view.ViewTreeObserver;
29f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurkaimport android.view.ViewTreeObserver.OnGlobalLayoutListener;
30f624f0bf0a28f1fcb193d6ed2a01c49e22b96e4aTony Wickham
31f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurkaimport com.android.photos.views.TiledImageRenderer.TileSource;
32f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurkaimport com.android.photos.views.TiledImageView;
33f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
34f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurkapublic class CropView extends TiledImageView implements OnScaleGestureListener {
35f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
36f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    private ScaleGestureDetector mScaleGestureDetector;
37f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    private long mTouchDownTime;
38f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    private float mFirstX, mFirstY;
39f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    private float mLastX, mLastY;
40f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    private float mCenterX, mCenterY;
41f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    private float mMinScale;
42f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    private boolean mTouchEnabled = true;
43f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    private RectF mTempEdges = new RectF();
44f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    private float[] mTempPoint = new float[] { 0, 0 };
45f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    private float[] mTempCoef = new float[] { 0, 0 };
46f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    private float[] mTempAdjustment = new float[] { 0, 0 };
47f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    private float[] mTempImageDims = new float[] { 0, 0 };
48f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    private float[] mTempRendererCenter = new float[] { 0, 0 };
49f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    TouchCallback mTouchCallback;
50f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    Matrix mRotateMatrix;
51f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    Matrix mInverseRotateMatrix;
52f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
53f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    public interface TouchCallback {
54f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        void onTouchDown();
55f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        void onTap();
56f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        void onTouchUp();
57f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    }
58f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
59f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    public CropView(Context context) {
60f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        this(context, null);
61f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    }
62f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
63f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    public CropView(Context context, AttributeSet attrs) {
64f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        super(context, attrs);
65f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        mScaleGestureDetector = new ScaleGestureDetector(context, this);
66f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        mRotateMatrix = new Matrix();
67f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        mInverseRotateMatrix = new Matrix();
68f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    }
69f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
70f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    private float[] getImageDims() {
71f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        final float imageWidth = mRenderer.source.getImageWidth();
72f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        final float imageHeight = mRenderer.source.getImageHeight();
73f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        float[] imageDims = mTempImageDims;
74f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        imageDims[0] = imageWidth;
75f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        imageDims[1] = imageHeight;
76f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        mRotateMatrix.mapPoints(imageDims);
77f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        imageDims[0] = Math.abs(imageDims[0]);
78f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        imageDims[1] = Math.abs(imageDims[1]);
79f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        return imageDims;
80f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    }
81f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
82f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    private void getEdgesHelper(RectF edgesOut) {
83f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        final float width = getWidth();
84f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        final float height = getHeight();
85f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        final float[] imageDims = getImageDims();
86f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        final float imageWidth = imageDims[0];
87f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        final float imageHeight = imageDims[1];
88f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
89f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        float initialCenterX = mRenderer.source.getImageWidth() / 2f;
90f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        float initialCenterY = mRenderer.source.getImageHeight() / 2f;
91f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
92f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        float[] rendererCenter = mTempRendererCenter;
93f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        rendererCenter[0] = mCenterX - initialCenterX;
94f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        rendererCenter[1] = mCenterY - initialCenterY;
95f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        mRotateMatrix.mapPoints(rendererCenter);
96f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        rendererCenter[0] += imageWidth / 2;
97f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        rendererCenter[1] += imageHeight / 2;
98f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
99f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        final float scale = mRenderer.scale;
100f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        float centerX = (width / 2f - rendererCenter[0] + (imageWidth - width) / 2f)
101f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                * scale + width / 2f;
102f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        float centerY = (height / 2f - rendererCenter[1] + (imageHeight - height) / 2f)
103f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                * scale + height / 2f;
104f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        float leftEdge = centerX - imageWidth / 2f * scale;
105f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        float rightEdge = centerX + imageWidth / 2f * scale;
106f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        float topEdge = centerY - imageHeight / 2f * scale;
107f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        float bottomEdge = centerY + imageHeight / 2f * scale;
108f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
109f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        edgesOut.left = leftEdge;
110f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        edgesOut.right = rightEdge;
111f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        edgesOut.top = topEdge;
112f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        edgesOut.bottom = bottomEdge;
113f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    }
114f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
115f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    public int getImageRotation() {
116f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        return mRenderer.rotation;
117f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    }
118f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
119f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    public RectF getCrop() {
120f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        final RectF edges = mTempEdges;
121f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        getEdgesHelper(edges);
122f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        final float scale = mRenderer.scale;
123f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
124f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        float cropLeft = -edges.left / scale;
125f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        float cropTop = -edges.top / scale;
126f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        float cropRight = cropLeft + getWidth() / scale;
127f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        float cropBottom = cropTop + getHeight() / scale;
128f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
129f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        return new RectF(cropLeft, cropTop, cropRight, cropBottom);
130f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    }
131f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
132f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    public Point getSourceDimensions() {
133f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        return new Point(mRenderer.source.getImageWidth(), mRenderer.source.getImageHeight());
134f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    }
135f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
136f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    public void setTileSource(TileSource source, Runnable isReadyCallback) {
137f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        super.setTileSource(source, isReadyCallback);
138f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        mCenterX = mRenderer.centerX;
139f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        mCenterY = mRenderer.centerY;
140f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        mRotateMatrix.reset();
141f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        mRotateMatrix.setRotate(mRenderer.rotation);
142f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        mInverseRotateMatrix.reset();
143f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        mInverseRotateMatrix.setRotate(-mRenderer.rotation);
144f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        updateMinScale(getWidth(), getHeight(), source, true);
145f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    }
146f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
147f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
148f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        updateMinScale(w, h, mRenderer.source, false);
149f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    }
150f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
151f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    public void setScale(float scale) {
152f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        synchronized (mLock) {
153f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka            mRenderer.scale = scale;
154f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        }
155f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    }
156f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
157f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    private void updateMinScale(int w, int h, TileSource source, boolean resetScale) {
158f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        synchronized (mLock) {
159f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka            if (resetScale) {
160f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                mRenderer.scale = 1;
161f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka            }
162f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka            if (source != null) {
163f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                final float[] imageDims = getImageDims();
164f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                final float imageWidth = imageDims[0];
165f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                final float imageHeight = imageDims[1];
166f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                mMinScale = Math.max(w / imageWidth, h / imageHeight);
167f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                mRenderer.scale =
168f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                        Math.max(mMinScale, resetScale ? Float.MIN_VALUE : mRenderer.scale);
169f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka            }
170f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        }
171f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    }
172f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
173f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    @Override
174f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    public boolean onScaleBegin(ScaleGestureDetector detector) {
175f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        return true;
176f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    }
177f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
178f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    @Override
179f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    public boolean onScale(ScaleGestureDetector detector) {
180f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        // Don't need the lock because this will only fire inside of
181f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        // onTouchEvent
182f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        mRenderer.scale *= detector.getScaleFactor();
183f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        mRenderer.scale = Math.max(mMinScale, mRenderer.scale);
184f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        invalidate();
185f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        return true;
186f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    }
187f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
188f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    @Override
189f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    public void onScaleEnd(ScaleGestureDetector detector) {
190f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    }
191f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
1927bd5e80dc5e26102e59b300dec8dfd8abd9184b4Tony Wickham    /**
1937bd5e80dc5e26102e59b300dec8dfd8abd9184b4Tony Wickham     * Offsets wallpaper preview according to the state it will be displayed in upon returning home.
1947bd5e80dc5e26102e59b300dec8dfd8abd9184b4Tony Wickham     * @param offset Ranges from 0 to 1, where 0 is the leftmost parallax and 1 is the rightmost.
1957bd5e80dc5e26102e59b300dec8dfd8abd9184b4Tony Wickham     */
19613d3a076ce25c531c450d139b37b3e5bbd6964e1Tony Wickham    public void setParallaxOffset(float offset, RectF crop) {
1977bd5e80dc5e26102e59b300dec8dfd8abd9184b4Tony Wickham        offset = Math.max(0, Math.min(offset, 1)); // Make sure the offset is in the correct range.
198f624f0bf0a28f1fcb193d6ed2a01c49e22b96e4aTony Wickham        float screenWidth = getWidth() / mRenderer.scale;
19913d3a076ce25c531c450d139b37b3e5bbd6964e1Tony Wickham        mCenterX = screenWidth / 2 + offset * (crop.width() - screenWidth) + crop.left;
2007bd5e80dc5e26102e59b300dec8dfd8abd9184b4Tony Wickham        updateCenter();
2017bd5e80dc5e26102e59b300dec8dfd8abd9184b4Tony Wickham    }
2027bd5e80dc5e26102e59b300dec8dfd8abd9184b4Tony Wickham
203f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    public void moveToLeft() {
204f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        if (getWidth() == 0 || getHeight() == 0) {
205f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka            final ViewTreeObserver observer = getViewTreeObserver();
206f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka            observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
207f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                    public void onGlobalLayout() {
208f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                        moveToLeft();
209f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                        getViewTreeObserver().removeOnGlobalLayoutListener(this);
210f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                    }
211f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                });
212f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        }
213f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        final RectF edges = mTempEdges;
214f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        getEdgesHelper(edges);
215f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        final float scale = mRenderer.scale;
216f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        mCenterX += Math.ceil(edges.left / scale);
217f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        updateCenter();
218f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    }
219f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
220f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    private void updateCenter() {
221f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        mRenderer.centerX = Math.round(mCenterX);
222f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        mRenderer.centerY = Math.round(mCenterY);
223f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    }
224f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
225f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    public void setTouchEnabled(boolean enabled) {
226f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        mTouchEnabled = enabled;
227f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    }
228f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
229f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    public void setTouchCallback(TouchCallback cb) {
230f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        mTouchCallback = cb;
231f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    }
232f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
233f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    @Override
234f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    public boolean onTouchEvent(MotionEvent event) {
235f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        int action = event.getActionMasked();
236f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
237f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        final int skipIndex = pointerUp ? event.getActionIndex() : -1;
238f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
239f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        // Determine focal point
240f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        float sumX = 0, sumY = 0;
241f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        final int count = event.getPointerCount();
242f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        for (int i = 0; i < count; i++) {
243f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka            if (skipIndex == i)
244f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                continue;
245f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka            sumX += event.getX(i);
246f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka            sumY += event.getY(i);
247f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        }
248f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        final int div = pointerUp ? count - 1 : count;
249f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        float x = sumX / div;
250f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        float y = sumY / div;
251f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
252f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        if (action == MotionEvent.ACTION_DOWN) {
253f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka            mFirstX = x;
254f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka            mFirstY = y;
255f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka            mTouchDownTime = System.currentTimeMillis();
256f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka            if (mTouchCallback != null) {
257f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                mTouchCallback.onTouchDown();
258f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka            }
259f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        } else if (action == MotionEvent.ACTION_UP) {
260f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka            ViewConfiguration config = ViewConfiguration.get(getContext());
261f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
262f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka            float squaredDist = (mFirstX - x) * (mFirstX - x) + (mFirstY - y) * (mFirstY - y);
263f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka            float slop = config.getScaledTouchSlop() * config.getScaledTouchSlop();
264f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka            long now = System.currentTimeMillis();
265f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka            if (mTouchCallback != null) {
266f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                // only do this if it's a small movement
267f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                if (squaredDist < slop &&
268f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                        now < mTouchDownTime + ViewConfiguration.getTapTimeout()) {
269f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                    mTouchCallback.onTap();
270f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                }
271f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                mTouchCallback.onTouchUp();
272f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka            }
273f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        }
274f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
275f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        if (!mTouchEnabled) {
276f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka            return true;
277f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        }
278f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
279f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        synchronized (mLock) {
280f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka            mScaleGestureDetector.onTouchEvent(event);
281f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka            switch (action) {
282f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                case MotionEvent.ACTION_MOVE:
283f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                    float[] point = mTempPoint;
284f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                    point[0] = (mLastX - x) / mRenderer.scale;
285f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                    point[1] = (mLastY - y) / mRenderer.scale;
286f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                    mInverseRotateMatrix.mapPoints(point);
287f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                    mCenterX += point[0];
288f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                    mCenterY += point[1];
289f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                    updateCenter();
290f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                    invalidate();
291f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                    break;
292f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka            }
293f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka            if (mRenderer.source != null) {
294f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                // Adjust position so that the wallpaper covers the entire area
295f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                // of the screen
296f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                final RectF edges = mTempEdges;
297f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                getEdgesHelper(edges);
298f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                final float scale = mRenderer.scale;
299f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
300f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                float[] coef = mTempCoef;
301f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                coef[0] = 1;
302f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                coef[1] = 1;
303f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                mRotateMatrix.mapPoints(coef);
304f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                float[] adjustment = mTempAdjustment;
305f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                mTempAdjustment[0] = 0;
306f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                mTempAdjustment[1] = 0;
307f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                if (edges.left > 0) {
308f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                    adjustment[0] = edges.left / scale;
309f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                } else if (edges.right < getWidth()) {
310f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                    adjustment[0] = (edges.right - getWidth()) / scale;
311f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                }
312f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                if (edges.top > 0) {
313afb348680c86b73f61c8886cb1c05b9f76dd4b00Neil Fuller                    adjustment[1] = (float) Math.ceil(edges.top / scale);
314f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                } else if (edges.bottom < getHeight()) {
315f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                    adjustment[1] = (edges.bottom - getHeight()) / scale;
316f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                }
317f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                for (int dim = 0; dim <= 1; dim++) {
318afb348680c86b73f61c8886cb1c05b9f76dd4b00Neil Fuller                    if (coef[dim] > 0) adjustment[dim] = (float) Math.ceil(adjustment[dim]);
319f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                }
320f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
321f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                mInverseRotateMatrix.mapPoints(adjustment);
322f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                mCenterX += adjustment[0];
323f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                mCenterY += adjustment[1];
324f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka                updateCenter();
325f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka            }
326f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        }
327f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka
328f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        mLastX = x;
329f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        mLastY = y;
330f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka        return true;
331f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka    }
332f1b220be2b401f203cfb490f2e78af32700ef4bbMichael Jurka}
333