/**
   * Initializes the internal state of the SlidingCardManager.
   *
   * @param phone the Phone app's Phone instance
   * @param inCallScreen the InCallScreen activity
   * @param mainFrame the InCallScreen's main frame containing the in-call UI elements
   */
  /* package */ public void init(Phone phone, InCallScreen inCallScreen, ViewGroup mainFrame) {
    if (DBG) log("init()...");
    mPhone = phone;
    mInCallScreen = inCallScreen;
    mMainFrame = mainFrame;

    // Slide hints
    mSlideUp = (ViewGroup) mInCallScreen.findViewById(R.id.slideUp);
    mSlideUpHint = (TextView) mInCallScreen.findViewById(R.id.slideUpHint);
    mSlideDown = (ViewGroup) mInCallScreen.findViewById(R.id.slideDown);
    mSlideDownHint = (TextView) mInCallScreen.findViewById(R.id.slideDownHint);

    mCallCard = (CallCard) mMainFrame.findViewById(R.id.callCard);
    mCallCard.setSlidingCardManager(this);
  }
  /**
   * Handles a touch event on the CallCard.
   *
   * @see CallCard.dispatchTouchEvent
   */
  /* package */ void handleCallCardTouchEvent(MotionEvent ev) {
    // if (DBG) log("handleCallCardTouchEvent(" + ev + ")...");

    if (mInCallScreen == null || mInCallScreen.isFinishing()) {
      Log.i(LOG_TAG, "handleCallCardTouchEvent: InCallScreen gone; ignoring touch...");
      return;
    }

    final int action = ev.getAction();

    // All the sliding code depends on deltas, so it
    // doesn't really matter in what coordinate space
    // we are, as long as it's independant of our position
    final int xAbsolute = (int) ev.getRawX();
    final int yAbsolute = (int) ev.getRawY();

    if (isSlideInProgress()) {
      if (SystemClock.elapsedRealtime() - mTouchDownTime > 1000) abortSlide();
      else
        switch (action) {
          case MotionEvent.ACTION_DOWN:
            // Shouldn't happen in this state.
            break;
          case MotionEvent.ACTION_MOVE:
            // Move the CallCard!
            updateWhileSliding(yAbsolute);
            break;
          case MotionEvent.ACTION_UP:
            // See if we've slid far enough to do some action
            // (ie. hang up, or answer an incoming call,
            // depending on our current state.)
            stopSliding(yAbsolute);
            break;
          case MotionEvent.ACTION_CANCEL:
            // Because we set the FLAG_IGNORE_CHEEK_PRESSES
            // WindowManager flag (see init()), we'll get an
            // ACTION_CANCEL event if a valid ACTION_DOWN is later
            // followed by an ACTION_MOVE that's a "fat touch".
            // In this case, abort the slide.
            if (DBG) log("handleCallCardTouchEvent: ACTION_CANCEL: " + ev);
            abortSlide();
            break;
        }
    } else {
      switch (action) {
        case MotionEvent.ACTION_DOWN:
          // This event is a touch DOWN on the card: start sliding!
          startSliding(xAbsolute, yAbsolute);
          break;
        case MotionEvent.ACTION_MOVE:
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
          // If we're not sliding (yet), ignore anything other than
          // ACTION_DOWN.
          break;
      }
    }
  }
  /**
   * Sets the text of the "slide hints" based on the specified resource IDs. (A resource ID of zero
   * means "hide the hint and arrow".)
   */
  private void setSlideHints(int upHintResId, int downHintResId) {
    // TODO: It would probably look really cool to do a "fade in" animation
    // when a hint becomes visible after previously being hidden, rather
    // than having it just pop on.

    // TODO: Also consider having both slide hints *always* visible,
    // so that as you slide you first cover up one and later reveal
    // the other.  But this is tricky: the text of the hint that
    // "starts off hidden" will need to be pre-set to the value it
    // should have *after* the slide is complete.

    if (DBG) {
      String upHint = (upHintResId != 0) ? mInCallScreen.getString(upHintResId) : "<empty>";
      String downHint = (downHintResId != 0) ? mInCallScreen.getString(downHintResId) : "<empty>";
      log("setSlideHints: UP '" + upHint + "', DOWN '" + downHint + "'");
    }

    mSlideUp.setVisibility((upHintResId != 0) ? View.VISIBLE : View.GONE);
    if (upHintResId != 0) mSlideUpHint.setText(upHintResId);

    mSlideDown.setVisibility((downHintResId != 0) ? View.VISIBLE : View.GONE);
    if (downHintResId != 0) mSlideDownHint.setText(downHintResId);
  }
  /**
   * The user successfully completed a "slide" operation. Activate whatever action the slide was
   * supposed to trigger.
   *
   * <p>(That could either be (1) hang up the ongoing call(s), or (2) answer an incoming call.)
   *
   * <p>This method is responsible for triggering any screen updates that need to happen, based on
   * any internal state changes due to the slide.
   */
  private void finishSuccessfulSlide() {
    if (DBG) log("finishSuccessfulSlide()...");

    mSlideInProgress = false;

    // TODO: Need to test lots of possible edge cases here, like if the
    // state of the Phone changes while the slide was happening.
    // (For example, say the user slid the card UP to answer an incoming
    // call, but the phone's no longer ringing by the time we got here...)

    // TODO: The state-checking logic here is very similar to the logic in
    // updateCardSlideHints().  Rather than duplicating this logic in both
    // places, maybe use a single helper function that generates a
    // complete "slidability matrix" (ie. all slide hints / states /
    // actions) based on the current state of the Phone.

    boolean phoneStateAboutToChange = false;

    // Perform the "successful slide" action.

    if (mCardAtTop) {
      // The downward slide action is to hang up any ongoing
      // call(s).
      if (DBG) log("  =========> Slide complete: HANGING UP...");
      mInCallScreen.reject();

      // Any "hangup" action is going to cause
      // the Phone state to change imminently.
      phoneStateAboutToChange = true;
    } else {
      // The upward slide action is to answer the incoming call.
      // (We put the ongoing call on hold if there's already one line in
      // use, or hang up the ongoing call if both lines are in use.)
      if (DBG) log("  =========> Slide complete: ANSWERING...");
      mInCallScreen.answer();

      // Either of the "answer call" functions is going to cause
      // the Phone state to change imminently.
      phoneStateAboutToChange = true;
    }

    // Finally, update the state of the UI depending on what just happened.
    // Update the "permanent" position of the sliding card, and the slide
    // hints.
    //
    // But *don't* do this if we know the Phone state's about to change,
    // like if the user just did a "slide up to answer".  In that case
    // we know we're going to get a onPhoneStateChanged() call in a few
    // milliseconds, and *that's* going to result in an updateScreen() call.
    // (And if we were to do that update now, we'd just get a brief flash
    // of the card at the bottom or the screen. So don't do anything
    // here.)

    if (!phoneStateAboutToChange) {
      updateCardPreferredPosition();
      updateCardSlideHints();

      // And force an immmediate re-layout.  (No need to do any
      // animation here, since the card's new "preferred position" is
      // exactly where the user just slid it.)
      mMainFrame.requestLayout();
    }
  }