static public interface OnPreferenceChangedListener {
    public void onSharedPreferenceChanged();
    public void onRestorePreferencesClicked();
    public void onOverriddenPreferencesClicked();
    public void onCameraPickerClicked(int cameraId);
} public  interface FocusUI {
    public boolean hasFaces();
    public void clearFocus();
    public void setFocusPosition(int x, int y);
    public void onFocusStarted();
    public void onFocusSucceeded(boolean timeOut);
    public void onFocusFailed(boolean timeOut);
    public void pauseFaceDetection();
    public void resumeFaceDetection();
} abstract public class AbstractSettingPopup extends RotateLayout {
    protected ViewGroup mSettingList;
    protected TextView mTitle;

    public AbstractSettingPopup(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        mTitle = (TextView) findViewById(R.id.title);
        mSettingList = (ViewGroup) findViewById(R.id.settingList);
    }

    abstract public void reloadPreference();
} public class CameraControls extends RotatableLayout {

    private static final String TAG = "CAM_Controls";

    private View mBackgroundView;
    private View mShutter;
    private View mSwitcher;
    private View mMenu;
    private View mIndicators;
    private View mPreview;

    public CameraControls(Context context, AttributeSet attrs) {
        super(context, attrs);
        setMeasureAllChildren(true);
    }

    public CameraControls(Context context) {
        super(context);
        setMeasureAllChildren(true);
    }

    @Override
    public void onFinishInflate() {
        super.onFinishInflate();
        mBackgroundView = findViewById(R.id.blocker);
        mSwitcher = findViewById(R.id.camera_switcher);
        mShutter = findViewById(R.id.shutter_button);
        mMenu = findViewById(R.id.menu);
        mIndicators = findViewById(R.id.on_screen_indicators);
        mPreview = findViewById(R.id.preview_thumb);
    }

    @Override
    public void onLayout(boolean changed, int l, int t, int r, int b) {
        int orientation = getResources().getConfiguration().orientation;
        int size = getResources().getDimensionPixelSize(R.dimen.camera_controls_size);
        int rotation = getUnifiedRotation();
        adjustBackground();
        // As l,t,r,b are positions relative to parents, we need to convert them
        // to child's coordinates
        r = r - l;
        b = b - t;
        l = 0;
        t = 0;
        for (int i = 0; i < getChildCount(); i++) {
            View v = getChildAt(i);
            v.layout(l, t, r, b);
        }
        Rect shutter = new Rect();
        topRight(mPreview, l, t, r, b);
        if (size > 0) {
            // restrict controls to size
            switch (rotation) {
            case 0:
            case 180:
                l = (l + r - size) / 2;
                r = l + size;
                break;
            case 90:
            case 270:
                t = (t + b - size) / 2;
                b = t + size;
                break;
            }
        }
        center(mShutter, l, t, r, b, orientation, rotation, shutter);
        center(mBackgroundView, l, t, r, b, orientation, rotation, new Rect());
        toLeft(mSwitcher, shutter, rotation);
        toRight(mMenu, shutter, rotation);
        toRight(mIndicators, shutter, rotation);
        View retake = findViewById(R.id.btn_retake);
        if (retake != null) {
            center(retake, shutter, rotation);
            View cancel = findViewById(R.id.btn_cancel);
            toLeft(cancel, shutter, rotation);
            View done = findViewById(R.id.btn_done);
            toRight(done, shutter, rotation);
        }
    }

    private void center(View v, int l, int t, int r, int b, int orientation, int rotation, Rect result) {
        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) v.getLayoutParams();
        int tw = lp.leftMargin + v.getMeasuredWidth() + lp.rightMargin;
        int th = lp.topMargin + v.getMeasuredHeight() + lp.bottomMargin;
        switch (rotation) {
        case 0:
            // phone portrait; controls bottom
            result.left = (r + l) / 2 - tw / 2 + lp.leftMargin;
            result.right = (r + l) / 2 + tw / 2 - lp.rightMargin;
            result.bottom = b - lp.bottomMargin;
            result.top = b - th + lp.topMargin;
            break;
        case 90:
            // phone landscape: controls right
            result.right = r - lp.rightMargin;
            result.left = r - tw + lp.leftMargin;
            result.top = (b + t) / 2 - th / 2 + lp.topMargin;
            result.bottom = (b + t) / 2 + th / 2 - lp.bottomMargin;
            break;
        case 180:
            // phone upside down: controls top
            result.left = (r + l) / 2 - tw / 2 + lp.leftMargin;
            result.right = (r + l) / 2 + tw / 2 - lp.rightMargin;
            result.top = t + lp.topMargin;
            result.bottom = t + th - lp.bottomMargin;
            break;
        case 270:
            // reverse landscape: controls left
            result.left = l + lp.leftMargin;
            result.right = l + tw - lp.rightMargin;
            result.top = (b + t) / 2 - th / 2 + lp.topMargin;
            result.bottom = (b + t) / 2 + th / 2 - lp.bottomMargin;
            break;
        }
        v.layout(result.left, result.top, result.right, result.bottom);
    }

    private void center(View v, Rect other, int rotation) {
        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) v.getLayoutParams();
        int tw = lp.leftMargin + v.getMeasuredWidth() + lp.rightMargin;
        int th = lp.topMargin + v.getMeasuredHeight() + lp.bottomMargin;
        int cx = (other.left + other.right) / 2;
        int cy = (other.top + other.bottom) / 2;
        v.layout(cx - tw / 2 + lp.leftMargin,
                cy - th / 2 + lp.topMargin,
                cx + tw / 2 - lp.rightMargin,
                cy + th / 2 - lp.bottomMargin);
    }

    private void toLeft(View v, Rect other, int rotation) {
        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) v.getLayoutParams();
        int tw = lp.leftMargin + v.getMeasuredWidth() + lp.rightMargin;
        int th = lp.topMargin + v.getMeasuredHeight() + lp.bottomMargin;
        int cx = (other.left + other.right) / 2;
        int cy = (other.top + other.bottom) / 2;
        int l = 0, r = 0, t = 0, b = 0;
        switch (rotation) {
        case 0:
            // portrait, to left of anchor at bottom
            l = other.left - tw + lp.leftMargin;
            r = other.left - lp.rightMargin;
            t = cy - th / 2 + lp.topMargin;
            b = cy + th / 2 - lp.bottomMargin;
            break;
        case 90:
            // phone landscape: below anchor on right
            l = cx - tw / 2 + lp.leftMargin;
            r = cx + tw / 2 - lp.rightMargin;
            t = other.bottom + lp.topMargin;
            b = other.bottom + th - lp.bottomMargin;
            break;
        case 180:
            // phone upside down: right of anchor at top
            l = other.right + lp.leftMargin;
            r = other.right + tw - lp.rightMargin;
            t = cy - th / 2 + lp.topMargin;
            b = cy + th / 2 - lp.bottomMargin;
            break;
        case 270:
            // reverse landscape: above anchor on left
            l = cx - tw / 2 + lp.leftMargin;
            r = cx + tw / 2 - lp.rightMargin;
            t = other.top - th + lp.topMargin;
            b = other.top - lp.bottomMargin;
            break;
        }
        v.layout(l, t, r, b);
    }

    private void toRight(View v, Rect other, int rotation) {
        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) v.getLayoutParams();
        int tw = lp.leftMargin + v.getMeasuredWidth() + lp.rightMargin;
        int th = lp.topMargin + v.getMeasuredHeight() + lp.bottomMargin;
        int cx = (other.left + other.right) / 2;
        int cy = (other.top + other.bottom) / 2;
        int l = 0, r = 0, t = 0, b = 0;
        switch (rotation) {
        case 0:
            l = other.right + lp.leftMargin;
            r = other.right + tw - lp.rightMargin;
            t = cy - th / 2 + lp.topMargin;
            b = cy + th / 2 - lp.bottomMargin;
            break;
        case 90:
            l = cx - tw / 2 + lp.leftMargin;
            r = cx + tw / 2 - lp.rightMargin;
            t = other.top - th + lp.topMargin;
            b = other.top - lp.bottomMargin;
            break;
        case 180:
            l = other.left - tw + lp.leftMargin;
            r = other.left - lp.rightMargin;
            t = cy - th / 2 + lp.topMargin;
            b = cy + th / 2 - lp.bottomMargin;
            break;
        case 270:
            l = cx - tw / 2 + lp.leftMargin;
            r = cx + tw / 2 - lp.rightMargin;
            t = other.bottom + lp.topMargin;
            b = other.bottom + th - lp.bottomMargin;
            break;
        }
        v.layout(l, t, r, b);
    }

    private void topRight(View v, int l, int t, int r, int b) {
        // layout using the specific margins; the rotation code messes up the others
        int mt = getContext().getResources().getDimensionPixelSize(R.dimen.capture_margin_top);
        int mr = getContext().getResources().getDimensionPixelSize(R.dimen.capture_margin_right);
        v.layout(r - v.getMeasuredWidth() - mr, t + mt, r - mr, t + mt + v.getMeasuredHeight());
    }

    private void adjustBackground() {
        int rotation = getUnifiedRotation();
        // remove current drawable and reset rotation
        mBackgroundView.setBackgroundDrawable(null);
        mBackgroundView.setRotationX(0);
        mBackgroundView.setRotationY(0);
        // if the switcher background is top aligned we need to flip the background
        // drawable vertically; if left aligned, flip horizontally
        switch (rotation) {
            case 180:
                mBackgroundView.setRotationX(180);
                break;
            case 270:
                mBackgroundView.setRotationY(180);
                break;
            default:
                break;
        }
        mBackgroundView.setBackgroundResource(R.drawable.switcher_bg);
    }

} @SuppressLint("NewApi")
public class CameraRootView extends FrameLayout {

    private int mTopMargin = 0;
    private int mBottomMargin = 0;
    private int mLeftMargin = 0;
    private int mRightMargin = 0;
    private Rect mCurrentInsets;
    private int mOffset = 0;
    private Object mDisplayListener;
    private MyDisplayListener mListener;

    public interface MyDisplayListener {
        public void onDisplayChanged();
    }

