content: /*
   * Copyright (C) 2014 AChep@xda <artemchep@gmail.com>
   *
   * This program is free software; you can redistribute it and/or
   * modify it under the terms of the GNU General Public License
   * as published by the Free Software Foundation; either version 2
   * of the License, or (at your option) any later version.
   *
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License
   * along with this program; if not, write to the Free Software
   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
   * MA  02110-1301, USA.
   */
  package com.achep.acdisplay.ui.view;
  
  import android.content.Context;
  import android.util.AttributeSet;
  import android.view.HapticFeedbackConstants;
  import android.view.MotionEvent;
  import android.view.View;
  import android.view.ViewGroup;
  import android.widget.LinearLayout;
  
  import com.achep.base.utils.ViewUtils;
  
  /**
   * Created by achep on 26.04.14.
   */
  public class ForwardingLayout extends LinearLayout {
  
      private static final String TAG = "ForwardingLayout";
  
      private View mPressedChild;
  
      private OnForwardedEventListener mOnForwardedEventListener;
      private boolean mVibrateOnForwarded;
      private boolean mForwardAll;
      private int mForwardDepth;
  
      public ForwardingLayout(Context context) {
          super(context);
      }
  
      public ForwardingLayout(Context context, AttributeSet attrs) {
          super(context, attrs);
      }
  
      public ForwardingLayout(Context context, AttributeSet attrs, int defStyle) {
          super(context, attrs, defStyle);
      }
  
      public interface OnForwardedEventListener {
  
          void onForwardedEvent(MotionEvent event, int activePointerId);
  
          void onPressedView(MotionEvent event, int activePointerId, View view);
      }
  
      public void setOnForwardedEventListener(OnForwardedEventListener listener) {
          mOnForwardedEventListener = listener;
      }
  
      /**
       * If enabled, forwarding motion event will be accompanied by
       * haptic feedback on press / un-press / click.
       */
      public void setVibrateOnForwardedEventEnabled(boolean enabled) {
          mVibrateOnForwarded = enabled;
      }
  
      public void setAllViewsForwardable(boolean enabled, int depth) {
          mForwardAll = enabled;
          mForwardDepth = depth;
      }
  
      /**
       * Handles forwarded events.
       *
       * @param activePointerId id of the pointer that activated forwarding
       * @return whether the event was handled
       */
      public boolean onForwardedEvent(MotionEvent event, int activePointerId) {
          boolean handledEvent = true;
          View vibrateView = null;
  
          final int actionMasked = event.getActionMasked();
          switch (actionMasked) {
              case MotionEvent.ACTION_CANCEL:
                  handledEvent = false;
                  break;
              case MotionEvent.ACTION_UP:
                  handledEvent = false;
                  // $FALL-THROUGH$
              case MotionEvent.ACTION_DOWN:
              case MotionEvent.ACTION_MOVE:
                  final int activeIndex = event.findPointerIndex(activePointerId);
                  if (activeIndex < 0) {
                      handledEvent = false;
                      break;
                  }
  
                  final int x = (int) event.getX(activeIndex);
                  final int y = (int) event.getY(activeIndex);
  
                  View pressedView = findViewByCoordinate(this, x, y, 0);
                  if (mPressedChild != pressedView) {
                      vibrateView = pressedView != null ? pressedView : mPressedChild;
                      if (pressedView != null) pressedView.setPressed(true);
                      if (mPressedChild != null) mPressedChild.setPressed(false);
                      mPressedChild = pressedView;
  
                      if (mOnForwardedEventListener != null) {
                          mOnForwardedEventListener.onPressedView(event, activePointerId, mPressedChild);
                      }
                  }
  
                  if (actionMasked == MotionEvent.ACTION_UP) {
                      vibrateView = mPressedChild;
                      clickPressedItem();
                  }
                  break;
          }
  
          // Failure to handle the event cancels forwarding.
          if (!handledEvent) {
              clearPressedItem();
          }
  
          if (vibrateView != null && mVibrateOnForwarded) {
              vibrateView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
          }
  
          if (mOnForwardedEventListener != null) {
              mOnForwardedEventListener.onForwardedEvent(event, activePointerId);
          }
  
          return handledEvent;
      }
  
      private View findViewByCoordinate(ViewGroup viewGroup, float x, float y, int depth) {
          final int childCount = viewGroup.getChildCount();
          for (int i = childCount - 1; i >= 0; i--) {
              final View child = viewGroup.getChildAt(i);
              assert child != null;
  
              if (child.getVisibility() != View.VISIBLE || !child.isEnabled()) {
                  continue;
              }
  
              if (child instanceof ViewGroup && (depth < mForwardDepth || !mForwardAll)) {
                  View view = findViewByCoordinate((ViewGroup) child,
                          x - child.getLeft(),
                          y - child.getTop(), depth + 1);
                  if (view != null) {
                      return view;
                  }
              }
  
              if ((child.isClickable() || mForwardAll) && ViewUtils.pointInView(child, x, y, 0)) {
                  return child;
              }
          }
  
          return null;
      }
  
      private void clearPressedItem() {
          if (mPressedChild == null) {
              return;
          }
  
          mPressedChild.setPressed(false);
          mPressedChild.refreshDrawableState();
          mPressedChild = null;
      }
  
      private void clickPressedItem() {
          if (mPressedChild == null) {
              return;
          }
  
          mPressedChild.performClick();
          clearPressedItem();
      }
  }
  