IndicatorControlWheel.java revision bed7386688cd7810cba23732ab71f370fe3222ff
16f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li/*
26f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li * Copyright (C) 2010 The Android Open Source Project
36f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li *
46f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li * Licensed under the Apache License, Version 2.0 (the "License");
56f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li * you may not use this file except in compliance with the License.
66f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li * You may obtain a copy of the License at
76f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li *
86f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li *      http://www.apache.org/licenses/LICENSE-2.0
96f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li *
106f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li * Unless required by applicable law or agreed to in writing, software
116f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li * distributed under the License is distributed on an "AS IS" BASIS,
126f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
136f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li * See the License for the specific language governing permissions and
146f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li * limitations under the License.
156f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li */
166f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li
176f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Lipackage com.android.camera.ui;
186f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li
196f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Liimport com.android.camera.R;
206f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li
216f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Liimport android.content.Context;
22e50d2553aeb9e55504697e2a72759e8f46ee2992Hung-ying Tyanimport android.content.res.Resources;
2380715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Liimport android.graphics.Canvas;
2480715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Liimport android.graphics.Paint;
2580715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Liimport android.graphics.RectF;
26a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Liimport android.os.SystemClock;
276f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Liimport android.util.AttributeSet;
28dfee7f813adb8de209472d24553b860d2515ea0aWu-cheng Liimport android.view.MotionEvent;
296f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Liimport android.view.View;
306f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li
317341b9b5d8194c4ab7140977a8c5218e6d83d516Wu-cheng Liimport java.util.ArrayList;
326f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li
336f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li/**
3480715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li * A view that contains shutter button and camera setting indicators. The
3580715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li * indicators are spreaded around the shutter button. The first child is always
36c5e37cbb96b8284203560c51dd5f41c705a21f58Wu-cheng Li * the shutter button.
376f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li */
38bed7386688cd7810cba23732ab71f370fe3222ffChung-yih Wangpublic class IndicatorWheel extends IndicatorControl {
39f5bffc476b8293b30bb500bed2ab7914bb4ca411Wu-cheng Li    private static final String TAG = "IndicatorWheel";
40bed7386688cd7810cba23732ab71f370fe3222ffChung-yih Wang
41e50d2553aeb9e55504697e2a72759e8f46ee2992Hung-ying Tyan    // The width of the edges on both sides of the wheel, which has less alpha.
42e50d2553aeb9e55504697e2a72759e8f46ee2992Hung-ying Tyan    private static final float EDGE_STROKE_WIDTH = 6f;
43e50d2553aeb9e55504697e2a72759e8f46ee2992Hung-ying Tyan    private static final int HIGHLIGHT_WIDTH = 4;
44e50d2553aeb9e55504697e2a72759e8f46ee2992Hung-ying Tyan    private static final int HIGHLIGHT_DEGREE = 30;
45a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li    private static final int TIME_LAPSE_ARC_WIDTH = 6;
46e50d2553aeb9e55504697e2a72759e8f46ee2992Hung-ying Tyan
47e50d2553aeb9e55504697e2a72759e8f46ee2992Hung-ying Tyan    private final int HIGHLIGHT_COLOR;
48a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li    private final int TIME_LAPSE_ARC_COLOR;
49e50d2553aeb9e55504697e2a72759e8f46ee2992Hung-ying Tyan
5080715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li    // The center of the shutter button.
51f5bffc476b8293b30bb500bed2ab7914bb4ca411Wu-cheng Li    private int mCenterX, mCenterY;
5280715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li    // The width of the wheel stroke.
5341ac285a1c97b1df78246456effb80de51033323Hung-ying Tyan    private int mStrokeWidth = 60;
5480715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li    private View mShutterButton;
55f5bffc476b8293b30bb500bed2ab7914bb4ca411Wu-cheng Li    private double mShutterButtonRadius;
5680715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li    private double mWheelRadius;
57f5bffc476b8293b30bb500bed2ab7914bb4ca411Wu-cheng Li    private double mSectorInitialRadians[];
5880715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li    private Paint mBackgroundPaint;
5980715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li    private RectF mBackgroundRect;
60039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li    // The index of the indicator that is being pressed. This starts from 0.
61039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li    // -1 means no indicator is being pressed.
62039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li    private int mPressedIndex = -1;
63dfee7f813adb8de209472d24553b860d2515ea0aWu-cheng Li
64a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li    // Time lapse recording variables.
65a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li    private int mTimeLapseInterval;  // in ms
66a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li    private long mRecordingStartTime = 0;
67a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li    private long mNumberOfFrames = 0;
68a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li
6980715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li    public IndicatorWheel(Context context, AttributeSet attrs) {
7080715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li        super(context, attrs);
71e50d2553aeb9e55504697e2a72759e8f46ee2992Hung-ying Tyan        Resources resources = context.getResources();
72e50d2553aeb9e55504697e2a72759e8f46ee2992Hung-ying Tyan        HIGHLIGHT_COLOR = resources.getColor(R.color.review_control_pressed_color);
73a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li        TIME_LAPSE_ARC_COLOR = resources.getColor(R.color.time_lapse_arc);
74bed7386688cd7810cba23732ab71f370fe3222ffChung-yih Wang
7580715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li        setWillNotDraw(false);
7680715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li
7780715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li        mBackgroundPaint = new Paint();
7880715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li        mBackgroundPaint.setStyle(Paint.Style.STROKE);
7980715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li        mBackgroundPaint.setAntiAlias(true);
8080715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li
8180715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li        mBackgroundRect = new RectF();
8280715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li    }
8380715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li
84bed7386688cd7810cba23732ab71f370fe3222ffChung-yih Wang    private boolean isInsideShutterButton(MotionEvent ev) {
85039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li        float x = ev.getX();
86039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li        float y = ev.getY();
87039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li        float shutterButtonX = mShutterButton.getX();
88039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li        float shutterButtonY = mShutterButton.getY();
89039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li        if (x >= shutterButtonX && y >= shutterButtonY
90039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                && (x < shutterButtonX + mShutterButton.getWidth())
91039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                && (y < shutterButtonY + mShutterButton.getHeight())) {
92039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li            return true;
934504e5b41a0f647ef3e16b1134e130fe3678aebfWu-cheng Li        }
944504e5b41a0f647ef3e16b1134e130fe3678aebfWu-cheng Li        return false;
954504e5b41a0f647ef3e16b1134e130fe3678aebfWu-cheng Li    }
964504e5b41a0f647ef3e16b1134e130fe3678aebfWu-cheng Li
97dfee7f813adb8de209472d24553b860d2515ea0aWu-cheng Li    @Override
98039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li    public boolean dispatchTouchEvent(MotionEvent event) {
99039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li        if (!onFilterTouchEventForSecurity(event)) return false;
1001c62e1b83c2fdda82c726bf712324444136fdc5dHung-ying Tyan
101f5bffc476b8293b30bb500bed2ab7914bb4ca411Wu-cheng Li        int action = event.getAction();
102039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li
103039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li        // Check if the event should be dispatched to the shutter button.
104039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li        if (isInsideShutterButton(event)) {
105039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li            if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_UP) {
106039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                return mShutterButton.dispatchTouchEvent(event);
107039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li            }
1087341b9b5d8194c4ab7140977a8c5218e6d83d516Wu-cheng Li            return false;
1097341b9b5d8194c4ab7140977a8c5218e6d83d516Wu-cheng Li        }
110039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li        if (!isEnabled()) return false;
111f5bffc476b8293b30bb500bed2ab7914bb4ca411Wu-cheng Li
1127341b9b5d8194c4ab7140977a8c5218e6d83d516Wu-cheng Li        double dx = event.getX() - mCenterX;
1137341b9b5d8194c4ab7140977a8c5218e6d83d516Wu-cheng Li        double dy = mCenterY - event.getY();
1147341b9b5d8194c4ab7140977a8c5218e6d83d516Wu-cheng Li        double radius = Math.sqrt(dx * dx + dy * dy);
1157341b9b5d8194c4ab7140977a8c5218e6d83d516Wu-cheng Li        // Ignore the event if it's too near to the shutter button or too far
1167341b9b5d8194c4ab7140977a8c5218e6d83d516Wu-cheng Li        // from the shutter button.
1177341b9b5d8194c4ab7140977a8c5218e6d83d516Wu-cheng Li        if (radius >= mShutterButtonRadius && radius <= mWheelRadius + mStrokeWidth) {
118f5bffc476b8293b30bb500bed2ab7914bb4ca411Wu-cheng Li            double delta = Math.atan2(dy, dx);
119f5bffc476b8293b30bb500bed2ab7914bb4ca411Wu-cheng Li            if (delta < 0) delta += Math.PI * 2;
120f5bffc476b8293b30bb500bed2ab7914bb4ca411Wu-cheng Li            // Check which sector is pressed.
121f5bffc476b8293b30bb500bed2ab7914bb4ca411Wu-cheng Li            if (delta > mSectorInitialRadians[0]) {
122039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                for (int i = 0; i < getChildCount() - 1; i++) {
123f5e80a8a5670798d52afe08e0c15aa4448d33316Wu-cheng Li                    if (delta < mSectorInitialRadians[i + 1]) {
124039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                        View child = getChildAt(i + 1);
125039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                        if (action == MotionEvent.ACTION_DOWN) {
126039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                            if (child instanceof AbstractIndicatorButton) {
127039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                                AbstractIndicatorButton b = (AbstractIndicatorButton) child;
128039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                                // If the same setting is pressed when the popup is open,
129039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                                // do not dismiss it because it will be handled in the child.
130039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                                if (b.getPopupWindow() == null) {
131039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                                    dismissSettingPopup();
132fba0439b62f19a2a19ff6559c1ae055e910400dbWu-cheng Li                                }
133039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                            } else {
134039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                                // Zoom button or back/front camera switch is pressed.
135039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                                dismissSettingPopup();
136823d57389b616091827059ca6401f675e416a53bWu-cheng Li                            }
137039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                            child.dispatchTouchEvent(event);
138039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                            invalidate();
139039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                            mPressedIndex = i;
140039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                        } else if (action == MotionEvent.ACTION_UP) {
141039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                            child.dispatchTouchEvent(event);
142039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                            invalidate();
143039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                            mPressedIndex = -1;
144039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                        } else if (action == MotionEvent.ACTION_MOVE) {
145039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                            // Dispatch the event if the location across a sector.
146039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                            if (mPressedIndex != -1 && i != mPressedIndex) {
147039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                                dismissSettingPopup();
148039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                                // Cancel the previous one.
149039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                                View cancelChild = getChildAt(mPressedIndex + 1);
150039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                                event.setAction(MotionEvent.ACTION_CANCEL);
151039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                                cancelChild.dispatchTouchEvent(event);
152039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                                // Send down to the current one.
153039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                                event.setAction(MotionEvent.ACTION_DOWN);
154039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                                child.dispatchTouchEvent(event);
155039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                                event.setAction(action); // Set the action back
156039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                                invalidate();
1574504e5b41a0f647ef3e16b1134e130fe3678aebfWu-cheng Li                            }
158039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                            // The children do not care about ACTION_MOVE. Besides, the press
159039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                            // state will be wrong because of View.pointInView.
160039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                            mPressedIndex = i;
1614504e5b41a0f647ef3e16b1134e130fe3678aebfWu-cheng Li                        }
162f5bffc476b8293b30bb500bed2ab7914bb4ca411Wu-cheng Li                        return true;
163f5bffc476b8293b30bb500bed2ab7914bb4ca411Wu-cheng Li                    }
164f5bffc476b8293b30bb500bed2ab7914bb4ca411Wu-cheng Li                }
165dfee7f813adb8de209472d24553b860d2515ea0aWu-cheng Li            }
166dfee7f813adb8de209472d24553b860d2515ea0aWu-cheng Li        }
167039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li        // The event is not on any of the child.
1684504e5b41a0f647ef3e16b1134e130fe3678aebfWu-cheng Li        dismissSettingPopup();
169039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li        if (mPressedIndex != -1) {
170039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li            View cancelChild = getChildAt(mPressedIndex + 1);
171039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li            event.setAction(MotionEvent.ACTION_CANCEL);
172039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li            cancelChild.dispatchTouchEvent(event);
173039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li            mPressedIndex = -1;
174039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li        }
175039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li        invalidate();
176dfee7f813adb8de209472d24553b860d2515ea0aWu-cheng Li        return false;
177dfee7f813adb8de209472d24553b860d2515ea0aWu-cheng Li    }
1786f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li
17980715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li    @Override
18080715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li    protected void onFinishInflate() {
18180715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li        super.onFinishInflate();
18280715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li        // The first view is shutter button.
18380715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li        mShutterButton = getChildAt(0);
184248232e937007384328592e0c269b42cf03c6899Wu-cheng Li        invalidate();
1856f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li    }
1866f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li
1876f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li    @Override
1886f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li    protected void onMeasure(int widthSpec, int heightSpec) {
1896f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li        // Measure all children.
19080715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li        int childCount = getChildCount();
1916f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li        int freeSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
19280715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li        for (int i = 0; i < childCount; i++) {
1936f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li            getChildAt(i).measure(freeSpec, freeSpec);
1946f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li        }
1956f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li
1966f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li        // Measure myself.
197039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li        int desiredWidth = mShutterButton.getMeasuredWidth() * 3;
19841ac285a1c97b1df78246456effb80de51033323Hung-ying Tyan        int desiredHeight = (int)(mShutterButton.getMeasuredHeight() * 4.5) + 2;
1996f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li        int widthMode = MeasureSpec.getMode(widthSpec);
2006f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li        int heightMode = MeasureSpec.getMode(heightSpec);
2016f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li        int measuredWidth, measuredHeight;
2026f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li        if (widthMode == MeasureSpec.UNSPECIFIED) {
2036f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li            measuredWidth = desiredWidth;
2046f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li        } else if (widthMode == MeasureSpec.AT_MOST) {
2056f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li            measuredWidth = Math.min(desiredWidth, MeasureSpec.getSize(widthSpec));
2066f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li        } else {  // MeasureSpec.EXACTLY
2076f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li            measuredWidth = MeasureSpec.getSize(widthSpec);
2086f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li        }
2096f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li        if (heightMode == MeasureSpec.UNSPECIFIED) {
2106f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li            measuredHeight = desiredHeight;
2116f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li        } else if (heightMode == MeasureSpec.AT_MOST) {
2126f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li            measuredHeight = Math.min(desiredHeight, MeasureSpec.getSize(heightSpec));
2136f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li        } else {  // MeasureSpec.EXACTLY
2146f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li            measuredHeight = MeasureSpec.getSize(heightSpec);
2156f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li        }
2166f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li        setMeasuredDimension(measuredWidth, measuredHeight);
2176f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li    }
2186f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li
2196f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li    @Override
2206f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li    protected void onLayout(
2216f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li            boolean changed, int left, int top, int right, int bottom) {
2226f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li        int count = getChildCount();
2236f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li        if (count == 0) return;
2246f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li
2256f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li        // Layout the shutter button.
22680715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li        int shutterButtonWidth = mShutterButton.getMeasuredWidth();
22741ac285a1c97b1df78246456effb80de51033323Hung-ying Tyan        mShutterButtonRadius = shutterButtonWidth / 2.0;
22880715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li        int shutterButtonHeight = mShutterButton.getMeasuredHeight();
22974c5164d19fa213c6904639e6e6b0d9e542cbf66Hung-ying Tyan        mStrokeWidth = (int) (mShutterButtonRadius * 1.05);
2309bd6c4687e40d86d5de95c3998eae7b079170d42Wu-cheng Li        int innerRadius = (int) (mShutterButtonRadius + mStrokeWidth * 0.84);
23147335563ade2c03ee37621b49a16d5c6905e3531Wu-cheng Li        // 64 is the requirement by UI design. The distance between the center
23247335563ade2c03ee37621b49a16d5c6905e3531Wu-cheng Li        // and the border is 64 pixels. This has to be consistent with the
23347335563ade2c03ee37621b49a16d5c6905e3531Wu-cheng Li        // background.
23447335563ade2c03ee37621b49a16d5c6905e3531Wu-cheng Li        mCenterX = right - left - 64;
23562776cd28a40b4c6ada4b4aa38683f45572ae266Wu-cheng Li        mCenterY = (bottom - top) / 2;
23680715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li        mShutterButton.layout(mCenterX - shutterButtonWidth / 2,
23780715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li                mCenterY - shutterButtonHeight / 2,
23880715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li                mCenterX + shutterButtonWidth / 2,
23980715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li                mCenterY + shutterButtonHeight / 2);
2406f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li
2416f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li        // Layout the settings. The icons are spreaded on the left side of the
2426f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li        // shutter button. So the angle starts from 90 to 270 degrees.
2436f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li        if (count == 1) return;
24474c5164d19fa213c6904639e6e6b0d9e542cbf66Hung-ying Tyan        mWheelRadius = innerRadius + mStrokeWidth * 0.5;
24519d029b83d65fb2e124f08aa8b98054974fc3acdHung-ying Tyan        double intervalDegrees = (count == 2) ? 90.0 : 180.0 / (count - 2);
2466f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li        double initialDegrees = 90.0;
2476f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li        int index = 0;
248f5bffc476b8293b30bb500bed2ab7914bb4ca411Wu-cheng Li        for (int i = 0; i < count; i++) {
2496f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li            View view = getChildAt(i);
25080715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li            if (view == mShutterButton) continue;
2516f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li            double degree = initialDegrees + intervalDegrees * index;
252f5bffc476b8293b30bb500bed2ab7914bb4ca411Wu-cheng Li            double radian = Math.toRadians(degree);
25380715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li            int x = mCenterX + (int)(mWheelRadius * Math.cos(radian));
25480715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li            int y = mCenterY - (int)(mWheelRadius * Math.sin(radian));
25580715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li            int width = view.getMeasuredWidth();
25680715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li            int height = view.getMeasuredHeight();
2576f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li            view.layout(x - width / 2, y - height / 2, x + width / 2,
2586f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li                    y + height / 2);
2596f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li            index++;
2606f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li        }
2616f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li
262f5bffc476b8293b30bb500bed2ab7914bb4ca411Wu-cheng Li        // Store the radian intervals for each icon.
263f5bffc476b8293b30bb500bed2ab7914bb4ca411Wu-cheng Li        mSectorInitialRadians = new double[count];
264f5bffc476b8293b30bb500bed2ab7914bb4ca411Wu-cheng Li        mSectorInitialRadians[0] = Math.toRadians(
265f5bffc476b8293b30bb500bed2ab7914bb4ca411Wu-cheng Li                initialDegrees - intervalDegrees / 2.0);
266f5bffc476b8293b30bb500bed2ab7914bb4ca411Wu-cheng Li        for (int i = 1; i < count; i++) {
267f5bffc476b8293b30bb500bed2ab7914bb4ca411Wu-cheng Li            mSectorInitialRadians[i] = mSectorInitialRadians[i - 1]
268f5bffc476b8293b30bb500bed2ab7914bb4ca411Wu-cheng Li                    + Math.toRadians(intervalDegrees);
269f5bffc476b8293b30bb500bed2ab7914bb4ca411Wu-cheng Li        }
2706f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li    }
2716f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li
272a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li    public void startTimeLapseAnimation(int timeLapseInterval, long startTime) {
273a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li        mTimeLapseInterval = timeLapseInterval;
274a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li        mRecordingStartTime = startTime;
275a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li        mNumberOfFrames = 0;
276a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li        invalidate();
277a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li    }
278a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li
279a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li    public void stopTimeLapseAnimation() {
280a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li        mTimeLapseInterval = 0;
281a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li        invalidate();
282a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li    }
283a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li
284039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li    private int getSelectedIndicatorIndex() {
285039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li        for (int i = 0; i < mIndicators.size(); i++) {
286039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li            AbstractIndicatorButton b = mIndicators.get(i);
287039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li            if (b.getPopupWindow() != null) {
288039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                return indexOfChild(b);
289039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li            }
290039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li        }
291039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li        return -1;
292039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li    }
293039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li
29480715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li    @Override
29580715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li    protected void onDraw(Canvas canvas) {
29619d029b83d65fb2e124f08aa8b98054974fc3acdHung-ying Tyan        // Draw highlight.
29774c5164d19fa213c6904639e6e6b0d9e542cbf66Hung-ying Tyan        float delta = mStrokeWidth * 0.5f;
29874c5164d19fa213c6904639e6e6b0d9e542cbf66Hung-ying Tyan        float radius = (float) (mWheelRadius + mStrokeWidth * 0.5 + EDGE_STROKE_WIDTH);
299039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li        mBackgroundRect.set(mCenterX - radius, mCenterY - radius, mCenterX + radius,
300039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                 mCenterY + radius);
301039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li
302039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li        int selectedIndex = getSelectedIndicatorIndex();
303039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li
304039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li        // Draw the highlight arc if an indicator is selected or being pressed.
305039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li        if (selectedIndex >= 0 || mPressedIndex >= 0) {
30619d029b83d65fb2e124f08aa8b98054974fc3acdHung-ying Tyan            int count = getChildCount();
30719d029b83d65fb2e124f08aa8b98054974fc3acdHung-ying Tyan            float initialDegrees = 90.0f;
30819d029b83d65fb2e124f08aa8b98054974fc3acdHung-ying Tyan            float intervalDegrees = (count <= 2) ? 0.0f : 180.0f / (count - 2);
309039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li            float degree;
310039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li            if (selectedIndex >= 0) {
311039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                degree = initialDegrees + intervalDegrees * (selectedIndex - 1);
312039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li            } else {
313039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li                degree = initialDegrees + intervalDegrees * mPressedIndex;
314039f1dc1fd89b5baeed8be05f3f21770cf75d4b3Wu-cheng Li            }
31519d029b83d65fb2e124f08aa8b98054974fc3acdHung-ying Tyan            mBackgroundPaint.setStrokeWidth(HIGHLIGHT_WIDTH);
31619d029b83d65fb2e124f08aa8b98054974fc3acdHung-ying Tyan            mBackgroundPaint.setStrokeCap(Paint.Cap.ROUND);
31719d029b83d65fb2e124f08aa8b98054974fc3acdHung-ying Tyan            mBackgroundPaint.setColor(HIGHLIGHT_COLOR);
31819d029b83d65fb2e124f08aa8b98054974fc3acdHung-ying Tyan            canvas.drawArc(mBackgroundRect, -degree - HIGHLIGHT_DEGREE / 2,
31919d029b83d65fb2e124f08aa8b98054974fc3acdHung-ying Tyan                    HIGHLIGHT_DEGREE, false, mBackgroundPaint);
32019d029b83d65fb2e124f08aa8b98054974fc3acdHung-ying Tyan        }
32119d029b83d65fb2e124f08aa8b98054974fc3acdHung-ying Tyan
322a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li        // Draw arc shaped indicator in time lapse recording.
323a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li        if (mTimeLapseInterval != 0) {
324a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li            // Setup rectangle and paint.
325a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li            mBackgroundRect.set((float)(mCenterX - mShutterButtonRadius),
326a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li                    (float)(mCenterY - mShutterButtonRadius),
327a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li                    (float)(mCenterX + mShutterButtonRadius),
328a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li                    (float)(mCenterY + mShutterButtonRadius));
329a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li            mBackgroundRect.inset(3f, 3f);
330a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li            mBackgroundPaint.setStrokeWidth(TIME_LAPSE_ARC_WIDTH);
331a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li            mBackgroundPaint.setStrokeCap(Paint.Cap.ROUND);
332a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li            mBackgroundPaint.setColor(TIME_LAPSE_ARC_COLOR);
333a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li
334a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li            // Compute the start angle and sweep angle.
335a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li            long timeDelta = SystemClock.uptimeMillis() - mRecordingStartTime;
336a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li            long numberOfFrames = timeDelta / mTimeLapseInterval;
337248232e937007384328592e0c269b42cf03c6899Wu-cheng Li            float sweepAngle;
338a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li            if (numberOfFrames > mNumberOfFrames) {
339248232e937007384328592e0c269b42cf03c6899Wu-cheng Li                // The arc just acrosses 0 degree. Draw a full circle so it
340248232e937007384328592e0c269b42cf03c6899Wu-cheng Li                // looks better.
341248232e937007384328592e0c269b42cf03c6899Wu-cheng Li                sweepAngle = 360;
342a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li                mNumberOfFrames = numberOfFrames;
343a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li            } else {
344248232e937007384328592e0c269b42cf03c6899Wu-cheng Li                sweepAngle = timeDelta % mTimeLapseInterval * 360f / mTimeLapseInterval;
345a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li            }
346a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li
347248232e937007384328592e0c269b42cf03c6899Wu-cheng Li            canvas.drawArc(mBackgroundRect, 0, sweepAngle, false, mBackgroundPaint);
348248232e937007384328592e0c269b42cf03c6899Wu-cheng Li            invalidate();
349a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li        }
350a18b115b025fe0c3b046348953946c8db07e12ceWu-cheng Li
35180715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li        super.onDraw(canvas);
35280715aae46e5a7a9aa378002301ba9d76f093776Wu-cheng Li    }
3536f2283fbf049ad1f9870a86474af295604d95600Wu-cheng Li}
354