    public CameraRootView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initDisplayListener();
    }

    @Override
    protected boolean fitSystemWindows(Rect insets) {
        mCurrentInsets = insets;
        // insets include status bar, navigation bar, etc
        // In this case, we are only concerned with the size of nav bar
        if (mOffset > 0) {
            return true;
        }

        if (insets.bottom > 0) {
            mOffset = insets.bottom;
        } else if (insets.right > 0) {
            mOffset = insets.right;
        }
        return true;
    }

    public void initDisplayListener() {
        if (ApiHelper.HAS_DISPLAY_LISTENER) {
            mDisplayListener = new DisplayListener() {

                @Override
                public void onDisplayAdded(int arg0) {}

                @Override
                public void onDisplayChanged(int arg0) {
                    if (mListener != null) {
                        mListener.onDisplayChanged();
                    }
                }

                @Override
                public void onDisplayRemoved(int arg0) {}
            };
        }
    }

    public void removeDisplayChangeListener() {
        mListener = null;
    }

    public void setDisplayChangeListener(MyDisplayListener listener) {
        mListener = listener;
    }

    @Override
    public void onAttachedToWindow() {
        super.onAttachedToWindow();
        if (ApiHelper.HAS_DISPLAY_LISTENER) {
            ((DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE))
            .registerDisplayListener((DisplayListener) mDisplayListener, null);
        }
    }

    @Override
    public void onDetachedFromWindow () {
        super.onDetachedFromWindow();
        if (ApiHelper.HAS_DISPLAY_LISTENER) {
            ((DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE))
            .unregisterDisplayListener((DisplayListener) mDisplayListener);
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int rotation = CameraUtil.getDisplayRotation((Activity) getContext());
        // all the layout code assumes camera device orientation to be portrait
        // adjust rotation for landscape
        int orientation = getResources().getConfiguration().orientation;
        int camOrientation = (rotation % 180 == 0) ? Configuration.ORIENTATION_PORTRAIT
                : Configuration.ORIENTATION_LANDSCAPE;
        if (camOrientation != orientation) {
            rotation = (rotation + 90) % 360;
        }
        // calculate margins
        mLeftMargin = 0;
        mRightMargin = 0;
        mBottomMargin = 0;
        mTopMargin = 0;
        switch (rotation) {
            case 0:
                mBottomMargin += mOffset;
                break;
            case 90:
                mRightMargin += mOffset;
                break;
            case 180:
                mTopMargin += mOffset;
                break;
            case 270:
                mLeftMargin += mOffset;
                break;
        }
        if (mCurrentInsets != null) {
            if (mCurrentInsets.right > 0) {
                // navigation bar on the right
                mRightMargin = mRightMargin > 0 ? mRightMargin : mCurrentInsets.right;
            } else {
                // navigation bar on the bottom
                mBottomMargin = mBottomMargin > 0 ? mBottomMargin : mCurrentInsets.bottom;
            }
        }
        // make sure all the children are resized
        super.onMeasure(widthMeasureSpec - mLeftMargin - mRightMargin,
                heightMeasureSpec - mTopMargin - mBottomMargin);
        setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    public void onLayout(boolean changed, int l, int t, int r, int b) {
        r -= l;
        b -= t;
        l = 0;
        t = 0;
        int orientation = getResources().getConfiguration().orientation;
        // Lay out children
        for (int i = 0; i < getChildCount(); i++) {
            View v = getChildAt(i);
            if (v instanceof CameraControls) {
                // Lay out camera controls to center on the short side of the screen
                // so that they stay in place during rotation
                int width = v.getMeasuredWidth();
                int height = v.getMeasuredHeight();
                if (orientation == Configuration.ORIENTATION_PORTRAIT) {
                    int left = (l + r - width) / 2;
                    v.layout(left, t + mTopMargin, left + width, b - mBottomMargin);
                } else {
                    int top = (t + b - height) / 2;
                    v.layout(l + mLeftMargin, top, r - mRightMargin, top + height);
                }
            } else {
                v.layout(l + mLeftMargin, t + mTopMargin, r - mRightMargin, b - mBottomMargin);
            }
        }
    }
} public class CountDownView extends FrameLayout {

    private static final String TAG = "CAM_CountDownView";
    private static final int SET_TIMER_TEXT = 1;
    private TextView mRemainingSecondsView;
    private int mRemainingSecs = 0;
    private OnCountDownFinishedListener mListener;
    private Animation mCountDownAnim;
    private SoundPool mSoundPool;
    private int mBeepTwice;
    private int mBeepOnce;
    private boolean mPlaySound;
    private final Handler mHandler = new MainHandler();

    public CountDownView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mCountDownAnim = AnimationUtils.loadAnimation(context, R.anim.count_down_exit);
        // Load the beeps
        mSoundPool = new SoundPool(1, AudioManager.STREAM_NOTIFICATION, 0);
        mBeepOnce = mSoundPool.load(context, R.raw.beep_once, 1);
        mBeepTwice = mSoundPool.load(context, R.raw.beep_twice, 1);
    }

    public boolean isCountingDown() {
        return mRemainingSecs > 0;
    };

    public interface OnCountDownFinishedListener {
        public void onCountDownFinished();
    }

    private void remainingSecondsChanged(int newVal) {
        mRemainingSecs = newVal;
        if (newVal == 0) {
            // Countdown has finished
            setVisibility(View.INVISIBLE);
            mListener.onCountDownFinished();
        } else {
            Locale locale = getResources().getConfiguration().locale;
            String localizedValue = String.format(locale, "%d", newVal);
            mRemainingSecondsView.setText(localizedValue);
            // Fade-out animation
            mCountDownAnim.reset();
            mRemainingSecondsView.clearAnimation();
            mRemainingSecondsView.startAnimation(mCountDownAnim);

            // Play sound effect for the last 3 seconds of the countdown
            if (mPlaySound) {
                if (newVal == 1) {
                    mSoundPool.play(mBeepTwice, 1.0f, 1.0f, 0, 0, 1.0f);
                } else if (newVal <= 3) {
                    mSoundPool.play(mBeepOnce, 1.0f, 1.0f, 0, 0, 1.0f);
                }
            }
            // Schedule the next remainingSecondsChanged() call in 1 second
            mHandler.sendEmptyMessageDelayed(SET_TIMER_TEXT, 1000);
        }
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mRemainingSecondsView = (TextView) findViewById(R.id.remaining_seconds);
    }

    public void setCountDownFinishedListener(OnCountDownFinishedListener listener) {
        mListener = listener;
    }

    public void startCountDown(int sec, boolean playSound) {
        if (sec <= 0) {
            Log.w(TAG, "Invalid input for countdown timer: " + sec + " seconds");
            return;
        }
        setVisibility(View.VISIBLE);
        mPlaySound = playSound;
        remainingSecondsChanged(sec);
    }

    public void cancelCountDown() {
        if (mRemainingSecs > 0) {
            mRemainingSecs = 0;
            mHandler.removeMessages(SET_TIMER_TEXT);
            setVisibility(View.INVISIBLE);
        }
    }

    private class MainHandler extends Handler {
        @Override
        public void handleMessage(Message message) {
            if (message.what == SET_TIMER_TEXT) {
                remainingSecondsChanged(mRemainingSecs -1);
            }
        }
    }
} public interface OnCountDownFinishedListener {
    public void onCountDownFinished();
} public class FaceView extends View
    implements FocusIndicator, Rotatable,
    PhotoUI.SurfaceTextureSizeChangedListener {
    private static final String TAG = "CAM FaceView";
    private final boolean LOGV = false;
    // The value for android.hardware.Camera.setDisplayOrientation.
    private int mDisplayOrientation;
    // The orientation compensation for the face indicator to make it look
    // correctly in all device orientations. Ex: if the value is 90, the
    // indicator should be rotated 90 degrees counter-clockwise.
    private int mOrientation;
    private boolean mMirror;
    private boolean mPause;
    private Matrix mMatrix = new Matrix();
    private RectF mRect = new RectF();
    // As face detection can be flaky, we add a layer of filtering on top of it
    // to avoid rapid changes in state (eg, flickering between has faces and
    // not having faces)
    private Face[] mFaces;
    private Face[] mPendingFaces;
    private int mColor;
    private final int mFocusingColor;
    private final int mFocusedColor;
    private final int mFailColor;
    private Paint mPaint;
    private volatile boolean mBlocked;

    private int mUncroppedWidth;
    private int mUncroppedHeight;
    private static final int MSG_SWITCH_FACES = 1;
    private static final int SWITCH_DELAY = 70;
    private boolean mStateSwitchPending = false;
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case MSG_SWITCH_FACES:
                mStateSwitchPending = false;
                mFaces = mPendingFaces;
                invalidate();
                break;
            }
        }
    };

    public FaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        Resources res = getResources();
        mFocusingColor = res.getColor(R.color.face_detect_start);
        mFocusedColor = res.getColor(R.color.face_detect_success);
        mFailColor = res.getColor(R.color.face_detect_fail);
        mColor = mFocusingColor;
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Style.STROKE);
        mPaint.setStrokeWidth(res.getDimension(R.dimen.face_circle_stroke));
    }

    @Override
    public void onSurfaceTextureSizeChanged(int uncroppedWidth, int uncroppedHeight) {
        mUncroppedWidth = uncroppedWidth;
        mUncroppedHeight = uncroppedHeight;
    }

    public void setFaces(Face[] faces) {
        if (LOGV) Log.v(TAG, "Num of faces=" + faces.length);
        if (mPause) return;
        if (mFaces != null) {
            if ((faces.length > 0 && mFaces.length == 0)
                    || (faces.length == 0 && mFaces.length > 0)) {
                mPendingFaces = faces;
                if (!mStateSwitchPending) {
                    mStateSwitchPending = true;
                    mHandler.sendEmptyMessageDelayed(MSG_SWITCH_FACES, SWITCH_DELAY);
                }
                return;
            }
        }
        if (mStateSwitchPending) {
            mStateSwitchPending = false;
            mHandler.removeMessages(MSG_SWITCH_FACES);
        }
        mFaces = faces;
        invalidate();
    }

    public void setDisplayOrientation(int orientation) {
        mDisplayOrientation = orientation;
        if (LOGV) Log.v(TAG, "mDisplayOrientation=" + orientation);
    }

    @Override
    public void setOrientation(int orientation, boolean animation) {
        mOrientation = orientation;
        invalidate();
    }

    public void setMirror(boolean mirror) {
        mMirror = mirror;
        if (LOGV) Log.v(TAG, "mMirror=" + mirror);
    }

    public boolean faceExists() {
        return (mFaces != null && mFaces.length > 0);
    }

    @Override
    public void showStart() {
        mColor = mFocusingColor;
        invalidate();
    }

    // Ignore the parameter. No autofocus animation for face detection.
    @Override
    public void showSuccess(boolean timeout) {
        mColor = mFocusedColor;
        invalidate();
    }

    // Ignore the parameter. No autofocus animation for face detection.
    @Override
    public void showFail(boolean timeout) {
        mColor = mFailColor;
        invalidate();
    }

    @Override
    public void clear() {
        // Face indicator is displayed during preview. Do not clear the
        // drawable.
        mColor = mFocusingColor;
        mFaces = null;
        invalidate();
    }

    public void pause() {
        mPause = true;
    }

    public void resume() {
        mPause = false;
    }

    public void setBlockDraw(boolean block) {
        mBlocked = block;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (!mBlocked && (mFaces != null) && (mFaces.length > 0)) {
            int rw, rh;
            rw = mUncroppedWidth;
            rh = mUncroppedHeight;
            // Prepare the matrix.
            if (((rh > rw) && ((mDisplayOrientation == 0) || (mDisplayOrientation == 180)))
                    || ((rw > rh) && ((mDisplayOrientation == 90) || (mDisplayOrientation == 270)))) {
                int temp = rw;
                rw = rh;
                rh = temp;
            }
            CameraUtil.prepareMatrix(mMatrix, mMirror, mDisplayOrientation, rw, rh);
            int dx = (getWidth() - rw) / 2;
            int dy = (getHeight() - rh) / 2;

            // Focus indicator is directional. Rotate the matrix and the canvas
            // so it looks correctly in all orientations.
            canvas.save();
            mMatrix.postRotate(mOrientation); // postRotate is clockwise
            canvas.rotate(-mOrientation); // rotate is counter-clockwise (for canvas)
            for (int i = 0; i < mFaces.length; i++) {
                // Filter out false positives.
                if (mFaces[i].score < 50) continue;

                // Transform the coordinates.
                mRect.set(mFaces[i].rect);
                if (LOGV) CameraUtil.dumpRect(mRect, "Original rect");
                mMatrix.mapRect(mRect);
                if (LOGV) CameraUtil.dumpRect(mRect, "Transformed rect");
                mPaint.setColor(mColor);
                mRect.offset(dx, dy);
                canvas.drawOval(mRect, mPaint);
            }
            canvas.restore();
        }
        super.onDraw(canvas);
    }
} public interface FocusIndicator {
    public void showStart();
    public void showSuccess(boolean timeout);
    public void showFail(boolean timeout);
    public void clear();
} public class ModuleSwitcher extends RotateImageView
        implements OnClickListener, OnTouchListener {

    @SuppressWarnings("unused")
    private static final String TAG = "CAM_Switcher";
    private static final int SWITCHER_POPUP_ANIM_DURATION = 200;

    public static final int PHOTO_MODULE_INDEX = 0;
    public static final int VIDEO_MODULE_INDEX = 1;
    public static final int WIDE_ANGLE_PANO_MODULE_INDEX = 2;
    public static final int LIGHTCYCLE_MODULE_INDEX = 3;
    public static final int GCAM_MODULE_INDEX = 4;

    private static final int[] DRAW_IDS = {
            R.drawable.ic_switch_camera,
            R.drawable.ic_switch_video,
            R.drawable.ic_switch_pan,
            R.drawable.ic_switch_photosphere,
            R.drawable.ic_switch_gcam,
    };

    public interface ModuleSwitchListener {
        public void onModuleSelected(int i);

        public void onShowSwitcherPopup();
    }

    private ModuleSwitchListener mListener;
    private int mCurrentIndex;
    private int[] mModuleIds;
    private int[] mDrawIds;
    private int mItemSize;
    private View mPopup;
    private View mParent;
    private boolean mShowingPopup;
    private boolean mNeedsAnimationSetup;
    private Drawable mIndicator;

    private float mTranslationX = 0;
    private float mTranslationY = 0;

    private AnimatorListener mHideAnimationListener;
    private AnimatorListener mShowAnimationListener;

    public ModuleSwitcher(Context context) {
        super(context);
        init(context);
    }

    public ModuleSwitcher(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    private void init(Context context) {
        mItemSize = context.getResources().getDimensionPixelSize(R.dimen.switcher_size);
        setOnClickListener(this);
        mIndicator = context.getResources().getDrawable(R.drawable.ic_switcher_menu_indicator);
        initializeDrawables(context);
    }

    public void initializeDrawables(Context context) {
        int numDrawIds = DRAW_IDS.length;

        if (!PhotoSphereHelper.hasLightCycleCapture(context)) {
            --numDrawIds;
        }

        // Always decrement one because of GCam.
        --numDrawIds;

        int[] drawids = new int[numDrawIds];
        int[] moduleids = new int[numDrawIds];
        int ix = 0;
        for (int i = 0; i < DRAW_IDS.length; i++) {
            if (i == LIGHTCYCLE_MODULE_INDEX && !PhotoSphereHelper.hasLightCycleCapture(context)) {
                continue; // not enabled, so don't add to UI
            }
            if (i == GCAM_MODULE_INDEX) {
                continue; // don't add to UI
            }
            moduleids[ix] = i;
            drawids[ix++] = DRAW_IDS[i];
        }
        setIds(moduleids, drawids);
    }

    public void setIds(int[] moduleids, int[] drawids) {
        mDrawIds = drawids;
        mModuleIds = moduleids;
    }

    public void setCurrentIndex(int i) {
        mCurrentIndex = i;
        if (i == GCAM_MODULE_INDEX) {
          setImageResource(R.drawable.ic_switch_camera);
        } else {
          setImageResource(mDrawIds[i]);
        }
    }

    public void setSwitchListener(ModuleSwitchListener l) {
        mListener = l;
    }

    @Override
    public void onClick(View v) {
        showSwitcher();
        mListener.onShowSwitcherPopup();
    }

    private void onModuleSelected(int ix) {
        hidePopup();
        if ((ix != mCurrentIndex) && (mListener != null)) {
            UsageStatistics.onEvent("CameraModeSwitch", null, null);
            UsageStatistics.setPendingTransitionCause(
                    UsageStatistics.TRANSITION_MENU_TAP);
            setCurrentIndex(ix);
            mListener.onModuleSelected(mModuleIds[ix]);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mIndicator.setBounds(getDrawable().getBounds());
        mIndicator.draw(canvas);
    }

    private void initPopup() {
        mParent = LayoutInflater.from(getContext()).inflate(R.layout.switcher_popup,
                (ViewGroup) getParent());
        LinearLayout content = (LinearLayout) mParent.findViewById(R.id.content);
        mPopup = content;
        // Set the gravity of the popup, so that it shows up at the right
        // position
        // on screen
        LayoutParams lp = ((LayoutParams) mPopup.getLayoutParams());
        lp.gravity = ((LayoutParams) mParent.findViewById(R.id.camera_switcher)
                .getLayoutParams()).gravity;
        mPopup.setLayoutParams(lp);

        mPopup.setVisibility(View.INVISIBLE);
        mNeedsAnimationSetup = true;
        for (int i = mDrawIds.length - 1; i >= 0; i--) {
            RotateImageView item = new RotateImageView(getContext());
            item.setImageResource(mDrawIds[i]);
            item.setBackgroundResource(R.drawable.bg_pressed);
            final int index = i;
            item.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (showsPopup()) {
                        onModuleSelected(index);
                    }
                }
            });
            switch (mDrawIds[i]) {
                case R.drawable.ic_switch_camera:
                    item.setContentDescription(getContext().getResources().getString(
                            R.string.accessibility_switch_to_camera));
                    break;
                case R.drawable.ic_switch_video:
                    item.setContentDescription(getContext().getResources().getString(
                            R.string.accessibility_switch_to_video));
                    break;
                case R.drawable.ic_switch_pan:
                    item.setContentDescription(getContext().getResources().getString(
                            R.string.accessibility_switch_to_panorama));
                    break;
                case R.drawable.ic_switch_photosphere:
                    item.setContentDescription(getContext().getResources().getString(
                            R.string.accessibility_switch_to_photo_sphere));
                    break;
                case R.drawable.ic_switch_gcam:
                    item.setContentDescription(getContext().getResources().getString(
                            R.string.accessibility_switch_to_gcam));
                    break;
                default:
                    break;
            }
            content.addView(item, new LinearLayout.LayoutParams(mItemSize, mItemSize));
        }
        mPopup.measure(MeasureSpec.makeMeasureSpec(mParent.getWidth(), MeasureSpec.AT_MOST),
                MeasureSpec.makeMeasureSpec(mParent.getHeight(), MeasureSpec.AT_MOST));
    }

    public boolean showsPopup() {
        return mShowingPopup;
    }

    public boolean isInsidePopup(MotionEvent evt) {
        if (!showsPopup()) {
            return false;
        }
        int topLeft[] = new int[2];
        mPopup.getLocationOnScreen(topLeft);
        int left = topLeft[0];
        int top = topLeft[1];
        int bottom = top + mPopup.getHeight();
        int right = left + mPopup.getWidth();
        return evt.getX() >= left && evt.getX() < right
                && evt.getY() >= top && evt.getY() < bottom;
    }

    private void hidePopup() {
        mShowingPopup = false;
        setVisibility(View.VISIBLE);
        if (mPopup != null && !animateHidePopup()) {
            mPopup.setVisibility(View.INVISIBLE);
        }
        mParent.setOnTouchListener(null);
    }

    @Override
    public void onConfigurationChanged(Configuration config) {
        if (showsPopup()) {
            ((ViewGroup) mParent).removeView(mPopup);
            mPopup = null;
            initPopup();
            mPopup.setVisibility(View.VISIBLE);
        }
    }

    private void showSwitcher() {
        mShowingPopup = true;
        if (mPopup == null) {
            initPopup();
        }
        layoutPopup();
        mPopup.setVisibility(View.VISIBLE);
        if (!animateShowPopup()) {
            setVisibility(View.INVISIBLE);
        }
        mParent.setOnTouchListener(this);
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        closePopup();
        return true;
    }

    public void closePopup() {
        if (showsPopup()) {
            hidePopup();
        }
    }

    @Override
    public void setOrientation(int degree, boolean animate) {
        super.setOrientation(degree, animate);
        ViewGroup content = (ViewGroup) mPopup;
        if (content == null) {
            return;
        }
        for (int i = 0; i < content.getChildCount(); i++) {
            RotateImageView iv = (RotateImageView) content.getChildAt(i);
            iv.setOrientation(degree, animate);
        }
    }

    private void layoutPopup() {
        int orientation = CameraUtil.getDisplayRotation((Activity) getContext());
        int w = mPopup.getMeasuredWidth();
        int h = mPopup.getMeasuredHeight();
        if (orientation == 0) {
            mPopup.layout(getRight() - w, getBottom() - h, getRight(), getBottom());
            mTranslationX = 0;
            mTranslationY = h / 3;
        } else if (orientation == 90) {
            mTranslationX = w / 3;
            mTranslationY = -h / 3;
            mPopup.layout(getRight() - w, getTop(), getRight(), getTop() + h);
        } else if (orientation == 180) {
            mTranslationX = -w / 3;
            mTranslationY = -h / 3;
            mPopup.layout(getLeft(), getTop(), getLeft() + w, getTop() + h);
        } else {
            mTranslationX = -w / 3;
            mTranslationY = h - getHeight();
            mPopup.layout(getLeft(), getBottom() - h, getLeft() + w, getBottom());
        }
    }

    @Override
    public void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        if (mPopup != null) {
            layoutPopup();
        }
    }

    private void popupAnimationSetup() {
        layoutPopup();
        mPopup.setScaleX(0.3f);
        mPopup.setScaleY(0.3f);
        mPopup.setTranslationX(mTranslationX);
        mPopup.setTranslationY(mTranslationY);
        mNeedsAnimationSetup = false;
    }

    private boolean animateHidePopup() {
        if (mHideAnimationListener == null) {
            mHideAnimationListener = new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    // Verify that we weren't canceled
                    if (!showsPopup() && mPopup != null) {
                        mPopup.setVisibility(View.INVISIBLE);
                        ((ViewGroup) mParent).removeView(mPopup);
                        mPopup = null;
                    }
                }
            };
        }
        mPopup.animate()
                .alpha(0f)
                .scaleX(0.3f).scaleY(0.3f)
                .translationX(mTranslationX)
                .translationY(mTranslationY)
                .setDuration(SWITCHER_POPUP_ANIM_DURATION)
                .setListener(mHideAnimationListener);
        animate().alpha(1f).setDuration(SWITCHER_POPUP_ANIM_DURATION)
                .setListener(null);
        return true;
    }

    private boolean animateShowPopup() {
        if (mNeedsAnimationSetup) {
            popupAnimationSetup();
        }
        if (mShowAnimationListener == null) {
            mShowAnimationListener = new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    // Verify that we weren't canceled
                    if (showsPopup()) {
                        setVisibility(View.INVISIBLE);
                        // request layout to make sure popup is laid out
                        // correctly on ICS
                        mPopup.requestLayout();
                    }
                }
            };
        }
        mPopup.animate()
                .alpha(1f)
                .scaleX(1f).scaleY(1f)
                .translationX(0)
                .translationY(0)
                .setDuration(SWITCHER_POPUP_ANIM_DURATION)
                .setListener(null);
        animate().alpha(0f).setDuration(SWITCHER_POPUP_ANIM_DURATION)
                .setListener(mShowAnimationListener);
        return true;
    }
} public class PieRenderer extends OverlayRenderer
        implements FocusIndicator {

    private static final String TAG = "CAM Pie";

    // Sometimes continuous autofocus starts and stops several times quickly.
    // These states are used to make sure the animation is run for at least some
    // time.
    private volatile int mState;
    private ScaleAnimation mAnimation = new ScaleAnimation();
    private static final int STATE_IDLE = 0;
    private static final int STATE_FOCUSING = 1;
    private static final int STATE_FINISHING = 2;
    private static final int STATE_PIE = 8;

    private static final float MATH_PI_2 = (float)(Math.PI / 2);

    private Runnable mDisappear = new Disappear();
    private Animation.AnimationListener mEndAction = new EndAction();
    private static final int SCALING_UP_TIME = 600;
    private static final int SCALING_DOWN_TIME = 100;
    private static final int DISAPPEAR_TIMEOUT = 200;
    private static final int DIAL_HORIZONTAL = 157;
    // fade out timings
    private static final int PIE_FADE_OUT_DURATION = 600;

    private static final long PIE_FADE_IN_DURATION = 200;
    private static final long PIE_XFADE_DURATION = 200;
    private static final long PIE_SELECT_FADE_DURATION = 300;
    private static final long PIE_OPEN_SUB_DELAY = 400;
    private static final long PIE_SLICE_DURATION = 80;

    private static final int MSG_OPEN = 0;
    private static final int MSG_CLOSE = 1;
    private static final int MSG_OPENSUBMENU = 2;

    protected static float CENTER = (float) Math.PI / 2;
    protected static float RAD24 = (float)(24 * Math.PI / 180);
    protected static final float SWEEP_SLICE = 0.14f;
    protected static final float SWEEP_ARC = 0.23f;

    // geometry
    private int mRadius;
    private int mRadiusInc;

    // the detection if touch is inside a slice is offset
    // inbounds by this amount to allow the selection to show before the
    // finger covers it
    private int mTouchOffset;

    private List<PieItem> mOpen;

    private Paint mSelectedPaint;
    private Paint mSubPaint;
    private Paint mMenuArcPaint;

    // touch handling
    private PieItem mCurrentItem;

    private Paint mFocusPaint;
    private int mSuccessColor;
    private int mFailColor;
    private int mCircleSize;
    private int mFocusX;
    private int mFocusY;
    private int mCenterX;
    private int mCenterY;
    private int mArcCenterY;
    private int mSliceCenterY;
    private int mPieCenterX;
    private int mPieCenterY;
    private int mSliceRadius;
    private int mArcRadius;
    private int mArcOffset;

    private int mDialAngle;
    private RectF mCircle;
    private RectF mDial;
    private Point mPoint1;
    private Point mPoint2;
    private int mStartAnimationAngle;
    private boolean mFocused;
    private int mInnerOffset;
    private int mOuterStroke;
    private int mInnerStroke;
    private boolean mTapMode;
    private boolean mBlockFocus;
    private int mTouchSlopSquared;
    private Point mDown;
    private boolean mOpening;
    private ValueAnimator mXFade;
    private ValueAnimator mFadeIn;
    private ValueAnimator mFadeOut;
    private ValueAnimator mSlice;
    private volatile boolean mFocusCancelled;
    private PointF mPolar = new PointF();
    private TextDrawable mLabel;
    private int mDeadZone;
    private int mAngleZone;
    private float mCenterAngle;



    private Handler mHandler = new Handler() {
        public void handleMessage(Message msg) {
            switch(msg.what) {
            case MSG_OPEN:
                if (mListener != null) {
                    mListener.onPieOpened(mPieCenterX, mPieCenterY);
                }
                break;
            case MSG_CLOSE:
                if (mListener != null) {
                    mListener.onPieClosed();
                }
                break;
            case MSG_OPENSUBMENU:
                onEnterOpen();
                break;
            }

        }
    };

    private PieListener mListener;

    static public interface PieListener {
        public void onPieOpened(int centerX, int centerY);
        public void onPieClosed();
    }

    public void setPieListener(PieListener pl) {
        mListener = pl;
    }

    public PieRenderer(Context context) {
        init(context);
    }

    private void init(Context ctx) {
        setVisible(false);
        mOpen = new ArrayList<PieItem>();
        mOpen.add(new PieItem(null, 0));
        Resources res = ctx.getResources();
        mRadius = (int) res.getDimensionPixelSize(R.dimen.pie_radius_start);
        mRadiusInc = (int) res.getDimensionPixelSize(R.dimen.pie_radius_increment);
        mCircleSize = mRadius - res.getDimensionPixelSize(R.dimen.focus_radius_offset);
        mTouchOffset = (int) res.getDimensionPixelSize(R.dimen.pie_touch_offset);
        mSelectedPaint = new Paint();
        mSelectedPaint.setColor(Color.argb(255, 51, 181, 229));
        mSelectedPaint.setAntiAlias(true);
        mSubPaint = new Paint();
        mSubPaint.setAntiAlias(true);
        mSubPaint.setColor(Color.argb(200, 250, 230, 128));
        mFocusPaint = new Paint();
        mFocusPaint.setAntiAlias(true);
        mFocusPaint.setColor(Color.WHITE);
        mFocusPaint.setStyle(Paint.Style.STROKE);
        mSuccessColor = Color.GREEN;
        mFailColor = Color.RED;
        mCircle = new RectF();
        mDial = new RectF();
        mPoint1 = new Point();
        mPoint2 = new Point();
        mInnerOffset = res.getDimensionPixelSize(R.dimen.focus_inner_offset);
        mOuterStroke = res.getDimensionPixelSize(R.dimen.focus_outer_stroke);
        mInnerStroke = res.getDimensionPixelSize(R.dimen.focus_inner_stroke);
        mState = STATE_IDLE;
        mBlockFocus = false;
        mTouchSlopSquared = ViewConfiguration.get(ctx).getScaledTouchSlop();
        mTouchSlopSquared = mTouchSlopSquared * mTouchSlopSquared;
        mDown = new Point();
        mMenuArcPaint = new Paint();
        mMenuArcPaint.setAntiAlias(true);
        mMenuArcPaint.setColor(Color.argb(140, 255, 255, 255));
        mMenuArcPaint.setStrokeWidth(10);
        mMenuArcPaint.setStyle(Paint.Style.STROKE);
        mSliceRadius = res.getDimensionPixelSize(R.dimen.pie_item_radius);
        mArcRadius = res.getDimensionPixelSize(R.dimen.pie_arc_radius);
        mArcOffset = res.getDimensionPixelSize(R.dimen.pie_arc_offset);
        mLabel = new TextDrawable(res);
        mLabel.setDropShadow(true);
        mDeadZone = res.getDimensionPixelSize(R.dimen.pie_deadzone_width);
        mAngleZone = res.getDimensionPixelSize(R.dimen.pie_anglezone_width);
    }

    private PieItem getRoot() {
        return mOpen.get(0);
    }

    public boolean showsItems() {
        return mTapMode;
    }

    public void addItem(PieItem item) {
        // add the item to the pie itself
        getRoot().addItem(item);
    }

    public void clearItems() {
        getRoot().clearItems();
    }

    public void showInCenter() {
        if ((mState == STATE_PIE) && isVisible()) {
            mTapMode = false;
            show(false);
        } else {
            if (mState != STATE_IDLE) {
                cancelFocus();
            }
            mState = STATE_PIE;
            resetPieCenter();
            setCenter(mPieCenterX, mPieCenterY);
            mTapMode = true;
            show(true);
        }
    }

    public void hide() {
        show(false);
    }

    /**
     * guaranteed has center set
     * @param show
     */
    private void show(boolean show) {
        if (show) {
            if (mXFade != null) {
                mXFade.cancel();
            }
            mState = STATE_PIE;
            // ensure clean state
            mCurrentItem = null;
            PieItem root = getRoot();
            for (PieItem openItem : mOpen) {
                if (openItem.hasItems()) {
                    for (PieItem item : openItem.getItems()) {
                        item.setSelected(false);
                    }
                }
            }
            mLabel.setText("");
            mOpen.clear();
            mOpen.add(root);
            layoutPie();
            fadeIn();
        } else {
            mState = STATE_IDLE;
            mTapMode = false;
            if (mXFade != null) {
                mXFade.cancel();
            }
            if (mLabel != null) {
                mLabel.setText("");
            }
        }
        setVisible(show);
        mHandler.sendEmptyMessage(show ? MSG_OPEN : MSG_CLOSE);
    }

    public boolean isOpen() {
        return mState == STATE_PIE && isVisible();
    }

    private void fadeIn() {
        mFadeIn = new ValueAnimator();
        mFadeIn.setFloatValues(0f, 1f);
        mFadeIn.setDuration(PIE_FADE_IN_DURATION);
        // linear interpolation
        mFadeIn.setInterpolator(null);
        mFadeIn.addListener(new AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                mFadeIn = null;
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
            }

            @Override
            public void onAnimationCancel(Animator arg0) {
            }
        });
        mFadeIn.start();
    }

    public void setCenter(int x, int y) {
        mPieCenterX = x;
        mPieCenterY = y;
        mSliceCenterY = y + mSliceRadius - mArcOffset;
        mArcCenterY = y - mArcOffset + mArcRadius;
    }

    @Override
    public void layout(int l, int t, int r, int b) {
        super.layout(l, t, r, b);
        mCenterX = (r - l) / 2;
        mCenterY = (b - t) / 2;

        mFocusX = mCenterX;
        mFocusY = mCenterY;
        resetPieCenter();
        setCircle(mFocusX, mFocusY);
        if (isVisible() && mState == STATE_PIE) {
            setCenter(mPieCenterX, mPieCenterY);
            layoutPie();
        }
    }

    private void resetPieCenter() {
        mPieCenterX = mCenterX;
        mPieCenterY = (int) (getHeight() - 2.5f * mDeadZone);
    }

    private void layoutPie() {
        mCenterAngle = getCenterAngle();
        layoutItems(0, getRoot().getItems());
        layoutLabel(getLevel());
    }

    private void layoutLabel(int level) {
        int x = mPieCenterX - (int) (FloatMath.sin(mCenterAngle - CENTER)
                * (mArcRadius + (level + 2) * mRadiusInc));
        int y = mArcCenterY - mArcRadius - (level + 2) * mRadiusInc;
        int w = mLabel.getIntrinsicWidth();
        int h = mLabel.getIntrinsicHeight();
        mLabel.setBounds(x - w/2, y - h/2, x + w/2, y + h/2);
    }

    private void layoutItems(int level, List<PieItem> items) {
        int extend = 1;
        Path path = makeSlice(getDegrees(0) + extend, getDegrees(SWEEP_ARC) - extend,
                mArcRadius, mArcRadius + mRadiusInc + mRadiusInc / 4,
                mPieCenterX, mArcCenterY - level * mRadiusInc);
        final int count = items.size();
        int pos = 0;
        for (PieItem item : items) {
            // shared between items
            item.setPath(path);
            float angle = getArcCenter(item, pos, count);
            int w = item.getIntrinsicWidth();
            int h = item.getIntrinsicHeight();
            // move views to outer border
            int r = mArcRadius + mRadiusInc * 2 / 3;
            int x = (int) (r * Math.cos(angle));
            int y = mArcCenterY - (level * mRadiusInc) - (int) (r * Math.sin(angle)) - h / 2;
            x = mPieCenterX + x - w / 2;
            item.setBounds(x, y, x + w, y + h);
            item.setLevel(level);
            if (item.hasItems()) {
                layoutItems(level + 1, item.getItems());
            }
            pos++;
        }
    }

    private Path makeSlice(float start, float end, int inner, int outer, int cx, int cy) {
        RectF bb =
                new RectF(cx - outer, cy - outer, cx + outer,
                        cy + outer);
        RectF bbi =
                new RectF(cx - inner, cy - inner, cx + inner,
                        cy + inner);
        Path path = new Path();
        path.arcTo(bb, start, end - start, true);
        path.arcTo(bbi, end, start - end);
        path.close();
        return path;
    }

    private float getArcCenter(PieItem item, int pos, int count) {
        return getCenter(pos, count, SWEEP_ARC);
    }

    private float getSliceCenter(PieItem item, int pos, int count) {
        float center = (getCenterAngle() - CENTER) * 0.5f + CENTER;
        return center + (count - 1) * SWEEP_SLICE / 2f
                - pos * SWEEP_SLICE;
    }

    private float getCenter(int pos, int count, float sweep) {
        return mCenterAngle + (count - 1) * sweep / 2f - pos * sweep;
    }

    private float getCenterAngle() {
        float center = CENTER;
        if (mPieCenterX < mDeadZone + mAngleZone) {
            center = CENTER - (mAngleZone - mPieCenterX + mDeadZone) * RAD24
                    / (float) mAngleZone;
        } else if (mPieCenterX > getWidth() - mDeadZone - mAngleZone) {
            center = CENTER + (mPieCenterX - (getWidth() - mDeadZone - mAngleZone)) * RAD24
                    / (float) mAngleZone;
        }
        return center;
    }

    /**
     * converts a
     * @param angle from 0..PI to Android degrees (clockwise starting at 3 o'clock)
     * @return skia angle
     */
    private float getDegrees(double angle) {
        return (float) (360 - 180 * angle / Math.PI);
    }

    private void startFadeOut(final PieItem item) {
        if (mFadeIn != null) {
            mFadeIn.cancel();
        }
        if (mXFade != null) {
            mXFade.cancel();
        }
        mFadeOut = new ValueAnimator();
        mFadeOut.setFloatValues(1f, 0f);
        mFadeOut.setDuration(PIE_FADE_OUT_DURATION);
        mFadeOut.addListener(new AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animator) {
            }

            @Override
            public void onAnimationEnd(Animator animator) {
                item.performClick();
                mFadeOut = null;
                deselect();
                show(false);
                mOverlay.setAlpha(1);
            }

            @Override
            public void onAnimationRepeat(Animator animator) {
            }

            @Override
            public void onAnimationCancel(Animator animator) {
            }

        });
        mFadeOut.start();
    }

    // root does not count
    private boolean hasOpenItem() {
        return mOpen.size() > 1;
    }

    // pop an item of the open item stack
    private PieItem closeOpenItem() {
        PieItem item = getOpenItem();
        mOpen.remove(mOpen.size() -1);
        return item;
    }

    private PieItem getOpenItem() {
        return mOpen.get(mOpen.size() - 1);
    }

    // return the children either the root or parent of the current open item
    private PieItem getParent() {
        return mOpen.get(Math.max(0, mOpen.size() - 2));
    }

    private int getLevel() {
        return mOpen.size() - 1;
    }

    @Override
    public void onDraw(Canvas canvas) {
        float alpha = 1;
        if (mXFade != null) {
            alpha = (Float) mXFade.getAnimatedValue();
        } else if (mFadeIn != null) {
            alpha = (Float) mFadeIn.getAnimatedValue();
        } else if (mFadeOut != null) {
            alpha = (Float) mFadeOut.getAnimatedValue();
        }
        int state = canvas.save();
        if (mFadeIn != null) {
            float sf = 0.9f + alpha * 0.1f;
            canvas.scale(sf, sf, mPieCenterX, mPieCenterY);
        }
        if (mState != STATE_PIE) {
            drawFocus(canvas);
        }
        if (mState == STATE_FINISHING) {
            canvas.restoreToCount(state);
            return;
        }
        if (mState != STATE_PIE) return;
        if (!hasOpenItem() || (mXFade != null)) {
            // draw base menu
            drawArc(canvas, getLevel(), getParent());
            List<PieItem> items = getParent().getItems();
            final int count = items.size();
            int pos = 0;
            for (PieItem item : getParent().getItems()) {
                drawItem(Math.max(0, mOpen.size() - 2), pos, count, canvas, item, alpha);
                pos++;
            }
            mLabel.draw(canvas);
        }
        if (hasOpenItem()) {
            int level = getLevel();
            drawArc(canvas, level, getOpenItem());
            List<PieItem> items = getOpenItem().getItems();
            final int count = items.size();
            int pos = 0;
            for (PieItem inner : items) {
                if (mFadeOut != null) {
                    drawItem(level, pos, count, canvas, inner, alpha);
                } else {
                    drawItem(level, pos, count, canvas, inner, (mXFade != null) ? (1 - 0.5f * alpha) : 1);
                }
                pos++;
            }
            mLabel.draw(canvas);
        }
        canvas.restoreToCount(state);
    }

    private void drawArc(Canvas canvas, int level, PieItem item) {
        // arc
        if (mState == STATE_PIE) {
            final int count = item.getItems().size();
            float start = mCenterAngle + (count * SWEEP_ARC / 2f);
            float end =  mCenterAngle - (count * SWEEP_ARC / 2f);
            int cy = mArcCenterY - level * mRadiusInc;
            canvas.drawArc(new RectF(mPieCenterX - mArcRadius, cy - mArcRadius,
                    mPieCenterX + mArcRadius, cy + mArcRadius),
                    getDegrees(end), getDegrees(start) - getDegrees(end), false, mMenuArcPaint);
        }
    }

    private void drawItem(int level, int pos, int count, Canvas canvas, PieItem item, float alpha) {
        if (mState == STATE_PIE) {
            if (item.getPath() != null) {
                int y = mArcCenterY - level * mRadiusInc;
                if (item.isSelected()) {
                    Paint p = mSelectedPaint;
                    int state = canvas.save();
                    float angle = 0;
                    if (mSlice != null) {
                        angle = (Float) mSlice.getAnimatedValue();
                    } else {
                        angle = getArcCenter(item, pos, count) - SWEEP_ARC / 2f;
                    }
                    angle = getDegrees(angle);
                    canvas.rotate(angle, mPieCenterX, y);
                    if (mFadeOut != null) {
                        p.setAlpha((int)(255 * alpha));
                    }
                    canvas.drawPath(item.getPath(), p);
                    if (mFadeOut != null) {
                        p.setAlpha(255);
                    }
                    canvas.restoreToCount(state);
                }
                if (mFadeOut == null) {
                    alpha = alpha * (item.isEnabled() ? 1 : 0.3f);
                    // draw the item view
                    item.setAlpha(alpha);
                }
                item.draw(canvas);
            }
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent evt) {
        float x = evt.getX();
        float y = evt.getY();
        int action = evt.getActionMasked();
        getPolar(x, y, !mTapMode, mPolar);
        if (MotionEvent.ACTION_DOWN == action) {
            if ((x < mDeadZone) || (x > getWidth() - mDeadZone)) {
                return false;
            }
            mDown.x = (int) evt.getX();
            mDown.y = (int) evt.getY();
            mOpening = false;
            if (mTapMode) {
                PieItem item = findItem(mPolar);
                if ((item != null) && (mCurrentItem != item)) {
                    mState = STATE_PIE;
                    onEnter(item);
                }
            } else {
                setCenter((int) x, (int) y);
                show(true);
            }
            return true;
        } else if (MotionEvent.ACTION_UP == action) {
            if (isVisible()) {
                PieItem item = mCurrentItem;
                if (mTapMode) {
                    item = findItem(mPolar);
                    if (mOpening) {
                        mOpening = false;
                        return true;
                    }
                }
                if (item == null) {
                    mTapMode = false;
                    show(false);
                } else if (!mOpening && !item.hasItems()) {
                        startFadeOut(item);
                        mTapMode = false;
                } else {
                    mTapMode = true;
                }
                return true;
            }
        } else if (MotionEvent.ACTION_CANCEL == action) {
            if (isVisible() || mTapMode) {
                show(false);
            }
            deselect();
            mHandler.removeMessages(MSG_OPENSUBMENU);
            return false;
        } else if (MotionEvent.ACTION_MOVE == action) {
            if (pulledToCenter(mPolar)) {
                mHandler.removeMessages(MSG_OPENSUBMENU);
                if (hasOpenItem()) {
                    if (mCurrentItem != null) {
                        mCurrentItem.setSelected(false);
                    }
                    closeOpenItem();
                    mCurrentItem = null;
                } else {
                    deselect();
                }
                mLabel.setText("");
                return false;
            }
            PieItem item = findItem(mPolar);
            boolean moved = hasMoved(evt);
            if ((item != null) && (mCurrentItem != item) && (!mOpening || moved)) {
                mHandler.removeMessages(MSG_OPENSUBMENU);
                // only select if we didn't just open or have moved past slop
                if (moved) {
                    // switch back to swipe mode
                    mTapMode = false;
                }
                onEnterSelect(item);
                mHandler.sendEmptyMessageDelayed(MSG_OPENSUBMENU, PIE_OPEN_SUB_DELAY);
            }
        }
        return false;
    }

    private boolean pulledToCenter(PointF polarCoords) {
        return polarCoords.y < mArcRadius - mRadiusInc;
    }

    private boolean inside(PointF polar, PieItem item, int pos, int count) {
        float start = getSliceCenter(item, pos, count) - SWEEP_SLICE / 2f;
        boolean res =  (mArcRadius < polar.y)
                && (start < polar.x)
                && (start + SWEEP_SLICE > polar.x)
                && (!mTapMode || (mArcRadius + mRadiusInc > polar.y));
        return res;
    }

    private void getPolar(float x, float y, boolean useOffset, PointF res) {
        // get angle and radius from x/y
        res.x = (float) Math.PI / 2;
        x = x - mPieCenterX;
        float y1 = mSliceCenterY - getLevel() * mRadiusInc - y;
        float y2 = mArcCenterY - getLevel() * mRadiusInc - y;
        res.y = (float) Math.sqrt(x * x + y2 * y2);
        if (x != 0) {
            res.x = (float) Math.atan2(y1,  x);
            if (res.x < 0) {
                res.x = (float) (2 * Math.PI + res.x);
            }
        }
        res.y = res.y + (useOffset ? mTouchOffset : 0);
    }

    private boolean hasMoved(MotionEvent e) {
        return mTouchSlopSquared < (e.getX() - mDown.x) * (e.getX() - mDown.x)
                + (e.getY() - mDown.y) * (e.getY() - mDown.y);
    }

    private void onEnterSelect(PieItem item) {
        if (mCurrentItem != null) {
            mCurrentItem.setSelected(false);
        }
        if (item != null && item.isEnabled()) {
            moveSelection(mCurrentItem, item);
            item.setSelected(true);
            mCurrentItem = item;
            mLabel.setText(mCurrentItem.getLabel());
            layoutLabel(getLevel());
        } else {
            mCurrentItem = null;
        }
    }

    private void onEnterOpen() {
        if ((mCurrentItem != null) && (mCurrentItem != getOpenItem()) && mCurrentItem.hasItems()) {
            openCurrentItem();
        }
    }

    /**
     * enter a slice for a view
     * updates model only
     * @param item
     */
    private void onEnter(PieItem item) {
        if (mCurrentItem != null) {
            mCurrentItem.setSelected(false);
        }
        if (item != null && item.isEnabled()) {
            item.setSelected(true);
            mCurrentItem = item;
            mLabel.setText(mCurrentItem.getLabel());
            if ((mCurrentItem != getOpenItem()) && mCurrentItem.hasItems()) {
                openCurrentItem();
                layoutLabel(getLevel());
            }
        } else {
            mCurrentItem = null;
        }
    }

    private void deselect() {
        if (mCurrentItem != null) {
            mCurrentItem.setSelected(false);
        }
        if (hasOpenItem()) {
            PieItem item = closeOpenItem();
            onEnter(item);
        } else {
            mCurrentItem = null;
        }
    }

    private int getItemPos(PieItem target) {
        List<PieItem> items = getOpenItem().getItems();
        return items.indexOf(target);
    }

    private int getCurrentCount() {
        return getOpenItem().getItems().size();
    }

    private void moveSelection(PieItem from, PieItem to) {
        final int count = getCurrentCount();
        final int fromPos = getItemPos(from);
        final int toPos = getItemPos(to);
        if (fromPos != -1 && toPos != -1) {
            float startAngle = getArcCenter(from, getItemPos(from), count)
                    - SWEEP_ARC / 2f;
            float endAngle = getArcCenter(to, getItemPos(to), count)
                    - SWEEP_ARC / 2f;
            mSlice = new ValueAnimator();
            mSlice.setFloatValues(startAngle, endAngle);
            // linear interpolater
            mSlice.setInterpolator(null);
            mSlice.setDuration(PIE_SLICE_DURATION);
            mSlice.addListener(new AnimatorListener() {
                @Override
                public void onAnimationEnd(Animator arg0) {
                    mSlice = null;
                }

                @Override
                public void onAnimationRepeat(Animator arg0) {
                }

                @Override
                public void onAnimationStart(Animator arg0) {
                }

                @Override
                public void onAnimationCancel(Animator arg0) {
                }
            });
            mSlice.start();
        }
    }

    private void openCurrentItem() {
        if ((mCurrentItem != null) && mCurrentItem.hasItems()) {
            mOpen.add(mCurrentItem);
            layoutLabel(getLevel());
            mOpening = true;
            if (mFadeIn != null) {
                mFadeIn.cancel();
            }
            mXFade = new ValueAnimator();
            mXFade.setFloatValues(1f, 0f);
            mXFade.setDuration(PIE_XFADE_DURATION);
            // Linear interpolation
            mXFade.setInterpolator(null);
            final PieItem ci = mCurrentItem;
            mXFade.addListener(new AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) {
                }

                @Override
                public void onAnimationEnd(Animator animation) {
                    mXFade = null;
                    ci.setSelected(false);
                    mOpening = false;
                }

                @Override
                public void onAnimationRepeat(Animator animation) {
                }

                @Override
                public void onAnimationCancel(Animator arg0) {
                }
            });
            mXFade.start();
        }
    }

    /**
     * @param polar x: angle, y: dist
     * @return the item at angle/dist or null
     */
    private PieItem findItem(PointF polar) {
        // find the matching item:
        List<PieItem> items = getOpenItem().getItems();
        final int count = items.size();
        int pos = 0;
        for (PieItem item : items) {
            if (inside(polar, item, pos, count)) {
                return item;
            }
            pos++;
        }
        return null;
    }


    @Override
    public boolean handlesTouch() {
        return true;
    }

    // focus specific code

    public void setBlockFocus(boolean blocked) {
        mBlockFocus = blocked;
        if (blocked) {
            clear();
        }
    }

    public void setFocus(int x, int y) {
        mOverlay.removeCallbacks(mDisappear);
        mFocusX = x;
        mFocusY = y;
        setCircle(mFocusX, mFocusY);
    }

    public void alignFocus(int x, int y) {
        mOverlay.removeCallbacks(mDisappear);
        mAnimation.cancel();
        mAnimation.reset();
        mFocusX = x;
        mFocusY = y;
        mDialAngle = DIAL_HORIZONTAL;
        setCircle(x, y);
        mFocused = false;
    }

    public int getSize() {
        return 2 * mCircleSize;
    }

    private int getRandomRange() {
        return (int)(-60 + 120 * Math.random());
    }

    private void setCircle(int cx, int cy) {
        mCircle.set(cx - mCircleSize, cy - mCircleSize,
                cx + mCircleSize, cy + mCircleSize);
        mDial.set(cx - mCircleSize + mInnerOffset, cy - mCircleSize + mInnerOffset,
                cx + mCircleSize - mInnerOffset, cy + mCircleSize - mInnerOffset);
    }

    public void drawFocus(Canvas canvas) {
        if (mBlockFocus) return;
        mFocusPaint.setStrokeWidth(mOuterStroke);
        canvas.drawCircle((float) mFocusX, (float) mFocusY, (float) mCircleSize, mFocusPaint);
        if (mState == STATE_PIE) return;
        int color = mFocusPaint.getColor();
        if (mState == STATE_FINISHING) {
            mFocusPaint.setColor(mFocused ? mSuccessColor : mFailColor);
        }
        mFocusPaint.setStrokeWidth(mInnerStroke);
        drawLine(canvas, mDialAngle, mFocusPaint);
        drawLine(canvas, mDialAngle + 45, mFocusPaint);
        drawLine(canvas, mDialAngle + 180, mFocusPaint);
        drawLine(canvas, mDialAngle + 225, mFocusPaint);
        canvas.save();
        // rotate the arc instead of its offset to better use framework's shape caching
        canvas.rotate(mDialAngle, mFocusX, mFocusY);
        canvas.drawArc(mDial, 0, 45, false, mFocusPaint);
        canvas.drawArc(mDial, 180, 45, false, mFocusPaint);
        canvas.restore();
        mFocusPaint.setColor(color);
    }

    private void drawLine(Canvas canvas, int angle, Paint p) {
        convertCart(angle, mCircleSize - mInnerOffset, mPoint1);
        convertCart(angle, mCircleSize - mInnerOffset + mInnerOffset / 3, mPoint2);
        canvas.drawLine(mPoint1.x + mFocusX, mPoint1.y + mFocusY,
                mPoint2.x + mFocusX, mPoint2.y + mFocusY, p);
    }

    private static void convertCart(int angle, int radius, Point out) {
        double a = 2 * Math.PI * (angle % 360) / 360;
        out.x = (int) (radius * Math.cos(a) + 0.5);
        out.y = (int) (radius * Math.sin(a) + 0.5);
    }

    @Override
    public void showStart() {
        if (mState == STATE_PIE) return;
        cancelFocus();
        mStartAnimationAngle = 67;
        int range = getRandomRange();
        startAnimation(SCALING_UP_TIME,
                false, mStartAnimationAngle, mStartAnimationAngle + range);
        mState = STATE_FOCUSING;
    }

    @Override
    public void showSuccess(boolean timeout) {
        if (mState == STATE_FOCUSING) {
            startAnimation(SCALING_DOWN_TIME,
                    timeout, mStartAnimationAngle);
            mState = STATE_FINISHING;
            mFocused = true;
        }
    }

    @Override
    public void showFail(boolean timeout) {
        if (mState == STATE_FOCUSING) {
            startAnimation(SCALING_DOWN_TIME,
                    timeout, mStartAnimationAngle);
            mState = STATE_FINISHING;
            mFocused = false;
        }
    }

    private void cancelFocus() {
        mFocusCancelled = true;
        mOverlay.removeCallbacks(mDisappear);
        if (mAnimation != null && !mAnimation.hasEnded()) {
            mAnimation.cancel();
        }
        mFocusCancelled = false;
        mFocused = false;
        mState = STATE_IDLE;
    }

    @Override
    public void clear() {
        if (mState == STATE_PIE) return;
        cancelFocus();
        mOverlay.post(mDisappear);
    }

    private void startAnimation(long duration, boolean timeout,
            float toScale) {
        startAnimation(duration, timeout, mDialAngle,
                toScale);
    }

    private void startAnimation(long duration, boolean timeout,
            float fromScale, float toScale) {
        setVisible(true);
        mAnimation.reset();
        mAnimation.setDuration(duration);
        mAnimation.setScale(fromScale, toScale);
        mAnimation.setAnimationListener(timeout ? mEndAction : null);
        mOverlay.startAnimation(mAnimation);
        update();
    }

    private class EndAction implements Animation.AnimationListener {
        @Override
        public void onAnimationEnd(Animation animation) {
            // Keep the focus indicator for some time.
            if (!mFocusCancelled) {
                mOverlay.postDelayed(mDisappear, DISAPPEAR_TIMEOUT);
            }
        }

        @Override
        public void onAnimationRepeat(Animation animation) {
        }

        @Override
        public void onAnimationStart(Animation animation) {
        }
    }

    private class Disappear implements Runnable {
        @Override
        public void run() {
            if (mState == STATE_PIE) return;
            setVisible(false);
            mFocusX = mCenterX;
            mFocusY = mCenterY;
            mState = STATE_IDLE;
            setCircle(mFocusX, mFocusY);
            mFocused = false;
        }
    }

    private class ScaleAnimation extends Animation {
        private float mFrom = 1f;
        private float mTo = 1f;

        public ScaleAnimation() {
            setFillAfter(true);
        }

        public void setScale(float from, float to) {
            mFrom = from;
            mTo = to;
        }

        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            mDialAngle = (int)(mFrom + (mTo - mFrom) * interpolatedTime);
        }
    }

} static public interface PieListener {
    public void onPieOpened(int centerX, int centerY);
    public void onPieClosed();
} public class ZoomRenderer extends OverlayRenderer
        implements ScaleGestureDetector.OnScaleGestureListener {

    private static final String TAG = "CAM_Zoom";

    private int mMaxZoom;
    private int mMinZoom;
    private OnZoomChangedListener mListener;

    private ScaleGestureDetector mDetector;
    private Paint mPaint;
    private Paint mTextPaint;
    private int mCircleSize;
    private int mCenterX;
    private int mCenterY;
    private float mMaxCircle;
    private float mMinCircle;
    private int mInnerStroke;
    private int mOuterStroke;
    private int mZoomSig;
    private int mZoomFraction;
    private Rect mTextBounds;

    public interface OnZoomChangedListener {
        void onZoomStart();
        void onZoomEnd();
        void onZoomValueChanged(int index);  // only for immediate zoom
    }

    public ZoomRenderer(Context ctx) {
        Resources res = ctx.getResources();
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setColor(Color.WHITE);
        mPaint.setStyle(Paint.Style.STROKE);
        mTextPaint = new Paint(mPaint);
        mTextPaint.setStyle(Paint.Style.FILL);
        mTextPaint.setTextSize(res.getDimensionPixelSize(R.dimen.zoom_font_size));
        mTextPaint.setTextAlign(Paint.Align.LEFT);
        mTextPaint.setAlpha(192);
        mInnerStroke = res.getDimensionPixelSize(R.dimen.focus_inner_stroke);
        mOuterStroke = res.getDimensionPixelSize(R.dimen.focus_outer_stroke);
        mDetector = new ScaleGestureDetector(ctx, this);
        mMinCircle = res.getDimensionPixelSize(R.dimen.zoom_ring_min);
        mTextBounds = new Rect();
        setVisible(false);
    }

    // set from module
    public void setZoomMax(int zoomMaxIndex) {
        mMaxZoom = zoomMaxIndex;
        mMinZoom = 0;
    }

    public void setZoom(int index) {
        mCircleSize = (int) (mMinCircle + index * (mMaxCircle - mMinCircle) / (mMaxZoom - mMinZoom));
    }

    public void setZoomValue(int value) {
        value = value / 10;
        mZoomSig = value / 10;
        mZoomFraction = value % 10;
    }

    public void setOnZoomChangeListener(OnZoomChangedListener listener) {
        mListener = listener;
    }

    @Override
    public void layout(int l, int t, int r, int b) {
        super.layout(l, t, r, b);
        mCenterX = (r - l) / 2;
        mCenterY = (b - t) / 2;
        mMaxCircle = Math.min(getWidth(), getHeight());
        mMaxCircle = (mMaxCircle - mMinCircle) / 2;
    }

    public boolean isScaling() {
        return mDetector.isInProgress();
    }

    @Override
    public void onDraw(Canvas canvas) {
        mPaint.setStrokeWidth(mInnerStroke);
        canvas.drawCircle(mCenterX, mCenterY, mMinCircle, mPaint);
        canvas.drawCircle(mCenterX, mCenterY, mMaxCircle, mPaint);
        canvas.drawLine(mCenterX - mMinCircle, mCenterY,
                mCenterX - mMaxCircle - 4, mCenterY, mPaint);
        mPaint.setStrokeWidth(mOuterStroke);
        canvas.drawCircle((float) mCenterX, (float) mCenterY,
                (float) mCircleSize, mPaint);
        String txt = mZoomSig+"."+mZoomFraction+"x";
        mTextPaint.getTextBounds(txt, 0, txt.length(), mTextBounds);
        canvas.drawText(txt, mCenterX - mTextBounds.centerX(), mCenterY - mTextBounds.centerY(),
                mTextPaint);
    }

    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        final float sf = detector.getScaleFactor();
        float circle = (int) (mCircleSize * sf * sf);
        circle = Math.max(mMinCircle, circle);
        circle = Math.min(mMaxCircle, circle);
        if (mListener != null && (int) circle != mCircleSize) {
            mCircleSize = (int) circle;
            int zoom = mMinZoom + (int) ((mCircleSize - mMinCircle) * (mMaxZoom - mMinZoom) / (mMaxCircle - mMinCircle));
            mListener.onZoomValueChanged(zoom);
        }
        return true;
    }

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
        setVisible(true);
        if (mListener != null) {
            mListener.onZoomStart();
        }
        update();
        return true;
    }

    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {
        setVisible(false);
        if (mListener != null) {
            mListener.onZoomEnd();
        }
    }

} public class CameraUtil {
    private static final String TAG = "Util";

    // For calculate the best fps range for still image capture.
    private final static int MAX_PREVIEW_FPS_TIMES_1000 = 400000;
    private final static int PREFERRED_PREVIEW_FPS_TIMES_1000 = 30000;

    // For creating crop intents.
    public static final String KEY_RETURN_DATA = "return-data";
    public static final String KEY_SHOW_WHEN_LOCKED = "showWhenLocked";

    // Orientation hysteresis amount used in rounding, in degrees
    public static final int ORIENTATION_HYSTERESIS = 5;

    public static final String REVIEW_ACTION = "com.android.camera.action.REVIEW";
    // See android.hardware.Camera.ACTION_NEW_PICTURE.
    public static final String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE";
    // See android.hardware.Camera.ACTION_NEW_VIDEO.
    public static final String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO";

    // Broadcast Action: The camera application has become active in picture-taking mode.
    public static final String ACTION_CAMERA_STARTED = "com.android.camera.action.CAMERA_STARTED";
    // Broadcast Action: The camera application is no longer in active picture-taking mode.
    public static final String ACTION_CAMERA_STOPPED = "com.android.camera.action.CAMERA_STOPPED";
    // When the camera application is active in picture-taking mode, it listens for this intent,
    // which upon receipt will trigger the shutter to capture a new picture, as if the user had
    // pressed the shutter button.
    public static final String ACTION_CAMERA_SHUTTER_CLICK =
            "com.android.camera.action.SHUTTER_CLICK";

    // Fields from android.hardware.Camera.Parameters
    public static final String FOCUS_MODE_CONTINUOUS_PICTURE = "continuous-picture";
    public static final String RECORDING_HINT = "recording-hint";
    private static final String AUTO_EXPOSURE_LOCK_SUPPORTED = "auto-exposure-lock-supported";
    private static final String AUTO_WHITE_BALANCE_LOCK_SUPPORTED = "auto-whitebalance-lock-supported";
    private static final String VIDEO_SNAPSHOT_SUPPORTED = "video-snapshot-supported";
    public static final String SCENE_MODE_HDR = "hdr";
    public static final String TRUE = "true";
    public static final String FALSE = "false";

    // Fields for the show-on-maps-functionality
    private static final String MAPS_PACKAGE_NAME = "com.google.android.apps.maps";
    private static final String MAPS_CLASS_NAME = "com.google.android.maps.MapsActivity";

    /** Has to be in sync with the receiving MovieActivity. */
    public static final String KEY_TREAT_UP_AS_BACK = "treat-up-as-back";

    public static boolean isSupported(String value, List<String> supported) {
        return supported == null ? false : supported.indexOf(value) >= 0;
    }

    public static boolean isAutoExposureLockSupported(Parameters params) {
        return TRUE.equals(params.get(AUTO_EXPOSURE_LOCK_SUPPORTED));
    }

    public static boolean isAutoWhiteBalanceLockSupported(Parameters params) {
        return TRUE.equals(params.get(AUTO_WHITE_BALANCE_LOCK_SUPPORTED));
    }

    public static boolean isVideoSnapshotSupported(Parameters params) {
        return TRUE.equals(params.get(VIDEO_SNAPSHOT_SUPPORTED));
    }

    public static boolean isCameraHdrSupported(Parameters params) {
        List<String> supported = params.getSupportedSceneModes();
        return (supported != null) && supported.contains(SCENE_MODE_HDR);
    }

    public static boolean isMeteringAreaSupported(Parameters params) {
        return params.getMaxNumMeteringAreas() > 0;
    }

    public static boolean isFocusAreaSupported(Parameters params) {
        return (params.getMaxNumFocusAreas() > 0
                && isSupported(Parameters.FOCUS_MODE_AUTO,
                        params.getSupportedFocusModes()));
    }

    // Private intent extras. Test only.
    private static final String EXTRAS_CAMERA_FACING =
            "android.intent.extras.CAMERA_FACING";

    private static float sPixelDensity = 1;
    private static ImageFileNamer sImageFileNamer;

    private CameraUtil() {
    }

    public static void initialize(Context context) {
        DisplayMetrics metrics = new DisplayMetrics();
        WindowManager wm = (WindowManager)
                context.getSystemService(Context.WINDOW_SERVICE);
        wm.getDefaultDisplay().getMetrics(metrics);
        sPixelDensity = metrics.density;
        sImageFileNamer = new ImageFileNamer(
                context.getString(R.string.image_file_name_format));
    }

    public static int dpToPixel(int dp) {
        return Math.round(sPixelDensity * dp);
    }

    // Rotates the bitmap by the specified degree.
    // If a new bitmap is created, the original bitmap is recycled.
    public static Bitmap rotate(Bitmap b, int degrees) {
        return rotateAndMirror(b, degrees, false);
    }

    // Rotates and/or mirrors the bitmap. If a new bitmap is created, the
    // original bitmap is recycled.
    public static Bitmap rotateAndMirror(Bitmap b, int degrees, boolean mirror) {
        if ((degrees != 0 || mirror) && b != null) {
            Matrix m = new Matrix();
            // Mirror first.
            // horizontal flip + rotation = -rotation + horizontal flip
            if (mirror) {
                m.postScale(-1, 1);
                degrees = (degrees + 360) % 360;
                if (degrees == 0 || degrees == 180) {
                    m.postTranslate(b.getWidth(), 0);
                } else if (degrees == 90 || degrees == 270) {
                    m.postTranslate(b.getHeight(), 0);
                } else {
                    throw new IllegalArgumentException("Invalid degrees=" + degrees);
                }
            }
            if (degrees != 0) {
                // clockwise
                m.postRotate(degrees,
                        (float) b.getWidth() / 2, (float) b.getHeight() / 2);
            }

            try {
                Bitmap b2 = Bitmap.createBitmap(
                        b, 0, 0, b.getWidth(), b.getHeight(), m, true);
                if (b != b2) {
                    b.recycle();
                    b = b2;
                }
            } catch (OutOfMemoryError ex) {
                // We have no memory to rotate. Return the original bitmap.
            }
        }
        return b;
    }

    /*
     * Compute the sample size as a function of minSideLength
     * and maxNumOfPixels.
     * minSideLength is used to specify that minimal width or height of a
     * bitmap.
     * maxNumOfPixels is used to specify the maximal size in pixels that is
     * tolerable in terms of memory usage.
     *
     * The function returns a sample size based on the constraints.
     * Both size and minSideLength can be passed in as -1
     * which indicates no care of the corresponding constraint.
     * The functions prefers returning a sample size that
     * generates a smaller bitmap, unless minSideLength = -1.
     *
     * Also, the function rounds up the sample size to a power of 2 or multiple
     * of 8 because BitmapFactory only honors sample size this way.
     * For example, BitmapFactory downsamples an image by 2 even though the
     * request is 3. So we round up the sample size to avoid OOM.
     */
    public static int computeSampleSize(BitmapFactory.Options options,
            int minSideLength, int maxNumOfPixels) {
        int initialSize = computeInitialSampleSize(options, minSideLength,
                maxNumOfPixels);

        int roundedSize;
        if (initialSize <= 8) {
            roundedSize = 1;
            while (roundedSize < initialSize) {
                roundedSize <<= 1;
            }
        } else {
            roundedSize = (initialSize + 7) / 8 * 8;
        }

        return roundedSize;
    }

    private static int computeInitialSampleSize(BitmapFactory.Options options,
            int minSideLength, int maxNumOfPixels) {
        double w = options.outWidth;
        double h = options.outHeight;

        int lowerBound = (maxNumOfPixels < 0) ? 1 :
                (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
        int upperBound = (minSideLength < 0) ? 128 :
                (int) Math.min(Math.floor(w / minSideLength),
                Math.floor(h / minSideLength));

        if (upperBound < lowerBound) {
            // return the larger one when there is no overlapping zone.
            return lowerBound;
        }

        if (maxNumOfPixels < 0 && minSideLength < 0) {
            return 1;
        } else if (minSideLength < 0) {
            return lowerBound;
        } else {
            return upperBound;
        }
    }

    public static Bitmap makeBitmap(byte[] jpegData, int maxNumOfPixels) {
        try {
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length,
                    options);
            if (options.mCancel || options.outWidth == -1
                    || options.outHeight == -1) {
                return null;
            }
            options.inSampleSize = computeSampleSize(
                    options, -1, maxNumOfPixels);
            options.inJustDecodeBounds = false;

            options.inDither = false;
            options.inPreferredConfig = Bitmap.Config.ARGB_8888;
            return BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length,
                    options);
        } catch (OutOfMemoryError ex) {
            Log.e(TAG, "Got oom exception ", ex);
            return null;
        }
    }

    public static void closeSilently(Closeable c) {
        if (c == null) return;
        try {
            c.close();
        } catch (Throwable t) {
            // do nothing
        }
    }

    public static void Assert(boolean cond) {
        if (!cond) {
            throw new AssertionError();
        }
    }

    private static void throwIfCameraDisabled(Activity activity) throws CameraDisabledException {
        // Check if device policy has disabled the camera.
        DevicePolicyManager dpm = (DevicePolicyManager) activity.getSystemService(
                Context.DEVICE_POLICY_SERVICE);
        if (dpm.getCameraDisabled(null)) {
            throw new CameraDisabledException();
        }
    }

    public static CameraManager.CameraProxy openCamera(
            Activity activity, final int cameraId,
            Handler handler, final CameraManager.CameraOpenErrorCallback cb) {
        try {
            throwIfCameraDisabled(activity);
            return CameraHolder.instance().open(handler, cameraId, cb);
        } catch (CameraDisabledException ex) {
            handler.post(new Runnable() {
                @Override
                public void run() {
                    cb.onCameraDisabled(cameraId);
                }
            });
        }
        return null;
    }

    public static void showErrorAndFinish(final Activity activity, int msgId) {
        DialogInterface.OnClickListener buttonListener =
                new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                activity.finish();
            }
        };
        TypedValue out = new TypedValue();
        activity.getTheme().resolveAttribute(android.R.attr.alertDialogIcon, out, true);
        new AlertDialog.Builder(activity)
                .setCancelable(false)
                .setTitle(R.string.camera_error_title)
                .setMessage(msgId)
                .setNeutralButton(R.string.dialog_ok, buttonListener)
                .setIcon(out.resourceId)
                .show();
    }

    public static <T> T checkNotNull(T object) {
        if (object == null) throw new NullPointerException();
        return object;
    }

    public static boolean equals(Object a, Object b) {
        return (a == b) || (a == null ? false : a.equals(b));
    }

    public static int nextPowerOf2(int n) {
        n -= 1;
        n |= n >>> 16;
        n |= n >>> 8;
        n |= n >>> 4;
        n |= n >>> 2;
        n |= n >>> 1;
        return n + 1;
    }

    public static float distance(float x, float y, float sx, float sy) {
        float dx = x - sx;
        float dy = y - sy;
        return (float) Math.sqrt(dx * dx + dy * dy);
    }

    public static int clamp(int x, int min, int max) {
        if (x > max) return max;
        if (x < min) return min;
        return x;
    }

    public static float clamp(float x, float min, float max) {
        if (x > max) return max;
        if (x < min) return min;
        return x;
    }

    public static int getDisplayRotation(Activity activity) {
        int rotation = activity.getWindowManager().getDefaultDisplay()
                .getRotation();
        switch (rotation) {
            case Surface.ROTATION_0: return 0;
            case Surface.ROTATION_90: return 90;
            case Surface.ROTATION_180: return 180;
            case Surface.ROTATION_270: return 270;
        }
        return 0;
    }

    /**
     * Calculate the default orientation of the device based on the width and
     * height of the display when rotation = 0 (i.e. natural width and height)
     * @param activity the activity context
     * @return whether the default orientation of the device is portrait
     */
    public static boolean isDefaultToPortrait(Activity activity) {
        Display currentDisplay = activity.getWindowManager().getDefaultDisplay();
        Point displaySize = new Point();
        currentDisplay.getSize(displaySize);
        int orientation = currentDisplay.getRotation();
        int naturalWidth, naturalHeight;
        if (orientation == Surface.ROTATION_0 || orientation == Surface.ROTATION_180) {
            naturalWidth = displaySize.x;
            naturalHeight = displaySize.y;
        } else {
            naturalWidth = displaySize.y;
            naturalHeight = displaySize.x;
        }
        return naturalWidth < naturalHeight;
    }

    public static int getDisplayOrientation(int degrees, int cameraId) {
        // See android.hardware.Camera.setDisplayOrientation for
        // documentation.
        Camera.CameraInfo info = new Camera.CameraInfo();
        Camera.getCameraInfo(cameraId, info);
        int result;
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            result = (info.orientation + degrees) % 360;
            result = (360 - result) % 360;  // compensate the mirror
        } else {  // back-facing
            result = (info.orientation - degrees + 360) % 360;
        }
        return result;
    }

    public static int getCameraOrientation(int cameraId) {
        Camera.CameraInfo info = new Camera.CameraInfo();
        Camera.getCameraInfo(cameraId, info);
        return info.orientation;
    }

    public static int roundOrientation(int orientation, int orientationHistory) {
        boolean changeOrientation = false;
        if (orientationHistory == OrientationEventListener.ORIENTATION_UNKNOWN) {
            changeOrientation = true;
        } else {
            int dist = Math.abs(orientation - orientationHistory);
            dist = Math.min( dist, 360 - dist );
            changeOrientation = ( dist >= 45 + ORIENTATION_HYSTERESIS );
        }
        if (changeOrientation) {
            return ((orientation + 45) / 90 * 90) % 360;
        }
        return orientationHistory;
    }

    private static Point getDefaultDisplaySize(Activity activity, Point size) {
        activity.getWindowManager().getDefaultDisplay().getSize(size);
        return size;
    }

    public static Size getOptimalPreviewSize(Activity currentActivity,
            List<Size> sizes, double targetRatio) {

        Point[] points = new Point[sizes.size()];

        int index = 0;
        for (Size s : sizes) {
            points[index++] = new Point(s.width, s.height);
        }

        int optimalPickIndex = getOptimalPreviewSize(currentActivity, points, targetRatio);
        return (optimalPickIndex == -1) ? null : sizes.get(optimalPickIndex);
    }

    public static int getOptimalPreviewSize(Activity currentActivity,
            Point[] sizes, double targetRatio) {
        // Use a very small tolerance because we want an exact match.
        final double ASPECT_TOLERANCE = 0.01;
        if (sizes == null) return -1;

        int optimalSizeIndex = -1;
        double minDiff = Double.MAX_VALUE;

        // Because of bugs of overlay and layout, we sometimes will try to
        // layout the viewfinder in the portrait orientation and thus get the
        // wrong size of preview surface. When we change the preview size, the
        // new overlay will be created before the old one closed, which causes
        // an exception. For now, just get the screen size.
        Point point = getDefaultDisplaySize(currentActivity, new Point());
        int targetHeight = Math.min(point.x, point.y);
        // Try to find an size match aspect ratio and size
        for (int i = 0; i < sizes.length; i++) {
            Point size = sizes[i];
            double ratio = (double) size.x / size.y;
            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
            if (Math.abs(size.y - targetHeight) < minDiff) {
                optimalSizeIndex = i;
                minDiff = Math.abs(size.y - targetHeight);
            }
        }
        // Cannot find the one match the aspect ratio. This should not happen.
        // Ignore the requirement.
        if (optimalSizeIndex == -1) {
            Log.w(TAG, "No preview size match the aspect ratio");
            minDiff = Double.MAX_VALUE;
            for (int i = 0; i < sizes.length; i++) {
                Point size = sizes[i];
                if (Math.abs(size.y - targetHeight) < minDiff) {
                    optimalSizeIndex = i;
                    minDiff = Math.abs(size.y - targetHeight);
                }
            }
        }
        return optimalSizeIndex;
    }

    // Returns the largest picture size which matches the given aspect ratio.
    public static Size getOptimalVideoSnapshotPictureSize(
            List<Size> sizes, double targetRatio) {
        // Use a very small tolerance because we want an exact match.
        final double ASPECT_TOLERANCE = 0.001;
        if (sizes == null) return null;

        Size optimalSize = null;

        // Try to find a size matches aspect ratio and has the largest width
        for (Size size : sizes) {
            double ratio = (double) size.width / size.height;
            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
            if (optimalSize == null || size.width > optimalSize.width) {
                optimalSize = size;
            }
        }

        // Cannot find one that matches the aspect ratio. This should not happen.
        // Ignore the requirement.
        if (optimalSize == null) {
            Log.w(TAG, "No picture size match the aspect ratio");
            for (Size size : sizes) {
                if (optimalSize == null || size.width > optimalSize.width) {
                    optimalSize = size;
                }
            }
        }
        return optimalSize;
    }

    public static void dumpParameters(Parameters parameters) {
        String flattened = parameters.flatten();
        StringTokenizer tokenizer = new StringTokenizer(flattened, ";");
        Log.d(TAG, "Dump all camera parameters:");
        while (tokenizer.hasMoreElements()) {
            Log.d(TAG, tokenizer.nextToken());
        }
    }

    /**
     * Returns whether the device is voice-capable (meaning, it can do MMS).
     */
    public static boolean isMmsCapable(Context context) {
        TelephonyManager telephonyManager = (TelephonyManager)
                context.getSystemService(Context.TELEPHONY_SERVICE);
        if (telephonyManager == null) {
            return false;
        }

        try {
            Class<?> partypes[] = new Class[0];
            Method sIsVoiceCapable = TelephonyManager.class.getMethod(
                    "isVoiceCapable", partypes);

            Object arglist[] = new Object[0];
            Object retobj = sIsVoiceCapable.invoke(telephonyManager, arglist);
            return (Boolean) retobj;
        } catch (java.lang.reflect.InvocationTargetException ite) {
            // Failure, must be another device.
            // Assume that it is voice capable.
        } catch (IllegalAccessException iae) {
            // Failure, must be an other device.
            // Assume that it is voice capable.
        } catch (NoSuchMethodException nsme) {
        }
        return true;
    }

    // This is for test only. Allow the camera to launch the specific camera.
    public static int getCameraFacingIntentExtras(Activity currentActivity) {
        int cameraId = -1;

        int intentCameraId =
                currentActivity.getIntent().getIntExtra(CameraUtil.EXTRAS_CAMERA_FACING, -1);

        if (isFrontCameraIntent(intentCameraId)) {
            // Check if the front camera exist
            int frontCameraId = CameraHolder.instance().getFrontCameraId();
            if (frontCameraId != -1) {
                cameraId = frontCameraId;
            }
        } else if (isBackCameraIntent(intentCameraId)) {
            // Check if the back camera exist
            int backCameraId = CameraHolder.instance().getBackCameraId();
            if (backCameraId != -1) {
                cameraId = backCameraId;
            }
        }
        return cameraId;
    }

    private static boolean isFrontCameraIntent(int intentCameraId) {
        return (intentCameraId == android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT);
    }

    private static boolean isBackCameraIntent(int intentCameraId) {
        return (intentCameraId == android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK);
    }

    private static int sLocation[] = new int[2];

    // This method is not thread-safe.
    public static boolean pointInView(float x, float y, View v) {
        v.getLocationInWindow(sLocation);
        return x >= sLocation[0] && x < (sLocation[0] + v.getWidth())
                && y >= sLocation[1] && y < (sLocation[1] + v.getHeight());
    }

    public static int[] getRelativeLocation(View reference, View view) {
        reference.getLocationInWindow(sLocation);
        int referenceX = sLocation[0];
        int referenceY = sLocation[1];
        view.getLocationInWindow(sLocation);
        sLocation[0] -= referenceX;
        sLocation[1] -= referenceY;
        return sLocation;
    }

    public static boolean isUriValid(Uri uri, ContentResolver resolver) {
        if (uri == null) return false;

        try {
            ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r");
            if (pfd == null) {
                Log.e(TAG, "Fail to open URI. URI=" + uri);
                return false;
            }
            pfd.close();
        } catch (IOException ex) {
            return false;
        }
        return true;
    }

    public static void dumpRect(RectF rect, String msg) {
        Log.v(TAG, msg + "=(" + rect.left + "," + rect.top
                + "," + rect.right + "," + rect.bottom + ")");
    }

    public static void rectFToRect(RectF rectF, Rect rect) {
        rect.left = Math.round(rectF.left);
        rect.top = Math.round(rectF.top);
        rect.right = Math.round(rectF.right);
        rect.bottom = Math.round(rectF.bottom);
    }

    public static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation,
            int viewWidth, int viewHeight) {
        // Need mirror for front camera.
        matrix.setScale(mirror ? -1 : 1, 1);
        // This is the value for android.hardware.Camera.setDisplayOrientation.
        matrix.postRotate(displayOrientation);
        // Camera driver coordinates range from (-1000, -1000) to (1000, 1000).
        // UI coordinates range from (0, 0) to (width, height).
        matrix.postScale(viewWidth / 2000f, viewHeight / 2000f);
        matrix.postTranslate(viewWidth / 2f, viewHeight / 2f);
    }

    public static String createJpegName(long dateTaken) {
        synchronized (sImageFileNamer) {
            return sImageFileNamer.generateName(dateTaken);
        }
    }

    public static void broadcastNewPicture(Context context, Uri uri) {
        context.sendBroadcast(new Intent(ACTION_NEW_PICTURE, uri));
        // Keep compatibility
        context.sendBroadcast(new Intent("com.android.camera.NEW_PICTURE", uri));
    }

    public static void fadeIn(View view, float startAlpha, float endAlpha, long duration) {
        if (view.getVisibility() == View.VISIBLE) return;

        view.setVisibility(View.VISIBLE);
        Animation animation = new AlphaAnimation(startAlpha, endAlpha);
        animation.setDuration(duration);
        view.startAnimation(animation);
    }

    public static void fadeIn(View view) {
        fadeIn(view, 0F, 1F, 400);

        // We disabled the button in fadeOut(), so enable it here.
        view.setEnabled(true);
    }

    public static void fadeOut(View view) {
        if (view.getVisibility() != View.VISIBLE) return;

        // Since the button is still clickable before fade-out animation
        // ends, we disable the button first to block click.
        view.setEnabled(false);
        Animation animation = new AlphaAnimation(1F, 0F);
        animation.setDuration(400);
        view.startAnimation(animation);
        view.setVisibility(View.GONE);
    }

    public static int getJpegRotation(int cameraId, int orientation) {
        // See android.hardware.Camera.Parameters.setRotation for
        // documentation.
        int rotation = 0;
        if (orientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
            CameraInfo info = CameraHolder.instance().getCameraInfo()[cameraId];
            if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
                rotation = (info.orientation - orientation + 360) % 360;
            } else {  // back-facing camera
                rotation = (info.orientation + orientation) % 360;
            }
        }
        return rotation;
    }

    /**
     * Down-samples a jpeg byte array.
     * @param data a byte array of jpeg data
     * @param downSampleFactor down-sample factor
     * @return decoded and down-sampled bitmap
     */
    public static Bitmap downSample(final byte[] data, int downSampleFactor) {
        final BitmapFactory.Options opts = new BitmapFactory.Options();
        // Downsample the image
        opts.inSampleSize = downSampleFactor;
        return BitmapFactory.decodeByteArray(data, 0, data.length, opts);
    }

    public static void setGpsParameters(Parameters parameters, Location loc) {
        // Clear previous GPS location from the parameters.
        parameters.removeGpsData();

        // We always encode GpsTimeStamp
        parameters.setGpsTimestamp(System.currentTimeMillis() / 1000);

        // Set GPS location.
        if (loc != null) {
            double lat = loc.getLatitude();
            double lon = loc.getLongitude();
            boolean hasLatLon = (lat != 0.0d) || (lon != 0.0d);

            if (hasLatLon) {
                Log.d(TAG, "Set gps location");
                parameters.setGpsLatitude(lat);
                parameters.setGpsLongitude(lon);
                parameters.setGpsProcessingMethod(loc.getProvider().toUpperCase());
                if (loc.hasAltitude()) {
                    parameters.setGpsAltitude(loc.getAltitude());
                } else {
                    // for NETWORK_PROVIDER location provider, we may have
                    // no altitude information, but the driver needs it, so
                    // we fake one.
                    parameters.setGpsAltitude(0);
                }
                if (loc.getTime() != 0) {
                    // Location.getTime() is UTC in milliseconds.
                    // gps-timestamp is UTC in seconds.
                    long utcTimeSeconds = loc.getTime() / 1000;
                    parameters.setGpsTimestamp(utcTimeSeconds);
                }
            } else {
                loc = null;
            }
        }
    }

    /**
     * For still image capture, we need to get the right fps range such that the
     * camera can slow down the framerate to allow for less-noisy/dark
     * viewfinder output in dark conditions.
     *
     * @param params Camera's parameters.
     * @return null if no appropiate fps range can't be found. Otherwise, return
     *         the right range.
     */
    public static int[] getPhotoPreviewFpsRange(Parameters params) {
        List<int[]> frameRates = params.getSupportedPreviewFpsRange();
        if (frameRates.size() == 0) {
            Log.e(TAG, "No suppoted frame rates returned!");
            return null;
        }

        // Find the lowest min rate in supported ranges who can cover 30fps.
        int lowestMinRate = MAX_PREVIEW_FPS_TIMES_1000;
        for (int[] rate : frameRates) {
            int minFps = rate[Parameters.PREVIEW_FPS_MIN_INDEX];
            int maxFps = rate[Parameters.PREVIEW_FPS_MAX_INDEX];
            if (maxFps >= PREFERRED_PREVIEW_FPS_TIMES_1000 &&
                    minFps <= PREFERRED_PREVIEW_FPS_TIMES_1000 &&
                    minFps < lowestMinRate) {
                lowestMinRate = minFps;
            }
        }

        // Find all the modes with the lowest min rate found above, the pick the
        // one with highest max rate.
        int resultIndex = -1;
        int highestMaxRate = 0;
        for (int i = 0; i < frameRates.size(); i++) {
            int[] rate = frameRates.get(i);
            int minFps = rate[Parameters.PREVIEW_FPS_MIN_INDEX];
            int maxFps = rate[Parameters.PREVIEW_FPS_MAX_INDEX];
            if (minFps == lowestMinRate && highestMaxRate < maxFps) {
                highestMaxRate = maxFps;
                resultIndex = i;
            }
        }

        if (resultIndex >= 0) {
            return frameRates.get(resultIndex);
        }
        Log.e(TAG, "Can't find an appropiate frame rate range!");
        return null;
    }

    public static int[] getMaxPreviewFpsRange(Parameters params) {
        List<int[]> frameRates = params.getSupportedPreviewFpsRange();
        if (frameRates != null && frameRates.size() > 0) {
            // The list is sorted. Return the last element.
            return frameRates.get(frameRates.size() - 1);
        }
        return new int[0];
    }

    private static class ImageFileNamer {
        private final SimpleDateFormat mFormat;

        // The date (in milliseconds) used to generate the last name.
        private long mLastDate;

        // Number of names generated for the same second.
        private int mSameSecondCount;

        public ImageFileNamer(String format) {
            mFormat = new SimpleDateFormat(format);
        }

        public String generateName(long dateTaken) {
            Date date = new Date(dateTaken);
            String result = mFormat.format(date);

            // If the last name was generated for the same second,
            // we append _1, _2, etc to the name.
            if (dateTaken / 1000 == mLastDate / 1000) {
                mSameSecondCount++;
                result += "_" + mSameSecondCount;
            } else {
                mLastDate = dateTaken;
                mSameSecondCount = 0;
            }

            return result;
        }
    }

    public static void playVideo(Activity activity, Uri uri, String title) {
        try {
            boolean isSecureCamera = ((CameraActivity)activity).isSecureCamera();
            if (!isSecureCamera) {
                Intent intent = IntentHelper.getVideoPlayerIntent(activity, uri)
                        .putExtra(Intent.EXTRA_TITLE, title)
                        .putExtra(KEY_TREAT_UP_AS_BACK, true);
                activity.startActivityForResult(intent, CameraActivity.REQ_CODE_DONT_SWITCH_TO_PREVIEW);
            } else {
                // In order not to send out any intent to be intercepted and
                // show the lock screen immediately, we just let the secure
                // camera activity finish.
                activity.finish();
            }
        } catch (ActivityNotFoundException e) {
            Toast.makeText(activity, activity.getString(R.string.video_err),
                    Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * Starts GMM with the given location shown. If this fails, and GMM could
     * not be found, we use a geo intent as a fallback.
     *
     * @param activity the activity to use for launching the Maps intent.
     * @param latLong a 2-element array containing {latitude/longitude}.
     */
    public static void showOnMap(Activity activity, double[] latLong) {
        try {
            // We don't use "geo:latitude,longitude" because it only centers
            // the MapView to the specified location, but we need a marker
            // for further operations (routing to/from).
            // The q=(lat, lng) syntax is suggested by geo-team.
            String uri = String.format(Locale.ENGLISH, "http://maps.google.com/maps?f=q&q=(%f,%f)",
                    latLong[0], latLong[1]);
            ComponentName compName = new ComponentName(MAPS_PACKAGE_NAME,
                    MAPS_CLASS_NAME);
            Intent mapsIntent = new Intent(Intent.ACTION_VIEW,
                    Uri.parse(uri)).setComponent(compName);
            activity.startActivityForResult(mapsIntent,
                    CameraActivity.REQ_CODE_DONT_SWITCH_TO_PREVIEW);
        } catch (ActivityNotFoundException e) {
            // Use the "geo intent" if no GMM is installed
            Log.e(TAG, "GMM activity not found!", e);
            String url = String.format(Locale.ENGLISH, "geo:%f,%f", latLong[0], latLong[1]);
            Intent mapsIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
            activity.startActivity(mapsIntent);
        }
    }

    /**
     * Dumps the stack trace.
     *
     * @param level How many levels of the stack are dumped. 0 means all.
     * @return A {@link java.lang.String} of all the output with newline
     * between each.
     */
    public static String dumpStackTrace(int level) {
        StackTraceElement[] elems = Thread.currentThread().getStackTrace();
        // Ignore the first 3 elements.
        level = (level == 0 ? elems.length : Math.min(level + 3, elems.length));
        String ret = new String();
        for (int i = 3; i < level; i++) {
            ret = ret + "\t" + elems[i].toString() + '\n';
        }
        return ret;
    }

    /**
     * Launches apps supporting action {@link Intent.ACTION_MAIN} of category
     * {@link Intent.CATEGORY_APP_GALLERY}. Note that
     * {@link Intent.CATEGORY_APP_GALLERY} is only available on API level 15+.
     *
     * @param ctx The {@link android.content.Context} to launch the app.
     * @return {@code true} on success.
     */
    public static boolean launchGallery(Context ctx) {
        if (ApiHelper.HAS_APP_GALLERY) {
            ctx.startActivity(IntentHelper.getGalleryIntent(ctx));
            return true;
        }
        return false;
    }
}
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.SurfaceTexture;
import android.graphics.drawable.ColorDrawable;
import android.hardware.Camera;
import android.hardware.Camera.Face;
import android.os.AsyncTask;
import android.util.Log;
import android.view.Gravity;
import android.view.TextureView;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLayoutChangeListener;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.widget.FrameLayout.LayoutParams;
import android.widget.ImageView;
import android.widget.PopupWindow;
import android.widget.Toast;
import com.android.camera.CameraPreference.OnPreferenceChangedListener;
import com.android.camera.FocusOverlayManager.FocusUI;
import com.android.camera.ui.AbstractSettingPopup;
import com.android.camera.ui.CameraControls;
import com.android.camera.ui.CameraRootView;
import com.android.camera.ui.CountDownView;
import com.android.camera.ui.CountDownView.OnCountDownFinishedListener;
import com.android.camera.ui.FaceView;
import com.android.camera.ui.FocusIndicator;
import com.android.camera.ui.ModuleSwitcher;
import com.android.camera.ui.PieRenderer;
import com.android.camera.ui.PieRenderer.PieListener;
import com.android.camera.ui.RenderOverlay;
import com.android.camera.ui.ZoomRenderer;
import com.android.camera.util.CameraUtil;
import com.radcam.camera.R;
import java.util.List;
/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package com.android.camera;




public class PhotoUI implements PieListener,
    PreviewGestures.SingleTapListener,
    FocusUI, TextureView.SurfaceTextureListener,
    LocationManager.Listener, CameraRootView.MyDisplayListener,
    CameraManager.CameraFaceDetectionCallback {

    private static final String TAG = "CAM_UI";
    private static final int DOWN_SAMPLE_FACTOR = 4;
    private final AnimationManager mAnimationManager;
    private CameraActivity mActivity;
    private PhotoController mController;
    private PreviewGestures mGestures;

    private View mRootView;
    private SurfaceTexture mSurfaceTexture;

    private PopupWindow mPopup;
    private ShutterButton mShutterButton;
    private CountDownView mCountDownView;

    private FaceView mFaceView;
    private RenderOverlay mRenderOverlay;
    private View mReviewCancelButton;
    private View mReviewDoneButton;
    private View mReviewRetakeButton;
    private ImageView mReviewImage;
    private DecodeImageForReview mDecodeTaskForReview = null;

    private View mMenuButton;
    private PhotoMenu mMenu;
    private ModuleSwitcher mSwitcher;
    private CameraControls mCameraControls;
    private AlertDialog mLocationDialog;

    // Small indicators which show the camera settings in the viewfinder.
    private OnScreenIndicators mOnScreenIndicators;

    private PieRenderer mPieRenderer;
    private ZoomRenderer mZoomRenderer;
    private Toast mNotSelectableToast;

    private int mZoomMax;
    private List<Integer> mZoomRatios;

    private int mPreviewWidth = 0;
    private int mPreviewHeight = 0;
    private float mSurfaceTextureUncroppedWidth;
    private float mSurfaceTextureUncroppedHeight;

    private ImageView mPreviewThumb;
    private View mFlashOverlay;

    private SurfaceTextureSizeChangedListener mSurfaceTextureSizeListener;
    private TextureView mTextureView;
    private Matrix mMatrix = null;
    private float mAspectRatio = 4f / 3f;

    public interface SurfaceTextureSizeChangedListener {
        public void onSurfaceTextureSizeChanged(int uncroppedWidth, int uncroppedHeight);
    }

    private OnLayoutChangeListener mLayoutListener = new OnLayoutChangeListener() {
        @Override
        public void onLayoutChange(View v, int left, int top, int right,
                int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
            int width = right - left;
            int height = bottom - top;
            if (mPreviewWidth != width || mPreviewHeight != height) {
                mPreviewWidth = width;
                mPreviewHeight = height;
                setTransformMatrix(width, height);
                mController.onScreenSizeChanged((int) mSurfaceTextureUncroppedWidth,
                        (int) mSurfaceTextureUncroppedHeight);
            }
        }
    };

    private class DecodeTask extends AsyncTask<Void, Void, Bitmap> {
        private final byte [] mData;
        private int mOrientation;
        private boolean mMirror;

        public DecodeTask(byte[] data, int orientation, boolean mirror) {
            mData = data;
            mOrientation = orientation;
            mMirror = mirror;
        }

        @Override
        protected Bitmap doInBackground(Void... params) {
            // Decode image in background.
            Bitmap bitmap = CameraUtil.downSample(mData, DOWN_SAMPLE_FACTOR);