1427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu/*
2427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu * Copyright (C) 2014 The Android Open Source Project
3427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu *
4427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu * Licensed under the Apache License, Version 2.0 (the "License");
5427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu * you may not use this file except in compliance with the License.
6427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu * You may obtain a copy of the License at
7427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu *
8427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu *      http://www.apache.org/licenses/LICENSE-2.0
9427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu *
10427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu * Unless required by applicable law or agreed to in writing, software
11427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu * distributed under the License is distributed on an "AS IS" BASIS,
12427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu * See the License for the specific language governing permissions and
14427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu * limitations under the License.
15427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu */
16427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
17427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hupackage com.android.camera.widget;
18427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
19427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Huimport android.animation.Animator;
20427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Huimport android.animation.AnimatorListenerAdapter;
21427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Huimport android.animation.AnimatorSet;
22427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Huimport android.animation.ValueAnimator;
23427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Huimport android.content.Context;
24427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Huimport android.graphics.Bitmap;
25427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Huimport android.graphics.BitmapShader;
26427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Huimport android.graphics.Canvas;
27427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Huimport android.graphics.Color;
28427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Huimport android.graphics.Matrix;
29427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Huimport android.graphics.Paint;
30427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Huimport android.graphics.RectF;
31427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Huimport android.graphics.Shader;
32427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Huimport android.util.AttributeSet;
33427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Huimport android.view.View;
34427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Huimport android.view.animation.AccelerateDecelerateInterpolator;
35427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Huimport android.view.animation.AnimationUtils;
36427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Huimport android.view.animation.Interpolator;
37427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
3830eda38142fd279e0f10796e39f469d97f2a5b43Senpo Huimport com.android.camera.async.MainThread;
39427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Huimport com.android.camera.debug.Log;
4054ac03ba6ae3e739df74c6d9e35fda9017be07d7Senpo Huimport com.android.camera.ui.motion.InterpolatorHelper;
41427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Huimport com.android.camera.util.ApiHelper;
42427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Huimport com.android.camera2.R;
4330eda38142fd279e0f10796e39f469d97f2a5b43Senpo Huimport com.google.common.base.Optional;
4430eda38142fd279e0f10796e39f469d97f2a5b43Senpo Hu
45427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu/**
46427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu * A view that shows a pop-out effect for a thumbnail image as the new capture indicator design for
47427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu * Haleakala. When a photo is taken, this view will appear in the bottom right corner of the view
48427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu * finder to indicate the capture is done.
49427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu *
50427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu * Thumbnail cropping:
51427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu *   (1) 100% width and vertically centered for portrait.
52427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu *   (2) 100% height and horizontally centered for landscape.
53427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu *
54427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu * General behavior spec: Hide the capture indicator by fading out using fast_out_linear_in (150ms):
55427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu *   (1) User open filmstrip.
56427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu *   (2) User switch module.
57427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu *   (3) User switch front/back camera.
58427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu *   (4) User close app.
59427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu *
60427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu * Visual spec:
61427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu *   (1) A 12dp spacing between mode option overlay and thumbnail.
62427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu *   (2) A circular mask that excludes the corners of the preview image.
63427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu *   (3) A solid white layer that sits on top of the preview and is also masked by 2).
64427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu *   (4) The preview thumbnail image.
65427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu *   (5) A 'ripple' which is just a white circular stroke.
66427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu *
67427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu * Animation spec:
68427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu * - For (2) only the scale animates, from 50%(24dp) to 114%(54dp) in 200ms then falls back to
69427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu *   100%(48dp) in 200ms. Both steps use the same easing: fast_out_slow_in.
70427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu * - For (3), change opacity from 50% to 0% over 150ms, easing is exponential.
71427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu * - For (4), doesn't animate.
72427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu * - For (5), starts animating after 100ms, when (1) is at its peak radius and all animations take
73427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu *   200ms, using linear_out_slow in. Opacity goes from 40% to 0%, radius goes from 40dp to 70dp,
74427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu *   stroke width goes from 5dp to 1dp.
75427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu */
76427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hupublic class RoundedThumbnailView extends View {
77427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    private static final Log.Tag TAG = new Log.Tag("RoundedThumbnailView");
78427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
79ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling     // Configurations for the thumbnail pop-out effect.
80427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    private static final long THUMBNAIL_STRETCH_DURATION_MS = 200;
81427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    private static final long THUMBNAIL_SHRINK_DURATION_MS = 200;
82427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    private static final float THUMBNAIL_REVEAL_CIRCLE_OPACITY_BEGIN = 0.5f;
83427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    private static final float THUMBNAIL_REVEAL_CIRCLE_OPACITY_END = 0.0f;
84ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling
85ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling    // Configurations for the ripple effect.
86427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    private static final long RIPPLE_DURATION_MS = 200;
87427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    private static final float RIPPLE_OPACITY_BEGIN = 0.4f;
88427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    private static final float RIPPLE_OPACITY_END = 0.0f;
89ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling
90ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling    // Configurations for the hit-state effect.
91cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu    private static final float HIT_STATE_CIRCLE_OPACITY_HIDDEN = -1.0f;
92cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu    private static final float HIT_STATE_CIRCLE_OPACITY_BEGIN = 0.7f;
93cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu    private static final float HIT_STATE_CIRCLE_OPACITY_END = 0.0f;
94cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu    private static final long HIT_STATE_DURATION_MS = 150;
95cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu
96ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling    /** Defines call events. */
97cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu    public interface Callback {
98cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu        public void onHitStateFinished();
99cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu    }
100cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu
101cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu    /** The registered callback. */
102cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu    private Optional<Callback> mCallback;
103427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
104ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling    // Fields for view layout.
105427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    private float mThumbnailPadding;
106d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde    private RectF mViewRect;
107427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
108ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling    // Fields for the thumbnail pop-out effect.
109ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling    /** The animators to move the thumbnail. */
110cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu    private AnimatorSet mThumbnailAnimatorSet;
111ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling    /** The current diameter for the thumbnail image. */
112cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu    private float mCurrentThumbnailDiameter;
113ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling    /** The current reveal circle opacity. */
114cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu    private float mCurrentRevealCircleOpacity;
115ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling    /** The duration of the stretch phase in thumbnail pop-out effect. */
116427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    private long mThumbnailStretchDurationMs;
117ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling    /** The duration of the shrink phase in thumbnail pop-out effect. */
118427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    private long mThumbnailShrinkDurationMs;
119ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling    /**
120ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling     * The beginning diameter of the thumbnail for the stretch phase in
121ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling     * thumbnail pop-out effect.
122ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling     */
123427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    private float mThumbnailStretchDiameterBegin;
124ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling    /**
125ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling     * The ending diameter of the thumbnail for the stretch phase in thumbnail
126ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling     * pop-out effect.
127ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling     */
128427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    private float mThumbnailStretchDiameterEnd;
129ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling    /**
130ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling     * The beginning diameter of the thumbnail for the shrink phase in thumbnail
131ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling     * pop-out effect.
132ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling     */
133427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    private float mThumbnailShrinkDiameterBegin;
134ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling    /**
135ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling     * The ending diameter of the thumbnail for the shrink phase in thumbnail
136ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling     * pop-out effect.
137ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling     */
138427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    private float mThumbnailShrinkDiameterEnd;
139ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling    /** Paint object for the reveal circle. */
140d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde    private final Paint mRevealCirclePaint;
141427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
142ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling    // Fields for the ripple effect.
143ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling    /** The start delay of the ripple effect. */
144427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    private long mRippleStartDelayMs;
145ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling    /** The duration of the ripple effect. */
146427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    private long mRippleDurationMs;
147ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling    /** The beginning diameter of the ripple ring. */
148427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    private float mRippleRingDiameterBegin;
149ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling    /** The ending diameter of the ripple ring. */
150427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    private float mRippleRingDiameterEnd;
151ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling    /** The beginning thickness of the ripple ring. */
152427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    private float mRippleRingThicknessBegin;
153ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling    /** The ending thickness of the ripple ring. */
154427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    private float mRippleRingThicknessEnd;
155ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling    /** A lazily loaded animator for the ripple effect. */
156427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    private ValueAnimator mRippleAnimator;
157ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling    /**
158ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling     * The current ripple ring diameter which is updated by the ripple animator
159ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling     * and used by onDraw().
160ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling     */
161427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    private float mCurrentRippleRingDiameter;
162ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling    /**
163ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling     * The current ripple ring thickness which is updated by the ripple animator
164ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling     * and used by onDraw().
165ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling     */
166427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    private float mCurrentRippleRingThickness;
167ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling    /**
168ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling     * The current ripple ring opacity which is updated by the ripple animator
169ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling     * and used by onDraw().
170ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling     */
171427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    private float mCurrentRippleRingOpacity;
172ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling    /** The paint used for drawing the ripple effect. */
173d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde    private final Paint mRipplePaint;
174427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
175ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling    // Fields for the hit state effect.
176ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling    /** The paint to draw hit state circle. */
177ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling    private final Paint mHitStateCirclePaint;
178cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu    /**
179ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling     * The current hit state circle opacity (0.0 - 1.0) which is updated by the
180ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling     * hit state animator. If -1, the hit state circle won't be drawn.
181cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu     */
182cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu    private float mCurrentHitStateCircleOpacity;
183cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu
184ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling    /**
185b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde     * The pending reveal request. This is created when start is called, but is
186b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde     * not drawn until the thumbnail is available. Once the bitmap is available
187b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde     * it is swapped into the foreground request.
188ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling     */
189b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde    private RevealRequest mPendingRequest;
190427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
191b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde    /** The currently animating reveal request. */
192b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde    private RevealRequest mForegroundRequest;
193427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
194ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling    /**
195b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde     * The latest finished reveal request. Its thumbnail will be shown until
196b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde     * a newer one replace it.
197ee669af495f2d3d22c97ab53fc5804c41e807e67Sascha Haeberling     */
198b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde    private RevealRequest mBackgroundRequest;
199427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
200cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu    private View.OnClickListener mOnClickListener = new View.OnClickListener() {
201cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu        @Override
202cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu        public void onClick(View v) {
203cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu            // Trigger the hit state animation. Fade out the hit state white
204cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu            // circle by changing the alpha.
205cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu            final ValueAnimator hitStateAnimator = ValueAnimator.ofFloat(
206cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu                    HIT_STATE_CIRCLE_OPACITY_BEGIN, HIT_STATE_CIRCLE_OPACITY_END);
207cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu            hitStateAnimator.setDuration(HIT_STATE_DURATION_MS);
208cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu            hitStateAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
209cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu            hitStateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
210cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu                @Override
211cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu                public void onAnimationUpdate(ValueAnimator valueAnimator) {
212cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu                    mCurrentHitStateCircleOpacity = (Float) valueAnimator.getAnimatedValue();
213cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu                    invalidate();
214cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu                }
215cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu            });
216cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu            hitStateAnimator.addListener(new AnimatorListenerAdapter() {
217cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu                @Override
218cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu                public void onAnimationEnd(Animator animation) {
219cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu                    super.onAnimationEnd(animation);
220cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu                    mCurrentHitStateCircleOpacity = HIT_STATE_CIRCLE_OPACITY_HIDDEN;
221cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu                    if (mCallback.isPresent()) {
222cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu                        mCallback.get().onHitStateFinished();
223cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu                    }
224cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu                }
225cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu            });
226cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu            hitStateAnimator.start();
227cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu        }
228cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu    };
229cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu
230427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    /**
231427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu     * Constructs a RoundedThumbnailView.
232427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu     */
233427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    public RoundedThumbnailView(Context context, AttributeSet attrs) {
234427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        super(context, attrs);
235427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
236cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu        mCallback = Optional.absent();
237cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu
238427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        // Make the view clickable.
239427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        setClickable(true);
240cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu        setOnClickListener(mOnClickListener);
241427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
242427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        mThumbnailPadding = getResources().getDimension(R.dimen.rounded_thumbnail_padding);
243427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
244427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        // Load thumbnail pop-out effect constants.
245427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        mThumbnailStretchDurationMs = THUMBNAIL_STRETCH_DURATION_MS;
246427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        mThumbnailShrinkDurationMs = THUMBNAIL_SHRINK_DURATION_MS;
247427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        mThumbnailStretchDiameterBegin =
248427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu                getResources().getDimension(R.dimen.rounded_thumbnail_diameter_min);
249427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        mThumbnailStretchDiameterEnd =
250427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu                getResources().getDimension(R.dimen.rounded_thumbnail_diameter_max);
251427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        mThumbnailShrinkDiameterBegin = mThumbnailStretchDiameterEnd;
252427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        mThumbnailShrinkDiameterEnd =
253427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu                getResources().getDimension(R.dimen.rounded_thumbnail_diameter_normal);
254427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        // Load ripple effect constants.
255427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        float startDelayRatio = 0.5f;
256427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        mRippleStartDelayMs = (long) (mThumbnailStretchDurationMs * startDelayRatio);
257427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        mRippleDurationMs = RIPPLE_DURATION_MS;
258427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        mRippleRingDiameterEnd =
259427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu                getResources().getDimension(R.dimen.rounded_thumbnail_ripple_ring_diameter_max);
260d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde
261d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde        mViewRect = new RectF(0, 0, mRippleRingDiameterEnd, mRippleRingDiameterEnd);
262d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde
263427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        mRippleRingDiameterBegin =
264427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu                getResources().getDimension(R.dimen.rounded_thumbnail_ripple_ring_diameter_min);
265427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        mRippleRingThicknessBegin =
266427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu                getResources().getDimension(R.dimen.rounded_thumbnail_ripple_ring_thick_max);
267427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        mRippleRingThicknessEnd =
268427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu                getResources().getDimension(R.dimen.rounded_thumbnail_ripple_ring_thick_min);
26930eda38142fd279e0f10796e39f469d97f2a5b43Senpo Hu
270cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu        mCurrentHitStateCircleOpacity = HIT_STATE_CIRCLE_OPACITY_HIDDEN;
271cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu        // Draw the reveal while circle.
272cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu        mHitStateCirclePaint = new Paint();
273cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu        mHitStateCirclePaint.setAntiAlias(true);
274cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu        mHitStateCirclePaint.setColor(Color.WHITE);
275cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu        mHitStateCirclePaint.setStyle(Paint.Style.FILL);
276cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu
277d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde        mRipplePaint = new Paint();
278d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde        mRipplePaint.setAntiAlias(true);
279d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde        mRipplePaint.setColor(Color.WHITE);
280d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde        mRipplePaint.setStyle(Paint.Style.STROKE);
281d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde
282d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde        mRevealCirclePaint = new Paint();
283d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde        mRevealCirclePaint.setAntiAlias(true);
284d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde        mRevealCirclePaint.setColor(Color.WHITE);
285d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde        mRevealCirclePaint.setStyle(Paint.Style.FILL);
286427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    }
287427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
288427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    @Override
289427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
290427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        // Ignore the spec since the size should be fixed.
291427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        int desiredSize = (int) mRippleRingDiameterEnd;
292427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        setMeasuredDimension(desiredSize, desiredSize);
293427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    }
294427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
295427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    @Override
296427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    protected void onDraw(Canvas canvas) {
297427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        super.onDraw(canvas);
298427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
299cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu        final float centerX = canvas.getWidth() / 2;
300cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu        final float centerY = canvas.getHeight() / 2;
301cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu
302cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu        final float viewDiameter = mRippleRingDiameterEnd;
303cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu        final float finalDiameter = mThumbnailShrinkDiameterEnd;
304d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde
305d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde        canvas.clipRect(mViewRect);
306427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
307427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        // Draw the thumbnail of latest finished reveal request.
308b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        if (mBackgroundRequest != null) {
309b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde            Paint thumbnailPaint = mBackgroundRequest.getThumbnailPaint();
310427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu            if (thumbnailPaint != null) {
311427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu                // Draw the old thumbnail with the final diameter.
312cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu                float scaleRatio = finalDiameter / viewDiameter;
313427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
314427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu                canvas.save();
315427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu                canvas.scale(scaleRatio, scaleRatio, centerX, centerY);
316427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu                canvas.drawRoundRect(
317d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde                        mViewRect,
318427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu                        centerX,
319427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu                        centerY,
320427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu                        thumbnailPaint);
321427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu                canvas.restore();
322427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu            }
323427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        }
324427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
325427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        // Draw animated parts (thumbnail and ripple) if there exists a reveal request.
326b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        if (mForegroundRequest != null) {
327427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu            // Draw ripple ring first or the ring will cover thumbnail.
328427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu            if (mCurrentRippleRingThickness > 0) {
329427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu                // Draw the ripple ring.
330d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde                mRipplePaint.setAlpha((int) (mCurrentRippleRingOpacity * 255));
331d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde                mRipplePaint.setStrokeWidth(mCurrentRippleRingThickness);
332427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
333427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu                canvas.save();
334d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde                canvas.drawCircle(centerX, centerY, mCurrentRippleRingDiameter / 2, mRipplePaint);
335427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu                canvas.restore();
336427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu            }
337427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
338427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu            // Achieve the animation effect by scaling the transformation matrix.
339427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu            float scaleRatio = mCurrentThumbnailDiameter / mRippleRingDiameterEnd;
340427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
341427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu            canvas.save();
342427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu            canvas.scale(scaleRatio, scaleRatio, centerX, centerY);
343427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
344427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu            // Draw the new popping up thumbnail.
345b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde            Paint thumbnailPaint = mForegroundRequest.getThumbnailPaint();
346427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu            if (thumbnailPaint != null) {
347427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu                canvas.drawRoundRect(
348d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde                        mViewRect,
349427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu                        centerX,
350427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu                        centerY,
351427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu                        thumbnailPaint);
352427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu            }
353427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
354427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu            // Draw the reveal while circle.
355d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde            mRevealCirclePaint.setAlpha((int) (mCurrentRevealCircleOpacity * 255));
356427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu            canvas.drawCircle(centerX, centerY,
357d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde                    mRippleRingDiameterEnd / 2, mRevealCirclePaint);
358427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
359427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu            canvas.restore();
360427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        }
361cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu
362cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu        // Draw hit state circle if necessary.
363cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu        if (mCurrentHitStateCircleOpacity != HIT_STATE_CIRCLE_OPACITY_HIDDEN) {
364cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu            canvas.save();
365cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu            final float scaleRatio = finalDiameter / viewDiameter;
366cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu            canvas.scale(scaleRatio, scaleRatio, centerX, centerY);
367cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu
368cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu            // Draw the hit state while circle.
369cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu            mHitStateCirclePaint.setAlpha((int) (mCurrentHitStateCircleOpacity * 255));
370cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu            canvas.drawCircle(centerX, centerY,
371cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu                    mRippleRingDiameterEnd / 2, mHitStateCirclePaint);
372cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu            canvas.restore();
373cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu        }
374cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu    }
375cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu
376cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu    /**
377cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu     * Sets the callback.
378cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu     *
379cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu     * @param callback The callback to be set.
380cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu     */
381cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu    public void setCallback(Callback callback) {
382cb8f66b4953e4fed4a0cd5133c2914b26097c133Senpo Hu        mCallback = Optional.of(callback);
383427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    }
384427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
385427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    /**
38654ac03ba6ae3e739df74c6d9e35fda9017be07d7Senpo Hu     * Gets the padding size with mode options and preview edges.
387427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu     *
38854ac03ba6ae3e739df74c6d9e35fda9017be07d7Senpo Hu     * @return The padding size with mode options and preview edges.
389427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu     */
39054ac03ba6ae3e739df74c6d9e35fda9017be07d7Senpo Hu    public float getThumbnailPadding() {
39154ac03ba6ae3e739df74c6d9e35fda9017be07d7Senpo Hu        return mThumbnailPadding;
39254ac03ba6ae3e739df74c6d9e35fda9017be07d7Senpo Hu    }
39354ac03ba6ae3e739df74c6d9e35fda9017be07d7Senpo Hu
39454ac03ba6ae3e739df74c6d9e35fda9017be07d7Senpo Hu    /**
39554ac03ba6ae3e739df74c6d9e35fda9017be07d7Senpo Hu     * Gets the diameter of the thumbnail image after the revealing animation.
39654ac03ba6ae3e739df74c6d9e35fda9017be07d7Senpo Hu     *
39754ac03ba6ae3e739df74c6d9e35fda9017be07d7Senpo Hu     * @return The diameter of the thumbnail image after the revealing animation.
39854ac03ba6ae3e739df74c6d9e35fda9017be07d7Senpo Hu     */
39954ac03ba6ae3e739df74c6d9e35fda9017be07d7Senpo Hu    public float getThumbnailFinalDiameter() {
40054ac03ba6ae3e739df74c6d9e35fda9017be07d7Senpo Hu        return mThumbnailShrinkDiameterEnd;
401427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    }
402427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
403427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    /**
404427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu     * Starts the thumbnail revealing animation.
405427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu     *
406427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu     * @param accessibilityString An accessibility String to be announced during the revealing
407427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu     *                            animation.
408427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu     */
409427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    public void startRevealThumbnailAnimation(String accessibilityString) {
41030eda38142fd279e0f10796e39f469d97f2a5b43Senpo Hu        MainThread.checkMainThread();
411427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        // Create a new request.
412b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        mPendingRequest = new RevealRequest(getMeasuredWidth(), accessibilityString);
413427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    }
414427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
415427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    /**
416427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu     * Updates the thumbnail image.
417427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu     *
418427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu     * @param thumbnailBitmap The thumbnail image to be shown.
41904aab100674dfdd11bffabc46e6a199f5e9a5267Paul Rohde     * @param rotation The orientation of the image in degrees.
420427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu     */
42104aab100674dfdd11bffabc46e6a199f5e9a5267Paul Rohde    public void setThumbnail(final Bitmap thumbnailBitmap, final int rotation) {
42230eda38142fd279e0f10796e39f469d97f2a5b43Senpo Hu        MainThread.checkMainThread();
423b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde
424b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        if(mPendingRequest != null) {
425b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde            mPendingRequest.setThumbnailBitmap(thumbnailBitmap, rotation);
426b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde
427b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde            runPendingRequestAnimation();
428427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        } else {
429b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde            Log.e(TAG, "Pending thumb was null!");
430427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        }
431427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    }
432427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
433427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    /**
434427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu     * Hide the thumbnail.
435427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu     */
436427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    public void hideThumbnail() {
43730eda38142fd279e0f10796e39f469d97f2a5b43Senpo Hu        MainThread.checkMainThread();
438427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        // Make this view invisible.
439427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        setVisibility(GONE);
440427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
441b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        clearAnimations();
442b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde
443b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        // Remove all pending reveal requests.
444b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        mPendingRequest = null;
445b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        mForegroundRequest = null;
446b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        mBackgroundRequest = null;
447b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde    }
448b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde
449b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde    /**
450b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde     * Stop currently running animators.
451b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde     */
452b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde    private void clearAnimations() {
453427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        // Stop currently running animators.
454427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        if (mThumbnailAnimatorSet != null && mThumbnailAnimatorSet.isRunning()) {
45568d834ff57c8e20a8c040cdcbf50d0e3332d6385Senpo Hu            mThumbnailAnimatorSet.removeAllListeners();
456daea74c9c1708908ae801d53ef3288500cc9532fSenpo Hu            mThumbnailAnimatorSet.cancel();
4572eb1f8531b4879b836206cbde0e57335e300b445I-Jong Lin            // Release the animator so that a new instance will be created and
4582eb1f8531b4879b836206cbde0e57335e300b445I-Jong Lin            // its listeners properly reconnected.  Fix for b/19034435
4592eb1f8531b4879b836206cbde0e57335e300b445I-Jong Lin            mThumbnailAnimatorSet = null;
460427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        }
4612eb1f8531b4879b836206cbde0e57335e300b445I-Jong Lin
462427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        if (mRippleAnimator != null && mRippleAnimator.isRunning()) {
46368d834ff57c8e20a8c040cdcbf50d0e3332d6385Senpo Hu            mRippleAnimator.removeAllListeners();
464daea74c9c1708908ae801d53ef3288500cc9532fSenpo Hu            mRippleAnimator.cancel();
4652eb1f8531b4879b836206cbde0e57335e300b445I-Jong Lin            // Release the animator so that a new instance will be created and
4662eb1f8531b4879b836206cbde0e57335e300b445I-Jong Lin            // its listeners properly reconnected.  Fix for b/19034435
4672eb1f8531b4879b836206cbde0e57335e300b445I-Jong Lin            mRippleAnimator = null;
468427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        }
469427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    }
470427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
471427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    /**
472b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde     * Set the foreground request to the background, complete it, and run the
473b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde     * animation for the pending thumbnail.
474427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu     */
475b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde    private void runPendingRequestAnimation() {
476b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        // Shift foreground to background, and pending to foreground.
477b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        if (mForegroundRequest != null) {
478b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde            mBackgroundRequest = mForegroundRequest;
479b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde            mBackgroundRequest.finishRippleAnimation();
480b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde            mBackgroundRequest.finishThumbnailAnimation();
481427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        }
482427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
483b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        mForegroundRequest = mPendingRequest;
484b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        mPendingRequest = null;
485427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
486427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        // Make this view visible.
487427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        setVisibility(VISIBLE);
488427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
489b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        // Ensure there are no running animations.
490b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        clearAnimations();
491427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
492b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        Interpolator stretchInterpolator;
493b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        if (ApiHelper.isLOrHigher()) {
494b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde            // Both phases use fast_out_flow_in interpolator.
495b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde            stretchInterpolator = AnimationUtils.loadInterpolator(
496b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde                  getContext(), android.R.interpolator.fast_out_slow_in);
497b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        } else {
498b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde            stretchInterpolator = new AccelerateDecelerateInterpolator();
499b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        }
500427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
501b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        // The first phase of thumbnail animation. Stretch the thumbnail to the maximal size.
502b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        ValueAnimator stretchAnimator = ValueAnimator.ofFloat(
503b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde              mThumbnailStretchDiameterBegin, mThumbnailStretchDiameterEnd);
504b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        stretchAnimator.setDuration(mThumbnailStretchDurationMs);
505b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        stretchAnimator.setInterpolator(stretchInterpolator);
506b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        stretchAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
507b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde            @Override
508b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde            public void onAnimationUpdate(ValueAnimator valueAnimator) {
509b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde                mCurrentThumbnailDiameter = (Float) valueAnimator.getAnimatedValue();
510b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde                float fraction = valueAnimator.getAnimatedFraction();
511b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde                float opacityDiff = THUMBNAIL_REVEAL_CIRCLE_OPACITY_END -
512b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde                      THUMBNAIL_REVEAL_CIRCLE_OPACITY_BEGIN;
513b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde                mCurrentRevealCircleOpacity =
514b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde                      THUMBNAIL_REVEAL_CIRCLE_OPACITY_BEGIN + fraction * opacityDiff;
515b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde                invalidate();
516b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde            }
517b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        });
518b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde
519b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        // The second phase of thumbnail animation. Shrink the thumbnail to the final size.
520b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        Interpolator shrinkInterpolator = stretchInterpolator;
521b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        ValueAnimator shrinkAnimator = ValueAnimator.ofFloat(
522b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde              mThumbnailShrinkDiameterBegin, mThumbnailShrinkDiameterEnd);
523b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        shrinkAnimator.setDuration(mThumbnailShrinkDurationMs);
524b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        shrinkAnimator.setInterpolator(shrinkInterpolator);
525b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        shrinkAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
526b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde            @Override
527b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde            public void onAnimationUpdate(ValueAnimator valueAnimator) {
528b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde                mCurrentThumbnailDiameter = (Float) valueAnimator.getAnimatedValue();
529b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde                invalidate();
530b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde            }
531b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        });
532b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde
533b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        // The stretch and shrink animators play sequentially.
534b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        mThumbnailAnimatorSet = new AnimatorSet();
535b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        mThumbnailAnimatorSet.playSequentially(stretchAnimator, shrinkAnimator);
536b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        mThumbnailAnimatorSet.addListener(new AnimatorListenerAdapter() {
537b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde            @Override
538b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde            public void onAnimationEnd(Animator animation) {
539b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde                if (mForegroundRequest != null) {
540b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde                    // Mark the thumbnail animation as finished.
541b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde                    mForegroundRequest.finishThumbnailAnimation();
542b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde                    processRevealRequests();
543427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu                }
544b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde            }
545b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        });
546427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
547427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        // Start thumbnail animation immediately.
548427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        mThumbnailAnimatorSet.start();
549427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
550427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        // Lazily load the ripple animator.
551b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        // Ripple effect uses linear_out_slow_in interpolator.
552b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        Interpolator rippleInterpolator =
553b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde              InterpolatorHelper.getLinearOutSlowInInterpolator(getContext());
554b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde
555b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        // When start shrinking the thumbnail, a ripple effect is triggered at the same time.
556b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        mRippleAnimator =
557b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde              ValueAnimator.ofFloat(mRippleRingDiameterBegin, mRippleRingDiameterEnd);
558b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        mRippleAnimator.setDuration(mRippleDurationMs);
559b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        mRippleAnimator.setInterpolator(rippleInterpolator);
560b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        mRippleAnimator.addListener(new AnimatorListenerAdapter() {
561b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde            @Override
562b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde            public void onAnimationEnd(Animator animation) {
563b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde                if (mForegroundRequest != null) {
564b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde                    mForegroundRequest.finishRippleAnimation();
565b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde                    processRevealRequests();
566427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu                }
567b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde            }
568b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        });
569b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        mRippleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
570b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde            @Override
571b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde            public void onAnimationUpdate(ValueAnimator valueAnimator) {
572b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde                mCurrentRippleRingDiameter = (Float) valueAnimator.getAnimatedValue();
573b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde                float fraction = valueAnimator.getAnimatedFraction();
574b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde                mCurrentRippleRingThickness = mRippleRingThicknessBegin +
575b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde                      fraction * (mRippleRingThicknessEnd - mRippleRingThicknessBegin);
576b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde                mCurrentRippleRingOpacity = RIPPLE_OPACITY_BEGIN +
577b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde                      fraction * (RIPPLE_OPACITY_END - RIPPLE_OPACITY_BEGIN);
578b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde                invalidate();
579b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde            }
580b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        });
581b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde
582427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        // Start ripple animation after delay.
583427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        mRippleAnimator.setStartDelay(mRippleStartDelayMs);
584427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        mRippleAnimator.start();
585427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
586427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        // Announce the accessibility string.
587b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        announceForAccessibility(mForegroundRequest.getAccessibilityString());
588b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde    }
589b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde
590b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde    private void processRevealRequests() {
591b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        if(mForegroundRequest != null && mForegroundRequest.isFinished()) {
592b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde            mBackgroundRequest = mForegroundRequest;
593b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde            mForegroundRequest = null;
594b759b67a8574fa30ea04d2340ecdfc2979ec0304Paul Rohde        }
595427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    }
596427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
597d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde    @Override
598d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde    public boolean hasOverlappingRendering() {
599d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde        return true;
600d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde    }
601d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde
602427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    /**
603427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu     * Encapsulates necessary information for a complete thumbnail reveal animation.
604427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu     */
605427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    private static class RevealRequest {
606427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        // The size of the thumbnail.
607427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        private float mViewSize;
608427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
609427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        // The accessibility string.
610427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        private String mAccessibilityString;
611427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
612427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        // The cached Paint object to draw the thumbnail.
613427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        private Paint mThumbnailPaint;
614427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
615427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        // The flag to indicate if thumbnail animation of this request is full-filled.
616427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        private boolean mThumbnailAnimationFinished;
617427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
618427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        // The flag to indicate if ripple animation of this request is full-filled.
619427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        private boolean mRippleAnimationFinished;
620427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
621427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        /**
622427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu         * Constructs a reveal request. Use setThumbnailBitmap() to specify a source bitmap for the
623427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu         * thumbnail.
624427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu         *
625427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu         * @param viewSize The size of the capture indicator view.
626427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu         * @param accessibilityString The accessibility string of the request.
627427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu         */
628427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        public RevealRequest(float viewSize, String accessibilityString) {
629427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu            mAccessibilityString = accessibilityString;
630427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu            mViewSize = viewSize;
631427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        }
632427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
633427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        /**
634427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu         * Returns the accessibility string.
635427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu         *
636427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu         * @return the accessibility string.
637427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu         */
638427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        public String getAccessibilityString() {
639427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu            return mAccessibilityString;
640427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        }
641427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
642427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        /**
643427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu         * Returns the paint object which can be used to draw the thumbnail on a Canvas.
644427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu         *
645427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu         * @return the paint object which can be used to draw the thumbnail on a Canvas.
646427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu         */
647427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        public Paint getThumbnailPaint() {
648d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde            return mThumbnailPaint;
649d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde        }
650d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde
651d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde        /**
652d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde         * Used to precompute the thumbnail paint from the given source bitmap.
653d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde         */
65404aab100674dfdd11bffabc46e6a199f5e9a5267Paul Rohde        private void precomputeThumbnailPaint(Bitmap srcBitmap, int rotation) {
655427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu            // Lazy loading the thumbnail paint object.
656427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu            if (mThumbnailPaint == null) {
657427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu                // Can't create a paint object until the thumbnail bitmap is available.
658d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde                if (srcBitmap == null) {
659d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde                    return;
660427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu                }
661a45abcb3cfc48f1718b77a616e000d1678fbd85bSenpo Hu                // The original bitmap should be a square shape.
662d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde                if (srcBitmap.getWidth() != srcBitmap.getHeight()) {
663d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde                    return;
664a45abcb3cfc48f1718b77a616e000d1678fbd85bSenpo Hu                }
665427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
666427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu                // Create a bitmap shader for the paint.
667427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu                BitmapShader shader = new BitmapShader(
668d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde                      srcBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
669d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde                if (srcBitmap.getWidth() != mViewSize) {
670a45abcb3cfc48f1718b77a616e000d1678fbd85bSenpo Hu                    // Create a transformation matrix for the bitmap shader if the size is not
671a45abcb3cfc48f1718b77a616e000d1678fbd85bSenpo Hu                    // matched.
672a45abcb3cfc48f1718b77a616e000d1678fbd85bSenpo Hu                    RectF srcRect = new RectF(
673d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde                          0.0f, 0.0f, srcBitmap.getWidth(), srcBitmap.getHeight());
674a45abcb3cfc48f1718b77a616e000d1678fbd85bSenpo Hu                    RectF dstRect = new RectF(0.0f, 0.0f, mViewSize, mViewSize);
67504aab100674dfdd11bffabc46e6a199f5e9a5267Paul Rohde
676a45abcb3cfc48f1718b77a616e000d1678fbd85bSenpo Hu                    Matrix shaderMatrix = new Matrix();
67704aab100674dfdd11bffabc46e6a199f5e9a5267Paul Rohde
67804aab100674dfdd11bffabc46e6a199f5e9a5267Paul Rohde                    // Scale the shader to fit the destination view size.
679a45abcb3cfc48f1718b77a616e000d1678fbd85bSenpo Hu                    shaderMatrix.setRectToRect(srcRect, dstRect, Matrix.ScaleToFit.FILL);
68004aab100674dfdd11bffabc46e6a199f5e9a5267Paul Rohde
68104aab100674dfdd11bffabc46e6a199f5e9a5267Paul Rohde                    // Rotate the image around the given source rect point.
68204aab100674dfdd11bffabc46e6a199f5e9a5267Paul Rohde                    shaderMatrix.preRotate(rotation,
68304aab100674dfdd11bffabc46e6a199f5e9a5267Paul Rohde                          srcRect.width() / 2,
68404aab100674dfdd11bffabc46e6a199f5e9a5267Paul Rohde                          srcRect.height() / 2);
68504aab100674dfdd11bffabc46e6a199f5e9a5267Paul Rohde
686a45abcb3cfc48f1718b77a616e000d1678fbd85bSenpo Hu                    shader.setLocalMatrix(shaderMatrix);
687a45abcb3cfc48f1718b77a616e000d1678fbd85bSenpo Hu                }
688427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
689427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu                // Create the paint for drawing the thumbnail in a circle.
690427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu                mThumbnailPaint = new Paint();
691427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu                mThumbnailPaint.setAntiAlias(true);
692427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu                mThumbnailPaint.setShader(shader);
693427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu            }
694427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        }
695427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
696427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        /**
697427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu         * Checks if the request is full-filled.
698427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu         *
699427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu         * @return True if both thumbnail animation and ripple animation are finished
700427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu         */
701427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        public boolean isFinished() {
702427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu            return mThumbnailAnimationFinished && mRippleAnimationFinished;
703427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        }
704427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
705427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        /**
706427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu         * Marks the thumbnail animation is finished.
707427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu         */
708427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        public void finishThumbnailAnimation() {
709427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu            mThumbnailAnimationFinished = true;
710427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        }
711427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
712427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        /**
713427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu         * Marks the ripple animation is finished.
714427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu         */
715427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        public void finishRippleAnimation() {
716427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu            mRippleAnimationFinished = true;
717427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        }
718427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
719427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        /**
720427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu         * Updates the thumbnail image.
721427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu         *
722427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu         * @param thumbnailBitmap The thumbnail image to be shown.
72304aab100674dfdd11bffabc46e6a199f5e9a5267Paul Rohde         * @param rotation The orientation of the image in degrees.
724427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu         */
72504aab100674dfdd11bffabc46e6a199f5e9a5267Paul Rohde        public void setThumbnailBitmap(Bitmap thumbnailBitmap, int rotation) {
726d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde            Bitmap originalBitmap = thumbnailBitmap;
727427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu            // Crop the image if it is not square.
728d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde            if (originalBitmap.getWidth() != originalBitmap.getHeight()) {
729d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde                originalBitmap = cropCenterBitmap(originalBitmap);
730427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu            }
731d6fad4a44e0afca5164704fcd906601744e68fb7Paul Rohde
73204aab100674dfdd11bffabc46e6a199f5e9a5267Paul Rohde            precomputeThumbnailPaint(originalBitmap, rotation);
733427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        }
734427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu
735427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        /**
736427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu         * Obtains a square bitmap by cropping the center of a bitmap. If the given image is
737427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu         * portrait, the cropped image keeps 100% original width and vertically centered to the
738427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu         * original image. If the given image is landscape, the cropped image keeps 100% original
739427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu         * height and horizontally centered to the original image.
740427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu         *
741427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu         * @param srcBitmap the bitmap image to be cropped in the center.
742427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu         * @return a result square bitmap.
743427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu         */
744427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        private Bitmap cropCenterBitmap(Bitmap srcBitmap) {
745427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu            int srcWidth = srcBitmap.getWidth();
746427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu            int srcHeight = srcBitmap.getHeight();
747427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu            Bitmap dstBitmap;
748427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu            if (srcWidth >= srcHeight) {
749427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu                dstBitmap = Bitmap.createBitmap(
750427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu                        srcBitmap, srcWidth / 2 - srcHeight / 2, 0, srcHeight, srcHeight);
751427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu            } else {
752427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu                dstBitmap = Bitmap.createBitmap(
753427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu                        srcBitmap, 0, srcHeight / 2 - srcWidth / 2, srcWidth, srcWidth);
754427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu            }
755427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu            return dstBitmap;
756427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu        }
757427a915be4bcc84fa1c32d9e9e1b7473c522f732Senpo Hu    }
75891bdb7fa1084af44c36dd7072033a90f4823ef5aSenpo Hu}
759