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();
 }
  @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;
  }