1e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka/*
2e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka * Copyright (C) 2013 The Android Open Source Project
3e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka *
4e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka * Licensed under the Apache License, Version 2.0 (the "License");
5e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka * you may not use this file except in compliance with the License.
6e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka * You may obtain a copy of the License at
7e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka *
8e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka *      http://www.apache.org/licenses/LICENSE-2.0
9e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka *
10e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka * Unless required by applicable law or agreed to in writing, software
11e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka * distributed under the License is distributed on an "AS IS" BASIS,
12e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka * See the License for the specific language governing permissions and
14e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka * limitations under the License.
15e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka */
16e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka/* Copied from Launcher3 */
17e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkapackage com.android.wallpapercropper;
18e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
19e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport android.content.Context;
2069784065ea78550944b7eddb559c0dac952e20e8Michael Jurkaimport android.graphics.Matrix;
21e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport android.graphics.Point;
22e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport android.graphics.RectF;
23e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport android.util.AttributeSet;
24e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport android.view.MotionEvent;
25e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport android.view.ScaleGestureDetector;
26e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport android.view.ScaleGestureDetector.OnScaleGestureListener;
27e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurkaimport android.view.ViewConfiguration;
28e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport android.view.ViewTreeObserver;
29e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport android.view.ViewTreeObserver.OnGlobalLayoutListener;
30e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
31e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport com.android.photos.views.TiledImageRenderer.TileSource;
32e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport com.android.photos.views.TiledImageView;
33e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
34e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkapublic class CropView extends TiledImageView implements OnScaleGestureListener {
35e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
36e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private ScaleGestureDetector mScaleGestureDetector;
37e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private long mTouchDownTime;
38e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private float mFirstX, mFirstY;
39e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private float mLastX, mLastY;
4069784065ea78550944b7eddb559c0dac952e20e8Michael Jurka    private float mCenterX, mCenterY;
41e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private float mMinScale;
42e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private boolean mTouchEnabled = true;
43e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private RectF mTempEdges = new RectF();
4469784065ea78550944b7eddb559c0dac952e20e8Michael Jurka    private float[] mTempPoint = new float[] { 0, 0 };
4569784065ea78550944b7eddb559c0dac952e20e8Michael Jurka    private float[] mTempCoef = new float[] { 0, 0 };
4669784065ea78550944b7eddb559c0dac952e20e8Michael Jurka    private float[] mTempAdjustment = new float[] { 0, 0 };
4769784065ea78550944b7eddb559c0dac952e20e8Michael Jurka    private float[] mTempImageDims = new float[] { 0, 0 };
4869784065ea78550944b7eddb559c0dac952e20e8Michael Jurka    private float[] mTempRendererCenter = new float[] { 0, 0 };
49e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    TouchCallback mTouchCallback;
5069784065ea78550944b7eddb559c0dac952e20e8Michael Jurka    Matrix mRotateMatrix;
5169784065ea78550944b7eddb559c0dac952e20e8Michael Jurka    Matrix mInverseRotateMatrix;
52e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
53e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    public interface TouchCallback {
54e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        void onTouchDown();
55e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        void onTap();
56e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka        void onTouchUp();
57e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
58e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
59e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    public CropView(Context context) {
60e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        this(context, null);
61e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
62e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
63e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    public CropView(Context context, AttributeSet attrs) {
64e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        super(context, attrs);
65e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        mScaleGestureDetector = new ScaleGestureDetector(context, this);
6669784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        mRotateMatrix = new Matrix();
6769784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        mInverseRotateMatrix = new Matrix();
6869784065ea78550944b7eddb559c0dac952e20e8Michael Jurka    }
6969784065ea78550944b7eddb559c0dac952e20e8Michael Jurka
7069784065ea78550944b7eddb559c0dac952e20e8Michael Jurka    private float[] getImageDims() {
7169784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        final float imageWidth = mRenderer.source.getImageWidth();
7269784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        final float imageHeight = mRenderer.source.getImageHeight();
7369784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        float[] imageDims = mTempImageDims;
7469784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        imageDims[0] = imageWidth;
7569784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        imageDims[1] = imageHeight;
7669784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        mRotateMatrix.mapPoints(imageDims);
7769784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        imageDims[0] = Math.abs(imageDims[0]);
7869784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        imageDims[1] = Math.abs(imageDims[1]);
7969784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        return imageDims;
80e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
81e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
82e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private void getEdgesHelper(RectF edgesOut) {
83e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        final float width = getWidth();
84e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        final float height = getHeight();
8569784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        final float[] imageDims = getImageDims();
8669784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        final float imageWidth = imageDims[0];
8769784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        final float imageHeight = imageDims[1];
8869784065ea78550944b7eddb559c0dac952e20e8Michael Jurka
8969784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        float initialCenterX = mRenderer.source.getImageWidth() / 2f;
9069784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        float initialCenterY = mRenderer.source.getImageHeight() / 2f;
9169784065ea78550944b7eddb559c0dac952e20e8Michael Jurka
9269784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        float[] rendererCenter = mTempRendererCenter;
9369784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        rendererCenter[0] = mCenterX - initialCenterX;
9469784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        rendererCenter[1] = mCenterY - initialCenterY;
9569784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        mRotateMatrix.mapPoints(rendererCenter);
9669784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        rendererCenter[0] += imageWidth / 2;
9769784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        rendererCenter[1] += imageHeight / 2;
9869784065ea78550944b7eddb559c0dac952e20e8Michael Jurka
99e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        final float scale = mRenderer.scale;
10069784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        float centerX = (width / 2f - rendererCenter[0] + (imageWidth - width) / 2f)
101e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                * scale + width / 2f;
10269784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        float centerY = (height / 2f - rendererCenter[1] + (imageHeight - height) / 2f)
103e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                * scale + height / 2f;
104e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        float leftEdge = centerX - imageWidth / 2f * scale;
105e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        float rightEdge = centerX + imageWidth / 2f * scale;
106e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        float topEdge = centerY - imageHeight / 2f * scale;
107e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        float bottomEdge = centerY + imageHeight / 2f * scale;
108e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
109e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        edgesOut.left = leftEdge;
110e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        edgesOut.right = rightEdge;
111e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        edgesOut.top = topEdge;
112e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        edgesOut.bottom = bottomEdge;
113e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
114e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
11569784065ea78550944b7eddb559c0dac952e20e8Michael Jurka    public int getImageRotation() {
11669784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        return mRenderer.rotation;
11769784065ea78550944b7eddb559c0dac952e20e8Michael Jurka    }
11869784065ea78550944b7eddb559c0dac952e20e8Michael Jurka
119e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    public RectF getCrop() {
120e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        final RectF edges = mTempEdges;
121e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        getEdgesHelper(edges);
122e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        final float scale = mRenderer.scale;
123e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
124e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        float cropLeft = -edges.left / scale;
125e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        float cropTop = -edges.top / scale;
126e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        float cropRight = cropLeft + getWidth() / scale;
127e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        float cropBottom = cropTop + getHeight() / scale;
128e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
129e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        return new RectF(cropLeft, cropTop, cropRight, cropBottom);
130e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
131e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
132e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    public Point getSourceDimensions() {
133e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        return new Point(mRenderer.source.getImageWidth(), mRenderer.source.getImageHeight());
134e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
135e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
136e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    public void setTileSource(TileSource source, Runnable isReadyCallback) {
137e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        super.setTileSource(source, isReadyCallback);
13869784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        mCenterX = mRenderer.centerX;
13969784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        mCenterY = mRenderer.centerY;
14069784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        mRotateMatrix.reset();
14169784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        mRotateMatrix.setRotate(mRenderer.rotation);
14269784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        mInverseRotateMatrix.reset();
14369784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        mInverseRotateMatrix.setRotate(-mRenderer.rotation);
144e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        updateMinScale(getWidth(), getHeight(), source, true);
145e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
146e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
147e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
148e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        updateMinScale(w, h, mRenderer.source, false);
149e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
150e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
151e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    public void setScale(float scale) {
152e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        synchronized (mLock) {
153e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            mRenderer.scale = scale;
154e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
155e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
156e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
157e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private void updateMinScale(int w, int h, TileSource source, boolean resetScale) {
158e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        synchronized (mLock) {
159e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            if (resetScale) {
160e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                mRenderer.scale = 1;
161e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            }
162e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            if (source != null) {
16369784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                final float[] imageDims = getImageDims();
16469784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                final float imageWidth = imageDims[0];
16569784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                final float imageHeight = imageDims[1];
16669784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                mMinScale = Math.max(w / imageWidth, h / imageHeight);
167c82618c660bbb76df05c11c548cc01724f5e5fc9Michael Jurka                mRenderer.scale =
168c82618c660bbb76df05c11c548cc01724f5e5fc9Michael Jurka                        Math.max(mMinScale, resetScale ? Float.MIN_VALUE : mRenderer.scale);
169e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            }
170e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
171e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
172e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
173e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    @Override
174e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    public boolean onScaleBegin(ScaleGestureDetector detector) {
175e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        return true;
176e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
177e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
178e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    @Override
179e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    public boolean onScale(ScaleGestureDetector detector) {
180e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        // Don't need the lock because this will only fire inside of
181e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        // onTouchEvent
182e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        mRenderer.scale *= detector.getScaleFactor();
183e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        mRenderer.scale = Math.max(mMinScale, mRenderer.scale);
184e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        invalidate();
185e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        return true;
186e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
187e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
188e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    @Override
189e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    public void onScaleEnd(ScaleGestureDetector detector) {
190e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
191e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
192e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka    public void moveToLeft() {
193e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        if (getWidth() == 0 || getHeight() == 0) {
194e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            final ViewTreeObserver observer = getViewTreeObserver();
195e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
196e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    public void onGlobalLayout() {
197e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka                        moveToLeft();
198e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                        getViewTreeObserver().removeOnGlobalLayoutListener(this);
199e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    }
200e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                });
201e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
202e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        final RectF edges = mTempEdges;
203e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        getEdgesHelper(edges);
204e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        final float scale = mRenderer.scale;
20569784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        mCenterX += Math.ceil(edges.left / scale);
20669784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        updateCenter();
20769784065ea78550944b7eddb559c0dac952e20e8Michael Jurka    }
20869784065ea78550944b7eddb559c0dac952e20e8Michael Jurka
20969784065ea78550944b7eddb559c0dac952e20e8Michael Jurka    private void updateCenter() {
21069784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        mRenderer.centerX = Math.round(mCenterX);
21169784065ea78550944b7eddb559c0dac952e20e8Michael Jurka        mRenderer.centerY = Math.round(mCenterY);
212e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
213e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
214e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    public void setTouchEnabled(boolean enabled) {
215e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        mTouchEnabled = enabled;
216e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
217e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
218e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    public void setTouchCallback(TouchCallback cb) {
219e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        mTouchCallback = cb;
220e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
221e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
222e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    @Override
223e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    public boolean onTouchEvent(MotionEvent event) {
224e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        int action = event.getActionMasked();
225e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
226e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        final int skipIndex = pointerUp ? event.getActionIndex() : -1;
227e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
228e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        // Determine focal point
229e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        float sumX = 0, sumY = 0;
230e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        final int count = event.getPointerCount();
231e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        for (int i = 0; i < count; i++) {
232e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            if (skipIndex == i)
233e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                continue;
234e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            sumX += event.getX(i);
235e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            sumY += event.getY(i);
236e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
237e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        final int div = pointerUp ? count - 1 : count;
238e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        float x = sumX / div;
239e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        float y = sumY / div;
240e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
241e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        if (action == MotionEvent.ACTION_DOWN) {
242e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            mFirstX = x;
243e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            mFirstY = y;
244e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            mTouchDownTime = System.currentTimeMillis();
245e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            if (mTouchCallback != null) {
246e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                mTouchCallback.onTouchDown();
247e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            }
248e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        } else if (action == MotionEvent.ACTION_UP) {
249e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            ViewConfiguration config = ViewConfiguration.get(getContext());
250e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
251e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            float squaredDist = (mFirstX - x) * (mFirstX - x) + (mFirstY - y) * (mFirstY - y);
252e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            float slop = config.getScaledTouchSlop() * config.getScaledTouchSlop();
253e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            long now = System.currentTimeMillis();
254e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka            if (mTouchCallback != null) {
255e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka                // only do this if it's a small movement
256e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka                if (squaredDist < slop &&
25769784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                        now < mTouchDownTime + ViewConfiguration.getTapTimeout()) {
258e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka                    mTouchCallback.onTap();
259e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka                }
260e72aa7f0a64ff55f747751b7972ccb8acebab7daMichael Jurka                mTouchCallback.onTouchUp();
261e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            }
262e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
263e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
264e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        if (!mTouchEnabled) {
265e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            return true;
266e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
267e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
268e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        synchronized (mLock) {
269e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            mScaleGestureDetector.onTouchEvent(event);
270e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            switch (action) {
271e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                case MotionEvent.ACTION_MOVE:
27269784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                    float[] point = mTempPoint;
27369784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                    point[0] = (mLastX - x) / mRenderer.scale;
27469784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                    point[1] = (mLastY - y) / mRenderer.scale;
27569784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                    mInverseRotateMatrix.mapPoints(point);
27669784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                    mCenterX += point[0];
27769784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                    mCenterY += point[1];
27869784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                    updateCenter();
279e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    invalidate();
280e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    break;
281e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            }
282e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            if (mRenderer.source != null) {
283e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                // Adjust position so that the wallpaper covers the entire area
284e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                // of the screen
285e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                final RectF edges = mTempEdges;
286e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                getEdgesHelper(edges);
287e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                final float scale = mRenderer.scale;
28869784065ea78550944b7eddb559c0dac952e20e8Michael Jurka
28969784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                float[] coef = mTempCoef;
29069784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                coef[0] = 1;
29169784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                coef[1] = 1;
29269784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                mRotateMatrix.mapPoints(coef);
29369784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                float[] adjustment = mTempAdjustment;
29469784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                mTempAdjustment[0] = 0;
29569784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                mTempAdjustment[1] = 0;
296e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                if (edges.left > 0) {
29769784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                    adjustment[0] = edges.left / scale;
29869784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                } else if (edges.right < getWidth()) {
29969784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                    adjustment[0] = (edges.right - getWidth()) / scale;
300e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                }
301e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                if (edges.top > 0) {
30233253a4baa6279f81a73425b49dfb6abe5f5416eNeil Fuller                    adjustment[1] = (float) Math.ceil(edges.top / scale);
30369784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                } else if (edges.bottom < getHeight()) {
30469784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                    adjustment[1] = (edges.bottom - getHeight()) / scale;
305e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                }
30669784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                for (int dim = 0; dim <= 1; dim++) {
30733253a4baa6279f81a73425b49dfb6abe5f5416eNeil Fuller                    if (coef[dim] > 0) adjustment[dim] = (float) Math.ceil(adjustment[dim]);
308e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                }
30969784065ea78550944b7eddb559c0dac952e20e8Michael Jurka
31069784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                mInverseRotateMatrix.mapPoints(adjustment);
31169784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                mCenterX += adjustment[0];
31269784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                mCenterY += adjustment[1];
31369784065ea78550944b7eddb559c0dac952e20e8Michael Jurka                updateCenter();
314e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            }
315e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
316e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
317e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        mLastX = x;
318e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        mLastY = y;
319e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        return true;
320e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
321e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka}
322