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.example.android.basicmultitouch;
  
  import android.content.Context;
  import android.graphics.Canvas;
  import android.graphics.Color;
  import android.graphics.Paint;
  import android.graphics.PointF;
  import android.util.AttributeSet;
  import android.util.SparseArray;
  import android.view.MotionEvent;
  import android.view.View;
  
  import com.example.android.basicmultitouch.Pools.SimplePool;
  
  /**
   * View that shows touch events and their history. This view demonstrates the
   * use of {@link #onTouchEvent(android.view.MotionEvent)} and {@link android.view.MotionEvent}s to keep
   * track of touch pointers across events.
   */
  public class TouchDisplayView extends View {
  
      // Hold data for active touch pointer IDs
      private SparseArray<TouchHistory> mTouches;
  
      // Is there an active touch?
      private boolean mHasTouch = false;
  
      /**
       * Holds data related to a touch pointer, including its current position,
       * pressure and historical positions. Objects are allocated through an
       * object pool using {@link #obtain()} and {@link #recycle()} to reuse
       * existing objects.
       */
      static final class TouchHistory {
  
          // number of historical points to store
          public static final int HISTORY_COUNT = 20;
  
          public float x;
          public float y;
          public float pressure = 0f;
          public String label = null;
  
          // current position in history array
          public int historyIndex = 0;
          public int historyCount = 0;
  
          // arrray of pointer position history
          public PointF[] history = new PointF[HISTORY_COUNT];
  
          private static final int MAX_POOL_SIZE = 10;
          private static final SimplePool<TouchHistory> sPool =
                  new SimplePool<TouchHistory>(MAX_POOL_SIZE);
  
          public static TouchHistory obtain(float x, float y, float pressure) {
              TouchHistory data = sPool.acquire();
              if (data == null) {
                  data = new TouchHistory();
              }
  
              data.setTouch(x, y, pressure);
  
              return data;
          }
  
          public TouchHistory() {
  
              // initialise history array
              for (int i = 0; i < HISTORY_COUNT; i++) {
                  history[i] = new PointF();
              }
          }
  
          public void setTouch(float x, float y, float pressure) {
              this.x = x;
              this.y = y;
              this.pressure = pressure;
          }
  
          public void recycle() {
              this.historyIndex = 0;
              this.historyCount = 0;
              sPool.release(this);
          }
  
          /**
           * Add a point to its history. Overwrites oldest point if the maximum
           * number of historical points is already stored.
           *
           * @param point
           */
          public void addHistory(float x, float y) {
              PointF p = history[historyIndex];
              p.x = x;
              p.y = y;
  
              historyIndex = (historyIndex + 1) % history.length;
  
              if (historyCount < HISTORY_COUNT) {
                  historyCount++;
              }
          }
  
      }
  
      public TouchDisplayView(Context context, AttributeSet attrs) {
          super(context, attrs);
  
          // SparseArray for touch events, indexed by touch id
          mTouches = new SparseArray<TouchHistory>(10);
  
          initialisePaint();
      }
  
      // BEGIN_INCLUDE(onTouchEvent)
      @Override
      public boolean onTouchEvent(MotionEvent event) {
  
          final int action = event.getAction();
  
          /*
           * Switch on the action. The action is extracted from the event by
           * applying the MotionEvent.ACTION_MASK. Alternatively a call to
           * event.getActionMasked() would yield in the action as well.
           */
          switch (action & MotionEvent.ACTION_MASK) {
  
              case MotionEvent.ACTION_DOWN: {
                  // first pressed gesture has started
  
                  /*
                   * Only one touch event is stored in the MotionEvent. Extract
                   * the pointer identifier of this touch from the first index
                   * within the MotionEvent object.
                   */
                  int id = event.getPointerId(0);
  
                  TouchHistory data = TouchHistory.obtain(event.getX(0), event.getY(0),
                          event.getPressure(0));
                  data.label = "id: " + 0;
  
                  /*
                   * Store the data under its pointer identifier. The pointer
                   * number stays consistent for the duration of a gesture,
                   * accounting for other pointers going up or down.
                   */
                  mTouches.put(id, data);
  
                  mHasTouch = true;
  
                  break;
              }
  
              case MotionEvent.ACTION_POINTER_DOWN: {
                  /*
                   * A non-primary pointer has gone down, after an event for the
                   * primary pointer (ACTION_DOWN) has already been received.
                   */
  
                  /*
                   * The MotionEvent object contains multiple pointers. Need to
                   * extract the index at which the data for this particular event
                   * is stored.
                   */
                  int index = event.getActionIndex();
                  int id = event.getPointerId(index);
  
                  TouchHistory data = TouchHistory.obtain(event.getX(index), event.getY(index),
                          event.getPressure(index));
                  data.label = "id: " + id;
  
                  /*
                   * Store the data under its pointer identifier. The index of
                   * this pointer can change over multiple events, but this
                   * pointer is always identified by the same identifier for this
                   * active gesture.
                   */
                  mTouches.put(id, data);
  
                  break;
              }
  
              case MotionEvent.ACTION_UP: {
                  /*
                   * Final pointer has gone up and has ended the last pressed
                   * gesture.
                   */
  
                  /*
                   * Extract the pointer identifier for the only event stored in
                   * the MotionEvent object and remove it from the list of active
                   * touches.
                   */
                  int id = event.getPointerId(0);
                  TouchHistory data = mTouches.get(id);
                  mTouches.remove(id);
                  data.recycle();
  
                  mHasTouch = false;
  
                  break;
              }
  
              case MotionEvent.ACTION_POINTER_UP: {
                  /*
                   * A non-primary pointer has gone up and other pointers are
                   * still active.
                   */
  
                  /*
                   * The MotionEvent object contains multiple pointers. Need to
                   * extract the index at which the data for this particular event
                   * is stored.
                   */
                  int index = event.getActionIndex();
                  int id = event.getPointerId(index);
  
                  TouchHistory data = mTouches.get(id);
                  mTouches.remove(id);
                  data.recycle();
  
                  break;
              }
  
              case MotionEvent.ACTION_MOVE: {
                  /*
                   * A change event happened during a pressed gesture. (Between
                   * ACTION_DOWN and ACTION_UP or ACTION_POINTER_DOWN and
                   * ACTION_POINTER_UP)
                   */
  
                  /*
                   * Loop through all active pointers contained within this event.
                   * Data for each pointer is stored in a MotionEvent at an index
                   * (starting from 0 up to the number of active pointers). This
                   * loop goes through each of these active pointers, extracts its
                   * data (position and pressure) and updates its stored data. A
                   * pointer is identified by its pointer number which stays
                   * constant across touch events as long as it remains active.
                   * This identifier is used to keep track of a pointer across
                   * events.
                   */
                  for (int index = 0; index < event.getPointerCount(); index++) {
                      // get pointer id for data stored at this index
                      int id = event.getPointerId(index);
  
                      // get the data stored externally about this pointer.
                      TouchHistory data = mTouches.get(id);
  
                      // add previous position to history and add new values
                      data.addHistory(data.x, data.y);
                      data.setTouch(event.getX(index), event.getY(index),
                              event.getPressure(index));
  
                  }
  
                  break;
              }
          }
  
          // trigger redraw on UI thread
          this.postInvalidate();
  
          return true;
      }
  
      // END_INCLUDE(onTouchEvent)
  
      @Override
      protected void onDraw(Canvas canvas) {
          super.onDraw(canvas);
  
          // Canvas background color depends on whether there is an active touch
          if (mHasTouch) {
              canvas.drawColor(BACKGROUND_ACTIVE);
          } else {
              // draw inactive border
              canvas.drawRect(mBorderWidth, mBorderWidth, getWidth() - mBorderWidth, getHeight()
                      - mBorderWidth, mBorderPaint);
          }
  
          // loop through all active touches and draw them
          for (int i = 0; i < mTouches.size(); i++) {
  
              // get the pointer id and associated data for this index
              int id = mTouches.keyAt(i);
              TouchHistory data = mTouches.valueAt(i);
  
              // draw the data and its history to the canvas
              drawCircle(canvas, id, data);
          }
      }
  
      /*
       * Below are only helper methods and variables required for drawing.
       */
  
      // radius of active touch circle in dp
      private static final float CIRCLE_RADIUS_DP = 75f;
      // radius of historical circle in dp
      private static final float CIRCLE_HISTORICAL_RADIUS_DP = 7f;
  
      // calculated radiuses in px
      private float mCircleRadius;
      private float mCircleHistoricalRadius;
  
      private Paint mCirclePaint = new Paint();
      private Paint mTextPaint = new Paint();
  
      private static final int BACKGROUND_ACTIVE = Color.WHITE;
  
      // inactive border
      private static final float INACTIVE_BORDER_DP = 15f;
      private static final int INACTIVE_BORDER_COLOR = 0xFFffd060;
      private Paint mBorderPaint = new Paint();
      private float mBorderWidth;
  
      public final int[] COLORS = {
              0xFF33B5E5, 0xFFAA66CC, 0xFF99CC00, 0xFFFFBB33, 0xFFFF4444,
              0xFF0099CC, 0xFF9933CC, 0xFF669900, 0xFFFF8800, 0xFFCC0000
      };
  
      /**
       * Sets up the required {@link android.graphics.Paint} objects for the screen density of this
       * device.
       */
      private void initialisePaint() {
  
          // Calculate radiuses in px from dp based on screen density
          float density = getResources().getDisplayMetrics().density;
          mCircleRadius = CIRCLE_RADIUS_DP * density;
          mCircleHistoricalRadius = CIRCLE_HISTORICAL_RADIUS_DP * density;
  
          // Setup text paint for circle label
          mTextPaint.setTextSize(27f);
          mTextPaint.setColor(Color.BLACK);
  
          // Setup paint for inactive border
          mBorderWidth = INACTIVE_BORDER_DP * density;
          mBorderPaint.setStrokeWidth(mBorderWidth);
          mBorderPaint.setColor(INACTIVE_BORDER_COLOR);
          mBorderPaint.setStyle(Paint.Style.STROKE);
  
      }
  
      /**
       * Draws the data encapsulated by a {@link TouchDisplayView.TouchHistory} object to a canvas.
       * A large circle indicates the current position held by the
       * {@link TouchDisplayView.TouchHistory} object, while a smaller circle is drawn for each
       * entry in its history. The size of the large circle is scaled depending on
       * its pressure, clamped to a maximum of <code>1.0</code>.
       *
       * @param canvas
       * @param id
       * @param data
       */
      protected void drawCircle(Canvas canvas, int id, TouchHistory data) {
          // select the color based on the id
          int color = COLORS[id % COLORS.length];
          mCirclePaint.setColor(color);
  
          /*
           * Draw the circle, size scaled to its pressure. Pressure is clamped to
           * 1.0 max to ensure proper drawing. (Reported pressure values can
           * exceed 1.0, depending on the calibration of the touch screen).
           */
          float pressure = Math.min(data.pressure, 1f);
          float radius = pressure * mCircleRadius;
  
          canvas.drawCircle(data.x, (data.y) - (radius / 2f), radius,
                  mCirclePaint);
  
          // draw all historical points with a lower alpha value
          mCirclePaint.setAlpha(125);
          for (int j = 0; j < data.history.length && j < data.historyCount; j++) {
              PointF p = data.history[j];
              canvas.drawCircle(p.x, p.y, mCircleHistoricalRadius, mCirclePaint);
          }
  
          // draw its label next to the main circle
          canvas.drawText(data.label, data.x + radius, data.y
                  - radius, mTextPaint);
      }
  
  }
  