2 * Copyright (C) 2012 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.gallery3d.filtershow.imageshow;
19 import android.content.Context;
20 import android.content.res.Resources;
21 import android.graphics.Bitmap;
22 import android.graphics.BitmapFactory;
23 import android.graphics.BitmapShader;
24 import android.graphics.Canvas;
25 import android.graphics.Color;
26 import android.graphics.Matrix;
27 import android.graphics.Paint;
28 import android.graphics.Point;
29 import android.graphics.Rect;
30 import android.graphics.Shader;
31 import android.graphics.drawable.NinePatchDrawable;
32 import android.util.AttributeSet;
33 import android.view.GestureDetector;
34 import android.view.GestureDetector.OnDoubleTapListener;
35 import android.view.GestureDetector.OnGestureListener;
36 import android.view.MotionEvent;
37 import android.view.ScaleGestureDetector;
38 import android.view.View;
39 import android.widget.LinearLayout;
41 import com.android.gallery3d.R;
42 import com.android.gallery3d.filtershow.FilterShowActivity;
43 import com.android.gallery3d.filtershow.filters.FilterMirrorRepresentation;
44 import com.android.gallery3d.filtershow.filters.FilterRepresentation;
45 import com.android.gallery3d.filtershow.filters.ImageFilter;
46 import com.android.gallery3d.filtershow.pipeline.ImagePreset;
47 import com.android.gallery3d.filtershow.tools.SaveImage;
50 import java.util.ArrayList;
51 import java.util.Collection;
53 public class ImageShow extends View implements OnGestureListener,
54 ScaleGestureDetector.OnScaleGestureListener,
57 private static final String LOGTAG = "ImageShow";
58 private static final boolean ENABLE_ZOOMED_COMPARISON = false;
60 protected Paint mPaint = new Paint();
61 protected int mTextSize;
62 protected int mTextPadding;
64 protected int mBackgroundColor;
66 private GestureDetector mGestureDetector = null;
67 private ScaleGestureDetector mScaleGestureDetector = null;
69 protected Rect mImageBounds = new Rect();
70 private boolean mOriginalDisabled = false;
71 private boolean mTouchShowOriginal = false;
72 private long mTouchShowOriginalDate = 0;
73 private final long mTouchShowOriginalDelayMin = 200; // 200ms
74 private int mShowOriginalDirection = 0;
75 private static int UNVEIL_HORIZONTAL = 1;
76 private static int UNVEIL_VERTICAL = 2;
78 private NinePatchDrawable mShadow = null;
79 private Rect mShadowBounds = new Rect();
80 private int mShadowMargin = 15; // not scaled, fixed in the asset
81 private boolean mShadowDrawn = false;
83 private Point mTouchDown = new Point();
84 private Point mTouch = new Point();
85 private boolean mFinishedScalingOperation = false;
87 private int mOriginalTextMargin;
88 private int mOriginalTextSize;
89 private String mOriginalText;
90 private boolean mZoomIn = false;
91 Point mOriginalTranslation = new Point();
93 float mStartFocusX, mStartFocusY;
94 private enum InteractionMode {
99 InteractionMode mInteractionMode = InteractionMode.NONE;
101 private static Bitmap sMask;
102 private Paint mMaskPaint = new Paint();
103 private Matrix mShaderMatrix = new Matrix();
104 private boolean mDidStartAnimation = false;
106 private static Bitmap convertToAlphaMask(Bitmap b) {
107 Bitmap a = Bitmap.createBitmap(b.getWidth(), b.getHeight(), Bitmap.Config.ALPHA_8);
108 Canvas c = new Canvas(a);
109 c.drawBitmap(b, 0.0f, 0.0f, null);
113 private static Shader createShader(Bitmap b) {
114 return new BitmapShader(b, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
117 private FilterShowActivity mActivity = null;
119 public FilterShowActivity getActivity() {
123 public boolean hasModifications() {
124 return MasterImage.getImage().hasModifications();
127 public void resetParameter() {
128 // TODO: implement reset
131 public void onNewValue(int parameter) {
135 public ImageShow(Context context, AttributeSet attrs, int defStyle) {
136 super(context, attrs, defStyle);
137 setupImageShow(context);
140 public ImageShow(Context context, AttributeSet attrs) {
141 super(context, attrs);
142 setupImageShow(context);
146 public ImageShow(Context context) {
148 setupImageShow(context);
151 private void setupImageShow(Context context) {
152 Resources res = context.getResources();
153 mTextSize = res.getDimensionPixelSize(R.dimen.photoeditor_text_size);
154 mTextPadding = res.getDimensionPixelSize(R.dimen.photoeditor_text_padding);
155 mOriginalTextMargin = res.getDimensionPixelSize(R.dimen.photoeditor_original_text_margin);
156 mOriginalTextSize = res.getDimensionPixelSize(R.dimen.photoeditor_original_text_size);
157 mBackgroundColor = res.getColor(R.color.background_screen);
158 mOriginalText = res.getString(R.string.original_picture_text);
159 mShadow = (NinePatchDrawable) res.getDrawable(R.drawable.geometry_shadow);
160 setupGestureDetector(context);
161 mActivity = (FilterShowActivity) context;
163 Bitmap mask = BitmapFactory.decodeResource(res, R.drawable.spot_mask);
164 sMask = convertToAlphaMask(mask);
168 public void attach() {
169 MasterImage.getImage().addObserver(this);
170 bindAsImageLoadListener();
173 public void detach() {
174 MasterImage.getImage().removeObserver(this);
178 public void setupGestureDetector(Context context) {
179 mGestureDetector = new GestureDetector(context, this);
180 mScaleGestureDetector = new ScaleGestureDetector(context, this);
184 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
185 int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
186 int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
187 setMeasuredDimension(parentWidth, parentHeight);
190 public ImageFilter getCurrentFilter() {
191 return MasterImage.getImage().getCurrentFilter();
194 /* consider moving the following 2 methods into a subclass */
196 * This function calculates a Image to Screen Transformation matrix
198 * @param reflectRotation set true if you want the rotation encoded
199 * @return Image to Screen transformation matrix
201 protected Matrix getImageToScreenMatrix(boolean reflectRotation) {
202 MasterImage master = MasterImage.getImage();
203 if (master.getOriginalBounds() == null) {
206 Matrix m = GeometryMathUtils.getImageToScreenMatrix(master.getPreset().getGeometryFilters(),
207 reflectRotation, master.getOriginalBounds(), getWidth(), getHeight());
208 Point translate = master.getTranslation();
209 float scaleFactor = master.getScaleFactor();
210 m.postTranslate(translate.x, translate.y);
211 m.postScale(scaleFactor, scaleFactor, getWidth() / 2.0f, getHeight() / 2.0f);
216 * This function calculates a to Screen Image Transformation matrix
218 * @param reflectRotation set true if you want the rotation encoded
219 * @return Screen to Image transformation matrix
221 protected Matrix getScreenToImageMatrix(boolean reflectRotation) {
222 Matrix m = getImageToScreenMatrix(reflectRotation);
223 Matrix invert = new Matrix();
228 public ImagePreset getImagePreset() {
229 return MasterImage.getImage().getPreset();
233 public void onDraw(Canvas canvas) {
234 MasterImage.getImage().setImageShowSize(
235 getWidth() - 2*mShadowMargin,
236 getHeight() - 2*mShadowMargin);
238 float cx = canvas.getWidth()/2.0f;
239 float cy = canvas.getHeight()/2.0f;
240 float scaleFactor = MasterImage.getImage().getScaleFactor();
241 Point translation = MasterImage.getImage().getTranslation();
245 mShadowDrawn = false;
248 // TODO: center scale on gesture
249 canvas.scale(scaleFactor, scaleFactor, cx, cy);
250 canvas.translate(translation.x, translation.y);
251 Bitmap highresPreview = MasterImage.getImage().getHighresImage();
253 boolean isDoingNewLookAnimation = MasterImage.getImage().onGoingNewLookAnimation();
255 if (!isDoingNewLookAnimation && highresPreview != null) {
256 drawImage(canvas, highresPreview, true);
258 drawImage(canvas, getFilteredImage(), true);
262 Bitmap partialPreview = MasterImage.getImage().getPartialImage();
263 if (!isDoingNewLookAnimation && partialPreview != null) {
265 Rect originalBounds = MasterImage.getImage().getOriginalBounds();
266 Collection<FilterRepresentation> geo = MasterImage.getImage().getPreset()
267 .getGeometryFilters();
269 Matrix compensation = GeometryMathUtils.getPartialToScreenMatrix(geo,
270 originalBounds, getWidth(), getHeight(),
271 partialPreview.getWidth(), partialPreview.getHeight());
272 canvas.drawBitmap(partialPreview, compensation, null);
277 canvas.scale(scaleFactor, scaleFactor, cx, cy);
278 canvas.translate(translation.x, translation.y);
279 drawPartialImage(canvas, getGeometryOnlyImage());
285 public void resetImageCaches(ImageShow caller) {
286 MasterImage.getImage().invalidatePreview();
289 public Bitmap getFiltersOnlyImage() {
290 return MasterImage.getImage().getFiltersOnlyImage();
293 public Bitmap getGeometryOnlyImage() {
294 return MasterImage.getImage().getGeometryOnlyImage();
297 public Bitmap getFilteredImage() {
298 return MasterImage.getImage().getFilteredImage();
301 public void drawImage(Canvas canvas, Bitmap image, boolean updateBounds) {
306 Rect d = computeImageBounds(image.getWidth(), image.getHeight());
312 float centerX = mShadowMargin + (getWidth() - 2 * mShadowMargin) / 2;
313 float centerY = mShadowMargin + (getHeight() - 2 * mShadowMargin) / 2;
315 MasterImage master = MasterImage.getImage();
317 if (master.onGoingNewLookAnimation()
318 || mDidStartAnimation) {
319 mDidStartAnimation = true;
320 if (master.getCurrentLookAnimation()
321 == MasterImage.CIRCLE_ANIMATION
322 && MasterImage.getImage().getPreviousImage() != null) {
323 float maskScale = MasterImage.getImage().getMaskScale();
324 if (maskScale > 0.0f) {
325 float maskW = sMask.getWidth() / 2.0f;
326 float maskH = sMask.getHeight() / 2.0f;
327 float x = centerX - maskW * maskScale;
328 float y = centerY - maskH * maskScale;
330 // Prepare the shader
331 mShaderMatrix.reset();
332 mShaderMatrix.setScale(1.0f / maskScale, 1.0f / maskScale);
333 mShaderMatrix.preTranslate(-x + d.left, -y + d.top);
334 float scaleImageX = d.width() / (float) image.getWidth();
335 float scaleImageY = d.height() / (float) image.getHeight();
336 mShaderMatrix.preScale(scaleImageX, scaleImageY);
338 mMaskPaint.setShader(createShader(image));
339 mMaskPaint.getShader().setLocalMatrix(mShaderMatrix);
341 drawImage(canvas, MasterImage.getImage().getPreviousImage());
342 canvas.translate(x, y);
343 canvas.scale(maskScale, maskScale);
344 canvas.drawBitmap(sMask, 0, 0, mMaskPaint);
346 drawImage(canvas, image);
348 } else if (master.getCurrentLookAnimation()
349 == MasterImage.ROTATE_ANIMATION) {
350 Rect d1 = computeImageBounds(master.getPreviousImage().getHeight(),
351 master.getPreviousImage().getWidth());
352 Rect d2 = computeImageBounds(master.getPreviousImage().getWidth(),
353 master.getPreviousImage().getHeight());
354 float finalScale = d1.width() / (float) d2.height();
355 finalScale = (1.0f * (1.0f - master.getAnimFraction()))
356 + (finalScale * master.getAnimFraction());
357 canvas.rotate(master.getAnimRotationValue(), centerX, centerY);
358 canvas.scale(finalScale, finalScale, centerX, centerY);
359 drawImage(canvas, master.getPreviousImage());
360 } else if (master.getCurrentLookAnimation()
361 == MasterImage.MIRROR_ANIMATION) {
362 if (master.getCurrentFilterRepresentation()
363 instanceof FilterMirrorRepresentation) {
364 FilterMirrorRepresentation rep =
365 (FilterMirrorRepresentation) master.getCurrentFilterRepresentation();
367 ImagePreset preset = master.getPreset();
368 ArrayList<FilterRepresentation> geometry =
369 (ArrayList<FilterRepresentation>) preset.getGeometryFilters();
370 GeometryMathUtils.GeometryHolder holder = null;
371 holder = GeometryMathUtils.unpackGeometry(geometry);
373 if (holder.rotation.value() == 90 || holder.rotation.value() == 270) {
374 if (rep.isHorizontal() && !rep.isVertical()) {
375 canvas.scale(1, master.getAnimRotationValue(), centerX, centerY);
376 } else if (rep.isVertical() && !rep.isHorizontal()) {
377 canvas.scale(1, master.getAnimRotationValue(), centerX, centerY);
378 } else if (rep.isHorizontal() && rep.isVertical()) {
379 canvas.scale(master.getAnimRotationValue(), 1, centerX, centerY);
381 canvas.scale(master.getAnimRotationValue(), 1, centerX, centerY);
384 if (rep.isHorizontal() && !rep.isVertical()) {
385 canvas.scale(master.getAnimRotationValue(), 1, centerX, centerY);
386 } else if (rep.isVertical() && !rep.isHorizontal()) {
387 canvas.scale(master.getAnimRotationValue(), 1, centerX, centerY);
388 } else if (rep.isHorizontal() && rep.isVertical()) {
389 canvas.scale(1, master.getAnimRotationValue(), centerX, centerY);
391 canvas.scale(1, master.getAnimRotationValue(), centerX, centerY);
395 drawImage(canvas, master.getPreviousImage());
398 drawImage(canvas, image);
401 if (!master.onGoingNewLookAnimation()
402 && mDidStartAnimation
403 && !master.getPreviousPreset().equals(master.getCurrentPreset())) {
404 mDidStartAnimation = false;
405 MasterImage.getImage().resetAnimBitmap();
410 private void drawImage(Canvas canvas, Bitmap image) {
411 Rect d = computeImageBounds(image.getWidth(), image.getHeight());
412 float scaleImageX = d.width() / (float) image.getWidth();
413 float scaleImageY = d.height() / (float) image.getHeight();
414 Matrix imageMatrix = new Matrix();
415 imageMatrix.postScale(scaleImageX, scaleImageY);
416 imageMatrix.postTranslate(d.left, d.top);
417 drawShadow(canvas, d);
419 canvas.drawBitmap(image, imageMatrix, mPaint);
422 private Rect computeImageBounds(int imageWidth, int imageHeight) {
423 float scale = GeometryMathUtils.scale(imageWidth, imageHeight,
424 getWidth(), getHeight());
426 float w = imageWidth * scale;
427 float h = imageHeight * scale;
428 float ty = (getHeight() - h) / 2.0f;
429 float tx = (getWidth() - w) / 2.0f;
430 return new Rect((int) tx + mShadowMargin,
431 (int) ty + mShadowMargin,
432 (int) (w + tx) - mShadowMargin,
433 (int) (h + ty) - mShadowMargin);
436 private void drawShadow(Canvas canvas, Rect d) {
438 mShadowBounds.set(d.left - mShadowMargin, d.top - mShadowMargin,
439 d.right + mShadowMargin, d.bottom + mShadowMargin);
440 mShadow.setBounds(mShadowBounds);
441 mShadow.draw(canvas);
446 public void drawPartialImage(Canvas canvas, Bitmap image) {
447 boolean showsOriginal = MasterImage.getImage().showsOriginal();
448 if (!showsOriginal && !mTouchShowOriginal)
452 if (mShowOriginalDirection == 0) {
453 if (Math.abs(mTouch.y - mTouchDown.y) > Math.abs(mTouch.x - mTouchDown.x)) {
454 mShowOriginalDirection = UNVEIL_VERTICAL;
456 mShowOriginalDirection = UNVEIL_HORIZONTAL;
462 if (mShowOriginalDirection == UNVEIL_VERTICAL) {
463 px = mImageBounds.width();
464 py = mTouch.y - mImageBounds.top;
466 px = mTouch.x - mImageBounds.left;
467 py = mImageBounds.height();
469 px = mImageBounds.width();
473 Rect d = new Rect(mImageBounds.left, mImageBounds.top,
474 mImageBounds.left + px, mImageBounds.top + py);
476 drawImage(canvas, image, false);
477 Paint paint = new Paint();
478 paint.setColor(Color.BLACK);
479 paint.setStrokeWidth(3);
481 if (mShowOriginalDirection == UNVEIL_VERTICAL) {
482 canvas.drawLine(mImageBounds.left, mTouch.y,
483 mImageBounds.right, mTouch.y, paint);
485 canvas.drawLine(mTouch.x, mImageBounds.top,
486 mTouch.x, mImageBounds.bottom, paint);
489 Rect bounds = new Rect();
490 paint.setAntiAlias(true);
491 paint.setTextSize(mOriginalTextSize);
492 paint.getTextBounds(mOriginalText, 0, mOriginalText.length(), bounds);
493 paint.setColor(Color.BLACK);
494 paint.setStyle(Paint.Style.STROKE);
495 paint.setStrokeWidth(3);
496 canvas.drawText(mOriginalText, mImageBounds.left + mOriginalTextMargin,
497 mImageBounds.top + bounds.height() + mOriginalTextMargin, paint);
498 paint.setStyle(Paint.Style.FILL);
499 paint.setStrokeWidth(1);
500 paint.setColor(Color.WHITE);
501 canvas.drawText(mOriginalText, mImageBounds.left + mOriginalTextMargin,
502 mImageBounds.top + bounds.height() + mOriginalTextMargin, paint);
507 public void bindAsImageLoadListener() {
508 MasterImage.getImage().addListener(this);
511 public void updateImage() {
515 public void imageLoaded() {
519 public void saveImage(FilterShowActivity filterShowActivity, File file) {
520 SaveImage.saveImage(getImagePreset(), filterShowActivity, file);
524 public boolean scaleInProgress() {
525 return mScaleGestureDetector.isInProgress();
529 public boolean onTouchEvent(MotionEvent event) {
530 super.onTouchEvent(event);
531 int action = event.getAction();
532 action = action & MotionEvent.ACTION_MASK;
534 mGestureDetector.onTouchEvent(event);
535 boolean scaleInProgress = scaleInProgress();
536 mScaleGestureDetector.onTouchEvent(event);
537 if (mInteractionMode == InteractionMode.SCALE) {
540 if (!scaleInProgress() && scaleInProgress) {
541 // If we were scaling, the scale will stop but we will
542 // still issue an ACTION_UP. Let the subclasses know.
543 mFinishedScalingOperation = true;
546 int ex = (int) event.getX();
547 int ey = (int) event.getY();
548 if (action == MotionEvent.ACTION_DOWN) {
549 mInteractionMode = InteractionMode.MOVE;
552 mTouchShowOriginalDate = System.currentTimeMillis();
553 mShowOriginalDirection = 0;
554 MasterImage.getImage().setOriginalTranslation(MasterImage.getImage().getTranslation());
557 if (action == MotionEvent.ACTION_MOVE && mInteractionMode == InteractionMode.MOVE) {
561 float scaleFactor = MasterImage.getImage().getScaleFactor();
562 if (scaleFactor > 1 && (!ENABLE_ZOOMED_COMPARISON || event.getPointerCount() == 2)) {
563 float translateX = (mTouch.x - mTouchDown.x) / scaleFactor;
564 float translateY = (mTouch.y - mTouchDown.y) / scaleFactor;
565 Point originalTranslation = MasterImage.getImage().getOriginalTranslation();
566 Point translation = MasterImage.getImage().getTranslation();
567 translation.x = (int) (originalTranslation.x + translateX);
568 translation.y = (int) (originalTranslation.y + translateY);
569 constrainTranslation(translation, scaleFactor);
570 MasterImage.getImage().setTranslation(translation);
571 mTouchShowOriginal = false;
572 } else if (enableComparison() && !mOriginalDisabled
573 && (System.currentTimeMillis() - mTouchShowOriginalDate
574 > mTouchShowOriginalDelayMin)
575 && event.getPointerCount() == 1) {
576 mTouchShowOriginal = true;
580 if (action == MotionEvent.ACTION_UP) {
581 mInteractionMode = InteractionMode.NONE;
582 mTouchShowOriginal = false;
587 if (MasterImage.getImage().getScaleFactor() <= 1) {
588 MasterImage.getImage().setScaleFactor(1);
589 MasterImage.getImage().resetTranslation();
596 protected boolean enableComparison() {
601 public boolean onDoubleTap(MotionEvent arg0) {
605 scale = MasterImage.getImage().getMaxScaleFactor();
607 if (scale != MasterImage.getImage().getScaleFactor()) {
608 MasterImage.getImage().setScaleFactor(scale);
609 float translateX = (getWidth() / 2 - arg0.getX());
610 float translateY = (getHeight() / 2 - arg0.getY());
611 Point translation = MasterImage.getImage().getTranslation();
612 translation.x = (int) (mOriginalTranslation.x + translateX);
613 translation.y = (int) (mOriginalTranslation.y + translateY);
614 constrainTranslation(translation, scale);
615 MasterImage.getImage().setTranslation(translation);
621 private void constrainTranslation(Point translation, float scale) {
622 float maxTranslationX = getWidth() / scale;
623 float maxTranslationY = getHeight() / scale;
624 if (Math.abs(translation.x) > maxTranslationX) {
625 translation.x = (int) (Math.signum(translation.x) *
627 if (Math.abs(translation.y) > maxTranslationY) {
628 translation.y = (int) (Math.signum(translation.y) *
636 public boolean onDoubleTapEvent(MotionEvent arg0) {
641 public boolean onSingleTapConfirmed(MotionEvent arg0) {
646 public boolean onDown(MotionEvent arg0) {
651 public boolean onFling(MotionEvent startEvent, MotionEvent endEvent, float arg2, float arg3) {
652 if (mActivity == null) {
655 if (endEvent.getPointerCount() == 2) {
662 public void onLongPress(MotionEvent arg0) {
666 public boolean onScroll(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3) {
671 public void onShowPress(MotionEvent arg0) {
675 public boolean onSingleTapUp(MotionEvent arg0) {
679 public boolean useUtilityPanel() {
683 public void openUtilityPanel(final LinearLayout accessoryViewList) {
687 public boolean onScale(ScaleGestureDetector detector) {
688 MasterImage img = MasterImage.getImage();
689 float scaleFactor = img.getScaleFactor();
691 scaleFactor = scaleFactor * detector.getScaleFactor();
692 if (scaleFactor > MasterImage.getImage().getMaxScaleFactor()) {
693 scaleFactor = MasterImage.getImage().getMaxScaleFactor();
695 if (scaleFactor < 0.5) {
698 MasterImage.getImage().setScaleFactor(scaleFactor);
699 scaleFactor = img.getScaleFactor();
700 float focusx = detector.getFocusX();
701 float focusy = detector.getFocusY();
702 float translateX = (focusx - mStartFocusX) / scaleFactor;
703 float translateY = (focusy - mStartFocusY) / scaleFactor;
704 Point translation = MasterImage.getImage().getTranslation();
705 translation.x = (int) (mOriginalTranslation.x + translateX);
706 translation.y = (int) (mOriginalTranslation.y + translateY);
707 constrainTranslation(translation, scaleFactor);
708 MasterImage.getImage().setTranslation(translation);
715 public boolean onScaleBegin(ScaleGestureDetector detector) {
716 Point pos = MasterImage.getImage().getTranslation();
717 mOriginalTranslation.x = pos.x;
718 mOriginalTranslation.y = pos.y;
719 mOriginalScale = MasterImage.getImage().getScaleFactor();
720 mStartFocusX = detector.getFocusX();
721 mStartFocusY = detector.getFocusY();
722 mInteractionMode = InteractionMode.SCALE;
727 public void onScaleEnd(ScaleGestureDetector detector) {
728 mInteractionMode = InteractionMode.NONE;
729 if (MasterImage.getImage().getScaleFactor() < 1) {
730 MasterImage.getImage().setScaleFactor(1);
735 public boolean didFinishScalingOperation() {
736 if (mFinishedScalingOperation) {
737 mFinishedScalingOperation = false;