@Override
        public void onTextChanged(
            final CharSequence s, final int start, final int before, final int count) {

          mLogger.info("onTextChanged", view);

          if ((view != null) && (view.getContent() instanceof EditableDrawable)) {
            final EditableDrawable editable = (EditableDrawable) view.getContent();

            if (!editable.isEditing()) return;

            editable.setText(s.toString());

            if (topHv.equals(view)) {
              editTopButton.setText(s);
              clearButtonTop.setVisibility(
                  s != null && s.length() > 0 ? View.VISIBLE : View.INVISIBLE);
            } else if (bottomHv.equals(view)) {
              editBottomButton.setText(s);
              clearButtonBottom.setVisibility(
                  s != null && s.length() > 0 ? View.VISIBLE : View.INVISIBLE);
            }

            view.forceUpdate();
            setIsChanged(true);
          }
        }
  /** Create and place the bottom editable text. */
  private void onAddBottomText() {

    final Matrix mImageMatrix = mImageView.getImageViewMatrix();
    final int width = (int) (mBitmap.getWidth());
    final int height = (int) (mBitmap.getHeight());

    final MemeTextDrawable text =
        new MemeTextDrawable("", (float) mBitmap.getHeight() / 7.0f, mTypeface);
    text.setTextColor(Color.WHITE);
    text.setTextStrokeColor(Color.BLACK);
    text.setContentSize(width, height);

    bottomHv = new DrawableHighlightView(mImageView, text);
    bottomHv.setAlignModeV(DrawableHighlightView.AlignModeV.Bottom);

    final int cropHeight = text.getIntrinsicHeight();
    final int x = 0;
    final int y = 0;

    final Matrix matrix = new Matrix(mImageMatrix);
    matrix.invert(matrix);

    final float[] pts =
        new float[] {
          x, y + height - cropHeight - (height / 30), x + width, y + height - (height / 30)
        };

    MatrixUtils.mapPoints(matrix, pts);
    final RectF cropRect = new RectF(pts[0], pts[1], pts[2], pts[3]);

    addEditable(bottomHv, mImageMatrix, cropRect);
  }
 /*
  * (non-Javadoc)
  *
  * @see
  * com.aviary.android.feather.widget.ImageViewDrawableOverlay.OnDrawableEventListener#onClick(com.aviary.android.feather.widget
  * .DrawableHighlightView)
  */
 @Override
 public void onClick(DrawableHighlightView view) {
   if (view != null) {
     if (view.getContent() instanceof EditableDrawable) {
       beginEditView(view);
     }
   }
 }
  /**
   * In top editable text click
   *
   * @param view the view
   */
  public void onTopClick(final DrawableHighlightView view) {

    mLogger.info("onTopClick", view);

    if (view != null)
      if (view.getContent() instanceof EditableDrawable) {
        beginEditView(view);
      }
  }
  /**
   * Terminates an edit view.
   *
   * @param hv the hv
   */
  private void endEditView(DrawableHighlightView hv) {
    EditableDrawable text = (EditableDrawable) hv.getContent();
    mLogger.info("endEditView", text.isEditing());
    if (text.isEditing()) {
      text.endEdit();
      endEditText(hv);
    }

    CharSequence value = text.getText();
    if (topHv.equals(hv)) {
      editTopButton.setText(value);
      clearButtonTop.setVisibility(
          value != null && value.length() > 0 ? View.VISIBLE : View.INVISIBLE);
    } else if (bottomHv.equals(hv)) {
      editBottomButton.setText(value);
      clearButtonBottom.setVisibility(
          value != null && value.length() > 0 ? View.VISIBLE : View.INVISIBLE);
    }
  }
  /*
   * (non-Javadoc)
   *
   * @see android.widget.TextView.OnEditorActionListener#onEditorAction(android.widget.TextView, int, android.view.KeyEvent)
   */
  @Override
  public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {

    mLogger.info("onEditorAction", v, actionId, event);

    if (v != null) {
      if (actionId == EditorInfo.IME_ACTION_DONE || actionId == EditorInfo.IME_ACTION_UNSPECIFIED) {
        final ImageViewDrawableOverlay image = (ImageViewDrawableOverlay) mImageView;
        if (image.getSelectedHighlightView() != null) {
          DrawableHighlightView d = image.getSelectedHighlightView();
          if (d.getContent() instanceof EditableDrawable) {
            endEditView(d);
          }
        }
      }
    }

    return false;
  }
  @Override
  public void onLayoutChanged(boolean changed, int left, int top, int right, int bottom) {
    if (changed) {
      final Matrix mImageMatrix = mImageView.getImageViewMatrix();
      float[] matrixValues = getMatrixValues(mImageMatrix);
      final float w = mBitmap.getWidth();
      final float h = mBitmap.getHeight();
      final float scale = matrixValues[Matrix.MSCALE_X];

      if (topHv != null) {
        MemeTextDrawable text = (MemeTextDrawable) topHv.getContent();
        text.setContentSize(w * scale, h * scale);
      }

      if (bottomHv != null) {
        MemeTextDrawable text = (MemeTextDrawable) bottomHv.getContent();
        text.setContentSize(w * scale, h * scale);
      }
    }
  }
  /**
   * Adds the editable.
   *
   * @param hv the hv
   * @param imageMatrix the image matrix
   * @param cropRect the crop rect
   */
  private void addEditable(DrawableHighlightView hv, Matrix imageMatrix, RectF cropRect) {
    final ImageViewDrawableOverlay image = (ImageViewDrawableOverlay) mImageView;

    hv.setRotateAndScale(true);
    hv.showAnchors(false);
    hv.drawOutlineFill(false);
    hv.drawOutlineStroke(false);
    hv.setup(imageMatrix, null, cropRect, false);
    hv.getOutlineFillPaint()
        .setXfermode(new PorterDuffXfermode(android.graphics.PorterDuff.Mode.SRC_ATOP));
    hv.setMinSize(10);
    hv.setOutlineFillColor(
        new ColorStateList(new int[][] {{android.R.attr.state_active}}, new int[] {0}));
    hv.setOutlineStrokeColor(
        new ColorStateList(new int[][] {{android.R.attr.state_active}}, new int[] {0}));
    image.addHighlightView(hv);
  }
  /**
   * Begin edit text.
   *
   * @param view the view
   */
  private void beginEditText(final DrawableHighlightView view) {
    mLogger.info("beginEditText", view);

    EditText editText = null;

    if (view == topHv) {
      editText = editTopText;
    } else if (view == bottomHv) {
      editText = editBottomText;
    }

    if (editText != null) {
      mEditTextWatcher.view = null;
      editText.removeTextChangedListener(mEditTextWatcher);

      final EditableDrawable editable = (EditableDrawable) view.getContent();
      final String oldText = (String) editable.getText();
      editText.setText(oldText);
      editText.setSelection(editText.length());
      editText.setImeOptions(EditorInfo.IME_ACTION_DONE);
      editText.requestFocusFromTouch();

      Handler handler = new Handler();
      ResultReceiver receiver = new ResultReceiver(handler);

      if (!mInputManager.showSoftInput(editText, 0, receiver)) {
        mInputManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); // TODO: verify
      }

      mEditTextWatcher.view = view;
      editText.setOnEditorActionListener(this);
      editText.addTextChangedListener(mEditTextWatcher);

      ((ImageViewDrawableOverlay) mImageView).setSelectedHighlightView(view);
      ((EditableDrawable) view.getContent())
          .setText(((EditableDrawable) view.getContent()).getText());
      view.forceUpdate();
    }
  }
  /**
   * Begins an edit view.
   *
   * @param hv the hv
   */
  private void beginEditView(DrawableHighlightView hv) {
    mLogger.info("beginEditView");
    final EditableDrawable text = (EditableDrawable) hv.getContent();

    if (hv == topHv) {
      endEditView(bottomHv);
    } else {
      endEditView(topHv);
    }

    if (!text.isEditing()) {
      text.beginEdit();
      beginEditText(hv);
    }
  }
 private void clearEditView(DrawableHighlightView hv) {
   final MemeTextDrawable text = (MemeTextDrawable) hv.getContent();
   text.setText("");
   text.invalidateSelf();
   hv.forceUpdate();
 }
  /**
   * Flatten text.
   *
   * @param hv the hv
   */
  private void flattenText(final DrawableHighlightView hv, final MemeFilter filter) {

    if (hv != null) {
      hv.setHidden(true);
      final Matrix mImageMatrix = mImageView.getImageViewMatrix();
      float[] matrixValues = getMatrixValues(mImageMatrix);

      mLogger.log("image scaled: " + matrixValues[Matrix.MSCALE_X]);

      // TODO: check this modification
      final int width = (int) (mBitmap.getWidth());
      final int height = (int) (mBitmap.getHeight());

      final RectF cropRect = hv.getCropRectF();
      final Rect rect =
          new Rect(
              (int) cropRect.left, (int) cropRect.top, (int) cropRect.right, (int) cropRect.bottom);
      final MemeTextDrawable editable = (MemeTextDrawable) hv.getContent();

      final int saveCount = mCanvas.save(Canvas.MATRIX_SAVE_FLAG);

      // force end edit and hide the blinking cursor
      editable.endEdit();
      editable.invalidateSelf();

      editable.setContentSize(width, height);
      editable.setBounds(rect.left, rect.top, rect.right, rect.bottom);
      editable.draw(mCanvas);

      if (topHv == hv) {

        filter.setTopText(
            (String) editable.getText(), (double) editable.getTextSize() / mBitmap.getWidth());
        filter.setTopOffset(
            (cropRect.left + (double) editable.getXoff()) / mBitmap.getWidth(),
            (cropRect.top + (double) editable.getYoff()) / mBitmap.getHeight());

        // action.setValue( "toptext", (String) editable.getText() );
        // action.setValue( "topsize", (double)editable.getTextSize()/mBitmap.getWidth() );
        // action.setValue( "topxoff", (cropRect.left +
        // (double)editable.getXoff())/mBitmap.getWidth() );
        // action.setValue( "topyoff", (cropRect.top +
        // (double)editable.getYoff())/mBitmap.getHeight() );
      } else {

        filter.setBottomText(
            (String) editable.getText(), (double) editable.getTextSize() / mBitmap.getWidth());
        filter.setBottomOffset(
            (cropRect.left + (double) editable.getXoff()) / mBitmap.getWidth(),
            (cropRect.top + (double) editable.getYoff()) / mBitmap.getHeight());

        // action.setValue( "bottomtext", (String) editable.getText() );
        // action.setValue( "bottomsize", (double)editable.getTextSize()/mBitmap.getWidth() );
        // action.setValue( "bottomxoff", (cropRect.left +
        // (double)editable.getXoff())/mBitmap.getWidth() );
        // action.setValue( "bottomyoff", (cropRect.top +
        // (double)editable.getYoff())/mBitmap.getHeight() );
      }

      filter.setTextScale(matrixValues[Matrix.MSCALE_X]);

      // action.setValue( "scale", matrixValues[Matrix.MSCALE_X] );
      // action.setValue( "textsize", editable.getTextSize() );

      mCanvas.restoreToCount(saveCount);
      mImageView.invalidate();
    }

    onPreviewChanged(mPreview, false);
  }