content: /*
   * Copyright (C) 2013 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.
   */
  
  package com.lb.launcher;
  
  import android.content.Context;
  import android.graphics.Matrix;
  import android.graphics.Point;
  import android.graphics.RectF;
  import android.util.AttributeSet;
  import android.util.FloatMath;
  import android.view.MotionEvent;
  import android.view.ScaleGestureDetector;
  import android.view.ScaleGestureDetector.OnScaleGestureListener;
  import android.view.ViewConfiguration;
  import android.view.ViewTreeObserver;
  import android.view.ViewTreeObserver.OnGlobalLayoutListener;
  
  import com.android.photos.views.TiledImageRenderer.TileSource;
  import com.android.photos.views.TiledImageView;
  
  public class CropView extends TiledImageView implements OnScaleGestureListener {
  
      private ScaleGestureDetector mScaleGestureDetector;
      private long mTouchDownTime;
      private float mFirstX, mFirstY;
      private float mLastX, mLastY;
      private float mCenterX, mCenterY;
      private float mMinScale;
      private boolean mTouchEnabled = true;
      private RectF mTempEdges = new RectF();
      private float[] mTempPoint = new float[] { 0, 0 };
      private float[] mTempCoef = new float[] { 0, 0 };
      private float[] mTempAdjustment = new float[] { 0, 0 };
      private float[] mTempImageDims = new float[] { 0, 0 };
      private float[] mTempRendererCenter = new float[] { 0, 0 };
      TouchCallback mTouchCallback;
      Matrix mRotateMatrix;
      Matrix mInverseRotateMatrix;
  
      public interface TouchCallback {
          void onTouchDown();
          void onTap();
          void onTouchUp();
      }
  
      public CropView(Context context) {
          this(context, null);
      }
  
      public CropView(Context context, AttributeSet attrs) {
          super(context, attrs);
          mScaleGestureDetector = new ScaleGestureDetector(context, this);
          mRotateMatrix = new Matrix();
          mInverseRotateMatrix = new Matrix();
      }
  
      private float[] getImageDims() {
          final float imageWidth = mRenderer.source.getImageWidth();
          final float imageHeight = mRenderer.source.getImageHeight();
          float[] imageDims = mTempImageDims;
          imageDims[0] = imageWidth;
          imageDims[1] = imageHeight;
          mRotateMatrix.mapPoints(imageDims);
          imageDims[0] = Math.abs(imageDims[0]);
          imageDims[1] = Math.abs(imageDims[1]);
          return imageDims;
      }
  
      private void getEdgesHelper(RectF edgesOut) {
          final float width = getWidth();
          final float height = getHeight();
          final float[] imageDims = getImageDims();
          final float imageWidth = imageDims[0];
          final float imageHeight = imageDims[1];
  
          float initialCenterX = mRenderer.source.getImageWidth() / 2f;
          float initialCenterY = mRenderer.source.getImageHeight() / 2f;
  
          float[] rendererCenter = mTempRendererCenter;
          rendererCenter[0] = mCenterX - initialCenterX;
          rendererCenter[1] = mCenterY - initialCenterY;
          mRotateMatrix.mapPoints(rendererCenter);
          rendererCenter[0] += imageWidth / 2;
          rendererCenter[1] += imageHeight / 2;
  
          final float scale = mRenderer.scale;
          float centerX = (width / 2f - rendererCenter[0] + (imageWidth - width) / 2f)
                  * scale + width / 2f;
          float centerY = (height / 2f - rendererCenter[1] + (imageHeight - height) / 2f)
                  * scale + height / 2f;
          float leftEdge = centerX - imageWidth / 2f * scale;
          float rightEdge = centerX + imageWidth / 2f * scale;
          float topEdge = centerY - imageHeight / 2f * scale;
          float bottomEdge = centerY + imageHeight / 2f * scale;
  
          edgesOut.left = leftEdge;
          edgesOut.right = rightEdge;
          edgesOut.top = topEdge;
          edgesOut.bottom = bottomEdge;
      }
  
      public int getImageRotation() {
          return mRenderer.rotation;
      }
  
      public RectF getCrop() {
          final RectF edges = mTempEdges;
          getEdgesHelper(edges);
          final float scale = mRenderer.scale;
  
          float cropLeft = -edges.left / scale;
          float cropTop = -edges.top / scale;
          float cropRight = cropLeft + getWidth() / scale;
          float cropBottom = cropTop + getHeight() / scale;
  
          return new RectF(cropLeft, cropTop, cropRight, cropBottom);
      }
  
      public Point getSourceDimensions() {
          return new Point(mRenderer.source.getImageWidth(), mRenderer.source.getImageHeight());
      }
  
      public void setTileSource(TileSource source, Runnable isReadyCallback) {
          super.setTileSource(source, isReadyCallback);
          mCenterX = mRenderer.centerX;
          mCenterY = mRenderer.centerY;
          mRotateMatrix.reset();
          mRotateMatrix.setRotate(mRenderer.rotation);
          mInverseRotateMatrix.reset();
          mInverseRotateMatrix.setRotate(-mRenderer.rotation);
          updateMinScale(getWidth(), getHeight(), source, true);
      }
  
      protected void onSizeChanged(int w, int h, int oldw, int oldh) {
          updateMinScale(w, h, mRenderer.source, false);
      }
  
      public void setScale(float scale) {
          synchronized (mLock) {
              mRenderer.scale = scale;
          }
      }
  
      private void updateMinScale(int w, int h, TileSource source, boolean resetScale) {
          synchronized (mLock) {
              if (resetScale) {
                  mRenderer.scale = 1;
              }
              if (source != null) {
                  final float[] imageDims = getImageDims();
                  final float imageWidth = imageDims[0];
                  final float imageHeight = imageDims[1];
                  mMinScale = Math.max(w / imageWidth, h / imageHeight);
                  mRenderer.scale =
                          Math.max(mMinScale, resetScale ? Float.MIN_VALUE : mRenderer.scale);
              }
          }
      }
  
      @Override
      public boolean onScaleBegin(ScaleGestureDetector detector) {
          return true;
      }
  
      @Override
      public boolean onScale(ScaleGestureDetector detector) {
          // Don't need the lock because this will only fire inside of
          // onTouchEvent
          mRenderer.scale *= detector.getScaleFactor();
          mRenderer.scale = Math.max(mMinScale, mRenderer.scale);
          invalidate();
          return true;
      }
  
      @Override
      public void onScaleEnd(ScaleGestureDetector detector) {
      }
  
      public void moveToLeft() {
          if (getWidth() == 0 || getHeight() == 0) {
              final ViewTreeObserver observer = getViewTreeObserver();
              observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
                      public void onGlobalLayout() {
                          moveToLeft();
                          getViewTreeObserver().removeOnGlobalLayoutListener(this);
                      }
                  });
          }
          final RectF edges = mTempEdges;
          getEdgesHelper(edges);
          final float scale = mRenderer.scale;
          mCenterX += Math.ceil(edges.left / scale);
          updateCenter();
      }
  
      private void updateCenter() {
          mRenderer.centerX = Math.round(mCenterX);
          mRenderer.centerY = Math.round(mCenterY);
      }
  
      public void setTouchEnabled(boolean enabled) {
          mTouchEnabled = enabled;
      }
  
      public void setTouchCallback(TouchCallback cb) {
          mTouchCallback = cb;
      }
  
      @Override
      public boolean onTouchEvent(MotionEvent event) {
          int action = event.getActionMasked();
          final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
          final int skipIndex = pointerUp ? event.getActionIndex() : -1;
  
          // Determine focal point
          float sumX = 0, sumY = 0;
          final int count = event.getPointerCount();
          for (int i = 0; i < count; i++) {
              if (skipIndex == i)
                  continue;
              sumX += event.getX(i);
              sumY += event.getY(i);
          }
          final int div = pointerUp ? count - 1 : count;
          float x = sumX / div;
          float y = sumY / div;
  
          if (action == MotionEvent.ACTION_DOWN) {
              mFirstX = x;
              mFirstY = y;
              mTouchDownTime = System.currentTimeMillis();
              if (mTouchCallback != null) {
                  mTouchCallback.onTouchDown();
              }
          } else if (action == MotionEvent.ACTION_UP) {
              ViewConfiguration config = ViewConfiguration.get(getContext());
  
              float squaredDist = (mFirstX - x) * (mFirstX - x) + (mFirstY - y) * (mFirstY - y);
              float slop = config.getScaledTouchSlop() * config.getScaledTouchSlop();
              long now = System.currentTimeMillis();
              if (mTouchCallback != null) {
                  // only do this if it's a small movement
                  if (squaredDist < slop &&
                          now < mTouchDownTime + ViewConfiguration.getTapTimeout()) {
                      mTouchCallback.onTap();
                  }
                  mTouchCallback.onTouchUp();
              }
          }
  
          if (!mTouchEnabled) {
              return true;
          }
  
          synchronized (mLock) {
              mScaleGestureDetector.onTouchEvent(event);
              switch (action) {
                  case MotionEvent.ACTION_MOVE:
                      float[] point = mTempPoint;
                      point[0] = (mLastX - x) / mRenderer.scale;
                      point[1] = (mLastY - y) / mRenderer.scale;
                      mInverseRotateMatrix.mapPoints(point);
                      mCenterX += point[0];
                      mCenterY += point[1];
                      updateCenter();
                      invalidate();
                      break;
              }
              if (mRenderer.source != null) {
                  // Adjust position so that the wallpaper covers the entire area
                  // of the screen
                  final RectF edges = mTempEdges;
                  getEdgesHelper(edges);
                  final float scale = mRenderer.scale;
  
                  float[] coef = mTempCoef;
                  coef[0] = 1;
                  coef[1] = 1;
                  mRotateMatrix.mapPoints(coef);
                  float[] adjustment = mTempAdjustment;
                  mTempAdjustment[0] = 0;
                  mTempAdjustment[1] = 0;
                  if (edges.left > 0) {
                      adjustment[0] = edges.left / scale;
                  } else if (edges.right < getWidth()) {
                      adjustment[0] = (edges.right - getWidth()) / scale;
                  }
                  if (edges.top > 0) {
                      adjustment[1] = FloatMath.ceil(edges.top / scale);
                  } else if (edges.bottom < getHeight()) {
                      adjustment[1] = (edges.bottom - getHeight()) / scale;
                  }
                  for (int dim = 0; dim <= 1; dim++) {
                      if (coef[dim] > 0) adjustment[dim] = FloatMath.ceil(adjustment[dim]);
                  }
  
                  mInverseRotateMatrix.mapPoints(adjustment);
                  mCenterX += adjustment[0];
                  mCenterY += adjustment[1];
                  updateCenter();
              }
          }
  
          mLastX = x;
          mLastY = y;
          return true;
      }
  }
  